【STC单片机学习】第八课:单片机的LED点阵
2022-08-18 来源:csdn
1.8.1.LED点阵简介
1.8.1.1、什么是LED点阵
点阵显示是后面显示显示器的基础,这节课对显示的理解很有帮助!
(1)外观
在板子上什么样呢?每一个圆点是一个LED灯!显示方法和数码管很像,需要提前做好断码表!
(2)作用
显示文字、数字、显示图标
(3)内部构造原理图。 2.单片机完全学习系列课程课程配套ARM3.0开发板光盘资料步骤3 51例程9 、LED点阵datasheet-相关芯片手册
16*16的LED单色灯!
点阵的优势就是16(J34+J28)个IO口独立控制8*8个LED亮灭
(4)LED点阵如何工作
点亮:纵向和横向结合
8*8的LED点阵可以并联/串联拼成更大的LED点阵!
1.8.1.2、如何驱动LED点阵
(1)单片机端口直接驱动。要驱动8*8的点阵需要2个IO端口(16个IO口)、要驱动16*16的点阵需要4个IO端口(32个IO口)。可以用,但是IO口用的太多了!
(2)使用串转并 移位 锁存器驱动。(常用方式) 看原理图!
(1) 串转并
SER 10101100 同一个引脚不同时间送进来,串行输入
QA 1
QB 0
QC 1 不同引脚同一时间送出去,并行输出
QD 0
QE 1
QF 1
QG 0
QH 0
从SER口一个一个走进去,然后通过Qx同时走出去!
(2) 移位
串行输入移进锁存器
(3) 锁存(Latch)
等到SER依次输入,等到输入完成,一瞬间锁存在锁存器
锁存之前,QA-QF的值是上一次锁存的结果!且不会改变!SER输入的二进制可以改变!
锁存之后,锁存器内的内容就会更新!
要驱动16*16点阵只需要4个74HC595+3个IO口即可(数据口、SCLK、RCLK)替换32个IO口,很划算!。SCLK、RCLK是时钟!如图具体实现看1.8.2
J24控制四个74HC595芯片,然后4个芯片共同控制LED点阵的亮灭!
1.8.2.原理图分析
1.8.2.1、POS就是Positive正极,NEG是Negative负极。
(1)POS1-16和NEG1-16分别接移位锁存器并行输出端
看图分析
LED点阵如果亮,就要控制POS和NEG两端的输入!74HC595的A、B接到POS、C、D接到NEG
1.8.2.2、74HC595的接法分析
(1)POS:芯片A的J27接到LED点阵的J28、芯片B的J32接到LED点阵的J34
(2)NEG:芯片C和D的QA-QH已经通过电路接到LED点阵的负极了,无需接线!
(3)QH'串行输出口接下一个74HC595的串行输入SER(串联顺序按照ABCD)也无需接线
(4)SER串行输入接:第一个595的SER通过杜邦线接P0.4,后面的每一个SER接前一个的QH'(内部已经接好)。这样就构成了ABCD4个595芯片依次串联。所以将来编程时整个4个74HC595的串行数据都是从P0.4出来的。
(4)SCLK(SRCLK)接P0.5
(5)RCLK接P0.6
总结:
(1)SCLK和RCLK是一样的接法,都在接在P0.5和P0.6上。
(2)总共涉及到的IO口有3个:SE:P0.4、SC:P0.5、RC:P0.6
(3)外部接线重点:2个8pin杜邦线 J27-J28,J32-J34
1.8.3.LED点阵编程实践1
本节课研究怎么让LED点阵亮起来。
SER负责数据出入,SRCLL和RCLK负责时序!
1.8.3.1、74HC595的时序分析
(1)芯片与芯片之间的通信,都是按照一定的时序进行的。
(2)时序:芯片(STC89C)与芯片(74HC595)之间的通信引脚上电平变化以时间轴为参考的变化顺序。
(3)时序是通信双方事先定义好的,通信的发送方必须按照时序来发送有意义的信息,通信的接收放按照时序去接收解析发送方发来的电平变化信息,然后就知道发送方要给我发送什么东西了。
(4)我们编程时:发送方是单片机,接收方是74HC595。因为595芯片本身是不能编程的,他的时序芯片出厂时已经设定好了。因此我们单片机必须迁就595芯片,按照595芯片的时序来给他发信息。
(5)所以我们要先搞清楚74HC595的时序规则。根据74HC595芯片手册上的时序描述(时序图)就可以明白595芯片的时序规则,然后将其用编程语言表述出来就是程序了。(参考别人的代码也行!)
看手册!参考链接:74HC595中文资料 / 74HC595 中文资料参数.pdf(推荐)
(6)74HC595的时序关键是:SCLK和RCLK。
SCLK是移位时钟,595芯片内部在每个SCLK的上升沿会对SER引脚进行一次采样输入(SER的电平高低),就向595内部输入了1位,如此循环8次就输入了8位二进制。74HC595有两路输出:1是QA-QH(并行输出),2是QH'(串行输出);
RCLK是锁存时钟,QA-QH的8位并行输出信号在RCLK的上升沿进行一次锁存更新。
(7)理解74HC595芯片的通信时序关键::SER进行数据的串行输入,SCLK提供移位时钟,RCLK提供锁存时钟。
真值表:
时序图:
每一次到达SRCLK一个上升沿,就从SER输入一个数据,当到达下一个SRCLK的上升沿时,数据通过QH'输出到下一个芯片。
到达一个RCLK的上升沿,就把数据放到QA-QH 现在不明白没事,等看两个程序就明白了!
1.8.4.LED点阵编程实践2
1.8.4.1、如何给芯片编程序
大致思路:
1.d1、d2、d3、d4分别输入到DCBA四个595芯片中,进而控制NEG1-16和POS1-16的引脚输入值,进而控制LED点阵的显示
2.如何输入呢?通过SER依次获取dx(x=1、2、3、4)的每一个bit值,然后统一锁存到四个芯片的QA-QH端!具体对应关系如图
1.8.4.2、全屏点亮测试
#include sbit SER = P0^4; //SER串行输入端 sbit SC = P0^5; //SRCLK 移位时钟 sbit RC = P0^6; //RCLK 锁存时钟 void main() { unsigned char i = 0; unsigned char d1,d2,d3,d4; //要个给4个595并行输出端输出的值 SC = 0; RC = 0; d1 = 0; d2 = 0; d3 = 0xff; d4 = 0xff; for(i = 0;i<8;i++) { SER = d1 >> 7; //将d1的最高bit给SER SC = 0; SC = 1; //2步制造了一个SCLK的上升沿。 d1 <<= 1; } //至此已经在8个SCLK的上升沿全部发出去了 //但是还没有进行锁存,所以QA-QH还没东西 for(i = 0;i<8;i++) { SER = d2 >> (7-i); //将d1的最高bit给SER SC = 0; SC = 1; //2步制造了一个SCLK的上升沿。 d2 <<= 1; } //至此已经把d1和d2都发出去了,并且d2已经把d1挤到下一个595芯片了 //但是还没有进行锁存,所以QA-QH还没东西 for(i = 0;i<8;i++) { SER = d3 >> (7-i); //将d1的最高bit给SER SC = 0; SC = 1; //2步制造了一个SCLK的上升沿。 d3 <<= 1; } //至此已经把d1、d2、d3都发出去了,d1在C、d2在B、d3在A //但是还没有进行锁存,所以QA-QH还没东西 for(i = 0;i<8;i++) { SER = d4 >> (7-i); //将d1的最高bit给SER SC = 0; SC = 1; //2步制造了一个SCLK的上升沿。 d4 <<= 1; } //至此已经把d1、d2、d3、d4都发出去了,d1在D、d2在C、d3在B、d4在A //但是还没有进行锁存,所以QA-QH还没东西 //截止这里,4个字节的数据d1、d2、d3、d4已经顺着74HC595的SER->QH'的串行输入 //串行输出路线,已经爬满了4个74HC595(最先送出的在D) //但是目前4个595的QA-QH都还没有输出呢,点阵自然不会亮 //现在进行一次锁存,4个595芯片同时进行锁存,各自锁存住自己的数据 RC = 0; RC = 1; //这时候会完成锁存,d1-d4会分别进入ABCD的QA-QH端,进而决定LED点阵的状态 //d3-POS1-8,d4-POS9-16,d1-NEG1-8,d2-NEG9-16 } 总结: (1)编写硬件控制代码,时序理解是关键。只要时序理解正确的,并且代码按照时序的要求去写,就没问题。 (2)时序操作部分的代码只要写好了并且是正确的,下来这一块就没问题了,很简单了,因为它是死板的不变的。 课上要求: 1.对代码进行改进! 提示:d1,d2,d3,d4用数组! #include typedef unsigned char u8; sbit SER = P0^4; //SER串行输入端 sbit SC = P0^5; //SRCLK 移位时钟 sbit RC = P0^6; //RCLK 锁存时钟 void main() { u8 i = 0; u8 j = 1; u8 d[] = {0,0,0xff,0xff}; SC = 0; RC = 0; /*d1 = 0; d2 = 0; d3 = 0xff; d4 = 0xff;*/ for(j = 0;j<4;j++) { for(i = 0;i<8;i++) { SER = d[j] >> 7; //将d1的最高bit给SER SC = 0; SC = 1; //2步制造了一个SCLK的上升沿。 d[j] <<= 1; } } RC = 0; //制造RCLK上升沿 RC = 1; } 2.把程序封装成一个函数! 编写移位寄存器传送函数SendData() #include typedef unsigned char u8; sbit SER = P0^4; //SER串行输入端 sbit SC = P0^5; //SRCLK 移位时钟 sbit RC = P0^6; //RCLK 锁存时钟 void SendData(u8 d[]) { u8 i = 0; u8 j = 0; SC = 0; RC = 0; for(j = 0;j<4;j++) { for(i = 0;i<8;i++) { SER = d[j] >> 7; //将d1的最高bit给SER SC = 0; SC = 1; //2步制造了一个SCLK的上升沿。 d[j] <<= 1; } } RC = 0; RC = 1; } void main() { u8 d[] = {0,0,0xff,0xff}; SendData(d); } 1.8.5.LED点阵编程实践3 1.8.5.1、对点阵点亮规律的探索(控制d1-d4)和函数没关系! d1、d2是负极,1灭0亮;d3、d4是正极,1亮0灭,并且低位对应POSx/NEG的最小值,例:d1的最低位控制NEG9 (1)编程点亮最上面8行 (2)编程点亮最下面8行 (3)编程点亮最左面8列 (4)编程点亮最上面1行 (5)编程点亮最下面1行 (6)编程点亮左上角1颗 (7)编程点亮角上4颗 参考答案 1.8.5.2、思考:如何显示文字?请同学说一下!下面是提示! 下一节开始,咱们开始显示数字/汉字!后面开始用专门的字模软件提取汉字! 1.8.6.字模介绍 1.8.6.1、何为字模 打开字模软件! (1)字模: 记录组成字的LED点阵亮灭信息(16*16点阵一共有256点,显示一个特定的字需要其中有些点亮而另一些不亮,如何记录哪些点亮哪些点不亮?用字模) (2)字模如何工作? 256个点用256个二进制位表示,1表示这个点亮,0表示不亮。256个点就是256个二进制位,也就是256/8=32个字节。所以一个大小为16*16的字的字模是32个字节大小。所以字模的表现形式就是32个unsigned char型数据。 (2)字模如何获取。一般都是用专门的字模提取软件去提取的。这种软件的作用就是给一个字就能自动得到这个字对应的32个字节的字模编码。就像上面的char code {...} (3)字模的结果不是唯一的,和你提取字模的方式有关的。(横向纵向、从上到下之类区分)提取字模时是没有标准的,怎么做都是对的或者都是错的,关键是你提取字模的方式和你用来在点阵上显示这个字模的函数必须对应。 1.8.6.2、字模提取软件的使用 (1)使用方式: 第一步先选择字型(实际开发板上点阵多大就选择多大); 第二步再选择合适的字体、字号等; 第三步选择编码方式和取模方向; D0->D7,纵向 D0->D7,横向 D7->D0,纵向 D7->D0,横向 第四步直接将得到的数组复制走 1.8.6.3、字模的手工分析和验证 (1)手工对比字模内容和屏幕显示,从而确认取模方式 (2)结合前面课程,思考如何将之显示出来d1-d4 1.8.7.横向取模的显示函数1(D0-D7,横向) 1.8.7.1、工程建立 假如咱们显示“冯”这个字。把他的code复制到main.c中! 大家main.c粘贴下面这些代码: #include typedef unsigned char u8; sbit SER = P0^4; // 74HC595的串行输入端 sbit SC = P0^5; // 移位时钟 sbit RC = P0^6; // 锁存时钟 void SendData(u8 d1, u8 d2, u8 d3, u8 d4) { unsigned char i = 0; SC = 0; RC = 0; for (i=0; i<8; i++) { SER = d1 >> 7; SC = 0; SC = 1; d1 = d1 << 1; } for (i=0; i<8; i++) { SER = d2 >> 7; SC = 0; SC = 1; d2 = d2 << 1; } for (i=0; i<8; i++) { SER = d3 >> 7; SC = 0; SC = 1; d3 = d3 << 1; } for (i=0; i<8; i++) { SER = d4 >> 7; SC = 0; SC = 1; d4 = d4 << 1; } RC = 0; RC = 1; } void main(void) { } 1.8.7.2、先显示第二行 思考一下,feng[ ]中的数,可以直接放在SendData()中吗?需要什么操作。 feng[] = {0,0,228,15,8,8,72,8,64,8,64,8,192,63,8,32,8,32,228,47,4,32,2,32,2,32,0,24,0,0,0,0}; 举个例子,要显示第2行,'228'对应的是d2,'15'对应的是d1,而且还要将d1,d2的值从正逻辑转化成负逻辑! //就变成了 SendData(~15,~228,0x00,0x02);//显示第二行 显示结果:正是‘冯’字的第二行! 1.8.7.3、多显示2行去摸索规律 搞到第8行,看看规律! SendData(~0, ~0, 0x00, 0x01); // 显示第1行 SendData(~15, ~228, 0x00, 0x02); // 显示第2行 SendData(~8, ~8, 0x00, 0x04); // 显示第3行 SendData(~8, ~72, 0x00, 0x08); // 显示第4行 SendData(~8, ~64, 0x00, 0x10); // 显示第5行 SendData(~8, ~64, 0x00, 0x20); // 显示第6行 SendData(~63, ~192, 0x00, 0x40); // 显示第7行 SendData(~32, ~8, 0x00, 0x80); // 显示第8行 1.8.7.4、规律 (1)规律1:d1和d2用字模来填充,填充时要取反 (2)规律2:d3和d4来选择哪一行被点亮,而d1和d2选择这行中哪一列被点亮。 (3)规律3:SendData一次送16个LED的亮灭信息(2字节),所以必须调用256/16=16次SendData函数,才能把整个点阵全部点亮完毕。 (4)规律4:每次调用SendData时,d1-d4变化都是有规律的,因此有希望通过循环来调用SendData而无需手工调用16次。 这一点如何做到?大家思考下!(2min) 1.8.8.横向取模的显示函数2 实现规律。 1.8.8.1、定义行选择数组 //第一种 u8 code hang[32] = { 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, }; //第二种 u8 code hang[] = { 0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80 }; 1.8.8.2、使用for循环进行显示 //第一种 for(i = 0;i<16;i++) { SendData(~feng[2*i+1],~feng[2*i],hang[2*i],hang[2*i+1]); } //第二种 /* for(i = 0;i<8;i++) { SendData(~feng[2*i+1],~feng[2*i],hang[0],hang[i+1]); } for(i = 8;i<16;i++) { SendData(~feng[2*i+1],~feng[2*i],hang[i-8],hang[i+1-8]); } */ 有一个问题,显示一个字还好,多个字就麻烦了,所以要有一个显示字的函数! 1.8.8.3、编写点阵字显示函数Display() 自己实现试试 1.8.9.纵向取模的显示函数(D0-D7,纵向) 1.8.9.1、先观察总结纵向取模的取模规律 1.8.9.2、显示第2列 1.8.9.3、多显示几列寻找规律 还是显示8列试试! 1.8.9.4、编写成函数然后实验测定 #include typedef unsigned char u8; sbit SER = P0^4; // 74HC595的串行输入端 sbit SC = P0^5; // 移位时钟 sbit RC = P0^6; // 锁存时钟 void SendData(u8 d1, u8 d2, u8 d3, u8 d4) { unsigned char i = 0; SC = 0; RC = 0; for (i=0; i<8; i++) { SER = d1 >> 7; SC = 0; SC = 1; d1 = d1 << 1; } for (i=0; i<8; i++) { SER = d2 >> 7; SC = 0; SC = 1; d2 = d2 << 1; } for (i=0; i<8; i++) { SER = d3 >> 7; SC = 0; SC = 1; d3 = d3 << 1; } for (i=0; i<8; i++) { SER = d4 >> 7; SC = 0; SC = 1; d4 = d4 << 1; } RC = 0; RC = 1; } void Display(u8 shumo[],u8 hang[]) { u8 i = 0; for(i = 0;i<16;i++) { SendData(~hang[2*i],~hang[2*i+1],shumo[i+16],shumo[i]); } } u8 code hang[] = { 0x00,0x01,0x00,0x02,0x00,0x04,0x00,0x08, 0x00,0x10,0x00,0x20,0x00,0x40,0x00,0x80, 0x01,0x00,0x02,0x00,0x04,0x00,0x08,0x00, 0x10,0x00,0x20,0x00,0x40,0x00,0x80,0x00 }; u8 code feng[] = { 0,0,2,140,0,2,122,66,66,66,66,126,64,192,0,0, 0,24,6,1,0,2,2,2,2,2,2,34,32,31,0,0 }; void main() { Display(feng,hang); } 课程彩蛋: