查看原文
其他

单片机点阵 LED 设计显示屏,超详细!

嵌入式ARM 2021-01-31

LED点阵的介绍

点阵 LED 显示屏作为一种现代电子媒体,具有灵活的显示面积(可任意分割和拼装)、高亮度、长寿命、数字化、实时性等特点,应用非常广泛。

前边学了 LED 小灯和 LED 数码管后,学 LED 点阵就要轻松得多了。一个数码管是 8 个LED 组成,同理,一个 8*8 的点阵就是由 64 个 LED 小灯组成。图 1 就是一个点阵 LED 最小单元,即一个 8*8 的点阵 LED,图2 是它的内部结构原理图。

图 1  8*8LED 点阵外观

图 2  8*8 点阵结构原理图

从图2上可以看出,其实点阵 LED 点亮原理还是很简单的。在图中大方框外侧的就是点阵 LED 的引脚号,左侧的 8 个引脚是接的内部 LED 的阳极,上侧的 8 个引脚接的是内部LED 的阴极。那么如果我们把 9 脚置成高电平、13 脚置成低电平的话,左上角的那个 LED小灯就会亮了。下面我们就用程序来实现一下,特别注意,控制点阵左侧引脚的 74HC138是原理图上的 U4,8 个引脚自上而下依次由 U4 的 Y0~Y7 输出来控制。

#include <reg52.h>    sbit LED = P0^0;  sbit ADDR0 = P1^0;  sbit ADDR1 = P1^1;  sbit ADDR2 = P1^2;  sbit ADDR3 = P1^3;  sbit ENLED = P1^4;    void main(){      ENLED = 0;  //U3、U4 两片 74HC138 总使能      ADDR3 = 0;  //使能 U4 使之正常输出      ADDR2 = 0;  //经 U4 的 Y0 输出开启三极管 Q10      ADDR1 = 0;      ADDR0 = 0;      LED = 0;  //向 P0.0 写入 0 来点亮左上角的一个点      while(1);  //程序停止在这里  }

那么同样的方法,通过对 P0 的整体赋值我们可以一次点亮点阵的一行,那么这次我们用程序来点亮点阵的第二行,对应的就需要编号 U4 的 74HC138 在其 Y1 引脚输出低电平了。

#include <reg52.h>    sbit ADDR0 = P1^0;  sbit ADDR1 = P1^1;  sbit ADDR2 = P1^2;  sbit ADDR3 = P1^3;  sbit ENLED = P1^4;    void main(){      ENLED = 0;  //U3、U4 两片 74HC138 总使能      ADDR3 = 0;  //使能 U4 使之正常输出      ADDR2 = 0;  //经 U4 的 Y1 输出开启三极管 Q11      ADDR1 = 0;      ADDR0 = 1;      P0 = 0x00;  //向 P0 写入 0 来点亮一行      while(1);  //程序停止在这里  }

从这里我们可以逐步发现点阵的控制原理了。我们前面讲了一个数码管就是 8 个 LED 小灯,一个点阵是 64 个 LED 小灯。同样的道理,我们还可以把一个点阵理解成是 8 个数码管。经过前面的学习已经掌握了 6 个数码管同时显示的方法,那 8 个数码管也应该轻轻松松了。下面我们就利用定时器中断和数码管动态显示的原理来把这个点阵全部点亮。

