51单片机使用TM1638驱动的数码管键盘模块

前几天差不多把清翔的视频教程写完了,然后玩了两天,这次准备把以前买的TM1638芯片驱动的数码管按键模块用在51单片机上。

这个模块我在pdd上买的,用了2块的优惠券后付款7元包邮,价格还行吧,对比了下这个价格几乎就是最低价了。TM1638芯片都要1块多,再加上PCB,焊接,LED,电阻,数码管,还有运费,已经比较划算了。51单片机使用TM1638驱动的数码管键盘模块_第1张图片

 买模块店家有送显示程序,但是我还是想自己看一下数据手册,趁热打铁,记录下学习过程

一、模块图片

上面的LED全亮是我刚才写的测试程序

51单片机使用TM1638驱动的数码管键盘模块_第2张图片

最上面是8个LED,下方是8个8段数码管 ,再下方是芯片,芯片右侧是二极管(非发光的普通二极管),左侧是VCC,GND以及3条数据线总共5条线。最下方是8个按键

我的这个模块数码管是共阴极的接法,其实这个芯片可以接共阴极也可以接共阳极数码管,不过我感觉共阳极的接法用起来比较麻烦。 

 二、TM1638

2.1 管脚定义

51单片机使用TM1638驱动的数码管键盘模块_第3张图片

 带k的都是可以按键扫描的,SEG和GR是数码管段和位,STB,CLK,DIO是与数据相关的引脚 

 数据手册有说,不管芯片连接的是共阳极数码管还是共阴极数码管,SEG都必须接阳极,GR接阴极,不能反过来,这也是跟他们是P管开漏输出和N管开漏输出有关。51单片机使用TM1638驱动的数码管键盘模块_第4张图片

 P管开漏输出和N管开漏输出有什么区别,我们不需要关心,这是电路设计才需要考虑的

51单片机使用TM1638驱动的数码管键盘模块_第5张图片

 这一段告诉我们如果要读取数值,需要在上升沿读才稳定。

2.2 按键扫描

一般按键扫描是最多接3*8=24个按键,可能店家为了节省成本,我这里只接了1*8=8个按键。

数据手册推荐按键接法

51单片机使用TM1638驱动的数码管键盘模块_第6张图片

 K1接8个,K2接8个,K3接8个按键。

实际我的模块上只用了K3的8个按键

51单片机使用TM1638驱动的数码管键盘模块_第7张图片

 虽然只接了K3,但是芯片还是考虑全部,所以读取的时候会发送4个字节。

读取方式:先发送读取按键命令,DIO就会按顺序输出4个字节,数据输出也是从低位到高位

输出数据结构51单片机使用TM1638驱动的数码管键盘模块_第8张图片

每个字节都是KS(偶)K1 K2 K3 KS(偶-1)K1 K2 K3 的结构,如果按下对应的按键,那么对应的位置就是1.比如我按下了K3和KS4的按键,那么在读取的第2个字节就是1001 0001,在程序里面就可以根据读到的数值判断出按下了哪个键,然后去处理什么事情。

2.3 命令

在管脚定义图里面说了,在STB为低的第一个字节视为命令

 2.3.1 显示控制命令

 51单片机使用TM1638驱动的数码管键盘模块_第9张图片

 显示控制命令的高4位固定是8,BIT3控制是否打开显示,剩下的控制亮度。这个芯片总共有8档亮度可调.比如我想打开显示,亮度1/16,那么就可以发送0x88(1000 1000)

2.3.2 数据命令

51单片机使用TM1638驱动的数码管键盘模块_第10张图片

 高四位固定是4,低四位设置具体功能。比如我想设置普通模式,自动增加地址,写数据到显示寄存器,就可以发送0x40(0100 0000),如果我想读取按键数据,就可以发送0x42(0100 0010)

2.3.3 显示地址设置

51单片机使用TM1638驱动的数码管键盘模块_第11张图片 这个命令的高四位固定是C,低四位设置具体数值,来设置16个显示寄存器地址。其实这个命令的低四位就是对应地址的低四位。上电默认地址是00H。

 显示寄存器地址

51单片机使用TM1638驱动的数码管键盘模块_第12张图片

 他这个表官方的就是没有对齐,有点难受。写数据的时候,在某个地址先发送低位再发送高位数据。SEG10后面的X直接写0就行。

根据我的原理图,51单片机使用TM1638驱动的数码管键盘模块_第13张图片

SEG1到8控制段选,GR控制位置。 

51单片机使用TM1638驱动的数码管键盘模块_第14张图片 板载的8个红色LED接在SEG9上,所以要想亮就需要把SEG9拉高

2.4 时序图

51单片机使用TM1638驱动的数码管键盘模块_第15张图片

 Twait要求大于1us,但是51单片机1个机器周期1.085us大于1us,所以在51单片机里面可以不考虑这个延时。

51单片机使用TM1638驱动的数码管键盘模块_第16张图片

 51单片机使用TM1638驱动的数码管键盘模块_第17张图片

51单片机使用TM1638驱动的数码管键盘模块_第18张图片 驱动共阳极数码管就是没有驱动共阴极方便。

2.5 地址增加模式数据流程

51单片机使用TM1638驱动的数码管键盘模块_第19张图片

 2.6 地址固定模式数据流程

51单片机使用TM1638驱动的数码管键盘模块_第20张图片

 2.7 读按键数据流程51单片机使用TM1638驱动的数码管键盘模块_第21张图片

 Command1就是发送读按键命令,这个命令由2.3.2 数据命令里面的一个位控制

 三、编程

知道理论之后,就可以开启编程了。

3.1 地址自动增加模式

#include 

typedef unsigned char uchar;
typedef unsigned char ucahr;
typedef unsigned int uint;

sbit TM1638STB = P1^0;
sbit TM1638CLK = P1^1;
sbit TM1638DIO = P1^2;

void TM1638_WByte(ucahr cmd);

void main()
{
    TM1638STB = 0;
    TM1638_WByte(0x88);//1000 1000显示开,脉冲宽度为1/16
    TM1638STB = 1;
    
    TM1638STB = 0;
    TM1638_WByte(0x40);//普通模式,地址自动增加,写显示数据
    TM1638_WByte(0x3f);//数字0------------------------此时地址为0x00
    TM1638_WByte(0x00);//让SEG9为0,这样上方LED就不会亮
    TM1638_WByte(0x06);//数字1------------------------此时地址为0x01
    TM1638_WByte(0x00);//让SEG9为0,这样上方LED就不会亮
    TM1638_WByte(0x5b);//数字2------------------------此时地址为0x02
    TM1638STB = 1;
    
    while(1);
}

    //TM1638写一个字节(只负责送数据到线上)
void TM1638_WByte(ucahr cmd)
{
    uchar i;
    for (i = 0; i < 8; i++)
    {
        TM1638CLK = 0;
        TM1638DIO = cmd & 0x01;
        cmd >>= 1;
        TM1638CLK = 1;
    }
}



先写一个简单的程序测试一下。数码管前3个显示123,上方8个LED全不亮。在这里我没有设置显示地址,是因为默认地址就是0x00,也就是第一个数码管,当写入一个命令,后面跟一个显示数据后,地址会自动增加1个(0x01),但是这个时候地址就增加到了SEG9和SEG10这一块了,其中SEG9是控制LED的,其余没用全为0就行了,如果再继续写入一个数据,才会到0x02,这个是和第二个数码管显示内容相关的数据,以此类推。

如果使用的是固定地址模式,那么每次每次发送显示数据前都要指明显示地址。

如果想让数码管模块在单片机按下复位键还能正常显示,那么就需要一个初始化函数,也就是清屏函数,如下:

void TM1638_clear()
{
    uchar i;
    TM1638STB = 1;
    TM1638STB = 0;
    TM1638_WByte(0x40);
    for (i = 0; i < 16; i ++)
        TM1638_WByte(0x00);//在所有16个地址中写入0x00
    TM1638STB = 1;
    TM1638STB = 0;
    TM1638_WByte(0xc0);//设置显示地址为0x00
    TM1638STB = 1;
}

void main()
{
    TM1638_clear();
    TM1638STB = 0;
    TM1638_WByte(0x88);//1000 1000显示开,脉冲宽度为1/16
    TM1638STB = 1;
    
    TM1638STB = 0;
    TM1638_WByte(0x40);//普通模式,地址自动增加,写显示数据
    TM1638_WByte(0x3f);//数字0------------------------此时地址为0x00
    TM1638_WByte(0x00);//让SEG9为0,这样上方LED就不会亮
    TM1638_WByte(0x06);//数字1------------------------此时地址为0x01
    TM1638_WByte(0x00);//让SEG9为0,这样上方LED就不会亮
    TM1638_WByte(0x5b);//数字2------------------------此时地址为0x02
    TM1638STB = 1;
    
    while(1);
}

