关键词: stm32 滴答时钟 数码管 四位数码管
主要内容:
文档参考:
最新地址: https://taotaodiy-mcu.readthedocs.io/en/latest/sensor/digit.html
数码管主要用来显示数据。
数码管内部实际上就是8个发光二极管,我通过给A-H编号的发光二极管给高低不同的电平,最终使得数码管显示不同的字符。
如图所示,数码管分共阴(即发光二极管阴极连到一起)还是共阳(即发光二极管阳极连到一起)。
比如说我们希望,数码管显示5这个数字。那我们的思路是什么呢?
首先我们想要的效果应该是这样:
也就是,通过电路和程序控制数码管的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占用光了吗?
于是就有了位选,数码管通用八根段选,使用位选来控制单个数码管显示。
当然这样还是比较费IO,于是就有了各种数码管控制芯片。
文章开头的图片是一个四位数码管,使用TM1637来控制显示,下面是它的显示和控制原理。
这里的四位数码管唯一不一样的地方就是中间多了两个点,主要是为了显示时间,有那种闪烁的效果。
显示数字的时候,其编码和上面提到的数码管编码略微有点不同。
#普通数码管 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
是一种带键盘扫描接口的LED(发光二极管显示器)驱动控制专用电路,内部集成有MCU
数字接口、数据锁存器、LED 高压驱动、键盘扫描等电路。
主要应用于电磁炉、微波炉及小家电产品的显示屏驱动。采用DIP/SOP20的封装形式。
微处理器的数据通过两线总线(DIO和CLK) 和 TM1637 通信, 在输入数据时,当
CLK 是高电平时,DIO 上的信号必须保持不变;只有 CLK
上的时钟信号为低电平时,DIO 上的信号才能改变。 数据输入的开始条件是
CLK为高电平时,DIO 由高变低;结束条件是 CLK 为高时,DIO
由低电平变为高电平。
TM1637 的数据传输带有应答信号
ACK,当传输数据正确时,会在第八个时钟的下降沿,芯片内部会产生一个应答信号
ACK 将 DIO 管脚拉低,在第九个时钟结束之后释放 DIO 口线。
参考手册
数码管驱动芯片规格书-TM1637_V2.pdf
或 点击文档资源链接,例程看下面的资料文件夹中有芯片手册。
写 SRAM 数据 存在两种模式,地址自动加1模式和固定地址模式
我们例程中使用的是地址增加模式,写数据流程如下:
指令用来设置显示模式和LED 驱动器的状态。
显示地址:如果地址设为0C6H
或更高,数据被忽略,直到有效地址被设定;上电时,地址默认设为00H。
/************************************************************************************
* @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++);
}
在主程序中启用定时器,每隔一秒就刷新数码管显示,这样就能看到一个时钟的效果。