#include <reg52.h>    sbit ADDR0 = P1^0;  sbit ADDR1 = P1^1;  sbit ADDR2 = P1^2; sbit ADDR3 = P1^3;  sbit ENLED = P1^4;    void main(){      EA = 1;  //使能总中断      ENLED = 0;  //使能 U4,选择 LED 点阵      ADDR3 = 0;  //因为需要动态改变 ADDR0-2 的值,所以不需要再初始化了      TMOD = 0x01; //设置 T0 为模式 1      TH0 = 0xFC; //为 T0 赋初值 0xFC67,定时 1ms      TL0 = 0x67;      ET0 = 1;  //使能 T0 中断      TR0 = 1;  //启动 T0      while (1);  //程序停在这里,等待定时器中断  }  /* 定时器 0 中断服务函数 */  void InterruptTimer0() interrupt 1{      static unsigned char i = 0; //动态扫描的索引        TH0 = 0xFC;  //重新加载初值      TL0 = 0x67;      //以下代码完成 LED 点阵动态扫描刷新      P0 = 0xFF;  //显示消隐      switch (i){          case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=0x00; break;          case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=0x00; break;          case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=0x00; break;          case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=0x00; break;          case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=0x00; break;          case 5: ADDR2=1; ADDR1=0; ADDR0=1; i++; P0=0x00; break;          case 6: ADDR2=1; ADDR1=1; ADDR0=0; i++; P0=0x00; break;          case 7: ADDR2=1; ADDR1=1; ADDR0=1; i=0; P0=0x00; break;          default: break;      }  }

LED点阵的图形显示

独立的 LED 小灯可以实现流水灯,数码管可以显示多位数字,那点阵 LED 就得来显示一点花样了。

我们要显示花样的时候,往往要先做出来一些小图形,这些小图形的数据要转换到我们的程序当中去,这个时候就需要取模软件。给大家介绍一款简单的取模软件,这种取模软件在网上都可以下载到,大家来了解一下如何使用,先看一下操作界面,如图3所示。

图3  字模提取软件界面

鼠标点一下“新建图形”,根据我们板子上的点阵,把宽度和高度分别改成 8,然后点确定,如图4所示。

图4  新建图形

点击左侧的“模拟动画”菜单,再点击“放大格点”选项,一直放大到最大,那我们就可以在我们的 8*8 的点阵图形中用鼠标填充黑点,就可以画图形了,如图5 所示。

图5  字模提取软件画图

经过我们的一番精心设计,画出来一个心形图形,并且填充满,最终出现我们想要的效果图,如图6 所示。

图6  心型图形

由于取模软件是把黑色取为 1,白色取为 0,但我们点阵是 1 对应 LED 熄灭,0 对应 LED点亮,而我们需要的是一颗点亮的“心”,所以我们要选“修改图像”菜单里的“黑白反显图像”这个选项,再点击“基本操作”菜单里边的“保存图像”可以把我们设计好的图片进行保存,如图7所示。

图7  保存图形

保存文件只是为了再次使用或修改使方便方便,当然你也可以不保存。操作完了这一步后,点击“参数设置”菜单里的“其他选项”,如图8所示。

图8  选项设置

这里的选项,要结合图2来进行设置,大家可以看到 P0 口控制的是一行,所以用“横向取模”,如果控制的是一列,就要选“纵向取模”。选中“字节倒序”这个选项,是因为图2中左边是低位 DB0,右边是高位 DB7,所以是字节倒序,其它两个选项大家自己了解,点确定后,选择“取模方式”这个菜单,点一下“C51 格式”后,在“点阵生成区”自动产生了 8 个字节的数据,这 8 个字节的数据就是取出来的“模”,如图9所示。

图9  取模结果

大家注意,虽然我们用了软件来取模,但是也得知道其原理是什么,在这个图片里,黑色的一个格子表示一位二进制的 1,白色的一个格子表示一位二进制的 0。第一个字节是 0xFF,其实就是这个 8*8 图形的第一行,全黑就是 0xFF;第二个字节是 0x99,低位在左边,高位在右边,大家注意看,黑色的表示 1,白色的表示 0,就组成了 0x99 这个数值。同理其它的数据大家也就知道怎么来的了。

那么下面我们就用程序把这些数据依次送到点阵上去,看看运行效果如何。

