写在前面
最近回头看之前写的文章感到一种很浓的公式感,我确实是提前写好了模板每次都套用,整篇看下来感觉就像是在交老师布置的实验报告,看起来很成熟但实际上背离了自己的初衷,接下来我会尽可能的复现自己在做的时候的尝试和思考过程
一、简单介绍
- 类似于笔记地总结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函数在上面贴了没有修改,记得包含进去~
下一篇考虑数组移位方式实现帧变换