Skip to content

LED点阵屏

作者:姜蘅洋

1.LED灯:

  • LED的英文全称是:Light Emitting Diode,即发光二极管。
  • 发光二极管具有单向导电性。
    • image-20230611135855960
    • 当左边接正极,右边接地的时候,LED发光二极管被点亮。

2.LED点阵屏介绍:

  • LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以LED灯亮灭来显示文字、图片、视频等。

  • 实验室使用的LED点阵屏是8 x 8的点阵屏:

    • 每行有8个LED灯,一共有8行,即64个LED灯。
  • image-20230611140236861

  • 点阵屏使用方式和数码管类似,都是利用人眼的余晖效应

    • 数码管:
      • 一个数码管可以直接点亮,多个数码管采用逐个扫描的方式,即利用余晖效应一次扫描点亮每个数码管。
    • LED点阵屏:
      • 一次可以对一行的LED灯进行操作,采用逐行扫描的方式控制点阵屏中的所有LED灯。
        • 对于该LED点阵屏,同样支持逐列操作。
        • 由于a、b、c、d、e、f、g、h表示段码值,因此我们采用逐行扫描操作。

3.点阵屏单图显示:

  • 单图显示爱心:

    • 由于采用逐行扫描,因此对应一个8字节的段码数组,每个字节操控每一行。
  • 取模软件:

    • 这里用的取模软件是:CopyLeft by Horse2000
    • 设置取模方式是:横向取模
      • 因为段码控制的是一行。
    • 字节倒序:
      • 段码中的a来自D0,h来自D7,即段码段的数据按照D0 ~ D7排列的,而不是D7 ~ D0,因此要采用字节倒序。
      • image-20230611151155004
    • image-20230611151359784
  • code关键字:

    • keil中提供了一个特殊的关键字"code",这个关键字在标准C中是没有的。
    • 我们知道,在单片机中一般都有两块存储区域,ROM和RAM,程序代码存储在ROM(只读存储器,相当于硬盘)中,程序要用的变量存储在RAM(随机读取存储器,相当于内存)中。
    • **"code"**的作用就是将其修饰过的变量存储在ROM中而非RAM。
    • 在单片机中,RAM空间都比较小,是比较宝贵的,**"code"**的意义就是将一些初始化后值一直保持不变的变量(如固定的常数、表格、常量数组、只读常量等)放置于ROM区,从而节省了RAM空间。
  • c
    //对点阵屏的每个LED灯进行检测的函数
    void dotMatrix_Examine(void)
    {
    	uint_8 Row,Col;
    	for (Row = 0 ; Row < 8 ; Row++)
    	{
    		DIOLA = 1;
    		DIOLA_Byte = 1 << Row;
    		DIOLA = 0;
    		for (Col = 0 ; Col < 8 ; Col++)
    		{	
    			DULA = 1;
    			DULA_Byte = ~(1 << Col);
    			DULA = 0;
    			dotMatrixExamineDelay_1s();
    		}
    	}
    	DIOLA = 0;
    	DULA = 0;
    }
  • c
    //dotMatrixScreen.h
    #ifndef __DOTMATRIXSCREEN_H__
    #define __DOTMATRIXSCREEN_H__
    #include "overwhelm.h"
    #include <intrins.h>
    extern code uint_8 DMS_1[];
    #define DULA P2_6
    #define DIOLA P2_5
    #define WELA P2_7
    #define DULA_Byte P0
    #define DIOLA_Byte P0
    void dotMatrix_Init(void);
    void dotMatrix_Examine(void);
    void Dynamic_dotMatrix(uint_8 *DMS);
    #endif
  • c
    //dotMatrixScreen.c
    code uint_8 DMS_1[] = {~0x00,~0x66,~0xFF,~0xFF,~0xFF,~0x7E,~0x3C,~0x18};//由于字模软件生成的段码适合段码端是阳极点亮的LED灯,但我们这款LED点阵屏的断码端对应阴极点亮,因此取反即可。
    void dotMatrix_Init(void)//初始化函数,由于P0端被复用,因此我们这里要对复用的锁存器的Latch Enable进行失能。
    {
    	DULA = 0;
    	WELA = 0;
    	DIOLA = 0;
    }
    void dotMatrixDelay_1ms()		//@11.0592MHz
    {
    	unsigned char data i, j;
    
    	_nop_();
    	i = 4;
    	j = 192;
    	do
    	{
    		while (--j);
    	} while (--i);
    
    }
    void Static_dotMatrix(uint_8 i , uint_8 num)//静态点亮1行。
    {
    	DIOLA = 1;
    	DIOLA_Byte = 1 << i;
    	DIOLA = 0;
    	DULA = 1;
    	DULA_Byte = num;
    	DULA = 0;
    }
    void Colse_dotMatrix(void)
    {
    	DIOLA = 1;
    	DIOLA_Byte = 0x00;
    	DIOLA = 0;
    	DULA = 1;
    	DULA_Byte = 0xFF;
    	DULA = 0;
    }
    void Dynamic_dotMatrix(uint_8 *DMS)//动态点亮8行。
    {
    	Static_dotMatrix(0 , DMS[0]);
    	dotMatrixDelay_1ms();
    	Colse_dotMatrix();
    	//如果点亮完之后不关闭,那么LED点阵屏刷新的时候,不该亮的灯会微微发光。
    	Static_dotMatrix(1 , DMS[1]);
    	dotMatrixDelay_1ms();
    	Colse_dotMatrix();
    	
    	Static_dotMatrix(2 , DMS[2]);
    	dotMatrixDelay_1ms();
    	Colse_dotMatrix();
    	
    	Static_dotMatrix(3 , DMS[3]);
    	dotMatrixDelay_1ms();
    	Colse_dotMatrix();
    	
    	Static_dotMatrix(4 , DMS[4]);
    	dotMatrixDelay_1ms();
    	Colse_dotMatrix();
    	
    	Static_dotMatrix(5 , DMS[5]);
    	dotMatrixDelay_1ms();
    	Colse_dotMatrix();
    	
    	Static_dotMatrix(6 , DMS[6]);
    	dotMatrixDelay_1ms();
    	Colse_dotMatrix();
    	
    	Static_dotMatrix(7 , DMS[7]);
    	dotMatrixDelay_1ms();
    	Colse_dotMatrix();
    }
  • 将上面的动态显示放在定时器中断里面:

    • 这样做的目的是方便后面动画代码的实现。

    • c
      //bsp_time_0.c
      //点阵屏的静态显示函数没有发生变化
      #include "bsp_time_0.h"
      uint_8 count_time0 = 0; 
      void Timer0_Isr(void) interrupt 1 //2ms * 20 
      {
      	TL0 = 0xCD;				
      	TH0 = 0xF8;
      	Colse_dotMatrix();
      	Static_dotMatrix(count_time0 , DMS_1[count_time0]);
      	if (++count_time0 == 8)
      		count_time0 = 0;
      }
      void Timer0_Init(void)		//@11.0592MHz,定时2ms
      {	
      	TMOD &= 0xF0;			
      	TMOD |= 0x01;			
      	TL0 = 0xCD;				
      	TH0 = 0xF8;					
      	TF0 = 0;				
      	TR0 = 1;				
      	ET0 = 1;	
      	EA = 1;
      }
  • 附一张爱心照片送给大家:

    • QQ图片20230611213816

