【51单片机】8. 矩阵LED显示自定义图案、动画
1. 显示原理
-
LED点阵屏与数码管类似,只是将数码管每一列的像素以8字型排列
-
与数码管一样,有共阴和共阳两种接法,不同接法对应电路结构不同
-
LED点阵需要逐行或逐列扫描,才能使所有LED同时显示
2. 74HC595电路图原理
看OE,是低电平有效,所以要弄一下J24这个接线帽
-
RCLK:Register Clock
-
SRCLR:Serial 串行清零端
-
SRCLK:串行时钟
-
SER:串行数据
74HC595是串行输入并行输出的移位寄存器,用3根线输入串行数据,8根线输出并行数据,多片级联可以输出16、24、32位等,常用于IO口扩展
数据通过SER一位一位地进来,上升沿移位SERCLK脉冲来一次就SER写一次数据,最后经上升沿锁存RCLK,将数据从左侧挪到右侧。
(弹匣装弹,霰弹枪出来的原理)
扩展多位的原理如下:
第一片板子SER里面的数据满了,就经过线送到下一片板子里,SERCLK和RCLK接在同一个位置。这样可以形成多片级联,形成更多位的输出。
3. 单片机
单片机输出端口是弱上拉的,给1的时候电流小,给0的时候VCC流回负极电流更大,如果想要将点阵直接接在单片机的端口上是不可行的,需要加一个三极管,三极管作为放大电路使用。
4. 74HC595的使用
江科大使用的单片机上74HC595模块是有接LED灯,以观察输出情况的,我买的普中板子上没有,所以只是按照看视频的理解写了相应的函数。
#include <REGX52.H>// 重新命名,会更清晰一些,要不然容易不清楚每个口的具体含义
// P3^X:这不是异或的意思,这样的形式表示选中P3的第5位
sbit RClK = P3^5; // 上升沿锁存,置1时将SER的内容全部写入输出口
sbit SRCLK = P3^6; // 上升沿移位,置1时写入SER的内容
sbit SER = P3^4;void _74HC595_WriteByte(unsigned char byteData)
{unsigned char i = 0;for (i = 0; i < 8; i++){SER = byteData & (0x80 >> i); // 通过这种方式取出第i位// byteData & 0x80相当于选出最高位的数据,结果要么是1000 0000,要么是0000 0000// 因为SER的类型是sbit,所以要么为1要么为0// 如果结果1000 0000,SER就会被置为1// 如果结果0000 0000,SER就会被置为0// SRCLK原本为0,置1给一个电平写入,然后置0SRCLK = 1; SRCLK = 0;}// RClK原本为0,置1给一个电平统一转移数字,然后置0RClK = 1;RClK = 0;
}void main()
{SRCLK = 0; // 初始置0,后面才更容易给电平RClK = 0; // 初始置0,后面才更容易给电平while(1){}
}
5. 点阵屏的显示
在4中函数的基础上,选中列后再对行进行选中,就可以实现点阵屏显示
#include <REGX52.H>
#include "Delay.h"// 重新命名,会更清晰一些,要不然容易不清楚每个口的具体含义
// P3^X:这不是异或的意思,这样的形式表示选中P3的第5位
sbit RClK = P3^5; // 上升沿锁存,置1时将SER的内容全部写入输出口
sbit SRCLK = P3^6; // 上升沿移位,置1时写入SER的内容
sbit SER = P3^4;void _74HC595_WriteByte(unsigned char byteData)
{unsigned char i = 0;for (i = 0; i < 8; i++){SER = byteData & (0x80 >> i); // 通过这种方式取出第i位// byteData & 0x80相当于选出最高位的数据,结果要么是1000 0000,要么是0000 0000// 因为SER的类型是sbit,所以要么为1要么为0// 如果结果1000 0000,SER就会被置为1// 如果结果0000 0000,SER就会被置为0// SRCLK原本为0,置1给一个电平写入,然后置0SRCLK = 1; SRCLK = 0;}// RClK原本为0,置1给一个电平统一转移数字,然后置0RClK = 1;RClK = 0;
}void MatrixLED_ShowColumn(unsigned char column, byteData)
{_74HC595_WriteByte(byteData); // 选中列P0 = ~(0x80) >> column; // 选中指定行,给低电平点灯,column表示点亮第几行,做一个右移// 避免显示下一个的时候出现残影,Delay后置灭Delay(1);P0 = 0xFF;
}void main()
{SRCLK = 0; // 初始置0,后面才更容易给电平RClK = 0; // 初始置0,后面才更容易给电平MatrixLED_ShowColumn(7,0xAA);while(1){ }
}
对于为什么要Delay和置灭,是因为这通常是一个:
段选→位选→段选→位选→…的过程
这会导致一个问题,在执行第三个段选的时候,上一次的位选还没改变,从而导致上一次的位选和下一次的段选结合,呈现错误的结果/残影,所以正确的是:
段选→位选→延时→位清零→段选→位选→…的过程
6. 矩阵LED屏显示自定义图案
有了上面的基础,显示笑脸就很简单了,用EXCEL画出需要亮灯的部分:
第一列:0011 1100 → 3C
第二列:0100 0010 → 42
第三列:1010 1001 → A9
第四列:1000 0101 → 85
第五列:1000 0101 → 85
第六列:1010 1001 → A9
第七列:0100 0010 → 42
第八列:0011 1100 → 3C
主函数修改为:
void main()
{SRCLK = 0; // 初始置0,后面才更容易给电平RClK = 0; // 初始置0,后面才更容易给电平while(1){MatrixLED_ShowColumn(0, 0x3C);MatrixLED_ShowColumn(1, 0x42);MatrixLED_ShowColumn(2, 0xA9);MatrixLED_ShowColumn(3, 0x85);MatrixLED_ShowColumn(4, 0x85);MatrixLED_ShowColumn(5, 0xA9);MatrixLED_ShowColumn(6, 0x42);MatrixLED_ShowColumn(7, 0x3C);}
}
效果如下(长曝光):
类似的如果是一个爱心:
第一列:0001 1000 → 18
第二列:0010 0100 → 24
第三列:0100 0010 → 42
第四列:0010 0001 → 21
第五列:0010 0001 → 21
第六列:0100 0010 → 42
第七列:0010 0100 → 24
第八列:0001 1000 → 18
主函数修改为:
void main()
{SRCLK = 0; // 初始置0,后面才更容易给电平RClK = 0; // 初始置0,后面才更容易给电平while(1){MatrixLED_ShowColumn(0, 0x18);MatrixLED_ShowColumn(1, 0x24);MatrixLED_ShowColumn(2, 0x42);MatrixLED_ShowColumn(3, 0x21);MatrixLED_ShowColumn(4, 0x21);MatrixLED_ShowColumn(5, 0x42);MatrixLED_ShowColumn(6, 0x24);MatrixLED_ShowColumn(7, 0x18);}
}
效果如下(长曝光):
7. 矩阵LED屏显示动画
我们的LED一共是8列,显示动画的原理实际上是将每列要显示内容组成一个数组,然后每次显示其中8列,例如第一次显示数组的0-7,第二次显示数组的1-8,以此类推。
具体显示的字符动画可以利用字模提取软件获得对应的结果,使用步骤如下:
第一步:新建图像,高度为8(开发板上的高度,宽度选择对应字符长度)
第二步:模拟动画里放大格点
第三步:绘制格点:
第四步:生成点阵
接下来就可以写代码,实际上就是用循环来遍历上面的这些内容:
#include <REGX52.H>
#include "MatrixLED.h"
#include "Delay.h"unsigned char Animation[] = {0x00,0x00,0x00,0x00,0x00,0xFF,0x81,0x42,0x3C,0x00,0x0C,0x12,0x12,0x0E,0x01,0x00,0xFF,0x81,0x42,0x3C,0x00,0x0C,0x12,0x12,0x0E,0x01,0x00,0xFD,0x00,0x00,0x00,0x00,
}; // 字符提取软件生成void main()
{unsigned int i = 0, offset = 0, count = 0; Init_Matrix_LED();while(1){for (i = 0; i < 8; i++) // for循环,8位循环显示{MatrixLED_ShowColumn(i, Animation[i+offset]); // offset表示数组中的便宜}// count这里实际上起到一个控制速度的作用,大于号后面的数字越大则动的越慢count++;if (count > 10){count = 0;offset++; // 增加偏移量if (offset > 24) offset = 0; // 偏移量到临界时需要重置,避免乱码}}
}
这里控制速度不能用Delay,用Delay的话点阵屏上的结果会消失,这应该是我们的点亮函数会置0导致的。
效果如下:
附上MatrixLED的内容:
MatrixLED.c:
#include <REGX52.H>
#include "Delay.h"// 重新命名,会更清晰一些,要不然容易不清楚每个口的具体含义
// P3^X:这不是异或的意思,这样的形式表示选中P3的第5位
sbit RClK = P3^5; // 上升沿锁存,置1时将SER的内容全部写入输出口
sbit SRCLK = P3^6; // 上升沿移位,置1时写入SER的内容
sbit SER = P3^4;#define MATRIX_LED_PORT P0/*** @brief 矩阵LED初始化* @param 无* @retval 无*/
void Init_Matrix_LED()
{SRCLK = 0; // 初始置0,后面才更容易给电平RClK = 0; // 初始置0,后面才更容易给电平
}/*** @brief 74HC595 写入一个字节* @param 要写入的字节* @retval 无*/
void _74HC595_WriteByte(unsigned char byteData)
{unsigned char i = 0;for (i = 0; i < 8; i++){SER = byteData & (0x80 >> i); // 通过这种方式取出第i位// byteData & 0x80相当于选出最高位的数据,结果要么是1000 0000,要么是0000 0000// 因为SER的类型是sbit,所以要么为1要么为0// 如果结果1000 0000,SER就会被置为1// 如果结果0000 0000,SER就会被置为0// SRCLK原本为0,置1给一个电平写入,然后置0SRCLK = 1; SRCLK = 0;}// RClK原本为0,置1给一个电平统一转移数字,然后置0RClK = 1;RClK = 0;
}/*** @brief LED点阵屏显示一列数据* @param * @retval*/
void MatrixLED_ShowColumn(unsigned char column, byteData)
{_74HC595_WriteByte(byteData); // 选中列MATRIX_LED_PORT = ~(0x80) >> column; // 选中指定行,给低电平点灯,column表示点亮第几行,做一个右移// 避免显示下一个的时候出现残影,Delay后置灭Delay(1);MATRIX_LED_PORT = 0xFF;
}
MatrixLED.h:
#ifndef __MATRIXLED_H__
#define __MATRIXLED_H__void Init_Matrix_LED();
void _74HC595_WriteByte(unsigned char byteData);
void MatrixLED_ShowColumn(unsigned char column, byteData);#endif
Delay.c:
#include <INTRINS.H>void Delay(unsigned int ms) //@11.0592MHz
{unsigned char i, j;while (ms){_nop_();i = 2;j = 199;do{while (--j);} while (--i);ms--;}
}
Delay.h:
#ifndef __DELAY_H__
#define __DELAY_H__void Delay(unsigned int ms);#endif