#include <reg52.h>    sbit ADDR0 = P1^0;  sbit ADDR1 = P1^1;  sbit ADDR2 = P1^2;  sbit ADDR3 = P1^3;  sbit ENLED = P1^4;    unsigned char code image[] = { //图片的字模表      0xFF, 0x99, 0x00, 0x00, 0x00, 0x81, 0xC3, 0xE7  };    void main(){      EA = 1;  //使能总中断      ENLED = 0;  //使能 U4,选择 LED 点阵      ADDR3 = 0;      TMOD = 0x01;  //设置 T0 为模式 1      TH0 = 0xFC;  //为 T0 赋初值 0xFC67,定时 1ms      TL0 = 0x67;      ET0 = 1;  //使能 T0 中断      TR0 = 1;//启动 T0      while (1);  }  /* 定时器 0 中断服务函数 */  void InterruptTimer0() interrupt 1{      static unsigned char i = 0;  //动态扫描的索引        TH0 = 0xFC; //重新加载初值      TL0 = 0x67;      //以下代码完成 LED 点阵动态扫描刷新      P0 = 0xFF;  //显示消隐      switch (i){          case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=image[0]; break;          case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=image[1]; break;          case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=image[2]; break;          case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=image[3]; break;          case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=image[4]; break;          case 5: ADDR2=1; ADDR1=0; ADDR0=1; i++; P0=image[5]; break;          case 6: ADDR2=1; ADDR1=1; ADDR0=0; i++; P0=image[6]; break;          case 7: ADDR2=1; ADDR1=1; ADDR0=1; i=0; P0=image[7]; break;          default: break;      }  }

对于 8*8 的点阵来说,我们可以显示一些简单的图形,字符等。但大部分汉字通常来说要用到 16*16 个点,而 8*8 的点阵只能显示一些简单笔画的汉字,大家可以自己取模做出来试试看。使用大屏显示汉字的方法和小屏的方法是类似的,所需要做的只是按照相同的原理来扩展行数和列数而已。

LED点阵的纵向移动(动态显示)

点阵的动画显示,说到底就是对多张图片分别进行取模,使用程序算法巧妙的切换图片,多张图片组合起来就成了一段动画了,我们所看到的动画片、游戏等等,它们的基本原理也都是这样的。

上一节我们学了如何在点阵上画一个❤形,有时候我们希望这些显示是动起来的,而不是静止的。对于点阵本身已经没有多少的知识点可以介绍了,主要就是编程算法来解决问题了。比如我们现在要让点阵显示一个 I ❤ U 的动画,首先我们要把这个图形用取模软件画出来看一下,如图10所示。

图10  上下移动横向取模

这张图片共有 40 行,每 8 行组成一张点阵图片,并且每向上移动一行就出现了一张新图片,一共组成了 32 张图片。

用一个变量 index 来代表每张图片的起始位置,每次从 index 起始向下数 8 行代表了当前的图片,250ms 改变一张图片,然后不停的动态刷新,这样图片就变成动画了。首先我们要对显示的图片进行横向取模,虽然这是 32 张图片,由于我们每一张图片都是和下一行连续的,所以实际的取模值只需要 40 个字节就可以完成,我们来看看程序。

#include <reg52.h>    sbit ADDR0 = P1^0;  sbit ADDR1 = P1^1;  sbit ADDR2 = P1^2;  sbit ADDR3 = P1^3;  sbit ENLED = P1^4;    unsigned char code image[] = { //图片的字模表      0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,      0xC3,0xE7,0xE7,0xE7,0xE7,0xE7,0xC3,0xFF,      0x99,0x00,0x00,0x00,0x81,0xC3,0xE7,0xFF,      0x99,0x99,0x99,0x99,0x99,0x81,0xC3,0xFF,      0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF  };    void main(){      EA = 1;  //使能总中断      ENLED = 0;  //使能 U4,选择 LED 点阵      ADDR3 = 0;      TMOD = 0x01; //设置 T0 为模式 1      TH0 = 0xFC; //为 T0 赋初值 0xFC67,定时 1ms      TL0 = 0x67;      ET0 = 1;  //使能 T0 中断      TR0 = 1;  //启动 T0      while (1);      }      /* 定时器 0 中断服务函数 */      void InterruptTimer0() interrupt 1{      static unsigned char i = 0; //动态扫描的索引      static unsigned char tmr = 0; //250ms 软件定时器      static unsigned char index = 0; //图片刷新索引        TH0 = 0xFC; //重新加载初值      TL0 = 0x67;      //以下代码完成 LED 点阵动态扫描刷新      P0 = 0xFF;  //显示消隐      switch (i){          case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=image[index+0]; break;          case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=image[index+1]; break;          case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=image[index+2]; break;          case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=image[index+3]; break;          case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=image[index+4]; break;          case 5: ADDR2=1; ADDR1=0; ADDR0=1; i++; P0=image[index+5]; break;          case 6: ADDR2=1; ADDR1=1; ADDR0=0; i++; P0=image[index+6]; break;          case 7: ADDR2=1; ADDR1=1; ADDR0=1; i=0; P0=image[index+7]; break;          default: break;      }      //以下代码完成每 250ms 改变一帧图像      tmr++;      if (tmr >= 250){  //达到 250ms 时改变一次图片索引          tmr = 0;          index++;          if (index >= 32){  //图片索引达到 32 后归零             index = 0;          }      }  }