4.点阵屏动画显示:

  • 让8 x 8点阵屏滚动显示:CCNUIOT

    • 正常来讲,每一个字符对应一张图片,那么CCNUIOT对应7张图片,这七张图片对应的点阵区域是(8 x 7) x 8 = 56 x 8。
    • image-20230611202500793
    • 点阵屏动画显示的关键:
      • 每 8 行组成一张点阵图片,每向上移动一行就出现一张新图片。
      • 对于56 x 8的点阵来讲,一共有56 - 8 = 48张图片。
      • 用一个变量DMS_index来代表每张图片的起始位置,[DMS_index,DMS_index + 7]代表了当前的图片,500ms 改变一张图片,并且每张图片都伴随着动态刷新,这样图片就变成动画了。
        • 在本例中,DMS_index的最大值是48,即最后一张图片对应的起始位置。
  • 未优化版代码:

    • c
      //其他代码均没有变化
      uint_8 count_time0 = 0;//用于扫描8行LED灯
      uint_8 count_time1 = 0;//用于定时500ms
      code uint_8 DMS_1[] = {
      	0x01,0x00,0xFC,0xFC,0xFC,0x00,0x01,0xFF,
      	0x01,0x00,0xFC,0xFC,0xFC,0x00,0x01,0xFF,
      	0x3C,0x30,0x20,0x04,0x0C,0x3C,0x3C,0xFF,
      	0x3C,0x3C,0x3C,0x3C,0x3C,0x00,0x00,0xFF,
      	0x00,0x00,0xE7,0xE7,0xE7,0x00,0x00,0xFF,
      	0xC3,0x99,0x3C,0x7E,0x3C,0x99,0xC3,0xFF,
      	0x00,0x00,0xE7,0xE7,0xE7,0xE7,0xE7,0xFF
      };
      uint_8 DMS_index = 0;//用于定位每张图片的起始位置
      void Timer0_Isr(void) interrupt 1 //2ms * 20 
      {
      	TL0 = 0xCD;				
      	TH0 = 0xF8;
      	Colse_dotMatrix();
      	Static_dotMatrix(count_time0 , DMS_1[count_time0 + DMS_index]);
          //DMS_index是每张图片的起始位置,加上count_time0就能表示每张图片的所有行。
      	if (++count_time0 == 8)
      		count_time0 = 0;//扫描8行LED灯
      	if (++count_time1 == 250)//定时500ms
      	{
      		if (DMS_index++ == 48)//由于DMS_index最大是48,因此当DMS_index = 49的时候,清0。
      			DMS_index = 0;
      		count_time1 = 0;
      	}
      }
    • 该版本代码的问题:

      • 当显示最后一张图片的时候,也就是完整的T,接下来的图片是完整的C,没有滚动到C的效果
        • 这是因为在我们的程序中,DMS_index最大是48,因为当count_time0 = 7的时候,DMS_index+count_time0 = 55,也就是数组的最后一个元素,因此DMS_index不能再大了。
          • 但如果想显示滚动的效果,DMS_index要到55*(对应上图的最后一行黑边),并且DMS_index的下一个值是0。
        • 通过上面的问题我们也找到了问题:DMS_1数组的索引。
          • 因此,我们采用取模的思想,对DMS_1数组的索引进行取模,即模56,这样,DMS_index的值就能到达55(使用一个if判断语句让下一个值是0),同时取模不影响静态显示的正确性。
  • 优化版代码:

    • c
      //其他代码均没有变化
      uint_8 count_time0 = 0;//用于扫描8行LED灯
      uint_8 count_time1 = 0;//用于定时500ms
      code uint_8 DMS_1[] = {
      	0x01,0x00,0xFC,0xFC,0xFC,0x00,0x01,0xFF,
      	0x01,0x00,0xFC,0xFC,0xFC,0x00,0x01,0xFF,
      	0x3C,0x30,0x20,0x04,0x0C,0x3C,0x3C,0xFF,
      	0x3C,0x3C,0x3C,0x3C,0x3C,0x00,0x00,0xFF,
      	0x00,0x00,0xE7,0xE7,0xE7,0x00,0x00,0xFF,
      	0xC3,0x99,0x3C,0x7E,0x3C,0x99,0xC3,0xFF,
      	0x00,0x00,0xE7,0xE7,0xE7,0xE7,0xE7,0xFF
      };
      uint_8 DMS_index = 0;//用于定位每张图片的起始位置
      void Timer0_Isr(void) interrupt 1 //2ms * 20 
      {
      	TL0 = 0xCD;				
      	TH0 = 0xF8;
      	Colse_dotMatrix();
      	Static_dotMatrix(count_time0 , DMS_1[(count_time0 + DMS_index) % 56]);
           //DMS_index是每张图片的起始位置,加上count_time0就能表示每张图片的所有行。
          //为了避免数组索引越界,采用模56(即数组大小)
      	if (++count_time0 == 8)
      		count_time0 = 0;
      	if (++count_time1 == 250)
      	{
      		if (DMS_index++ == 55)//注意,这里到55,如果到56的话,56和0实际上取模56之后是相同的,即产生了两张一样的点阵图片。
      			DMS_index = 0;
      		count_time1 = 0;
      	}
      }

Released under the MIT License.