Stm32 四位数码管

四位数码管

关键词: stm32 滴答时钟 数码管 四位数码管

主要内容:

  • 数码管原理
  • 四位数码管
  • 驱动程序
  • 测试程序

文档参考:

最新地址: https://taotaodiy-mcu.readthedocs.io/en/latest/sensor/digit.html

Stm32 四位数码管_第1张图片

数码管原理

数码管主要用来显示数据。

Stm32 四位数码管_第2张图片

数码管内部实际上就是8个发光二极管,我通过给A-H编号的发光二极管给高低不同的电平,最终使得数码管显示不同的字符。
如图所示,数码管分共阴(即发光二极管阴极连到一起)还是共阳(即发光二极管阳极连到一起)。

Stm32 四位数码管_第3张图片

比如说我们希望,数码管显示5这个数字。那我们的思路是什么呢?

首先我们想要的效果应该是这样:

Stm32 四位数码管_第4张图片

也就是,通过电路和程序控制数码管的A,F,G,C,D这五个发光二极管发光即可。

上图已经对数码管用A-H进行了排序,如果用0/1来表示发光二极管的亮灭,

5这个数字就可以得到下面的一串有序编码。

#对应的数码管编号
H G F E D C B A
#共阴二极管亮为1,灭为0
0 1 1 0 1 1 0 1  --> 0x6b

这样就得到一个编码,0x6b,
是的,数码管原理就是如此,我们给他这样一个编码就能在数码管上显示5

共阳数码管则相反

#共阳二极管亮为0,灭为1
1 0 0 1 0 0 1 0  --> 0x92

对于单个数码管我们就可以得出 0-f 的编码,如下。