大家把这个程序下载到单片机上看看效果,一个 I ❤ U 一直往上走动的动画就出现了,现在还有哪位敢说我们工科同学不懂浪漫的?还需要用什么玫瑰花取悦女朋友吗?一点技术含量都没有,要玩就玩点高科技,呵呵。

当然,别光图开心,学习我们还要继续。往上走动的动画我写出来了,那往下走动的动画,大家就要自己独立完成了,不要偷懒,一定要去写代码调试代码。瞪眼看只能了解知识,而能力是在真正的写代码、调试代码这种实践中培养起来的。

LED点阵的横向移动(动态显示)

上下移动我们会了,那我们还想左右移动该如何操作呢?

方法一、最简单,就是把板子侧过来放,纵向取模就可以完成。

这里大家是不是有种头顶冒汗的感觉?我们要做好技术,但是不能沉溺于技术。技术是我们的工具,我们在做开发的时候除了用好这个工具外,也得多拓展自己解决问题的思路,要慢慢培养自己的多角度思维方式。

那把板子正过来,左右移动就完不成了吗?当然不是。大家慢慢的学多了就会培养了一种感觉,就是一旦硬件设计好了,我们要完成一种功能,大脑就可以直接思考出来能否完成这个功能,这个在我们进行电路设计的时候最为重要。我们在开发产品的时候,首先是设计电路,设计电路的时候,工程师就要在大脑中通过思维来验证板子硬件和程序能否完成我们想要的功能,一旦硬件做好了,做好板子回来剩下的就是靠编程来完成了。只要是硬件逻辑上没问题,功能上软件肯定可以实现。

当然了,我们在进行硬件电路设计的时候,也得充分考虑软件编程的方便性。因为我们的程序是用 P0 来控制点阵的整行,所以对于我们这样的电路设计,上下移动程序是比较好编写的。那如果我们设计电路的时候知道我们的图形要左右移动,那我们设计电路画板子的时候就要尽可能的把点阵横过来放,有利于我们编程方便,减少软件工作量。

方法二、利用二维数组来实现,算法基本上和上下移动相似。

二维数组,前边提过一次,他的使用其实也没什么复杂的。它的声明方式是:

    数据类型 数组名[数组长度 1][数组长度 2];

与一位数组类似,数据类型是全体元素的数据类型,数组名是标识符,数组长度 1 和数组长度 2 分别代表数组具有的行数和列数。数组元素的下标一律从 0 开始。

例如:unsigned char a[2][3];声明了一个具有 2 行 3 列的无符号字符型的二维数组 a。

二维数组的数组元素总个数是两个长度的乘积。二维数组在内存中存储的时候,采用行优先的方式来存储,即在内存中先存放第 0 行的元素,再存放第一行的元素......,同一行中再按照列顺序存放,刚才定义的那个 a[2][3]的存放形式就如表 7-1 所示。

表7-1 二维数组的物理存储结构
a[0][0]
a[0][1]
a[0][2]
a[1][0]
a[1][1]
a[1][2]

二维数组的初始化方法分两种情况,我们前边学一维数组的时候学过,数组元素的数量可以小于数组元素个数,没有赋值的会自动给 0。当数组元素的数量等于数组个数的时候,如下所示:
    unsigned char a[2][3] = {{1,2,3}, {4,5,6}};
