LED 点阵简介

        LED 点阵是由发光二极管排列组成的显示器件,如下所示为8*8LED 点阵:

        以 16*16LED 点阵为例,其内部结构图如下所示:

        16*16 点阵共由 256 个发光二极管组成,且每个发光二极管是放置在行线和列线的交叉点上,正极连接成一行,负极连接成一排。当对应的某一行置 1 电平,某一列置 0 电平,则行列交点处相应的二极管就亮。

硬件部分

        使用到硬件资源如下:

  1. 16*16LED 点阵模块
  2. 74HC595 模块

        74HC595 模块电路在前文IO 扩展(串转并)介绍过,电路如下:

        该模块独立,使用了 4 块级联的 74HC595 芯片,即 RCLK 和 SRCLK 管脚并联在一起,并且 74HC595 (A)的串行数据输出 /QH 连接到 74HC595(B)的串行数据输入口 SI(SER),而 74HC595(B)的输出/ QH 又连接到 74HC595(C)的串行输入口 SI(SER),依次类推。 

        74HC595 需要用到的控制管脚有 RCK(RCLK)、SCK(SRCLK)、SI(SER) 。

        要想控制 LED 点阵,可以将单片机管脚按照 74HC595 芯片的通信时序要求来传输数据。因为使用了 4 片 74HC595 芯片,A、B 两块控制点阵行: POS1-POS16,C、D 两块控制点阵列: NEG1-NEG16,即可控制 LED 点阵。根据 LED 发光二极管导通原理,当阳极(行)为高电平,阴极(列)为低电平则点亮,否则熄灭。因此通过单片机发送 4 组设置行列的数据,通过74HC595将这四组数据分配到对应输出,即可控制 LED 点阵。

软件部分

LED 点阵~点亮一个点

        代码很简单,与上一个实验核心代码是一样的,主要是理解如何让 LED 点阵的左上角第一个LED点亮。实际上就是将第一个点对应的行为高电平,列为低电平即可。也就是让第一个 74HC595 输出 0X01(0000 0001),第二个 74HC595 输出 0X00(0000 0000),这样点阵第一行就是高电平,其余行为低电平。而第 3 个 74HC595 输出 0XFE(1111 1110),第 4 个 74HC595 输出 0XFF (1111 1111),这样点阵第一列就是低电平,其余列为低电平,从而让 LED 点阵第一个LED点亮。

        源代码:

#include "reg52.h"

typedef unsigned int u16;//使用关键字 typedef 对系统默认数据类型 unsigned int 重新命名
typedef unsigned char u8;
 
/*定义74HC595控制管脚*/
sbit SI=P3^4;	 //串行数据输入
sbit RCK=P3^5;	 //存储寄存器时钟输入
sbit SCK=P3^6;   //移位寄存器时钟输入

void delay_10us(u16 ten_us)//延时函数,ten_us=1 时,大约延时 10us
{
 	while(ten_us--);
}

void HC595_WRITE_DATA(u8 cs_data1,u8 cs_data2,u8 cs_data3,u8 cs_data4) //最后两个形参表示所有列的数据,前面两个形参表示所有行的数据
{
 	u8 i=0;
	for(i=0;i<8;i++)//传入第四个形参
	{
	 	SI=cs_data4>>7;	 //因为595芯片先传高位,所以将最高位移到最低位(比如 12345678——>00000001,此处12345678这样写只为方便理解),然后给个上升沿把1传入寄存器中
		
		cs_data4<<=1;	 /*这一步是将原先cs_data(比如12345678)的第二个高位(1后面的2)移到第一个高位(即变成了23455670),这一步是
		                   为了执行下一次循环的时候,将最高位移到最低位做准备,也就是SI_SER=cs_date4>>7;*/
						   	
		SCK=0;	 //此处开始产生上升沿,先设为0,后面再设置为1,上升沿就有了,数据在 SCK 的上升沿输入存储器
		delay_10us(1);
		SCK=1;	  //此刻,最高位就输入到寄存器中了,然后下一次循环依次按照此方法传入第二个高位、第三个....
		delay_10us(1);
	}
	for(i=0;i<8;i++)//传入第三个形参
	{
	 	SI=cs_data3>>7;	 
		cs_data3<<=1;	 		              	
		SCK=0;
		delay_10us(1);
		SCK=1;
		delay_10us(1);
	}
	for(i=0;i<8;i++)//传入第二个形参
	{
	 	SI=cs_data2>>7;	 
		cs_data2<<=1;	 		              	
		SCK=0;
		delay_10us(1);
		SCK=1;
		delay_10us(1);
	}
	for(i=0;i<8;i++)//传入第一个形参
	{
	 	SI=cs_data1>>7;	 
		cs_data1<<=1;	 		              	
		SCK=0;
		delay_10us(1);
		SCK=1;
		delay_10us(1);
	}
	RCK=0;	 //这里需要一个上升沿,将存储器的数据,在 RCK 的上升沿的作用下,输入到锁存器中(此时输出使能控制端/G或/OE接地)
	delay_10us(1);
	RCK=1;
}

