失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > 51定时中断系统控制LED点阵屏显示逐帧动画

51定时中断系统控制LED点阵屏显示逐帧动画

时间:2023-04-30 19:54:01

相关推荐

51定时中断系统控制LED点阵屏显示逐帧动画

写在前面

最近回头看之前写的文章感到一种很浓的公式感,我确实是提前写好了模板每次都套用,整篇看下来感觉就像是在交老师布置的实验报告,看起来很成熟但实际上背离了自己的初衷,接下来我会尽可能的复现自己在做的时候的尝试和思考过程

一、简单介绍

类似于笔记地总结LED点阵屏使用需要用到的模块知识静态显示图形逐帧显示,这里只做了3帧,“I ❤ U”,帧率可调

二、原理和知识总结

1、LED点阵屏

LED点阵屏的结构和数码管非常类似,需要对其逐行或者逐列扫描,一次扫描显示一行/列的亮灭,当扫描间隔极短时人眼看来就像是同时显示

通过原理图显示LED点阵屏的列接在了单片机的P0 I/O口,而行接在了D0~D7,就是接下来要提的

2、74HC595

功能简单的说就是串转并输出,595内部有一个8位缓冲区移位寄存器,先接收串行输入数据一位后将其移位,然后再接收下一位,8位接收完了就将8位数据并行一次输出

OE非:也许是open enable?低电平芯片输出有效

SER: 串行输入

SRCLK: SERCLK 的缩写,上升沿时移位寄存器的数据移位

RCLK : 上升沿时移位寄存器的数据进入数据存储寄存器(也就是送到了输出端)并直到下一个上升沿来之前保持不变

下面是图解,较好理解

2、Sfr和Sbit声明

sfr(special function register):特殊功能寄存器声明

例:sfr P0 = 0x80//声明P0口寄存器,物理地址为0x80

普中的板子开发板原理图里可以看到P0就是连接LED点阵的列端口,即通过这个命令把单片机地址为0x80的寄存器资源分配给了外设LED点阵屏,当我们要使用其他寄存器的时候也可以自己声明寄存器,分配给需要的外设,在常用的头文件<REGX52.H>中可以找到相关声明

这个物理地址可以在官方手册里找到

可位寻址/不可位寻址:在单片机系统中,操作任意寄存器或者某一位的数据时,必须给出其物理地址,又因为一个寄存器里有8位,所以位的数量是寄存器数量的8倍,单片机无法对所有位进行编码,故每8个寄存器中,只有一个是可以位寻址的。对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、“^=”的方法进行位操作,

sbit(special bit):特殊位声明

可用于声明某寄存器某一位

例:sbit P0_1 = 0x81; 或 sbit P0_1 = P0^1 //声明P0寄存器的第一位

2.2、实际应用

51单片机内部寄存器都是已经声明过了的,但实际应用却需要对着电路原理图才知道P0、P1、P2以及其中的具体位等等这些是分配给了哪些外设(因为P0之类名字本身没有特殊意义)

举个例子,如果在某个项目里,P0分给了矩阵按键,整个代码中P0将会多次出现,而移植到其他项目里是,就需要看原来项目的原理图查看P0的分配,当然了可以采用注释,但注释也最多写一行,而代码如果复杂了前面的注释后面可能会忘记,综合考虑可以自己对寄存器进行声明

比如本次要用到的SER,SERLCK,RLCK,找到对应寄存器地址重新声明,注意声明名称不能重复

首先看到开发板这三个分别对应P3_4、P3_6、P3_5,在头文件里已经声明过了寄存器地址,我们没必要更改头文件,就直接重新声明就可以

//自定义寄存器名称,方便使用sbit SERCLK = P3 ^ 6; //上升沿移位 sbit RCK = P3 ^ 5; //RCLK,上升沿锁存,并行输出sbit SER = P3 ^ 4; //串行输入位

关于sbit还有一个问题是,sbit声明的部分是编译器预处理的部分,必须放在函数外部

三、程序设计和实现步骤

3.1、尝试显示静态图像

3.1.1 74HC595写函数

功能:将要写入的一个字节串行输入到74HC595中,就可以选择点亮点阵屏的哪些行

实现原理:输出寄存器的高位在下,因此将写入的字节数据从高位到低位串行输入和向下移位,依次和0x80(1000 0000)、0x40...取&运算就可以实现串行输入,采用for循环和移位运算符简化

SER = Byte & (0x80>>i); //从高位到低位依次赋给串行输入位SERCLK = 1; //每输入一位给一个上升沿使其移位SERCLK = 0; //一位移位后立刻将SERCLK复位

完整代码