或者是
    unsigned char a[2][3] = {1,2,3,4,5,6};
当数组元素的数量小于数组个数的时候,如下所示:
    unsigned char a[2][3] = {{1,2}, {3,4}};
等价于
    unsigned char a[2][3] = {1,2,0,3,4,0};
而反过来的写法
    unsigned char a[2][3] = {1,2,3,4};
等价于
    unsigned char a[2][3] = {{1,2,3}, {4,0,0}};

此外,二维数组初始化的时候,行数可以省略,编译系统会自动根据列数计算出行数,但是列数不能省略。

讲这些,只是为了让大家了解一下,看别人写的代码的时候别发懵就行了,但是我们今后写程序的时候,按照规范,行数列数都不要省略,全部写齐,初始化的时候,全部写成unsigned char a[2][3] = {{1,2,3}, {4,5,6}};的形式,而不允许写成一维数组的格式,防止大家出错,同时也是提高程序的可读性。

那么下面我们要进行横向做 I ❤ U 的动画了,先把我们需要的图片画出来,再逐一取模,和上一张图片类似的是,我们这个图形共有 30 张图片,通过程序每 250ms 改变一张图片,就可以做出来动画效果了。但是不同的是,我们这个是要横向移动,横向移动的图片切换时的字模数据不是连续的,所以这次我们要对 30 张图片分别取模,如图11所示。

图11  横向动画取模图片

图11中最上面的图形是横向连在一起的效果,而实际上我们要把它分解为 30 个帧,每帧图片单独取模,取出来都是 8 个字节的数据,一共就是 30*8 个数据,我们用一个二维数组来存储它们。

