51单片机外设篇:LED点阵

点阵简介

LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等。LED点阵屏广泛应用于各种公共场合,如汽车报站器、广告屏以及公告牌等。

LED点阵屏分类:

按颜色:单色、双色、全彩

按像素:8*8、16*16等(大规模的LED点阵通常由很多个小点阵拼接而成)

显示原理:

LED点阵屏的结构类似于数码管;

LED点阵屏与数码管一样,有共阴和共阳两种接法,不同的接法对应的电路结构不同;

统一规定:

每行的8个LED的负极连接一起,构成8根行线,每列的8个LED的正极连接一起,构成8根列线,就是共阴极接法。

每行的8个LED的正极连接一起,构成8根行线,每列的8个LED的负极连接一起,构成8根列线,就是共阳极接法。

不过感觉这里的共阴极和共阳极没太大的意义,就是个方向的问题。

由显示原理可知,如果要驱动一个8*8点阵屏,需要16个引脚,也就是2个I/O口,如果要驱动16*16点阵屏,就需要4个端口,很显然,只是驱动点阵屏就把单片机的端口全用完了,这肯定是不行的。怎么办呢?有没有办法像38译码器那样可以满足功能要求,又可以减少引脚的芯片呢?

移位寄存器

74HC595

74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。

芯片引脚图:

在这里插入图片描述

数据端:

QA--QH:八位并行输出端,可以直接控制数码管的8个段;

QH': 级联输出端。将它接下一个595的SER端;

SER:串行数据输入端。

控制端:

/SCLR(10脚):低电平时将移位寄存器的数据清零。

SCLK(11脚):输入控制引脚。上升沿时数据寄存器的数据移位。QA-->QB-->QC-->...-->QH;下降沿移位寄存器数据不变。(脉冲宽度:5V时,大于几十纳秒就行了。通常都选微秒级) 。

RCLK(12脚):输出控制引脚。上升沿时移位寄存器的数据进入数据存储寄存器,下降沿时存储寄存器数据不变。通常将RCLK置为低点平,当移位结束后,给RCLK端一个正脉冲(5V时,大于几十纳秒就行了。我通常都选微秒级),更新显示数据。

/OE(13脚): 使能引脚。高电平时禁止输出(高阻态)。如果单片机的引脚不紧张,用一个引脚控制它,可以方便地产生闪烁和熄灭效果。比通过数据端移位控制要省时省力。

总的来说,有三个功能:串转并、移位、锁存。

通常,如果想要控制一个8*8的点阵屏,就需要16个引脚,但是,如果使用了移位寄存器,就只需要1个串行输入引脚,两个移位寄存器即可,这两个移位寄存器通过QH'级联输出端连接起来。这时候,另外再加两个控制引脚,即SCLK和RCLK。

总结来说,3个引脚,2个移位寄存器即可实现8*8点阵屏的显示。

时序问题

单片机的时钟,我所理解的应该主要是用来执行操作指令的,即在时钟周期内执行多少条指令,并且修复反馈延时等功能。

我之前以为某个引脚的上升沿和下降沿就是这个时钟的上升沿和下降沿,其实这种想法不太正确。引脚的上升沿是这样的:用代码先将某个引脚置0,再将其置1拉高,这样就有了上升沿。

如果某个引脚是电平触发的,通常不用额外的引脚,只需接地或者接电源即可。但如果是边沿触发,就需要连接芯片控制端到单片机引脚了,以提供上升沿或者下降沿。

比如,怎么给某个引脚提供上升沿:

sbit a = P0^0;
a = 0;
a = 1;

这个就能提供上升沿了。

查看原理图

点亮点阵

我在原reg51.h中定义了p3.4/p3.5/p3.6;

/* customize */
sbit SER  = P3^4;
sbit RCLK = P3^5;
sbit SCLK = P3^6;

每一组从最高位开始送

/**
  *@file    lattice.c
  *@author  Timi
  *@date    2022.07.20
  */

#include <reg51.h>

#define uchar unsigned char

void LightSpecificLattices(uchar latticeRow, uchar latticeColumn);
        
//函数入口
void main(void)
{   
    uchar latticeRow = 0xFF;    //经测试,确认电路连线控制的是行,最高位对应的是最底下一行
    uchar latticeColumn = 0x80;  //经测试,确认电路连线控制的是列,最高位对应的是最右边一列
    
    LightSpecificLattices(latticeRow, latticeColumn);
}

/**
  *@brief
  *@param[in]
  *@param[out] 
  *@return
  */
void LightSpecificLattices(uchar latticeRow, uchar latticeColumn)
{
    int i = 0, j = 0;
    for(i; i<8; i++)
    {
        uchar rowHighestBit = (latticeRow >> 7);
        SER  = rowHighestBit;
        SCLK = 0;
        SCLK = 1;
        latticeRow <<= 1;
    }
    
    for(j; j<8; j++)
    {
        uchar columnHighestBit = (latticeColumn >> 7);
        SER  = columnHighestBit;
        SCLK = 0;
        SCLK = 1;
        latticeColumn <<= 1;
    }
    
    RCLK = 0;
    RCLK = 1;   //经测试,锁存必须要,否则点不亮
}

字模取模

点阵中的汉字数字图形等,实质上就是亮灭的组合,那么,哪些点亮哪些点灭就是一个需要解决的问题,我们可以手动构造,只是这样过于麻烦,所以,就可以借助字模软件来获取亮灭信息的编码。

以8*8点阵为例,有64个点,每个点需要一位二进制数,那么,就需要64个二进制数来显示,共8个字节。字模软件就会根据既定的规则生成一个unsigned char型的8字节数组。

我们拿到数组,然后根据生成规则去使用这些数据,就可以显示对应的数字了。比如:

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

以上图为准,当共阳极,低电平点亮时,数组如下:

unsigned char code TableH[] = 
{
	0x83,0xEF,0xEF,0x83,0xEF,0xEF,0x83,0xFF,
};

这里有个问题需要想清楚,共阴极时,当第一个0进入时,其实整个一行都是灭的,因为阳极都是连到一起的。这样,只能控制整行是亮的还是灭的,怎么能控制单独的点是亮的还是灭的呢?

我仔细想了想,只靠一端,肯定是没办法控制单个点的,即使是扫描,也是按行来显示或者不显示的。

应该是要靠“段选+位选”的思路来扫描。

逐行扫描的双重理解

以第一行为例,阳极作为行选,先选中第一行,然后依次将取模数组放入阴极,就可以点亮第一行中的对应点。

接着,依次行选+点选,就可以实现逐行扫描。

在这之前,我的理解是一个点一个点地进行扫描,即从左上角第一个点开始,从左到右,从上到下依次扫描。

这种理解其实没错,这里的行扫描,其实是因为使用了移位寄存器,所有的点是一次性过来的,如果是点选是一位一位地过来,过来8个点就成了一行,也就是之前以为的“逐点扫描”。

简单来说,就是,先一行一行显示,然后连起来。

显示汉字

/**
  *@file    lattice.c
  *@author  Timi
  *@date    2022.07.20
  */

#include <reg51.h>

#define uchar unsigned char

void SendData(uchar latticeRow, uchar latticeColumn);
void ShowWord(uchar typeFace[]);
void Delay();
        
//函数入口
void main(void)
{    
    uchar typeFace[] = 
    {
        0xC1, 0xF7, 0xF7, 0xC1, 0xF7, 0xF7, 0xC1, 0xFF  //王字
    };  //经测试,确认电路连线控制的是列,最高位对应的是最右边一列
    
    ShowWord(typeFace);
}

/**
  *@brief
  *@param[in]
  *@param[out]
  *@return
  */
void ShowWord(uchar typeFace[])
{
    uchar latticeRow[] = 
    {
        0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
    };  //经测试,确认电路连线控制的是行,最高位对应的是最底下一行
    
    while(1)
    {  
        int i = 0;
        
        for(i; i < 8; i++)
        {
            SendData(latticeRow[i], typeFace[i]);
        }
    }   
}

/**
  *@brief
  *@param[in]
  *@param[out]
  *@return
  */
void SendData(uchar latticeRow, uchar latticeColumn)
{  
    int i = 0, j = 0;
    
    for(i; i < 8; i++)
    {
        uchar rowHighestBit = (latticeRow >> 7);
        SER  = rowHighestBit;
        SCLK = 0;
        SCLK = 1;
        latticeRow <<= 1; 
    }
    
    for(j; j < 8; j++)
    {
        uchar columnHighestBit = (latticeColumn >> 7);
        SER  = columnHighestBit;
        SCLK = 0;
        SCLK = 1;
        latticeColumn <<= 1; 
    }
    
    RCLK = 0;
    RCLK = 1;   //经测试,锁存必须要,否则点不亮
}

~~~~~~~~~~~~~~~~~~~~~~遇到了一个很恶心的问题,找了大半天都没找到原因,就是一到第二行就会出现显示问题,其他行都很正常。

uchar latticeRow = 0x02;

程序都是一样的逻辑,为啥就是在第二行出现错误显示呢?搞不明白~~~

~~~~~~~~~~~~~~~~~~~~~~

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

单独循环显示第二行,每次显示的结果都不一样,而且还能把第一行点亮。

到底是哪里出了问题?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

~~~~~~~~~~~~~~~~~~~~~~~~~先不纠结了,后面再说吧!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值