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

写在前面

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

一、简单介绍

  1. 类似于笔记地总结LED点阵屏使用需要用到的模块知识
  2. 静态显示图形
  3. 逐帧显示,这里只做了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个寄存器中,只有一个是可以位寻址的。对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、“^=”的方法进行位操作,

 sbitspecial 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点阵屏函数

实现原理:

  1. 调用74HC595写函数选择要点亮的行,如0x42(0100 0010 )就是选择2和7行
  2. 选中列为位选信号取低电平,参数Column传递第几列
  3. 位选信号清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都被置高电平,因此需手动赋初值0
	RCK = 0;
void main()
{
    SERCLK = 0;        //由于一上电SERCLK、RCK都被置高电平,因此需手动赋初值0
    RCK = 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都被置高电平,因此需手动赋初值0
	RCK = 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函数在上面贴了没有修改,记得包含进去~ 

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

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值