void main(void)	 //主函数
{				  	   
	while(1) 
	{
		HC595_WRITE_DATA(0x01,0x00,0xfe,0xff); 		
	}
}

        现象:

LED 点阵~显示数字

        同时点亮多个 LED 灯来显示数字,实现行列不同位置亮灯,需要用到动态数码管的动态扫描原理。在第一行亮灯一段时间以后灭掉,点亮第二行一段时间以后灭掉,点亮第三行一 段时间以后灭掉,如此点亮,直到八行全部点亮一次。在第一行点亮到最后一行灭掉的总时间不能超过人肉眼可识别的时间,即 24 毫秒。在每一行点亮的时候, 给列一个新的数据,此时对应列的数据就可以体现在这行上要点亮的灯上。这样就和动态数码管的显示一样,只不过数码管的 LED 灯是段值。这里使用 LED 点阵显示数字,也是多个 LED 同时点亮。

        要想在点阵上显示数字等字符,首先要获取在 LED 点阵上显示数字字符所需的数据,即一个数字字符在 LED 点阵上显示,对应的每行每列都会有一些灯点亮或者熄灭,这样就会构成一组数据,也就是数字字符的显示数据,只要将这些数据通过 74HC595 发送到点阵对应的行或列就能显示数字字符。

        通常使用"文字取模软件" 获取数字字符数据。文字取模软件链接: https://pan.baidu.com/s/15o5n-w3xjDEF0O2zeUNMIg?pwd=57rf 提取码: 57rf 

        文字取模软件使用方法:

        1,双击打开该软件,首先选择“基本操作->新建图像”,设置图像的宽度和,度为 16,点击确定后将在显示窗口出现一个 16*16 的白色格子,这个就类似于 16*16LED 点阵,具体操作如下:

        2,选择“模拟动画”, 后点击“放大格点”,如下所示:

        3,然后在这个 16*16 白色格子里面点击,点击后会在对应位置出现 一个黑点,表示在 LED 点阵对应位置的 LED 灯点亮,未点击位置(白色)表示 LED 点阵对应位置的 LED 灯熄灭。 比如在 16*16LED 点阵上显示数字 0,那么可以在图中 16*16 白色框内通过点击对应位置描出一个数字 0 的外形,如下所示:

        4,然后设置取模数据的取模方式等内容,选择“参数设置”后点击“其他 选项”。具体操作如下:

         5,然后点击“取模方式”,选择 C51 格式选项,然后在点阵生成区自动会生成数字字符对应的数据(如果是使用汇编编程,那么汇编对应的汉字数据可选择 A51 格式)。如下所示:

        6,数字 0 的数据生成之后,然后将生成的数据复制到程序定义的数组中,如下所示:

//LED点阵显示数字0的列数据
u8 gled_col[32]=
{0x00,0x00,0xE0,0x03,0x10,0x04,0x08,0x08,0x04,0x10,0x04,0x10,0x04,0x10,0x04,0x10,
0x04,0x10,0x04,0x10,0x04,0x10,0x04,0x10,0x08,0x08,0x10,0x04,0xE0,0x03,0x00,0x00};

          这些数据就是上述描绘的数字 0 从上到下依次每行对应的列数据。比如0x00,0x00表示第一行。0xE0,0x03表示第二行,0xE0为第二行前8位,0x03为第二行后8位。

        这里解释一下参数设置中一些选项的意思,横向取模是按行读取数据,纵向取模是按列读取数据。字节倒序指的是字节数据的高位和低位的位置。比如第一行为:0x00,0x00,第二行为:0xE0,0x03。以第二行为例,0xE3化为二进制:1110 0000, 0x03化为二进制:0000 0011,因为设置了字节倒序,所有右边为高位,左边为低位,体现在图像上第二行就是:0000011111000000  。同理,第三行 0x10,0x04,体现在图像上第三行就是:0000100000100000 。而且,取模的参数设置,应该视具体硬件连接和程序设计的情况而定。

        既然是动态扫描,就需要不断的扫描每列或每行,因此可以把 LED 点阵的行控制也用数组存储起来,为后面循环调用提供方便。根据数字 0 取模的数据特点是从上至下每行对应的列数据,因此扫描时也应该从上至下的顺序,如下:

//LED 点阵显示数字 0 的行数据
u8 gled_row[32]=
{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

         其它数字或字符及简单图形的显示取模方法与上述类似。此处使用了_nop_();函数,详细了解请阅览文章_nop_();的由来和作用

        源代码:

#include "reg52.h"
#include "intrins.h"

typedef unsigned int u16;//使用关键字 typedef 对系统默认数据类型 unsigned int 重新命名
typedef unsigned char u8;
 
/*定义74HC595控制管脚*/
sbit SI=P3^4;	 //串行数据输入
sbit RCK=P3^5;	 //存储寄存器时钟输入
sbit SCK=P3^6;   //移位寄存器时钟输入

//LED点阵显示数字0的列数据
u8 gled_col[32]=
{0x00,0x00,0xE0,0x03,0x10,0x04,0x08,0x08,0x04,0x10,0x04,0x10,0x04,0x10,0x04,0x10,
0x04,0x10,0x04,0x10,0x04,0x10,0x04,0x10,0x08,0x08,0x10,0x04,0xE0,0x03,0x00,0x00};
//LED点阵显示数字0的行数据
u8 gled_row[32]=
{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

void delay_10us(u16 ten_us)//延时函数,ten_us=1 时,大约延时 10us
{
 	while(ten_us--);
}

void HC595_WRITE_DATA(u8 cs_data1,u8 cs_data2,u8 cs_data3,u8 cs_data4) //最后两个形参表示所有列的数据,前面两个形参表示所有行的数据
{
 	u8 i=0;
	for(i=0;i<8;i++)//传入第四个形参
	{
	 	SI=cs_data4>>7;	 //因为595芯片先传高位,所以将最高位移到最低位(比如 12345678——>00000001,此处12345678这样写只为方便理解),然后给个上升沿把1传入寄存器中
		
		cs_data4<<=1;	 /*这一步是将原先cs_data(比如12345678)的第二个高位(1后面的2)移到第一个高位(即变成了23455670),这一步是
		                   为了执行下一次循环的时候,将最高位移到最低位做准备,也就是SI_SER=cs_date4>>7;*/
						   	
		SCK=0;	 //此处开始产生上升沿,先设为0,后面再设置为1,上升沿就有了,数据在 SCK 的上升沿输入存储器
		_nop_();  //该函数是在51单片机中用的延时函数,表示执行一条没有什么意义的指令,延时一个指令周期
		//delay_10us(5);
		SCK=1;	  //此刻,最高位就输入到寄存器中了,然后下一次循环依次按照此方法传入第二个高位、第三个....
		_nop_();  
		//delay_10us(5);		
	}
	for(i=0;i<8;i++)//传入第三个形参
	{
	 	SI=cs_data3>>7;	 
		cs_data3<<=1;	 		              	
		SCK=0;
		_nop_();
		SCK=1;
		_nop_();
	}
	for(i=0;i<8;i++)//传入第二个形参
	{
	 	SI=cs_data2>>7;	 
		cs_data2<<=1;	 		              	
		SCK=0;
		_nop_();
		SCK=1;
		_nop_();
	}
	for(i=0;i<8;i++)//传入第一个形参
	{
	 	SI=cs_data1>>7;	 
		cs_data1<<=1;	 		              	
		SCK=0;
		_nop_();
		SCK=1;
		_nop_();
	}
	RCK=0;	 //这里需要一个上升沿,将存储器的数据,在 RCK 的上升沿的作用下,输入到锁存器中(此时输出使能控制端/G或/OE接地)
	delay_10us(1);
	RCK=1;
}

void main(void)	   //主函数
{
	u8 i=0;				  	   
	while(1)  
	{
		for(i=0;i<16;i++)	//16行,一行一轮回,16个轮回
		{
		 	HC595_WRITE_DATA(gled_row[i],gled_row[i+16],~gled_col[i*2],~gled_col[i*2+1]); //传送行列选数据
			delay_10us(10);	 //延时一段时间,等待显示稳定
			HC595_WRITE_DATA(0x00,0x00,0x00,0x00); 	//消影	
		}	
	}
}

        现象:

LED 点阵~显示汉字

        取模软件不仅可以手动描点取模,还可以直接取汉字等字符数据,这里以汉字宋体常规小四号“风”为例,教大家如何使用该软件来获取汉字数据。

        1,首先打开文字取模软件,选择“参数设置”,对字体大小进行设置, 如下所示

        2, 然后设置取模数据的取模方式等内容,具体操作如下:

        3, 然后在文字输入区内输入所要显示的汉字:风,然后按住键盘的 Ctrl+Enter 组合键完成汉字的输入。此时显示区就会显示刚才输入的汉字,通过放大格点可观察的更清楚,默认生成的是 16*16 大小的点阵数据。 具体操作如下:

        4, 点击取模方式,选择 C51 格式选项,然后在点阵生成区自动会生成汉字对应的数据(如果是使用汇编开发,那么汇编对应的汉字数据可选择 A51 格式)。如下所示:

        至此,就完成了汉字取模,然后将生成的汉字数据复制到程序内定义的数组中,如下所示:

//LED 点阵显示汉字“风”数据 
u8 gled_col[32]= 
{0x00,0x00,0xFC,0x0F,0x04,0x08,0x04,0x08,0x14,0x0A,0x24,0x0A,0x44,0x09,0x44,0x09,
0x84,0x08,0x84,0x08,0x44,0x09,0x44,0x49,0x24,0x52,0x12,0x52,0x02,0x60,0x01,0x40}; 

        源代码:

#include "reg52.h"
#include "intrins.h"

typedef unsigned int u16;//使用关键字 typedef 对系统默认数据类型 unsigned int 重新命名
typedef unsigned char u8;
 
/*定义74HC595控制管脚*/
sbit SI=P3^4;	 //串行数据输入
sbit RCK=P3^5;	 //存储寄存器时钟输入
sbit SCK=P3^6;   //移位寄存器时钟输入

//LED 点阵显示汉字“风”数据 
u8 gled_col[32]= 
{0x00,0x00,0xFC,0x0F,0x04,0x08,0x04,0x08,0x14,0x0A,0x24,0x0A,0x44,0x09,0x44,0x09,
0x84,0x08,0x84,0x08,0x44,0x09,0x44,0x49,0x24,0x52,0x12,0x52,0x02,0x60,0x01,0x40}; 
//LED点阵显示数字0的行数据
u8 gled_row[32]=
{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

void delay_10us(u16 ten_us)//延时函数,ten_us=1 时,大约延时 10us
{
 	while(ten_us--);
}

void HC595_WRITE_DATA(u8 cs_data1,u8 cs_data2,u8 cs_data3,u8 cs_data4) //最后两个形参表示所有列的数据,前面两个形参表示所有行的数据
{
 	u8 i=0;
	for(i=0;i<8;i++)//传入第四个形参
	{
	 	SI=cs_data4>>7;	 //因为595芯片先传高位,所以将最高位移到最低位(比如 12345678——>00000001,此处12345678这样写只为方便理解),然后给个上升沿把1传入寄存器中
		
		cs_data4<<=1;	 /*这一步是将原先cs_data(比如12345678)的第二个高位(1后面的2)移到第一个高位(即变成了23455670),这一步是
		                   为了执行下一次循环的时候,将最高位移到最低位做准备,也就是SI_SER=cs_date4>>7;*/
						   	
		SCK=0;	 //此处开始产生上升沿,先设为0,后面再设置为1,上升沿就有了,数据在 SCK 的上升沿输入存储器
		_nop_();  //该函数是在51单片机中用的延时函数,表示执行一条没有什么意义的指令,延时一个指令周期
		//delay_10us(5);
		SCK=1;	  //此刻,最高位就输入到寄存器中了,然后下一次循环依次按照此方法传入第二个高位、第三个....
		_nop_();  
		//delay_10us(5);		
	}
	for(i=0;i<8;i++)//传入第三个形参
	{
	 	SI=cs_data3>>7;	 
		cs_data3<<=1;	 		              	
		SCK=0;
		_nop_();
		SCK=1;
		_nop_();
	}
	for(i=0;i<8;i++)//传入第二个形参
	{
	 	SI=cs_data2>>7;	 
		cs_data2<<=1;	 		              	
		SCK=0;
		_nop_();
		SCK=1;
		_nop_();
	}
	for(i=0;i<8;i++)//传入第一个形参
	{
	 	SI=cs_data1>>7;	 
		cs_data1<<=1;	 		              	
		SCK=0;
		_nop_();
		SCK=1;
		_nop_();
	}
	RCK=0;	 //这里需要一个上升沿,将存储器的数据,在 RCK 的上升沿的作用下,输入到锁存器中(此时输出使能控制端/G或/OE接地)
	delay_10us(1);
	RCK=1;
}

void main(void)	   //主函数
{
	u8 i=0;				  	   
	while(1)  
	{
		for(i=0;i<16;i++)	//16行,一行一轮回,16个轮回
		{
		 	HC595_WRITE_DATA(gled_row[i],gled_row[i+16],~gled_col[i*2],~gled_col[i*2+1]); //传送行列选数据
			delay_10us(10);	 //延时一段时间,等待显示稳定
			HC595_WRITE_DATA(0x00,0x00,0x00,0x00); 	//消影	
		}	
	}
}

        现象:

LED 点阵~显示图

        跟上面原理是一样的,只是数据不同罢了,使用取模软件获取16*16 大小的点阵数据之后,替换掉程序中的点阵的行列数据就行,这里直接给出程序:

/**************************************************************************************
实验名称:LED点阵实验(显示图像)
接线说明:	
实验现象:下载程序后,8*8LED点阵显示心形
注意事项:																				  
***************************************************************************************/
#include "reg52.h"
#include "intrins.h"

typedef unsigned int u16;	//对系统默认数据类型进行重定义
typedef unsigned char u8;

//定义74HC595控制管脚
sbit SRCLK=P3^6;	//移位寄存器时钟输入
sbit rCLK=P3^5;		//存储寄存器时钟输入
sbit SER=P3^4; 		//串行数据输入


//LED点阵显示爱心数据
//LED点阵列扫描数据
u8 gled_col[32]=
{0x00,0x00,0x66,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x7E,0x00,0x3C,0x00,0x18,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
//LED点阵行扫描数据
u8 gled_row[32]=
{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

/*******************************************************************************
* 函 数 名       : delay_10us
* 函数功能		 : 延时函数,ten_us=1时,大约延时10us
* 输    入       : ten_us
* 输    出    	 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
	while(ten_us--);	
}

/*******************************************************************************
* 函 数 名         : hc595_write_data(u8 dat)
* 函数功能		   : 向74HC595写入4个字节的数据
* 输    入         : dat1:对应74HC595(A)输出第1行-第8行
					 dat2:对应74HC595(B)输出第9行-第16行
					 dat3:对应74HC595(C)输出第1列-第8列
					 dat4:对应74HC595(D)输出第9列-第16列
* 输    出         : 无
*******************************************************************************/
void hc595_write_data(u8 dat1,u8 dat2,u8 dat3,u8 dat4)
{
	u8 i=0;
	
	for(i=0;i<8;i++)//循环8次即可将一个字节写入寄存器中
	{
		SER=dat4>>7;//优先传输一个字节中的高位
		dat4<<=1;//将低位移动到高位
		SRCLK=0;
//		delay_10us(1);
		_nop_();
		SRCLK=1;
		_nop_();
//		delay_10us(1);//移位寄存器时钟上升沿将端口数据送入寄存器中	
	}
	
	for(i=0;i<8;i++)
	{
		SER=dat3>>7;
		dat3<<=1;
		SRCLK=0;
//		delay_10us(1);
		_nop_();
		SRCLK=1;
		_nop_();
//		delay_10us(1);//移位寄存器时钟上升沿将端口数据送入寄存器中	
	}

	for(i=0;i<8;i++)
	{
		SER=dat2>>7;
		dat2<<=1;
		SRCLK=0;
//		delay_10us(1);
		_nop_();
		SRCLK=1;
		_nop_();
//		delay_10us(1);//移位寄存器时钟上升沿将端口数据送入寄存器中	
	}

	for(i=0;i<8;i++)
	{
		SER=dat1>>7;
		dat1<<=1;
		SRCLK=0;
//		delay_10us(1);
		_nop_();
		SRCLK=1;
		_nop_();
//		delay_10us(1);//移位寄存器时钟上升沿将端口数据送入寄存器中	
	}

	rCLK=0;
	delay_10us(1);
	rCLK=1;//存储寄存器时钟上升沿将前面写入到寄存器的数据输出	
}


/*******************************************************************************
* 函 数 名       : main
* 函数功能		 : 主函数
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void main()
{	
	u8 i=0;

	while(1)
	{			
		for(i=0;i<16;i++)//循环16次扫描16行、列
		{
			hc595_write_data(gled_row[i],gled_row[i+16],~gled_col[i*2],~gled_col[i*2+1]);//传送行列选数据
			delay_10us(10);//延时一段时间,等待显示稳定
			hc595_write_data(0x00,0x00,0x00,0x00);//消影	
		}									
	}		
}

扩展(实现滚屏向下滚动显示的效果)

        鉴于很多人问如何实现滚屏显示的效果,所以这里补充一下。

       观看过上面文章之后,各位应该知晓,LED点阵显示图案靠的是点亮特定位置的LED组成我们想要的图案,所以,关键点在于LED 点阵动态扫描的行列扫描数据。其中,行扫描数据大家应该注意到了是固定的,行扫描数据是用来为16*16点阵中的其中一行提供高电平的,这一行中的哪一个LED最终被点亮取决于扫描数据。如图所示(以第一行为例):

       因此,要实现LED点阵显示的图案向下滚动显示的效果,关键在于扫描数据的移动,其实就是将所有列的 16 位数据整体做“循环右移一位”。

       以在上一节“LED点阵~显示图”为例,先解释一下主函数的含义,for循环的一次循环实现一行LED的亮灭,16次循环就是16行LED整体的亮灭,因为16行LED整体的亮灭这个过程的时间很短,在人眼不能分辨的时间之外,所以for循环的效果就是人眼能在LED点阵上看到一副图案,for循环在while(1)无限循环内,使得图案一直保持这个状态显示。

       知晓了主函数的含义之后,就能明白,若是图案向下移动一行,就得改变函数hc595_write_data()中的后两位“~gled_col[i*2],~gled_col[i*2+1]”,使传送的列的扫描数据是数组gled_col[]中原有数据的后面两位,即让gled_col[]的数据循环右移两位之后,再让函数hc595_write_data()显示,图案就整体下移了一行。

       代码:LED 点阵“爱心”图案向下循环滚动显示

/**************************************************************************************
实验名称:LED点阵实验(显示汉字滚动)
接线说明:使用74HC595控制16x16点阵行列
实验现象:下载程序后,16*16 LED 点阵中显示的“爱心”图案会向下移动并循环滚动
注意事项:																			  
***************************************************************************************/
#include "reg52.h"
#include "intrins.h"

typedef unsigned int  u16;
typedef unsigned char u8;

// 74HC595 控制管脚定义
sbit SRCLK = P3^6;	// 移位寄存器时钟输入
sbit rCLK  = P3^5;	// 存储寄存器时钟输入
sbit SER   = P3^4;  // 串行数据输入

// LED点阵显示爱心数据(每列两个字节,共16列)
// 列扫描数据
u8 gled_col[32]=
{
    0x00,0x00,0x66,0x00,0xFF,0x00,0xFF,0x00,
	0xFF,0x00,0x7E,0x00,0x3C,0x00,0x18,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

// 行扫描数据(固定不变)
u8 gled_row[32] =
{
    0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80
};

// 简单延时,ten_us=1时约为10微秒(粗略)
void delay_10us(u16 ten_us)
{
	while(ten_us--);
}

// 向 4 个 74HC595 寄存器写入数据(行、列各 2 个)
void hc595_write_data(u8 dat1, u8 dat2, u8 dat3, u8 dat4)
{
	u8 i;
	for(i = 0; i < 8; i++) {
		SER = dat4 >> 7;
		dat4 <<= 1;
		SRCLK = 0; _nop_(); SRCLK = 1; _nop_();
	}
	for(i = 0; i < 8; i++) {
		SER = dat3 >> 7;
		dat3 <<= 1;
		SRCLK = 0; _nop_(); SRCLK = 1; _nop_();
	}
	for(i = 0; i < 8; i++) {
		SER = dat2 >> 7;
		dat2 <<= 1;
		SRCLK = 0; _nop_(); SRCLK = 1; _nop_();
	}
	for(i = 0; i < 8; i++) {
		SER = dat1 >> 7;
		dat1 <<= 1;
		SRCLK = 0; _nop_(); SRCLK = 1; _nop_();
	}
	rCLK = 0;
	delay_10us(1);
	rCLK = 1; // 输出数据
}

// 将 gled_col[32] 的内容整体循环右移两位
void shift_array_right_2bytes(u8 *arr, int size)
{
    u8 last1 = arr[size - 2];
    u8 last2 = arr[size - 1];

    for (int i = size - 1; i >= 2; i--) {
        arr[i] = arr[i - 2];
    }

    arr[0] = last1;
    arr[1] = last2;
}

// 主函数:实现扫描显示 + 向下滚动动画
void main()
{
	u8 i = 0;
	u16 t = 0; // 用于控制滚动速度

	while(1)
	{
		for(i = 0; i < 16; i++) // 扫描16行
		{
			hc595_write_data(
				gled_row[i], gled_row[i + 16],
				~gled_col[i * 2], ~gled_col[i * 2 + 1]
			);
			delay_10us(1000);
			hc595_write_data(0x00, 0x00, 0x00, 0x00); // 消影
		}

		t++;
		if(t >= 100) // 控制滚动速度(数值越大越慢)
		{
			t = 0;
			shift_array_right_2bytes(); // 使gled_col循环右移两位,经过下一次循环的for循环之后,图像向下移动一行
		}
	}
}

       重点说明:实现滚屏效果的关键在于列扫描数据的变化。主函数中t的作用是控制滚动的速率,当t<100时,图案保持原有位置不变,一直在不断地刷新显示图案,当t=100后,程序进入if函数,使数组gled_col[]循环右移两位,hc595_write_data()传递改变过后的数组gled_col[]的列数据,使得显示的图案整体下移了一行,同时t归零,在t<100的这一段时间内,整体下移了一行的图案也保持原有位置不变,直至t=100,再次改变数组gled_col[]的值,使图案又整体下移一行,所以,t是用来控制图案下移的速率的指标

       滚屏的方式还有很多,比如:“左右滚动”、“多字滚动轮播”、“滚动队列”,这里就不做补充了,感兴趣的同学可以查看相关资料或者问Chartgpt。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