八、LED点阵
1、LED点阵简介
a、什么是LED点阵
(1)外观:由一颗颗LED组成,外观通常是一个正方形黑色底板上有一个个白色小圆点组成。大的点阵由小的点阵拼
接而成。也有的点阵是外面以个透明黑色外壳,里面直接显露LED。
(2)作用:显示数字、文字、图案。
(3)内部构造原理:是一种点阵式的设计,例如16*16的LED点阵就是将正方形排列的LED每一行的正极都接在一起,
把每一列的负极都接在一起,再将它们都引出来。这样做的好处就是可以节省IO端口,如不用这个方法而是每颗
LED都连在单片机上,需要16*16*2=512个IO口。采用点阵设计后只需要32个IO口就可以驱动整个LED点阵了。
如果使用多块74HC595来辅助,则最少只需要三个IO口就可以驱动整个16*16点阵了。
(4)LED点阵如何工作:以简单的8X8点阵为例,它共由64个发光二极管组成,且每个发光二极管是放置在行线和列
线的交叉点上,当对应的某一行置1电平,某一列置0电平,则相应的二极管就亮。要将第一个点点亮,则9脚接高
电平13脚接低电平,则第一个点就亮了,如果要将第一行点亮,则第9脚要接高电平,而(13,3,4,10,6、
11,15,16)这些引脚接低电平,那么第一行就会点亮,如要将第一列点亮,则第13脚接低电平,而(9,14,
8,12,1,7,2,5)接高电平,那么第一列就会点亮。
b、如何驱动LED点阵
(1)单片机端口直接驱动。要驱动8*8的点阵需要2个IO端口(16个IO口)、要驱动16*16的点阵需要4个IO端口
(32个IO口)。
(2)使用串转并移位锁存器驱动。要驱动16*16的点阵,只需要4个74HC595+3个IO口(数据口、SCLK、RCLK)。
2、原理图分析
a、P0S1~16和NEG1~16分别接移位锁存器并行输出端
POS就是positive,正极;NEG就是negative,负极
b、74HC595的接法分析
(1)QA~QH八路并行输出接到点阵的一个端口
(2)QH'串行输出口接下一个74HC595的串行输入SER(串联顺序安装ABCD)。
(3)SER串行输入接:第一个595的SER通过跳线帽JP595接P3.4,后面每一个SER接前一个595的QH'。这样就构成
了ABCD4个595芯片依次串联。所以将来编程时整个4个74HC595的串行数据都是从P3.4出来的。
(4)SCLK(SRCLK)接P3.6。
(5)RCLK接P3.5。
总结:
(1)所有595的SCLK和RCLK都接在P3.5和P3.6上。
(2)总共设计到的IO口一共有三个:P3.4、P3.5、P3.6。
(3)外部接线重点:2个8PIN的杜邦线和一个跳线帽
c、外部跳线接法
d、单片机相关IO口
3、LED点阵编程实现1
a、74HC595时序分析
(1)芯片与芯片的通信,都是按照一定的时序进行的。
(2)时序就是芯片与芯片之间的通信引脚上电平变化以时间轴为参考的变化顺序。
(3)时序是通信双方事先定义好的,通信的发送方必须按照时序来发送有意义的信息,通信的接收方按照时序去解析
发送方发来的电平变化信息,然后就知道发送方要给我发送什么东西了。
(4)我们编程时:发送方式单片机,接收方是74HC595.因为595芯片本身是不能编程的,它的时序在芯片出厂时已经
设定好了。因此我们单片机必须迁就595芯片,按照595芯片的时序来给芯片发送信息。
(5)所以我们应当先搞清楚74HC595的时序规则。595的芯片手册上就有这个芯片的时序描述(时序图),参考描述
就可以明白595芯片的时序规则,然后将其用编程语言表述出来就是程序了。
(6)74HC595的时序的关键是:SCLK和RCLK。SCLK是移位时钟,595芯片内部在每个SCLK的上升沿进行一次采样
输入,就向595内部输入了一位,如此循环8次,就输入了8位二进制。RCLK是锁存时钟,QA-QH的8位并行输出
信号在RCLK的上升沿进行一次锁存更新。
(7)理解74HC595芯片的通信时序的关键,其实就是:SER进行数据的串行输入,SCLK提供移位时钟,RCLK提供锁
存时钟。
b、sbit定义位变量
(1)之前编程都是单独操控一个IO端口,可以用端口名(P0、P1)来整体操作一个IO端口中的8个引脚。但是这种方
法不能操控单独一个IO口。
(2)今天编程需要单独操控1个IO引脚,譬如要操控P3.4,但是直接写P3.4的话C语言不认识的,写P3^4随然C语言
认识,但是程序员或者说读程序的人不一定认识,而且当程序简单或许好记或者好猜,当程序复杂了,要单独操控
的引脚多了就不好记得其用途了。因此必须使用sbit来定义一个引脚。譬如:sbit SER = P3^4;
4、LED点阵编程实现2
a、全屏点亮测试
代码:
#include <REGX51.H>
#include <intrins.h>
sbit SER = P3^4; //595的串行输入
sbit RCLK = P3^5; //595的锁存时钟
sbit SCLK = P3^6; //595的移位时钟
void main(void)
{
while (1)
{
unsigned int i;
unsigned char d1, d2, d3, d4; //给4个595并行输出端的值
d1 = 0;
d2 = 0;
d3 = 0xff;
d4 = 0xff;
SCLK = 0;
RCLK = 0;
for (i=0; i<8; i++)
{
SER = d1 >> 7; //把d1的最高位取出来给SER
SCLK = 0;
_nop_();
SCLK = 1; //制造了一个SCLK的上升沿
d1 = d1 << 1;
}
//至此已经在8个上升沿把d1的8位依次全部发送出去了
//但是还没有进行锁存,所以AQ-QH还没有东西
for (i=0; i<8; i++)
{
SER = d2 >> 7; //把d2的最高位取出来给SER
SCLK = 0;
_nop_();
SCLK = 1; //制造了一个SCLK的上升沿
d2 = d2 << 1;
}
//至此已经把d1和d2都发送出去了,而且d1已经被d2挤到第二个595芯片里面去了
//但是还没有进行锁存,所以AQ-QH还没有东西
for(i=0; i<8; i++)
{
SER = d3>>7; //把d3的最高位取出来给SER
SCLK = 0;
_nop_();
SCLK = 1; //制造了一个SCLK的上升沿
d3 = d3 << 1;
}
//至此已经把d1、d2和d3都发送出去了,而且d1和d2已经被d3挤到第三个和第二个595芯片里面去了
//但是还没有进行锁存,所以AQ-QH还没有东西
for(i=0; i<8; i++)
{
SER = d4>>7; //把d4的最高位取出来给SER
SCLK = 0;
_nop_();
SCLK = 1; //制造了一个SCLK的上升沿
d4 = d4 << 1;
}
//至此已经把d1、d2、d3和d4都发送出去了,而且d1、d2和d3已经被d4挤到第四个、第三个和第二个595芯片里面去了
//但是还没有进行锁存,所以AQ-QH还没有东西
//到此处为止,4个字节的数据d1、d2、d3和d4已经顺着74HC595的SER->QH'的串行出的线路,已经爬满了4个595芯片(最先送出去的
//了最后一个595芯片当中),但是目前为止4个595的QA-QH还没有输出,LED点阵自然不会亮。
//然后要进行一次锁存,4个595芯片同时再进行锁存,各自锁存住了自己的数据
RCLK = 0;
_nop_();
RCLK = 1;
//这一段代码执行完之后595就完成了锁存,d1-d4就会影响4个595的并行输出端,进而会影响点阵中LED正负极的值,
//控制LED的亮或者灭
//在本程序中,d1d2是负极,d3d4是正极
}
}
b、编写移位寄存器传送函数SendData
(1)代码:
void SendBata(unsigned int d1, d2, d3, d4)
{
unsigned int i;
SCLK = 0;
RCLK = 0;
for(i=0; i<8; i++)
{
SER = d1 >> 7;
SCLK = 0;
_nop_();
SCLK = 1;
d1 = d1 << 1;
}
for(i=0; i<8; i++)
{
SER = d2 >> 7;
SCLK = 0;
_nop_();
SCLK = 1;
d2 = d2 << 1;
}
for(i=0; i<8; i++)
{
SER = d3 >> 7;
SCLK = 0;
_nop_();
SCLK = 1;
d3 = d3 << 1;
}
for(i=0; i<8; i++)
{
SER = d4 >> 7;
SCLK = 0;
_nop_();
SCLK = 1;
d4 = d4 << 1;
}
RCLK = 0;
RCLK = 1;
}
(2)在主函数调用这个函数,再给这个函数传参后即可完成对LED点阵的控制。
e、总结:
(1)编写硬件控制代码,时序理解是关键。只要时序理解正确的,并且代码按照时序的要求去写,就没有问题。
(2)时序操作部分的代码只要写好了并且是正确的,接下来这一块就没有问题了,很简单了,因为他是死板不变了。
5、LED点阵编程实践3
a、宏定义的引入和uchar、u8
在程序开头写:#define uchar unsigned char这条代码后,后续的定义unsigned char类型的变量都可以由
unsigned char a --> uchar a。注:unsigned char仍然可以使用
b、对点阵点亮规律进行探索
(1)点亮最上面一排
关键代码:
d1 = 0x00;
d2 = 0x00;
d3 = 0x00; //POS9-16都给低电平,因此9-16行都不亮,表现为下半屏都灭
d4 = 0x01; //POS2-8都给低电平,POS1为高电平,因此第一行亮其余都灭,表现为上半屏除第
//一行外其余行都灭
SendBata(d1,d2,d3,d4);
(2)点亮最下面一排
关键代码:
d1 = 0x00;
d2 = 0x00;
d3 = 0x80;
d4 = 0x00; //P0S1-15都给低电平,POS16给高电平。表现为最后一行亮,其余都灭
SendBata(d1,d2,d3,d4);
(3)点亮最左面一列
关键代码:
d1 = 0xff;
d2 = 0xfe;
d3 = 0xff;
d4 = 0xff;
//POS1-16都给高电平,但是NEG2-16给的都是高电平,只有NEG1是低电平因此表现为最左面的一
//列亮,其余都是灭的。
SendBata(d1,d2,d3,d4);
(4)点亮左上角一颗
关键代码:
d1 = 0xff;
d2 = 0xfe;
d3 = 0x00;
d4 = 0x01;
//POS1给了高电平其余POS均为低电平;NEG1为低电平,其余NEG均为高电平,因此只有左上角一颗
//亮了
SendBata(d1,d2,d3,d4);
(5)点亮角上四颗
关键代码:
d1 = 0x7f;
d2 = 0xfe;
d3 = 0x80;
d4 = 0x01;
//POS1和16给了高电平,其余POS均为低电平;NEG1和16给了低电平,其余均为高电平,因此表现
//为四个叫上的亮了,其余都是灭的。
SendBata(d1,d2,d3,d4);
c、思考:如何显示文字
(1)确定要显示的文字在点阵屏上的显示从而确定那些LED亮,那些LED灭
(2)计算出d1,d2,d3,d4的值
(3)显示的时候可能需要扫描显示,即单次显示一行或一列,但是每次发送间隔时间极短,让人眼以为这个文字是完
整的,而不是拆成了很多部分依次显示的。其原理和动态数码管有些类似,每次只能亮一个数码管,但是间隔极短让
人以为所有数码管同时亮起。在显示文字中也是,每次只亮起一行或者一列,但是间隔时间极短,让人以为所有行或
者所有列都是同时亮起的。
6、字模介绍
a、何为字模
(1)如何记录组成字的LED点阵的亮灭信息?
16*16点阵一共有256个点,显示一个特定的字需要其中以LED亮一些LED灭,要记录所有LED亮和灭的信息就需
要字模。
那么字模是如何工作的呢?
256个点用256个二进制来表示,1表示这个点亮,0表示这个点灭。256个点就是256个二进制,也就是256/8
= 32个字节。因此一个16*16点阵的字模就是32个字节大小。所以字模的表现形式就是32个unsigned char
型数据。
(2)字模如何获取:一般使用字模提取软件去提取(也可以手工计算的到)。这种软件的作用就是给一个字就能自动
得到这个字对应的32个字节的字模编码。
(3)字模的结果不是唯一的,和你提取字模的方式是有关的。一般由横向纵向、上下之类的区分,取模方式不同其编
码也就不同。但是没有一个标准,无论什么方式取模都是正确的。关键在于字模提取方式和在程序中在点阵上显
示这个字的函数必须是对应的。
b、字模提取软件的使用
(1)使用方式:第一步先新建图像,选择你点阵的大小;第二步确定你要显示的是图像还是文字,文字就输入文字,
图像就导入图像;第三步设置取模的相关设置,文字的格式,如果是图像的也可以对图像进行简单的修改;第四
步生成字模编码;第五步将编码整合到程序中,在点阵中显示出来。
c、字模的手工分析和验证
(1)手工对比字模内容和屏幕显示,从而确定取模方式
(2)结合前面的课程思考如何将之显示出来
7、横向取模显示函数1
a、工程建立
b、先显示一行
c、多显示两行探索规律
(1)d1和d2用字模来填充,填充的时候要取反
(2)d3和d4来选择哪一行被点亮,而d1和d2选择这行中那一列被点亮。
(3)SendData一次送16个LED的亮灭信息(2字节),所以必须调用256/16=16次该函数,才能把整个点阵全部点
亮完毕。
(4)每次调用SendData函数是,d1-d4的变化都是有规律的,因此有希望通过循环来调用SendData函数而无需手
动调用16次。
8、横向取模的显示函数2
a、定义行选择数组
代码:
unsigned char 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};
b、使用for循环进行显示
代码:
for (i=0; i<16; i++)
{
d1 = ~zhong[2*i+1];
d2 = ~zhong[2*i];
d3 = hang[2*i];
d4 = hang[2*i+1];
SendBata(d1,d2,d3,d4);
}
c、编写点阵文字显示函数
代码:
void display(unsigned char word[], unsigned char select[])
{
unsigned char i, d1, d2, d3, d4;
for (i=0; i<16; i++)
{
d1 = ~word[2*i+1];
d2 = ~word[2*i];
d3 = select[2*i];
d4 = select[2*i+1];
SendBata(d1,d2,d3,d4);
}
}
9、纵向取模的显示函数
a、先观察总结纵向取模的取模规律
总的来说取模软件把整个要显示的内容分为两部分,上半和下半。软件以0->7或者7->0的顺序先取上半的模,取完后
再取下半部分的字模编码。对比横向取模,纵向取模相对来说不直观一些。由上述的发现,我们应当把字模编码分为两部分
譬如unsigned char zhong[32]这个纵向取模的字模中,zhong[0]和zhong[16]共同组成了一列内容;而如果这个
字模编码是横向取模,那么zhong[0]和zhong[1]是一组,共组成了一行的内容。
b、显示第一列
代码:
SendBata(~0x00, ~0x01, 0x00, 0x00);
c、多显示几列寻找规律
代码:
SendBata(~0x00, ~0x01, 0x00, 0x00);
SendBata(~0x00, ~0x02, 0x00, 0x00);
SendBata(~0x00, ~0x04, 0x0F, 0xF0);
d、编写成函数然后实验测定
代码:
void display(unsigned char select[], unsigned char word[])
{
unsigned char i, d1, d2, d3, d4;
for (i=0; i<16; i++)
{
d1 = ~select[2*i];
d2 = ~select[2*i+1];
d3 = word[i+16];
d4 = word[i];
SendBata(d1,d2,d3,d4);
}
}
10、横向取模编码和纵向取模编码LED点阵显示
a、横向取模代码:
#include <REGX51.H>
#include <intrins.h>
sbit SER = P3^4; //595的串行输入
sbit RCLK = P3^5; //595的锁存时钟
sbit SCLK = P3^6; //595的移位时钟
unsigned char code zhong[] ={
0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xFC,0x1F,0x84,0x10,0x84,0x10,0x84,0x10,
0x84,0x10,0x84,0x10,0xFC,0x1F,0x84,0x10,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00};
unsigned char code hua[] ={
0x10,0x01,0x10,0x11,0x08,0x09,0x0C,0x07,0x8A,0x01,0x69,0x21,0x08,0x21,0x08,0x3E,
0x88,0x00,0x80,0x00,0xFF,0x7F,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00};
unsigned char code ren[] ={
0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x40,0x01,0x40,0x01,
0x20,0x02,0x20,0x02,0x10,0x04,0x10,0x04,0x08,0x08,0x04,0x10,0x02,0x20,0x01,0x40};
unsigned char code min[] ={
0x00,0x00,0xFC,0x1F,0x04,0x10,0x04,0x10,0x04,0x10,0xFC,0x1F,0x04,0x01,0x04,0x01,
0xFC,0x3F,0x04,0x01,0x04,0x02,0x04,0x02,0x24,0x24,0x14,0x28,0x0C,0x30,0x04,0x20};
unsigned char code gong[] ={
0x20,0x04,0x20,0x04,0x20,0x04,0x20,0x04,0xFC,0x3F,0x20,0x04,0x20,0x04,0x20,0x04,
0x20,0x04,0x20,0x04,0xFE,0x7F,0x00,0x00,0x20,0x04,0x10,0x08,0x08,0x10,0x04,0x20};
unsigned char code he[] ={
0x20,0x00,0x70,0x00,0x1E,0x00,0x10,0x3E,0x10,0x22,0xFF,0x22,0x10,0x22,0x18,0x22,
0x38,0x22,0x54,0x22,0x54,0x22,0x12,0x22,0x11,0x3E,0x10,0x22,0x10,0x00,0x10,0x00};
unsigned char code guo[] ={
0x00,0x00,0xFE,0x3F,0x02,0x20,0x02,0x20,0xFA,0x2F,0x82,0x20,0x82,0x20,0xF2,0x27,
0x82,0x20,0x82,0x22,0x82,0x24,0xFA,0x2F,0x02,0x20,0x02,0x20,0xFE,0x3F,0x02,0x20};
unsigned char 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
};
//函数原型声明
void SendBata(unsigned char d1, unsigned char d2, unsigned char d3, unsigned char d4);
void display(unsigned char word[], unsigned char select[]);
void main(void)
{
int i;
while(1)
{
//中
for (i=80; i>0; i--)
{
display(zhong, hang);
}
SendBata(0xff, 0xff, 0x00, 0x00);
//华
for (i=80; i>0; i--)
{
display(hua, hang);
}
SendBata(0xff, 0xff, 0x00, 0x00);
//人
for (i=80; i>0; i--)
{
display(ren, hang);
}
SendBata(0xff, 0xff, 0x00, 0x00);
//民
for (i=80; i>0; i--)
{
display(min, hang);
}
SendBata(0xff, 0xff, 0x00, 0x00);
//共
for (i=80; i>0; i--)
{
display(gong, hang);
}
SendBata(0xff, 0xff, 0x00, 0x00);
//和
for (i=80; i>0; i--)
{
display(he, hang);
}
SendBata(0xff, 0xff, 0x00, 0x00);
//国
for (i=80; i>0; i--)
{
display(guo, hang);
}
SendBata(0xff, 0xff, 0x00, 0x00);
}
}
void SendBata(unsigned char d1, unsigned char d2, unsigned char d3, unsigned char d4)
{
unsigned char i;
SCLK = 0;
RCLK = 0;
for(i=0; i<8; i++)
{
SER = d1 >> 7;
SCLK = 0;
//_nop_();
SCLK = 1;
d1 = d1 << 1;
}
for(i=0; i<8; i++)
{
SER = d2 >> 7;
SCLK = 0;
//_nop_();
SCLK = 1;
d2 = d2 << 1;
}
for(i=0; i<8; i++)
{
SER = d3 >> 7;
SCLK = 0;
//_nop_();
SCLK = 1;
d3 = d3 << 1;
}
for(i=0; i<8; i++)
{
SER = d4 >> 7;
SCLK = 0;
//_nop_();
SCLK = 1;
d4 = d4 << 1;
}
RCLK = 0;
RCLK = 1;
}
void display(unsigned char word[], unsigned char select[])
{
unsigned char i, d1, d2, d3, d4;
for (i=0; i<16; i++)
{
d1 = ~word[2*i+1];
d2 = ~word[2*i];
d3 = select[2*i];
d4 = select[2*i+1];
SendBata(d1,d2,d3,d4);
}
}
b、纵向取模代码:
#include <REGX51.H>
#include <intrins.h>
sbit SER = P3^4; //595的串行输入
sbit RCLK = P3^5; //595的锁存时钟
sbit SCLK = P3^6; //595的移位时钟
unsigned char code zhong[]={
0x00,0x00,0xF0,0x10,0x10,0x10,0x10,0xFF,0x10,
0x10,0x10,0x10,0xF0,0x00,0x00,0x00,0x00,0x00,
0x0F,0x04,0x04,0x04,0x04,0xFF,0x04,0x04,0x04,
0x04,0x0F,0x00,0x00,0x00
};
unsigned char code guo[] ={
0x00,0xFE,0x02,0x12,0x92,0x92,0x92,0xF2,0x92,
0x92,0x92,0x12,0x02,0xFE,0x00,0x00,0x00,0xFF,
0x40,0x48,0x48,0x48,0x48,0x4F,0x48,0x4A,0x4C,
0x48,0x40,0xFF,0x00,0x00
};
unsigned char code lie[] ={
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
};
//函数原型声明
void SendBata(unsigned char d1, unsigned char d2, unsigned char d3, unsigned char d4);
void display(unsigned char word[], unsigned char select[]);
void main(void)
{
int i;
while(1)
{
//中
for (i=80; i>0; i--)
{
display(lie, zhong);
}
SendBata(0xff, 0xff, 0x00, 0x00);
//国
for (i=80; i>0; i--)
{
display(lie, guo);
}
SendBata(0xff, 0xff, 0x00, 0x00);
// SendBata(~0x00, ~0x01, 0x00, 0x00);
// SendBata(~0x00, ~0x02, 0x00, 0x00);
// SendBata(~0x00, ~0x04, 0x0F, 0xF0);
}
}
void SendBata(unsigned char d1, unsigned char d2, unsigned char d3, unsigned char d4)
{
unsigned char i;
SCLK = 0;
RCLK = 0;
for(i=0; i<8; i++)
{
SER = d1 >> 7;
SCLK = 0;
//_nop_();
SCLK = 1;
d1 = d1 << 1;
}
for(i=0; i<8; i++)
{
SER = d2 >> 7;
SCLK = 0;
//_nop_();
SCLK = 1;
d2 = d2 << 1;
}
for(i=0; i<8; i++)
{
SER = d3 >> 7;
SCLK = 0;
//_nop_();
SCLK = 1;
d3 = d3 << 1;
}
for(i=0; i<8; i++)
{
SER = d4 >> 7;
SCLK = 0;
//_nop_();
SCLK = 1;
d4 = d4 << 1;
}
RCLK = 0;
RCLK = 1;
}
void display(unsigned char select[], unsigned char word[])
{
unsigned char i, d1, d2, d3, d4;
for (i=0; i<16; i++)
{
d1 = ~select[2*i];
d2 = ~select[2*i+1];
d3 = word[i+16];
d4 = word[i];
SendBata(d1,d2,d3,d4);
}
}