写一个清屏函数,在主函数调用一下就可以了 

写一个命令还需要自己先把STB拉低,写完再拉高,比较麻烦,封装一下

void TM1638_Wcmd(ucahr cmd)
{
    TM1638STB = 0;
    TM1638_WByte(cmd);
    TM1638STB = 1;
}

 

3.2 现象

51单片机使用TM1638驱动的数码管键盘模块_第22张图片

显示出了123,但是我没有设置地址,这就是地址增加模式。肉眼看到的是和LED一样的红色,但是拍出来颜色编程黄白色了。

 3.3 固定地址模式

void main()
{
    TM1638_clear();
    TM1638STB = 0;
    TM1638_WByte(0x88);//1000 1000显示开,脉冲宽度为1/16
    TM1638STB = 1;
    
    TM1638STB = 0;
    TM1638_WByte(0x44);//普通模式,地址固定,写显示数据
    TM1638STB = 1;
    
    TM1638STB = 0;
    TM1638_WByte(0xc0);//设置显示地址为0x00
    TM1638_WByte(0x3f);//数字0------------------------此时地址为0x00
    TM1638STB = 1;
    
    TM1638STB = 0;
    TM1638_WByte(0xc2);//设置显示地址为0x02
    TM1638_WByte(0x06);//数字1------------------------此时地址为0x02
    TM1638STB = 1;
    
    TM1638STB = 0;
    TM1638_WByte(0xc4);//设置显示地址为0x04
    TM1638_WByte(0x5b);//数字2------------------------此时地址为0x04
    TM1638STB = 1;
    
    TM1638STB = 0;
    TM1638_WByte(0xc8);//设置显示地址为0x08
    TM1638_WByte(0x4f);//数字3------------------------此时地址为0x08
    TM1638STB = 1;
    
    while(1);
}

固定地址模式,就是在每次发送显示数据之前都要发送显示位置地址

3.4 现象

51单片机使用TM1638驱动的数码管键盘模块_第23张图片

我在代码里面跳过了第4个数码管的显示地址,所以第四个数码管没有显示。

经过对比发现,如果用地址自动增加模式,我也可以实现发一次地址写一个数据,相当于拥有固定地址模式可以实现的功能,也可以指定一个起始地址然后连续发送数据,可见地址自动增加模式似乎更有优势。

3.5 按键扫描

我这个按键扫描读取到的数值和数据手册不一样,不知道为什么,我实际读取到的值与对应按键关系如下表

按键 \数据 byte1 byte2 byte3 byte4
S1 01 00 00 00
S2 00 01 00 00
S3 00 00 01 00
S4 00 00 00 01
S5 10 00 00 00
S6 00 10 00 00
S7 00 00 10 00
S8 00 00 00 10

我就按我的情况来编程了。我判断键值的想法是先把读取到的4个字节数据存放在一个数组里面,然后判断数组里面的数是否有0x01或者0x10,如果有这两个任何一个,说明有按键被按下了,再看如果有的是0x01,那么说明前4个按键里面的某个按键被按下了,直接跳出数组循环,当前循环次数就是按键序号。如果检测到0x10,说明后4个按键被按下,当前循环次数+4就是对应的按键序号。

读取数据并判断键值的函数

uchar TM1638_RByte()
{
    uchar temp,i;
    temp = 0;
    TM1638DIO = 1;//释放数据线
    for (i = 0; i < 8; i++)
    {
        temp >>= 1;
        TM1638CLK = 0;
        TM1638CLK = 1;
        if (TM1638DIO) temp |= 0x80;
        
    }
    return temp;
}

uchar TM1638_keyscan()
{
    uchar i,temp[4];
    TM1638STB = 0;
    TM1638_WByte(0x42);
    for (i = 0; i < 4; i++)
        temp[i] = TM1638_RByte();
    TM1638STB = 1;
    
    for (i = 0; i < 4; i++)
    {
        if (temp[i] == 0x01 || temp[i] == 0x10)
            return ((temp[i] == 0x01) ? i:(4+i));
    }
    return 8;
}

如果有按键被按下,返回值哪里用了三元运算符。

完整代码如下。我的程序写的是按下S1让LED8翻转,按下S2让蜂鸣器翻转。

