欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

LED点阵

程序员文章站 2024-03-15 13:48:35
...

1 LED点阵简介

1.1 什么是LED点阵

(1)外观。

(2)作用。

(3)内部构造原理图。点阵的优势就是8+8个IO口独立控制8*8个LED亮灭。

(4)LED点阵如何工作。

1.2 如何驱动LED点阵

(1)单片机端口直接驱动。要驱动8*8的点阵需要2个IO端口(16个IO口)、要驱动16*16的点阵需要4个IO端口(32个IO口)。

(2)使用串转并移位锁存器驱动。要驱动16*16点阵只需要4个74HC595+3个IO口即可(数据口、SCLK、RCLK)。

SER 10101100 同一个引脚不同的时间送进来,串入(串行输入)
QA 1 同时在不同引脚输出,并出(并行输出)
QB 0
… …
QH 0

2 原理图分析

2.1 POS1-16和NEG1-16分别接移位锁存器并行输出端

(1)POS就是Positive正极,NEG是negetive负极。

2.2 74HC595的接法分析

(1)QA-QH8路并行输出接到点阵的一个端口。

(2)QH’串行输出口接下一个74HC595的串行输入SER(串联顺序按照ABCD)。

(3)SER串行输入接:第一个595的SER通过跳线帽JP595接P3.4,后面的每一个SER接前一个的QH’。这样就构成了ABCD4个595芯片依次串联。所以将来编程时整个4个74HC595的串行数据都是从P3.4出来的。

(4)SCLK(SRCLK)接P3.6。

(5)RCLK接P3.5。

总结:
(1)SCLK和RCLK是一样的接法,都在接在P3.5和P3.6上。
(2)总共涉及到的IO口有3个:P3.4、P3.5、P3.6。
(3)外部接线重点:2个8pin杜邦线+1个跳线帽(JP595)。

LED点阵

3 LED点阵编程实践

3.1 74HC595的时序分析

(1)芯片与芯片之间的通信,都是按照一定的时序进行的。

(2)时序就是芯片与芯片之间的通信引脚上电平变化以时间轴为参考的变化顺序。

(3)时序是通信双方事先定义好的,通信的发送方必须按照时序来发送有意义的信息,通信的接收放按照时序去接收解析发送方发来的电平变化信息,然后就知道发送方要给我发送什么东西了。

(4)我们编程时:发送方是单片机,接收方是74HC595。因为595芯片本身是不能编程的,他的时序芯片出厂时已经设定好了。因此我们单片机必须迁就595芯片,按照595芯片的时序来给他发信息。

(5)所以我们要先搞清楚74HC595的时序规则。595的芯片手册上就有它的时序描述(时序图),参考描述就可以明白595芯片的时序规则,然后将其用编程语言表述出来就是程序了。

(6)74HC595的时序关键是:SCLK和RCLK。SCLK是移位时钟,595芯片内部在每个SCLK的上升沿会对SER引脚进行一次采样输入,就向595内部输入了1位,如此循环8次就输入了8位二进制。RCLK是锁存时钟,QA-QH的8位并行输出信号在RCLK的上升沿进行一次锁存更新。(锁存的含义是指在下一次锁存之前维持输出引脚的电平信号不变,但是如果不进行锁存的话,输出引脚的电平还是会随着输入引脚电平的变化而发生变化)。

(7)理解74HC595芯片的通信时序关键,其实就是:SER进行数据的串行输入,SCLK提供移位时钟,RCLK提供锁存时钟。

3.2 sbit定义位变量

(1)之前编程都是直接操作一个IO端口,可以用端口名(P0、P1)来整体操作一个IO端口中的8个引脚。但是这种方法不能操控单独1个IO口。

(2)今天编程需要单独操作1个IO引脚,譬如要操作P3.4,但是直接写P3.4的话C语言是不认识的,而必须使用sbit关键字来定义一个引脚。

sbit SER = P3^4;

3.3 全屏点亮测试

总结:
(1)编写硬件控制代码,时序理解是关键。只要时序理解正确的,并且代码按照时序的要求去写,就没问题。
(2)时序操作部分的代码只要写好了并且是正确的,下来这一块就没问题了,很简单了,因为它是死板的不变的。

#include <reg51.h>


sbit SER  = P3^4;           // 74HC595的串行输入端
sbit RCLK = P3^5;           // 锁存时钟
sbit SCLK = P3^6;           // 移位时钟


void main(void)
{
    unsigned char i = 0;
    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的最高bit取出来给SER 
        SCLK = 0;   
        SCLK = 1;               // 2步制造了一个SCLK的上升沿
        d1 = d1 << 1;
    }
    // 至此已经在8个SCLK上升沿把d1的8位依次全部发出去了
    // 但是还没有进行锁存,所以QA-QH还没东西

    for (i=0; i<8; i++)
    {
        SER = d2 >> 7;          // 将d2的最高bit取出来给SER
        SCLK = 0;
        SCLK = 1;               // 2步制造了一个SCLK的上升沿
        d2 = d2 << 1;
    }
    // 至此已经把d1和d2都发出去了,并且d1已经被d2挤到第2个595芯片里面去了
    // 但是还没有进行锁存,所以QA-QH还没东西


    for (i=0; i<8; i++)
    {
        SER = d3 >> 7;          // 将d3的最高bit取出来给SER
        SCLK = 0;
        SCLK = 1;               // 2步制造了一个SCLK的上升沿
        d3 = d3 << 1;
    }
    // 至此已经把d1和d2和d3都发出去了,并且d1已经被d2和d3挤到第3个595芯片里面去了
    // 但是还没有进行锁存,所以QA-QH还没东西


    for (i=0; i<8; i++)
    {
        SER = d4 >> 7;          // 将d4的最高bit取出来给SER
        SCLK = 0;
        SCLK = 1;               // 2步制造了一个SCLK的上升沿
        d4 = d4 << 1;
    }
    // 至此已经把d1和d2和d3和d4都发出去了,并且d1已经被d2、d3、d4挤到第4个595芯片里面去了
    // 但是还没有进行锁存,所以QA-QH还没东西


    // 截至这里,4个字节的数据d1、d2、d3、d4已经顺着74HC595的SER->QH'的串行输入
    // 串行输出路线,已经爬满了4个74HC595(最先送出去的到了最后没一个595中)
    // 但是目前为止4个595的QA-QH还都没有输出呢,点阵自然不会亮。

    // 然后要进行一次锁存,4个595芯片同时在进行锁存,各自锁存住了自己的数据
    RCLK = 0;
    RCLK = 1;
    // 这两句之后595就完成了锁存,d1-d4就会影响4个595芯片的并行输出端,进而
    // 会影响点阵中LED的正负极的值,然后LED就会亮或者灭。

    // 按照我们写的程序,d1和d2是负极,d3和d4是正极。
}

3.4 对点阵点亮规律的探索

(1)编程点亮最上面一排。

(2)编程点亮最下面一排。

(3)编程点亮最左面一列。

(4)编程点亮左上角一颗。

(5)编程点亮角上4颗。

#include <reg51.h>

#define uchar unsigned char     // #define要尽量靠前写,而且后面是无分号的



sbit SER  = P3^4;           // 74HC595的串行输入端
sbit RCLK = P3^5;           // 锁存时钟
sbit SCLK = P3^6;           // 移位时钟


// 函数原型声明
//void SendData(unsigned char d1, unsigned char d2, unsigned char d3, unsigned char d4);
void SendData(uchar d1, uchar d2, uchar d3, uchar d4);



void main(void)
{
    // d1 d2 对应负极, d3,d4对应正极
    // d1 d3 对应9-16, d2 d4对应的是1-8
    // d1-d4都是最低bit位表示编号小的数字,譬如d4的bit0对应1
    // d3和d4是正极,规律是1亮0灭;d1和d2是负极,规律是1灭0亮


     //SendData(0x00, 0x00, 0xff, 0xff);            // 全亮
     //SendData(0x00, 0x00, 0x00, 0xff);            // 亮半屏,上半屏幕亮
     //SendData(0x00, 0x00, 0xff, 0x00);            // 亮半屏,下半屏幕亮
     //SendData(0x00, 0x00, 0x00, 0x01);            // 最上面一行
     //SendData(0x00, 0x00, 0x80, 0x00);            // 最下面一行
     //SendData(0xff, 0xfe, 0xff, 0xff);            // 最左面一列
     SendData(0x7f, 0xfe, 0x80, 0x01);              // 角上4颗亮

}

4 字模介绍

4.1 何为字模

(1)如何记录组成字的LED点阵亮灭信息(16*16点阵一共有256点,显示一个特定的字需要其中有些点亮而另一些不亮,如何记录哪些点亮哪些点不亮?用字模)。

字模如何工作?256个点用256个二进制位表示,1表示这个点亮,0表示不亮。256个点就是256个二进制位,也就是256/8=32个字节。所以一个大小为16*16的字的字模是32个字节大小。所以字模的表现形式就是32个unsigned char型数据。

(2)字模如何获取。一般都是用专门的字模提取软件去提取的。这种软件的作用就是给一个字就能自动得到这个字对应的32个字节的字模编码。

(3)字模的结果不是唯一的,和你提取字模的方式有关的。(横向纵向、从上到下之类区分)提取字模时是没有标准的,怎么做都是对的或者都是错的,关键是你提取字模的方式和你用来在点阵上显示这个字模的函数必须对应。

4.2 字模提取软件的使用

(1)使用方式:第一步先选择字形(实际开发板上点阵多大就选择多大),第二步再选择合适的字体、字号等,第三步选择编码方式和取模方向,第四步直接将得到的数组复制走。

4.3 字模的手工分析和验证

(1)手工对比字模内容和屏幕显示,从而确认取模方式。

(2)结合前面课程,思考如何将之显示出来。

5 横向取模的显示函数

5.1 工程建立

5.2 先显示第一行

5.3 多显示2行去摸索规律

(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次。

// 字模
unsigned char zhu[32] = {128,0,136,0,136,0,248,31,132,0,130,0,128,0,255,127,192,1,160,2,144,4,136,8,132,16,131,96,128,0,128,0};


void main(void)
{
     SendData(~0, ~128, 0x00, 0x01);      // 显示第1行
     SendData(~0, ~136, 0x00, 0x02);      // 显示第2行
     SendData(~0, ~136, 0x00, 0x04);      // 显示第3行
     SendData(~31, ~248, 0x00, 0x08);     // 显示第4行
     SendData(~0, ~132, 0x00, 0x10);      // 显示第5行
     SendData(~0, ~130, 0x00, 0x20);      // 显示第6行
     SendData(~0, ~128, 0x00, 0x40);      // 显示第7行
     SendData(~127, ~255, 0x00, 0x80);        // 显示第8行

}

5.4 继续完善

(1)定义行选择数组。
(2)使用for循环进行显示。
(3)编写点阵字显示函数。

unsigned char peng[32] = {0,8,238,4,170,62,170,34,170,38,238,42,170,34,170,50,170,2,238,126,170,64,170,64,170,94,173,64,145,80,200,32};


unsigned char 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, 
};


void main(void)
{
     Display(peng, hang);
}

// 函数定义
void Display(uchar zimu[32], uchar hang[32])
{
     uchar i = 0;

     for (i=0; i<16; i++)
     {
         SendData(~zimu[2*i+1], ~zimu[2*i], hang[2*i], hang[2*i+1]);      // 每次亮一行
     }
}

6 纵向取模的显示函数

(1)先观察总结纵向取模的取模规律。
(2)显示第1列。
(3)多显示几列寻找规律。
(4)编写成函数然后实验测定。

// 字模
unsigned char zhu[] = {128,160,144,142,136,136,136,255,136,136,136,136,136,128,128,0,32,32,16,8,4,2,1,255,1,2,4,8,16,32,32,0};
unsigned char lie[] = 
{
0xff, 0xfe, 0xff, 0xfd, 0xff, 0xfb, 0xff, 0xf7,
0xff, 0xef, 0xff, 0xdf, 0xff, 0xbf, 0xff, 0x7f,
0xfe, 0xff, 0xfd, 0xff, 0xfb, 0xff, 0xf7, 0xff, 
0xef, 0xff, 0xdf, 0xff, 0xbf, 0xff, 0x7f, 0xff, 
};



void main(void)
{
    uchar i = 0;

    for (i=0; i<16; i++)
    {
        SendData(lie[2*i], lie[2*i+1], zhu[i+16], zhu[i]);
    }

     //SendData(0xff, 0xfe, 32, 128);           // 最左面一列
     //SendData(0xff, 0xfd, 32, 160);           // 最左面2列

}