目录
1. 单位数码管概述
2. 对应编码
2.1 共阳数码管
2.2 共阴数码管
3. TM1637驱动数码管
3.1 工作原理
3.1.1 读键扫数据
3.1.2 显示器寄存器地址和显示模式
3.2 时序
3.2.1 指令数据传输过程(读案件数据时序)
3.2.2 写SRAM数据地址自动加1模式
3.2.3 写SRAM数据固定地址模式
3.3 数据指令
3.3.1 数据命令设置
3.3.2 显示控制命令设置
3.3.3 地址命令设置
3.4 程序流程图
3.4.1 采用地址自动加一模式的程序流程图
3.4.2 采用固定地址的程序设计流程图
4. 代码编写
4.1 准备配置
4.1.1 C语言调用文件
4.1.2 宏定义
4.1.3 段码表
4.1.4 数码管地址表
4.1.5 引脚初始化
4.1.6 电平配置
4.2 IIC相关配置
4.2.1 起始信号
4.2.2 终止信号
4.2.3 接收应答
4.2.4 发送一个字节
4.3 TM1637相关配置
4.3.1 发送写显存的数据命令
4.3.2 向指定地址写入数据
4.3.3 在四个数码管上显示数据
4.3.4 设置亮度
4.3.5 显示开关
4.3.6 初始化配置
4.3.7 显示配置
4.3.8 TM1637.h文件配置
4.4 主函数
1. 单位数码管概述
数码管的内部基本单元是发光二极管,数码管是发光器件之一,内部由七个条形发光二极管(a、b、c、d、e、f、g)和一个圆点发光二极管(dp)构成。
按照数码管的公共接线不同,数码管又可分类为共阴极数码管和共阳极数码管两种,共阴极数码管的公共端接地,而共阳极数码管的公共端接电源。
2. 对应编码
2.1 共阳数码管
数码管中所有的正极连接在一起,这个端口被称之为位选端口,其余的数码管引脚a-h都为段选端口2、共阳极数码管的编码(CA):
u8 code[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e}; //0-F
2.2 共阴数码管
是数码管所有的负极连接在一起,这个端口被称之为位选端口,其余的数码管引脚a-h都为段选端口4、共阴极数码管的编码(CC/CK):
u8 code[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; //0-F
3. TM1637驱动数码管
TM1637 是一种带键盘扫描接口的LED(发光二极管显示器)驱动控制专用电路,内部集成有MCU 数字接口、数据锁存器、LED 高压驱动、键盘扫描等电路。本产品性能优良,质量可靠。主要应用于电磁炉、 微波炉及小家电产品的显示屏驱动。采用DIP/SOP20的封装形式。
3.1 工作原理
微处理器的数据通过两线总线接口和 TM1637 通信,在输入数据时当 CLK 是高电平时,DIO 上的信号必须保持不变;只有 CLK 上的时钟信号为低电平时,DIO 上的信号才能改变。数据输入的开始条件是 CLK 为高电平时,DIO 由高变低;结束条件是 CLK 为高时,DIO 由低电平变为高电平。
TM1637 的数据传输带有应答信号 ACK,当传输数据正确时,会在第八个时钟的下降沿,芯片内部会产生一个应答信号 ACK 将 DIO 管脚拉低,在第九个时钟结束之后释放 DIO 口线。
符号 | 管脚名称 | 管脚号 | 说明 |
DIO | 数据输入/输出 | 17 | 串行数据输入/输出,输入数据在 SLCK 的低电平变化,在SCLK 的高电平被传输,每传输一个字节芯片内部都将在第八个时钟下降沿产生一个 ACK |
CLK | 时钟输入 | 18 | 在上升沿输入/输出数据 |
K1~K2 | 键扫数据输入 | 19~20 | 输入该脚的数据在显示周期结束后被锁存 |
SG1~SG8 | 输出(段) | 2~9 | 段输出(也用作键扫描),N 管开漏输出 |
GRID6~GRID1 | 输出(位) | 10~15 | 位输出,P 管开漏输出 |
VDD | 逻辑电源 | 16 | 5V±10% |
GND | 逻辑地 | 1 | 接地 |
3.1.1 读键扫数据
在有按键按下时,读键数据如下:
SG1 | SG2 | SG3 | SG4 | SG5 | SG6 | SG7 | SG8 | |
K1 | 1110_1111 | 0110_1111 | 1010_1111 | 0010_1111 | 1100_1111 | 0100_1111 | 1000_1111 | 0000_1111 |
K2 | 1111_0111 | 0111_0111 | 1011_0111 | 0011_0111 | 1101_0111 | 0101_0111 | 1001_0111 | 0001_0111 |
注意:在无按键按下时,读键数据为:1111_1111,低位在前,高位在后。
3.1.2 显示器寄存器地址和显示模式
该寄存器存储通过串行接口从外部器件传送到TM1637 的数据,地址00H-05H共6个字节单元,分别与芯片SGE和GRID管脚所接的LED灯对应,分配如下图:
写LED显示数据的时候,按照从显示地址从低位到高位,从数据字节的低位到高位操作。
SEG1 | SEG2 | SEG3 | SEG4 | SEG5 | SEG6 | SEG7 | SEG8 | |
xxHL(第四位) | xxHU(高四位) | |||||||
B0 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | |
00HL | 00HU | GRID1 | ||||||
01HL | 01HU | GRID2 | ||||||
02HL | 02HU | GRID3 | ||||||
03HL | 03HU | GRID4 | ||||||
04HL | 04HU | GRID5 | ||||||
05HL | 05HU | GRID6 |
3.2 时序
3.2.1 指令数据传输过程(读案件数据时序)
注意:先读低位在读高位
3.2.2 写SRAM数据地址自动加1模式
Command1:设置数据
Command2:设置地址
Data1~N:传输数据
Command3:控制显示
3.2.3 写SRAM数据固定地址模式
Command1:设置数据
Command2:设置地址
Data1~N:传输数据
Command3:控制显示
3.3 数据指令
指令用来设置显示模式和LED 驱动器的状态。
在CLK下降沿后由DIO输入的第一个字节作为一条指令。经过译码,取最高B7、B6两位比特位以区别不同的指令。
B7 | B6 | 指令 |
0 | 1 | 数据命令设置 |
1 | 0 | 显示控制命令设置 |
1 | 1 | 地址命令设置 |
如果在指令或数据传输时发送STOP命令,串行通讯被初始化,并且正在传送的指令或数据无效(之前传送的指令或数据保持有效)。
3.3.1 数据命令设置
3.3.2 显示控制命令设置
3.3.3 地址命令设置
3.4 程序流程图
3.4.1 采用地址自动加一模式的程序流程图
3.4.2 采用固定地址的程序设计流程图
4. 代码编写
开始前可以先了解一下IIC的用法,以及相关时序:
STM32F1之I2C通信_stm32f1 i2c-CSDN博客
STM32F1之I2C通信·软件I2C代码编写-CSDN博客
TM1637采用的是IIC通信,但是又不是标准的IIC通信,标准的IIC协议是从高位到低位传输的,即MSB方式,但是TM1637介绍也介绍到工作原理时曾介绍到,其是从低位到高位进行数据传输。
4.1 准备配置
4.1.1 C语言调用文件
STM32的编写,有些需要调用C语言的头文件才能正常使用:
#include <string.h>//提供了一些用于处理字符串和内存块的函数。
#include <stdint.h>//定义了具有确定大小的整数类型。
#include <stdbool.h>//提供布尔类型支持。
4.1.2 宏定义
这个宏定义了一个名为 TUBE_DISPLAY_NULL 的常量,值为 26。通常情况下,这个值可能用于表示一个不显示的状态或占位符,在显示器的控制代码中用来指示数字管不应显示任何有效数字:
#define TUBE_DISPLAY_NULL 26
这个宏定义了一个名为 TUBE_DISPLAY_DECIMAL_POINT_OFFSET 的常量,值为 16。这个值可能用于表示小数点在显示器中的位置偏移量。例如,在一个七段显示器上,使用这个偏移量可以帮助确定小数点的位置,以便正确显示浮点数或需要小数点的数值:
#define TUBE_DISPLAY_DECIMAL_PIONT_OFFSET 16
4.1.3 段码表
用于存储七段显示器中每个数字和字母的段码:
const uint8_t u8NumTab[] =
{
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, b, C, d, E, F,
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//0., 1., 2., 3., 4., 5., 6., 7., 8., 9. Null
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x00
};
4.1.4 数码管地址表
//最左至最右数码管 ,依次为0-3号,对应的显示寄存器地址
const uint8_t u8TubeAddrTab[] =
{
0xC0,0xC1,0xC2,0xC3
};
4.1.5 引脚初始化
由于这里我们使用的软件I2C不受引脚限制,随便找两个普通的GPIO口就可以使用,首先我们随机找两个引脚对其进行初始化:
void TM1637_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为开漏输出
/*设置默认电平*/
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); //设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}
4.1.6 电平配置
/**
* 函 数:I2C写SCL引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
*/
void TM1637_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue); //根据BitValue,设置SCL引脚的电平
Delay_us(10); //延时10us,防止时序频率超过要求
}
/**
* 函 数:I2C写SDA引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平
*/
void TM1637_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue); //根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
Delay_us(10); //延时10us,防止时序频率超过要求
}
/**
* 函 数:I2C读SDA引脚电平
* 参 数:无
* 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
* 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
*/
uint8_t TM1637_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); //读取SDA电平
Delay_us(10); //延时10us,防止时序频率超过要求
return BitValue; //返回SDA电平
}
4.2 IIC相关配置
4.2.1 起始信号
起始条件:SCL高电平期间,SDA从高电平切换到低电平。
根据上图虚线框柱的地方进行配置:
void TM1637_Start(void)
{
TM1637_W_SDA(1); //释放SDA,确保SDA为高电平
TM1637_W_SCL(1); //释放SCL,确保SCL为高电平
TM1637_W_SDA(0); //在SCL高电平期间,拉低SDA,产生起始信号
TM1637_W_SCL(0); //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
4.2.2 终止信号
终止条件:SCL高电平期间,SDA从低电平切换到高电平。
同理,根据上图虚线框柱的地方进行配置:
void TM1637_Stop(void)
{
TM1637_W_SCL(0); //拉低SCL,确保SCL为低电平
TM1637_W_SDA(0); //拉低SDA,确保SDA为低电平
TM1637_W_SCL(1); //释放SCL,使SCL呈现高电平
TM1637_W_SDA(1); //在SCL高电平期间,释放SDA,产生终止信号
}
4.2.3 接收应答
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
//应答信号
uint8_t TM1637_ReceiveAck(void)
{
uint8_t AckBit; //定义应答位变量
TM1637_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送
TM1637_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDA
AckBit = TM1637_R_SDA(); //将应答位存储到变量里
TM1637_W_SCL(0); //拉低SCL,开始下一个时序模块
return AckBit; //返回定义应答位变量
}
4.2.4 发送一个字节
发送一个字节: SCL低电平期间,主机将数据位依次放到SDA线上(高位先行) ,然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
主机首先在SCL低电平,主机如果想发送0,就拉低SDA到低电平,主机如果想发送1,就放手,SDA回弹到高电平,在SCL低电平期间允许改变SDA的电平,如图黄色部分:
但是这里需要注意标准的IIC协议是从高位到低位传输的,但是TM1637是从低位到高位进行数据传输,因此:
// 向 TM1637 发送一个字节数据
void TM1637_Write_Byte(uint8_t data)
{
uint8_t i;
TM1637_W_SCL(0); // 设置时钟线 SCL 为低电平,准备开始发送数据
// 循环发送 8 位数据
for (i = 0; i < 8; i++)
{
// 根据数据的最低位决定数据线 SDA 的状态
if(data & 0x01)
{
TM1637_W_SDA(1); // 发送 1
}
else
{
TM1637_W_SDA(0); // 发送 0
}
data = data >> 1; // 右移数据,处理下一个位
TM1637_W_SCL(1); // 拉高时钟线,表示位数据有效
TM1637_W_SCL(0); // 拉低时钟线,准备发送下一个位
}
}
4.3 TM1637相关配置
为了方便地对任意一个数码管写入数据,我们采用固定地址的方式,其流程如下:
相关时序:
4.3.1 发送写显存的数据命令
// 向 TM1637 数码管写入命令
void TM1637_WriteCmd(uint8_t u8Cmd)
{
// 开始信号,表示通信开始
TM1637_Start();
// 发送命令字节
TM1637_Write_Byte(u8Cmd);
// 接收确认应答
TM1637_ReceiveAck();
// 停止信号,表示通信结束
TM1637_Stop();
}
4.3.2 向指定地址写入数据
// 向指定地址写入数据
// u8Addr: 要写入的地址
// u8Data: 要写入的数据
void TM1637_WriteData(uint8_t u8Addr, uint8_t u8Data)
{
// 开始信号,表示通信开始
TM1637_Start();
// 发送地址字节
TM1637_Write_Byte(u8Addr);
// 接收确认应答
TM1637_ReceiveAck();
// 发送数据字节
TM1637_Write_Byte(u8Data);
// 接收确认应答
TM1637_ReceiveAck();
// 停止信号,表示通信结束
TM1637_Stop();
}
4.3.3 在四个数码管上显示数据
// 在四个数码管上显示数据
// sData: 显示数据结构体,包含四个数码管的显示值
void TM1637_TubeDisplay(TM1637Tube_ts sData)
{
uint8_t temp[4], i;
// 根据 sData 中的值,从 u8NumTab 数组中获取对应的段码
temp[0] = u8NumTab[sData.tube0]; // 获取第一个数码管的段码
temp[1] = u8NumTab[sData.tube1]; // 获取第二个数码管的段码
temp[2] = u8NumTab[sData.tube2]; // 获取第三个数码管的段码
temp[3] = u8NumTab[sData.tube3]; // 获取第四个数码管的段码
// 遍历四个数码管,逐个写入数据
for (i = 0; i < 4; i++)
{
// 向每个数码管的地址写入对应的段码
TM1637_WriteData(u8TubeAddrTab[i], temp[i]);
}
}
结构体TM1637Tube_ts,该结构体存放到.h中,若是存放到.c文件需要将其放到头文件后面,防止TM1637_TubeDisplay()函数搜索不到:
typedef struct
{
uint8_t tube0;
uint8_t tube1;
uint8_t tube2;
uint8_t tube3;
}TM1637Tube_ts;
4.3.4 设置亮度
//设置亮度
//0x88为开显示,u8Brt亮度
void TM1637_SetBrightness(uint8_t u8Brt)
{
TM1637_WriteCmd(0x88 | u8Brt);
}
4.3.5 显示开关
//显示开关
//0x88为开显示,0x80关显示
void TM1637_Switch(bool bState)
{
bState ? TM1637_WriteCmd(0x88) : TM1637_WriteCmd(0x80);
}
4.3.6 初始化配置
void Display_Init(void)
{
TM1637_Switch(0);//关显示
TM1637_SetBrightness(0x87);//设置亮度,开显示
TM1637_WriteCmd(0x44);//写数据到寄存器,固定地址模式
memset(&sDisplayData, 0xFF, sizeof(sDisplayData));
}
4.3.7 显示配置
void Display_TubeDataProcess(uint16_t u16Data)
{
memset(&sDisplayData, 0xFF, sizeof(sDisplayData));
if (u16Data > 9999)
{
u16Data = 9999;//最多四位数
}
if (u16Data > 999)//四位数
{
sDisplayData.tube0 = (uint8_t)(u16Data / 1000);//千位
sDisplayData.tube1 = (uint8_t)(u16Data / 100 % 10);//百位
sDisplayData.tube2 = (uint8_t)(u16Data % 100 / 10);//十位
sDisplayData.tube3 = (uint8_t)(u16Data % 10);//个位
}
else if (u16Data > 99)//三位数
{
sDisplayData.tube0 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube1 = (uint8_t)(u16Data / 100);//百位
sDisplayData.tube2 = (uint8_t)(u16Data / 10 % 10);//十位
sDisplayData.tube3 = (uint8_t)(u16Data % 10);//个位
}
else if (u16Data > 9)//两位数
{
sDisplayData.tube0 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube1 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube2 = (uint8_t)(u16Data / 10);//十位
sDisplayData.tube3 = (uint8_t)(u16Data % 10);//个位
}
else//一位数
{
sDisplayData.tube0 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube1 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube2 = TUBE_DISPLAY_NULL;//不显示
sDisplayData.tube3 = (uint8_t)u16Data;//个位
}
TM1637_TubeDisplay(sDisplayData);
}
4.3.8 TM1637.h文件配置
以上代码为.c文件配置,下面是.h文件配置:
#ifndef _TM1637_H_
#define _TM1637_H_
#include <stdbool.h>
typedef struct
{
uint8_t tube0;
uint8_t tube1;
uint8_t tube2;
uint8_t tube3;
}TM1637Tube_ts;
void TM1637_Init(void);
void TM1637_Start(void);
void TM1637_Stop(void);
uint8_t TM1637_ReceiveAck(void);
void TM1637_Write_Byte(uint8_t data);
void TM1637_WriteCmd(uint8_t u8Cmd);
void TM1637_WriteData(uint8_t u8Addr, uint8_t u8Data);
void TM1637_TubeDisplay(TM1637Tube_ts sData);
void TM1637_SetBrightness(uint8_t u8Brt);
void TM1637_Switch(bool bState);
void Display_Init(void);
void Display_TubeDataProcess(uint16_t u16Data);
#endif
4.4 主函数
#include "stm32f10x.h"
#include "Delay.h"
#include "TM1637.h"
int main(void)
{
TM1637_Init();
Display_Init();
while (1)
{
Display_TubeDataProcess(1234);
}
}
基于STM32通过SN74HC595驱动4位数码管详细解析-CSDN博客