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

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

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

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

一、模块图片

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

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

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

 二、TM1638

2.1 管脚定义

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

 数据手册有说,不管芯片连接的是共阳极数码管还是共阴极数码管,SEG都必须接阳极,GR接阴极,不能反过来,这也是跟他们是P管开漏输出和N管开漏输出有关。

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

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

2.2 按键扫描

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

数据手册推荐按键接法

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

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

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

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

输出数据结构

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

2.3 命令

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

 2.3.1 显示控制命令

 

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

2.3.2 数据命令

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

2.3.3 显示地址设置

 这个命令的高四位固定是C,低四位设置具体数值,来设置16个显示寄存器地址。其实这个命令的低四位就是对应地址的低四位。上电默认地址是00H。

 显示寄存器地址

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

根据我的原理图,

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

 板载的8个红色LED接在SEG9上,所以要想亮就需要把SEG9拉高

2.4 时序图

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

 

 驱动共阳极数码管就是没有驱动共阴极方便。

2.5 地址增加模式数据流程

 2.6 地址固定模式数据流程

 2.7 读按键数据流程

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

 三、编程

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

3.1 地址自动增加模式

#include <reg52.h>

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 现象

显示出了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 现象

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

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

3.5 按键扫描

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

按键 \数据byte1byte2byte3byte4
S101000000
S200010000
S300000100
S400000001
S510000000
S600100000
S700001000
S800000010

我就按我的情况来编程了。我判断键值的想法是先把读取到的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 <reg52.h>

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控制封装,用起来更方便。

  • 25
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是51单片机驱动TM1640点亮数码管的完整代码,包括TM1640的驱动代码: ```c #include <reg51.h> #define TM1640_DIN P1_0 #define TM1640_SCLK P1_1 #define DEL 1 void delay_us(unsigned int us) { while (us--) { // 延时一微秒 } } void TM1640_start() { TM1640_DIN = 1; TM1640_SCLK = 1; delay_us(DEL); TM1640_DIN = 0; delay_us(DEL); TM1640_SCLK = 0; delay_us(DEL); } void TM1640_stop() { TM1640_DIN = 0; TM1640_SCLK = 0; delay_us(DEL); TM1640_SCLK = 1; delay_us(DEL); TM1640_DIN = 1; delay_us(DEL); } void TM1640_write(unsigned char data) { unsigned char i; for (i = 0; i < 8; i++) { TM1640_SCLK = 0; TM1640_DIN = data & 0x01; data >>= 1; delay_us(DEL); TM1640_SCLK = 1; delay_us(DEL); } } void TM1640_display(unsigned char *data) { unsigned char i; TM1640_start(); TM1640_write(0x40); TM1640_stop(); TM1640_start(); TM1640_write(0xC0); for (i = 0; i < 16; i++) { TM1640_write(data\[i\]); } TM1640_stop(); } void main() { unsigned char displayData\[16\] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; TM1640_display(displayData); while (1) { // 循环显示 } } ``` 这段代码使用51单片机的GPIO口来驱动TM1640点亮数码管。其中,TM1640_start()函数用于启动通信时序,TM1640_stop()函数用于结束通信时序,TM1640_write()函数用于向TM1640写入数据,TM1640_display()函数用于显示数据。在main()函数中,我们定义了一个显示数据的数组displayData,并通过TM1640_display()函数将数据显示在数码管上。你可以根据需要修改displayData数组中的数据来实现不同的显示效果。 #### 引用[.reference_title] - *1* *2* *3* [【洋桃一号板】STM32F103CBT6标准库函数驱动TM1640点亮数码管](https://blog.csdn.net/wsq_666/article/details/130093966)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值