在main函数里面我对按键进行了消抖

#include 

typedef unsigned char uchar;
typedef unsigned char ucahr;
typedef unsigned int uint;

sbit TM1638STB = P1^0;
sbit TM1638CLK = P1^1;
sbit TM1638DIO = P1^2;
sbit LED8 = P1^7;
sbit beep = P2^3;

uchar code du[23];

void TM1638_clear();
void TM1638_WByte(ucahr cmd);
void TM1638_Wcmd(ucahr cmd);
uchar TM1638_RByte();
uchar TM1638_keyscan();
void delay(unsigned int i);

void main()
{
    uchar key_val;
    TM1638_clear();
    TM1638_Wcmd(0x8A);//亮度4/16
    TM1638STB = 0;
    TM1638_WByte(0X40);//普通模式,地址自动增加,写显示数据
    TM1638_WByte(du[0]);//在默认的0x00地址写数字0
    TM1638STB = 1;
    
    while(1)
    {
        key_val = TM1638_keyscan();
        delay(20);
        if (TM1638_keyscan() != key_val)
            key_val = 8;
        
        if (key_val != 8)
        {
            switch (key_val)
            {
                case 0: LED8 = !LED8; key_val = 8;break;
                case 1: beep = !beep; key_val = 8;break;
            }
        }
        delay(50);
    }
}


    //TM1638写一个字节(只负责送数据到线上)
void TM1638_WByte(ucahr cmd)
{
    uchar i;
    for (i = 0; i < 8; i++)
    {
        TM1638CLK = 0;
        TM1638DIO = cmd & 0x01;
        cmd >>= 1;
        TM1638CLK = 1;
    }
}

void TM1638_Wcmd(ucahr cmd)
{
    TM1638STB = 0;
    TM1638_WByte(cmd);
    TM1638STB = 1;
}

uchar TM1638_RByte()
{
    uchar temp,i;
    temp = 0;
    TM1638DIO = 1;//释放数据线
    for (i = 0; i < 8; i++)
    {
        temp >>= 1;
        TM1638CLK = 0;
        TM1638CLK = 1;
        if (TM1638DIO) temp |= 0x80;
        
    }
    return temp;
}

uchar TM1638_keyscan()
{
    uchar i,temp[4];
    TM1638STB = 0;
    TM1638_WByte(0x42);
    for (i = 0; i < 4; i++)
        temp[i] = TM1638_RByte();
    TM1638STB = 1;
    
    for (i = 0; i < 4; i++)
    {
        if (temp[i] == 0x01 || temp[i] == 0x10)
            return ((temp[i] == 0x01) ? i:(4+i));
    }
    return 8;
}


void TM1638_clear()
{
    uchar i;
    TM1638STB = 1;
    TM1638STB = 0;
    TM1638_WByte(0x40);
    for (i = 0; i < 16; i ++)
        TM1638_WByte(0x00);//在所有16个地址中写入0x00
    TM1638STB = 1;
    TM1638STB = 0;
    TM1638_WByte(0xc0);//设置显示地址为0x00
    TM1638STB = 1;
}

void delay(unsigned int i)
{
    unsigned int j;
    for (; i > 0; i--)
        for (j = 114; j > 0; j--);
}

uchar code du[]={ 
                //0    1     2     3     4     5     6     7     8
                0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F,
                //9   A(10) B(11)  C(12) D(13) E(14) F(15) H(16) L(17)     
                0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x76, 0x38,
                //n(18) u(19) -(20) 熄灭(21)  .(22)
                0x37, 0x3E, 0x40, 0x00,        0x80};

3.6 LED控制

控制LED,只需要 在对应地址让SEG9=0,比如我想让第2个LED亮,那么我就先把地址设置为0x03,然后发送数据0x01,这样就可以了。

TM1638模块差不多都说完了,我没有逻辑分析仪,示波器之类的工具,在分析获取到的数据的时候费点时间,我是把接收到的4个字节数据放在数组里面,然后把数组4个字节通过串口发送到电脑来看我到底接受到了什么信息,然后根据键值的特点来写的程序,我还不清楚为什么我收到的数据跟手册里写的不一样,不过还是用起来了。

|

在自己使用时,还可以把显示函数封装起来,以及LED控制封装,用起来更方便。

你可能感兴趣的:(清翔51学习笔记,51单片机,计算机外设,嵌入式硬件)