上一篇逐帧显示用的是定时和中断控制,碰到了一些麻烦,我觉得也是一种不错的办法,因为Delay的时间更短,而且如果不那么介意亮度的情况甚至可以不用Delay()函数,这里采用将要显示的图像按顺序存储在数组里,通过改变偏移量来逐帧显示和流动显示
一、简单介绍
- 类似于笔记地总结使用需要用到的模块知识
- 两个工作模式,逐帧显示和流动显示
- 通过独立按键可以改变工作模式
二、原理和知识总结
1、Rom、Flash和Ram
内存: 内部存储器 用来运行程序的 RAM 举例(DRAM、 SRAM 、 DDR)
外存: 外部存储器 用来存储东西的 ROM 举例(硬盘、 Flash、(..U盘、SSD)、 光盘)
Rom(Read Only Memory):只读存储器,只能读取资料的内存。其资料内容在写入后就不能更改
Ram(ramdom access memory):随机访问存储器(cpu直接地址访问,速度快),单片机的Ram很小
Flash 存储器(FLASH EEPROM)又称闪存,存储用户程序和需要永久保存的数据,单片机中存储空间比Ram大
由于显示的图像可以有很多帧,因此申请的数组内存都存在Ram里可能会被干爆,在数组名前加一个“code”,即可将数组存储到Flash中,但是在程序内部不可被修改,掉电数据不丢失
unsigned char code Column[] = {
0x00,0x00,0x42,0x42,0x7E,0x42,0x42,0x00,0x00,0x30,0x48,0x44,
0x22,0x44,0x48,0x30,0x00,0x00,0x7C,0x02,0x02,0x02,0x7C,0x00};//将三帧动画按顺序存进数组
关于点阵屏的扫描点亮原理在上一篇讲了,这里就不在提了下面直接上代码
三、程序设计
1、Key函数
1.1、功能:接收独立按键按下返回的对应键码
1.2、代码
#include <REGX52.H>
#include"Delay.h"
/**
* @brief 独立按键检测获取键码
* @param 无
* @retval 按下按键键码,范围0-4,无按键按下返回0
*/
unsigned char Key()
{
unsigned char KeyNumber = 0;
if(P3_1 == 0){Delay(20);while(!P3_1);Delay(20);KeyNumber = 1;}
if(P3_0 == 0){Delay(20);while(!P3_0);Delay(20);KeyNumber = 2;}
if(P3_2 == 0){Delay(20);while(!P3_2);Delay(20);KeyNumber = 3;}
if(P3_3 == 0){Delay(20);while(!P3_3);Delay(20);KeyNumber = 4;}
return KeyNumber;
}
2、上篇整理的用到的函数/声明
类型声明
#include <REGX52.H>
#include"Delay.h"
//自定义寄存器名称,方便使用
sbit SERCLK = P3 ^ 6; //上升沿移位
sbit RCK = P3 ^ 5; //RCLK,上升沿锁存,并行输出
sbit SER = P3 ^ 4; //串行输入位
#define MatrixLED_Port P0
74hc595
/**
* @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; //输出完立刻复位
}
LED点阵屏列扫描显示
/**
* @brief LED显示一列数据
* @param Column 要显示的那一列(从左到右为0~7列)
* @param Data 要显示列的数据,高位在上低位在下,1亮,0灭
* @retval 无
*/
void MatrixLED_ShowColumn(unsigned char Column, Data)
{
_74HC595_WriteByte(Data); //选中给高电平点阵屏的行
MatrixLED_Port = ~(0x80 >> Column); //选中列,即可确定具体该列LED的亮灭
Delay10us(40);
MatrixLED_Port = 0xFF; //位选信号清零,用于再循环中可以同时显示多列
}
SERCLK、RCK初始化
/**
* @brief 手动将SERCLK 、RCK置零
* @param 无
* @retval 无
*/
void MatrixLED_Init()
{
SERCLK = 0; //由于一上电SERCLK、RCK都被置高电平,因此需手动赋初值0
RCK = 0;
}
3、主函数
声明和定义
Offset为偏移量,流动显示模式的时候,每帧扫描20次后偏移量+1,即从数组的第二列开始扫描显示八列,以此类推,就实现了整个三帧图像的顺序左移流动显示
Count用于计数扫描次数,i为一次扫描内的循环变量,KeyNum用于接收返回键码,Mode为0、1两种工作模式,按下按键切换
#include <REGX52.H>
#include"Delay.h"
#include"MatrixLED.h"
#include"Key.h"
unsigned char code Column[] = {
0x00,0x00,0x42,0x42,0x7E,0x42,0x42,0x00,0x00,0x30,0x48,0x44,
0x22,0x44,0x48,0x30,0x00,0x00,0x7C,0x02,0x02,0x02,0x7C,0x00};//将三帧动画按顺序存进数组
//定义偏移量、计数变量、按键返回值,模式
unsigned char Offset, Count,i,KeyNum,Mode; //模式1为流动显示,模式二为逐帧显示
一次扫描,需要从第左至右一列一列逐列扫描显示,一帧显示需要列扫描8次,当前列为 i+Offset
KeyNum = Key(); //接收按键按下的键码
for(i=0;i<8;i++)
{
MatrixLED_ShowColumn(i, Column[i+Offset]); //显示带偏移量的当前帧
}
模式切换,Count用于扫描一帧的次数,检测按键按下模式切换,并给Count赋初值
Count++; //while循环扫描显示当前帧,扫描一次计数+1
if(KeyNum) //按键按下模式转换
{
Count = 0; //转换给计数赋初值,增加模式转换灵敏度
Offset = 0; //偏移量清0,重新回到第一帧
}
if(Mode == 2) //使模式只在0、1切换
Mode = 0;
模式1,每扫描20次向左偏移一列当偏移量到16刚好显示完整的最后一帧,这时将偏移量清0重新从第一帧第一列开始显示,,为流动扫描模式
模式0, 每扫描100次向左偏移8列直接显示第二帧图像同样显示完第三帧重新开始,为逐帧扫描
if(Mode == 1) //模式1
{
if(Count >= 20) //扫描20次
{
Count = 0; //重新计数
Offset++; //偏移量+1,实现每间隔20次扫描时间三帧图像平铺左移一列
if(Offset>16) //越界判断,偏移量到16正好移到第三帧完整图像显示
{
Offset = 0; //三帧平移显示完重新从第一帧开始
}
}
}
else //模式0
{
if(Count >= 100) //一帧显示间隔为100个扫描周期
{
Count = 0;
Offset += 8; //偏移量+8,直接显示第二帧
if(Offset>16) //越界判断,重复执行
{
Offset = 0;
}
}
}
完整代码
#include <REGX52.H>
#include"Delay.h"
#include"MatrixLED.h"
#include"Key.h"
unsigned char code Column[] = {
0x00,0x00,0x42,0x42,0x7E,0x42,0x42,0x00,0x00,0x30,0x48,0x44,
0x22,0x44,0x48,0x30,0x00,0x00,0x7C,0x02,0x02,0x02,0x7C,0x00};//将三帧动画按顺序存进数组
//定义偏移量、计数变量、按键返回值,模式
unsigned char Offset, Count,i,KeyNum,Mode; //模式1为流动显示,模式二为逐帧显示
void main()
{
MatrixLED_Init(); //初始化SERCLK、RCK为0
while(1)
{
KeyNum = Key(); //接收按键按下的键码
for(i=0;i<8;i++)
{
MatrixLED_ShowColumn(i, Column[i+Offset]); //显示带偏移量的当前帧
}
Count++; //while循环扫描显示当前帧,扫描一次计数+1
if(KeyNum) //按键按下模式转换
{
Mode++;
Count = 0; //转换给计数赋初值,增加模式转换灵敏度
Offset = 0; //偏移量清0,重新回到第一帧
}
if(Mode == 2) //使模式只在0、1切换
Mode = 0;
if(Mode == 1) //模式1
{
if(Count >= 20) //扫描20次
{
Count = 0; //重新计数
Offset++; //偏移量+1,实现每间隔20次扫描时间三帧图像平铺左移一列
if(Offset>16) //越界判断,偏移量到16正好移到第三帧完整图像显示
{
Offset = 0; //三帧平移显示完重新从第一帧开始
}
}
}
else //模式0
{
if(Count >= 100) //一帧显示间隔为100个扫描周期
{
Count = 0;
Offset += 8; //偏移量+8,直接显示第二帧
if(Offset>16) //越界判断,重复执行
{
Offset = 0;
}
}
}
}
}
四、结果演示
51单片机LED点阵屏逐帧显示和流动显示图像结果演示
五、遇到的问题
前面提到每次切换模式都要给Count赋初值,如果没赋值会怎么样?
由于模式1的是间隔20个扫描周期偏移量增加,Count最多到20就清0了,这是如果从模式1切换到模式0,需要保持当前状态傻傻地等到Count到100才会切换到模式0,这时候就会显得很卡顿,如果逐帧显示的间隔更长卡的更明显.
1、一切换给它赋值为99,这样很快就会到100触发越界条件清零从第一帧开始逐帧显示,切换就会比较流畅了
2、12.2更新,仅仅给Count赋初值为99仍然会出现问题,因为偏移量决定显示位置当偏移量累积到显示第一帧和第二帧的各一半的时候,切换会很快触发越界条件,就是下面这几句代码
if(Count >= 100) //一帧显示间隔为100个扫描周期
{
Count = 0;
Offset += 8; //偏移量+8,直接显示第二帧
使偏移量+8,还是会先跳转到显示到第二三帧左右各一半的情况,直到下一个100个扫描周期使Offset越界才会回到第一帧重新开始
切换的时候保留当前偏移量是好的,因为可以从按下的时刻当前显示的画面开始无缝切换模式,尤其是逐帧显示切换到流动显示的时候,但是从流动显示模式切换到逐帧显示的时候,如果按下的时候显示的画面不完整,那下一帧显示也是不完整的,效果并不好
因此不如统一切换时两种模式都复位到第一帧重新开始显示
if(KeyNum) //按键按下模式转换
{
Mode++;
Count = 0; //转换给计数赋初值,增加模式转换灵敏度
Offset = 0; //偏移量清0,重新回到第一帧
}
上面的代码已修改过了可以直接用~