#共阳数码管编码
unsigned int DIGIT_ANODE[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e}; //0~f
#共阴数码管编码
unsigned int DIGIT_CATHODE[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//0~f

多位数码管

上面是单个数码管的显示原理和数码管编码由来,当我们需要同时显示一段数据的时候就需要多段数码管,也就是多个数码管的组合。
这就存在一个问题,一个数码管需要8个IO来控制,那么再加几个数码管,不就把IO占用光了吗?

于是就有了位选,数码管通用八根段选,使用位选来控制单个数码管显示。

Stm32 四位数码管_第5张图片

当然这样还是比较费IO,于是就有了各种数码管控制芯片。

文章开头的图片是一个四位数码管,使用TM1637来控制显示,下面是它的显示和控制原理。

Stm32 四位数码管_第6张图片

数码管编码

这里的四位数码管唯一不一样的地方就是中间多了两个点,主要是为了显示时间,有那种闪烁的效果。
显示数字的时候,其编码和上面提到的数码管编码略微有点不同。

#普通数码管 0-f 编码
unsigned int DIGIT_CATHODE[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//0~f
#点亮中间的小点 0-f 编码
unsigned int DIGIT_CATHODE_POINT[]={0xBf,0x86,0xDb,0xCf,0xE6,0xEd,0xFd,0x87,0xFf,0xEf,0xF7,0xFc,0xB9,0xDe,0xF9,0xF1};//0~f

控制原理(TM1637)

TM1637
是一种带键盘扫描接口的LED(发光二极管显示器)驱动控制专用电路,内部集成有MCU
数字接口、数据锁存器、LED 高压驱动、键盘扫描等电路。
主要应用于电磁炉、微波炉及小家电产品的显示屏驱动。采用DIP/SOP20的封装形式。

微处理器的数据通过两线总线(DIO和CLK) 和 TM1637 通信, 在输入数据时,当
CLK 是高电平时,DIO 上的信号必须保持不变;只有 CLK
上的时钟信号为低电平时,DIO 上的信号才能改变。 数据输入的开始条件是
CLK为高电平时,DIO 由高变低;结束条件是 CLK 为高时,DIO
由低电平变为高电平。

TM1637 的数据传输带有应答信号
ACK,当传输数据正确时,会在第八个时钟的下降沿,芯片内部会产生一个应答信号
ACK 将 DIO 管脚拉低,在第九个时钟结束之后释放 DIO 口线。

Stm32 四位数码管_第7张图片

参考手册
数码管驱动芯片规格书-TM1637_V2.pdf
或 点击文档资源链接,例程看下面的资料文件夹中有芯片手册。

写 SRAM 数据 存在两种模式,地址自动加1模式和固定地址模式

Stm32 四位数码管_第8张图片

我们例程中使用的是地址增加模式,写数据流程如下:

tm1637003

  • 第一步 Command1:设置数据
  • 第二步
    Command2:设置起始地址,随后开始传输数据,Data1~N:为数码管显示数据,每写一次地址自增(从00H-05H)。
  • 第三步 Command3:控制显示,刷新显示

Stm32 四位数码管_第9张图片

指令用来设置显示模式和LED 驱动器的状态。

Stm32 四位数码管_第10张图片

显示地址:如果地址设为0C6H
或更高,数据被忽略,直到有效地址被设定;上电时,地址默认设为00H。

Stm32 四位数码管_第11张图片

驱动程序

/************************************************************************************ 
* @fileName      digit.h
* @brief        四位数码管驱动代码   
* @author            taotaodiy www.taotaodiy.com
* @date         2020-12-7
***********************************************************************************/

#ifndef __DIGIT_H
#define __DIGIT_H    

#include "sys.h"
/************************************************************************************ 
*
*   @describe 宏
*
************************************************************************************/

//位带操作
//数码管引脚
#define CLK_BIT   10
#define DIO_BIT   11
//数码管端口定义 
#define CLK PBout(CLK_BIT)
#define DIO_IN  PBin(DIO_BIT)
#define DIO_OUT PBout(DIO_BIT)

#define CLK_GPIO_CLK        RCC_APB2Periph_GPIOB        /* 时钟 */
#define CLK_GPIO_PORT       GPIOB                         /* 端口 */
#define CLK_GPIO_PIN            GPIO_Pin_10                 /* 位 */

#define DIO_GPIO_CLK        RCC_APB2Periph_GPIOB        /* 时钟 */
#define DIO_GPIO_PORT       GPIOB                         /* 端口 */
#define DIO_GPIO_PIN            GPIO_Pin_11                 /* 位 */

/************************************************************************************ 
*
*   @describe 函数
*
************************************************************************************/
void I2C_Start(void);
void I2C_Ask(void);
void I2C_Stop(void);
void I2C_WriteByte(u8 byte);

void DIGIT_Init(void);
void DIGIT_DisplayTime(int sec);
void DIGIT_DisplayNumber(int num);   

void DIGIT_Test(void);

#endif
/************************************************************************************ 
* @fileName      digit.c
* @brief        四位数码管驱动代码   
* @author            taotaodiy www.taotaodiy.com
* @date         2020-12-7
***********************************************************************************/

#include "digit.h"
#include "delay.h"

/************************************************************************************ 
*
*   @describe 数码管编码
*
************************************************************************************/

u8 DIGIT_ANODE[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e}; //0~f
u8 DIGIT_CATHODE[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//0~f

u8 DIGIT_CATHODE_POINT[]={0xBf,0x86,0xDb,0xCf,0xE6,0xEd,0xFd,0x87,0xFf,0xEf,0xF7,0xFc,0xB9,0xDe,0xF9,0xF1};//0~f

/************************************************************************************ 
*
*   @describe 数码管驱动程序
*
************************************************************************************/

/** 
* @describe 设置数据线模式
* @param         无
* 
* @return    无
*/
static void DIO_SetIN(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = DIO_GPIO_PIN;                     //端口配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;   //浮空输入
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    
    GPIO_Init(DIO_GPIO_PORT, &GPIO_InitStructure);
}
static void DIO_SetOUT(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = DIO_GPIO_PIN;              //端口配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;     //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    
    GPIO_Init(DIO_GPIO_PORT, &GPIO_InitStructure);
}


/** 
* @describe 数码管引脚初始化  修改头文件中的宏进行引脚配置
* @param         无
* 
* @return    无
*/
void DIGIT_Init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(CLK_GPIO_CLK|DIO_GPIO_CLK, ENABLE);   //使能PA端口时钟

    GPIO_InitStructure.GPIO_Pin = CLK_GPIO_PIN;              //端口配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;     //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    
    GPIO_Init(CLK_GPIO_PORT, &GPIO_InitStructure);

    DIO_SetOUT();
}

/** 
* @describe 显示时间
* @param         无
* 
* @return    无
*/
void DIGIT_DisplayTime(int sec)
{
    if(sec>59*59)
        sec=59*59;

    I2C_Start();
    I2C_WriteByte(0x40); // 40H 地址自动加 1 模式,44H 固定地址模式,本程序采用自加 1 模式
    I2C_Ask();
    I2C_Stop();
    I2C_Start();
    I2C_WriteByte(0xc0); //设置首地址,
    I2C_Ask();

    I2C_WriteByte(DIGIT_CATHODE[sec/60/10]); //送数据
    I2C_Ask();

    if(sec%2==0)
        I2C_WriteByte(DIGIT_CATHODE_POINT[sec/60%10]); //送数据
    else
        I2C_WriteByte(DIGIT_CATHODE[sec/60%10]); //送数据

    I2C_Ask();
    I2C_WriteByte(DIGIT_CATHODE[sec%60/10]); //送数据
    I2C_Ask();
    I2C_WriteByte(DIGIT_CATHODE[sec%60%10]); //送数据
    I2C_Ask();

    I2C_Stop();
    I2C_Start();
    I2C_WriteByte(0x8f); //开显示 ,最大亮度
    I2C_Ask();
    I2C_Stop();
}

/** 
* @describe 显示数字
* @param         无
* 
* @return    无
*/
void DIGIT_DisplayNumber(int num)
{
    if(num>9999)
        num=9999;
    if(num<-999)
        num=-999;

    I2C_Start();
    I2C_WriteByte(0x40); // 40H 地址自动加 1 模式,44H 固定地址模式,本程序采用自加 1 模式
    I2C_Ask();
    I2C_Stop();
    I2C_Start();
    I2C_WriteByte(0xc0); //设置首地址,
    I2C_Ask();

    if(num>=0)
    {
        I2C_WriteByte(DIGIT_CATHODE[num/1000]); //送数据
        I2C_Ask();
        I2C_WriteByte(DIGIT_CATHODE[num/100]); //送数据
        I2C_Ask();
        I2C_WriteByte(DIGIT_CATHODE[num/10]); //送数据
        I2C_Ask();
        I2C_WriteByte(DIGIT_CATHODE[num%10]); //送数据
        I2C_Ask();
    }
    else if(num<0)
    {
        num=-num;
        I2C_WriteByte(0x40); //送数据
        I2C_Ask();
        I2C_WriteByte(DIGIT_CATHODE[num/100]); //送数据
        I2C_Ask();
        I2C_WriteByte(DIGIT_CATHODE[num%100/10]); //送数据
        I2C_Ask();
        I2C_WriteByte(DIGIT_CATHODE[num%10]); //送数据
        I2C_Ask();
    }

    I2C_Stop();
    I2C_Start();
    I2C_WriteByte(0x8f); //开显示 ,最大亮度
    I2C_Ask();
    I2C_Stop();
}

/************************************************************************************ 
*
*   @describe 通信时序
*
************************************************************************************/
void I2C_Start(void)
{
    DIO_SetOUT();
    CLK=1;
    DIO_OUT=1;
    delay_us(2);
    DIO_OUT=0;
}
void I2C_Ask(void)
{
    DIO_SetIN();
    CLK=0;
    delay_us(5);
    while(DIO_IN);
    CLK=1;
    delay_us(2);
    CLK=0;
}
void I2C_Stop(void)
{
    DIO_SetOUT();
    CLK=0;
    delay_us(2);
    DIO_OUT=0;
    delay_us(2);
    CLK=1;
    delay_us(2);
    DIO_OUT=1;
}
void I2C_WriteByte(u8 byte)
{
    u8 i;
    DIO_SetOUT();
    for(i=0; i<8;i++)
    {
        CLK=0;
        if(byte&0x01)
            DIO_OUT=1;
        else
            DIO_OUT=0;
        delay_us(3);

        byte=byte>>1;
        CLK=1;
        delay_us(3);
    }
}

代码中涉及到到 I2C_XXXX() 请参考
https://taotaodiy-mcu.readthedocs.io/en/latest/stm32/i2c.html

测试程序

我分别封装一个显示时间和显示数据的函数。以显示时间为例,首先设置TM1637的模式,设置地址自动加1模式,
随后设置首地址0xc0,随后就是写数码管要显示的数据。最后打开显示,显示我们设置的数据。

/** 
 * @describe 显示时间
 * @param        无
 * 
 * @return   无
 */
void DIGIT_DisplayTime(int sec)
{
    if(sec>59*59)
        sec=59*59;

    I2C_Start();
    I2C_WriteByte(0x40); // 40H 地址自动加 1 模式,44H 固定地址模式,本程序采用自加 1 模式
    I2C_Ask();
    I2C_Stop();
    I2C_Start();
    I2C_WriteByte(0xc0); //设置首地址,
    I2C_Ask();

    I2C_WriteByte(DIGIT_CATHODE[sec/60/10]); //送数据
    I2C_Ask();

    if(sec%2==0)//四位数码管中间两个小点闪烁
        I2C_WriteByte(DIGIT_CATHODE_POINT[sec/60%10]); //送数据
    else
        I2C_WriteByte(DIGIT_CATHODE[sec/60%10]); //送数据

    I2C_Ask();
    I2C_WriteByte(DIGIT_CATHODE[sec%60/10]); //送数据
    I2C_Ask();
    I2C_WriteByte(DIGIT_CATHODE[sec%60%10]); //送数据
    I2C_Ask();

    I2C_Stop();
    I2C_Start();
    I2C_WriteByte(0x8f); //开显示 ,最大亮度
    I2C_Ask();
    I2C_Stop();
}
void DIGIT_Test(void)
{
    static int time=0;
    DIGIT_DisplayTime(time++);
    //DIGIT_DisplayNumber(time++);
}

在主程序中启用定时器,每隔一秒就刷新数码管显示,这样就能看到一个时钟的效果。

Stm32 四位数码管_第12张图片

你可能感兴趣的:(#,STM32,嵌入式开发,stm32,数码管,四段数码管,滴答时钟)