#include <reg52.h>    sbit ADDR0 = P1^0;  sbit ADDR1 = P1^1;  sbit ADDR2 = P1^2;  sbit ADDR3 = P1^3;  sbit ENLED = P1^4;    unsigned char code image[30][8] = {      {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}, //动画帧 1      {0xFF,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0x7F}, //动画帧 2      {0xFF,0x3F,0x7F,0x7F,0x7F,0x7F,0x7F,0x3F}, //动画帧 3      {0xFF,0x1F,0x3F,0x3F,0x3F,0x3F,0x3F,0x1F}, //动画帧 4      {0xFF,0x0F,0x9F,0x9F,0x9F,0x9F,0x9F,0x0F}, //动画帧 5      {0xFF,0x87,0xCF,0xCF,0xCF,0xCF,0xCF,0x87}, //动画帧 6      {0xFF,0xC3,0xE7,0xE7,0xE7,0xE7,0xE7,0xC3}, //动画帧 7      {0xFF,0xE1,0x73,0x73,0x73,0xF3,0xF3,0xE1}, //动画帧 8      {0xFF,0x70,0x39,0x39,0x39,0x79,0xF9,0xF0}, //动画帧 9      {0xFF,0x38,0x1C,0x1C,0x1C,0x3C,0x7C,0xF8}, //动画帧 10      {0xFF,0x9C,0x0E,0x0E,0x0E,0x1E,0x3E,0x7C}, //动画帧 11      {0xFF,0xCE,0x07,0x07,0x07,0x0F,0x1F,0x3E}, //动画帧 12      {0xFF,0x67,0x03,0x03,0x03,0x07,0x0F,0x9F}, //动画帧 13      {0xFF,0x33,0x01,0x01,0x01,0x03,0x87,0xCF}, //动画帧 14      {0xFF,0x99,0x00,0x00,0x00,0x81,0xC3,0xE7}, //动画帧 15      {0xFF,0xCC,0x80,0x80,0x80,0xC0,0xE1,0xF3}, //动画帧 16      {0xFF,0xE6,0xC0,0xC0,0xC0,0xE0,0xF0,0xF9}, //动画帧 17      {0xFF,0x73,0x60,0x60,0x60,0x70,0x78,0xFC}, //动画帧 18      {0xFF,0x39,0x30,0x30,0x30,0x38,0x3C,0x7E}, //动画帧 19      {0xFF,0x9C,0x98,0x98,0x98,0x9C,0x1E,0x3F}, //动画帧 20      {0xFF,0xCE,0xCC,0xCC,0xCC,0xCE,0x0F,0x1F}, //动画帧 21      {0xFF,0x67,0x66,0x66,0x66,0x67,0x07,0x0F}, //动画帧 22      {0xFF,0x33,0x33,0x33,0x33,0x33,0x03,0x87}, //动画帧 23      {0xFF,0x99,0x99,0x99,0x99,0x99,0x81,0xC3}, //动画帧 24      {0xFF,0xCC,0xCC,0xCC,0xCC,0xCC,0xC0,0xE1}, //动画帧 25      {0xFF,0xE6,0xE6,0xE6,0xE6,0xE6,0xE0,0xF0}, //动画帧 26      {0xFF,0xF3,0xF3,0xF3,0xF3,0xF3,0xF0,0xF8}, //动画帧 27      {0xFF,0xF9,0xF9,0xF9,0xF9,0xF9,0xF8,0xFC}, //动画帧 28      {0xFF,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFE}, //动画帧 29      {0xFF,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFF} //动画帧 30  };    void main(){      EA = 1;  //使能总中断      ENLED = 0;  //使能 U4,选择 LED 点阵      ADDR3 = 0;      TMOD = 0x01; //设置 T0 为模式 1      TH0 = 0xFC; //为 T0 赋初值 0xFC67,定时 1ms      TL0 = 0x67;      ET0 = 1;  //使能 T0 中断      TR0 = 1;  //启动 T0      while (1);  }  /* 定时器 0 中断服务函数 */  void InterruptTimer0() interrupt 1{      static unsigned char i = 0; //动态扫描的索引      static unsigned char tmr = 0; //250ms 软件定时器      static unsigned char index = 0; //图片刷新索引      TH0 = 0xFC; //重新加载初值      TL0 = 0x67;      //以下代码完成 LED 点阵动态扫描刷新      P0 = 0xFF;  //显示消隐        switch (i){          case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=image[index][0]; break;          case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=image[index][1]; break;          case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=image[index][2]; break;          case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=image[index][3]; break;          case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=image[index][4]; break;          case 5: ADDR2=1; ADDR1=0; ADDR0=1; i++; P0=image[index][5]; break;          case 6: ADDR2=1; ADDR1=1; ADDR0=0; i++; P0=image[index][6]; break;          case 7: ADDR2=1; ADDR1=1; ADDR0=1; i=0; P0=image[index][7]; break;          default: break;      }      //以下代码完成每 250ms 改变一帧图像      tmr++;      if (tmr >= 250){ //达到 250ms 时改变一次图片索引          tmr = 0;          index++;          if (index >= 30){ //图片索引达到 30 后归零             index = 0;          }      }  }

下载进到板子上瞧瞧,是不是有一种帅到掉渣的感觉呢。技术这东西,外行人看的是很神秘的,其实我们做出来会发现,也就是那么回事而已,每250ms更改一张图片,每1ms在定时器中断里刷新单张图片的某一行。

不管是上下移动还是左右移动,大家要建立一种概念,就是我们是对一帧帧的图片的切换,这种切换带给我们的视觉效果就是一种动态的了。比如我们的DV拍摄动画,实际上就是快速的拍摄了一帧帧的图片,然后对这些图片的快速回放,把动画效果给显示了出来。因为我们硬件设计的缘故,所以在写上下移动程序的时候,数组定义的元素比较少,但是实际上大家也得理解成是32张图片的切换显示,而并非是真正的“移动”。

-END-



推荐阅读



【01】最强干货!延时功能进化论(适用于单片机和任何延时平台)【02】嵌入式软件面试那点事【03】嵌入式软件工程师从初级迈入合格,可以这么来做!【04】回想一下你操作正确吗?FPGA复位的正确打开方式【05】关于嵌入式 Qt 最全最棒的教程(万字干货)

免责声明:整理文章为传播相关技术,版权归原作者所有,如有侵权,请联系删除

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存