/*** @brief 74HC595写入的一个字节* @param Byte 要写入的字节* @retval 无*/void _74HC595_WriteByte(unsigned char Byte){unsigned char i;for(i=0; i<8; i++){SER = Byte & (0x80>>i); //从高位到低位依次赋给串行输入位SERCLK = 1; //每输入一位给一个上升沿使其移位SERCLK = 0; //一位移位后立刻将SERCLK复位}RCK = 1;//八位串行输入完毕使其并行输出RCK = 0;//输出完立刻复位}

3.1.2、 按列显示LED点阵屏函数

实现原理:

调用74HC595写函数选择要点亮的行,如0x42(0100 0010 )就是选择2和7行选中列为位选信号取低电平,参数Column传递第几列位选信号清0,如果不清零,就无法显示其他列,清0后在while里可以同时调用多次显示不同列

完整代码

/*** @brief LED显示一列数据* @param Column 要显示的那一列(从左到右为0~7列)* @param Data 要显示列的数据,高位在上低位在下,1亮,0灭* @retval 无*/void MatrixLED(unsigned char Column, Data){_74HC595_WriteByte(Data);//选中给高电平点阵屏的行MatrixLED_Port = ~(0x80 >> Column); //选中列,即可确定具体该列LED的亮灭Delay(1);//保证亮度MatrixLED_Port = 0xFF;//位选信号清零,用于再循环中可以同时显示多列}

3.1.3、主函数设计

尝试显示静态图像,只需要在主函数的while里调用MatrixLED()随便敲几个

当然首先得对SERLCK,RLCK初始化,因为单片机一上电就给高电平

SERCLK = 0;//由于一上电SERCLK、RCK都被置高电平,因此需手动赋初值0RCK = 0;

void main(){SERCLK = 0; //由于一上电SERCLK、RCK都被置高电平,因此需手动赋初值0RCK = 0;while(1){MatrixLED(0,a);MatrixLED(1,b);MatrixLED(2,c);MatrixLED(3,d);MatrixLED(4,e);MatrixLED(5,f);MatrixLED(6,g);MatrixLED(7,h);}}

这图怎么才能弄成正的啊...

3.2、逐帧图像显示

做到这就想要让点阵屏一帧一帧的显示图像

1、最开始想法简单粗暴,直接把三帧,,也就是24次调用MatrixLED()写在while里,显示一帧delay一秒,然后清空再写下一帧,为此还写了个“初始化函数”,为了方便

void LED_ShowInit(){MatrixLED(0,0x00);MatrixLED(1,0x00);MatrixLED(2,0x00);MatrixLED(3,0x00);MatrixLED(4,0x00);MatrixLED(5,0x00);MatrixLED(6,0x00);MatrixLED(7,0x00);}

这个主函数极其丑陋,并不打算拿出来丢人

直接上问题,这样写的结果是每一帧刚亮就立刻灭了,因为MatrixLED()中有个位选清零,但是不加清0无法显示多列,直接写在while循环里不能解决问题

2、想到了定时和中断控制,调用之前写过的1ms定时初始化函数,每1ms转到中断函数,调用显示一帧动画,这样亮灭的时间间隔就是1ms(当时是这么以为的)

定义列位选变量,可以用数组,感觉差不多,用于后续更改显示下一帧,

a = 0x00; b = 0x42; c = 0x42; d = 0x7E; e = 0x42; //第一帧列位选变量,用于后续更改显示下一帧f = 0x42; g = 0x00; h = 0x00;

定义计数变量,每一次进入中断函数变量+1,加到1000,改变位选变量显示第二帧,加到2000显示第三帧,按照这时的想法,计数一次就是1ms,加到1000的时候一帧显示1s...

定时初始化函数

void Timer0Init(){TMOD = TMOD & 0XF0; //低四位置零,高四位不变TMOD = TMOD | 0X01; //最低位置1,高四位不变TR0 = 1;TF0 = 0;TL0 = 0x18;//设置定时初值TH0 = 0xFC;//设置定时初值ET0 = 1;EA = 1;PT0 = 0;}

中断函数代码

void Timer0_Routine() interrupt 1{static unsigned int T0Count;T0Count++;TL0 = 0xFF;//设置定时初值TH0 = 0xF7;//设置定时初值MatrixLED(0,a); //显示一帧图像MatrixLED(1,b);MatrixLED(2,c);MatrixLED(3,d);MatrixLED(4,e);MatrixLED(5,f);MatrixLED(6,g);MatrixLED(7,h);if(T0Count == 1000) //帧间隔{LED_ShowInit(); //LED全灭a = 0x30; b = 0x48; c = 0x44; d = 0x22; e = 0x44; //修改为第二帧位选f = 0x48; g = 0x30; h = 0x00;}if(T0Count == 2000) {LED_ShowInit();a = 0x00; b = 0x7E; c = 0x01; d = 0x01; e = 0x01; //第三帧f = 0x7E; g = 0x00; h = 0x00;}}

运行结果仍然有问题

闪烁频率低,肉眼可见,用手机录视频的时候更明显,实在上不了台面帧与帧之间间隔时间远远不止1s

重新检查代码发现问题出在MatrixLED()函数中的Delay(1),Delay是自己编写的延时函数,单位是1ms,而代码里在中断函数里调用8次,那么一次中断就会延时8ms,不考虑程序其他执行时间,亮灭间隔也有8ms,离得近是可以看出来闪烁的(晚上尤其明显)

一共进入1000次中断显示下一帧,那么一帧的显示时间就是8000ms,8秒钟还是很长的

3、定时溢出时间由1ms改成1us,结果闪的还是明显甚至可以看见列从上到下的“滚动”

4、 尝试删除MatrixLED()中的Delay,观察到的结果

闪烁频率效果不错,但是亮度很低,拍出来看着还行实际真的很暗

5、还是降低延时时间,重写一个以10us为单位的延时函数,测试出同时兼顾亮度和闪烁频率的延时时间

void Delay10us(unsigned int x10us)//@11.0592MHz{unsigned char i;while(x10us){i = 2;while (--i);x10us--;}}

延时为400us时效果不错,稳定亮度也够

使用定时中断函数显示LED点阵屏的多帧图像

是的,一开始就是奔着浪漫~

代码实现

#include <REGX52.H>#include"Delay.h"//自定义寄存器名称,方便使用sbit SERCLK = P3 ^ 6; //上升沿移位 sbit RCK = P3 ^ 5; //RCLK,上升沿锁存,并行输出sbit SER = P3 ^ 4; //串行输入位#define MatrixLED_Port P0/*** @brief 74HC595写入的一个字节* @param Byte 要写入的字节* @retval 无*/void _74HC595_WriteByte(unsigned char Byte){unsigned char i;for(i=0; i<8; i++){SER = Byte & (0x80>>i); //从高位到低位依次赋给串行输入位SERCLK = 1; //每输入一位给一个上升沿使其移位SERCLK = 0; //一位移位后立刻将SERCLK复位}RCK = 1;//八位串行输入完毕使其并行输出RCK = 0;//输出完立刻复位}/*** @brief LED显示一列数据* @param Column 要显示的那一列(从左到右为0~7列)* @param Data 要显示列的数据,高位在上低位在下,1亮,0灭* @retval 无*/void MatrixLED(unsigned char Column, Data){_74HC595_WriteByte(Data);//选中给高电平点阵屏的行MatrixLED_Port = ~(0x80 >> Column); //选中列,即可确定具体该列LED的亮灭Delay10us(40);MatrixLED_Port = 0xFF;//位选信号清零,用于再循环中可以同时显示多列}void LED_ShowInit(){MatrixLED(0,0x00);MatrixLED(1,0x00);MatrixLED(2,0x00);MatrixLED(3,0x00);MatrixLED(4,0x00);MatrixLED(5,0x00);MatrixLED(6,0x00);MatrixLED(7,0x00);}unsigned char a,b,c,d,e,f,g,h;void main(){SERCLK = 0;//由于一上电SERCLK、RCK都被置高电平,因此需手动赋初值0RCK = 0;Timer0Init();a = 0x00; b = 0x42; c = 0x42; d = 0x7E; e = 0x42; //第一帧列位选变量,用于后续更改显示下一帧f = 0x42; g = 0x00; h = 0x00;while(1){}}void Timer0_Routine() interrupt 1{static unsigned int T0Count;T0Count++;TL0 = 0xFF;//设置定时初值TH0 = 0xF7;//设置定时初值MatrixLED(0,a); //显示一帧图像MatrixLED(1,b);MatrixLED(2,c);MatrixLED(3,d);MatrixLED(4,e);MatrixLED(5,f);MatrixLED(6,g);MatrixLED(7,h);if(T0Count == 100) //帧间隔{LED_ShowInit(); //LED全灭a = 0x30; b = 0x48; c = 0x44; d = 0x22; e = 0x44; //修改为第二帧位选f = 0x48; g = 0x30; h = 0x00;}if(T0Count == 200) {LED_ShowInit();a = 0x00; b = 0x7E; c = 0x01; d = 0x01; e = 0x01; //第三帧f = 0x7E; g = 0x00; h = 0x00;}}

上面的定时初始化中的 修改,溢出时间间隔为1us

TL0 = 0xFF;//设置定时初值TH0 = 0xFF;//设置定时初值

Delay10us函数在上面贴了没有修改,记得包含进去~

下一篇考虑数组移位方式实现帧变换

如果觉得《51定时中断系统控制LED点阵屏显示逐帧动画》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。