简介:本资源面向STM32嵌入式初学者与需要快速实现数码管显示的开发者,提供基于STM32F103C8T6驱动TM1637芯片控制4位数码管的完整工程。TM1637集成段码编码与动态扫描,仅需DIO、CLK两根线即可通信,可显著简化硬件设计。压缩包共79个文件,约322KB,包含32个h头文件、31个c源文件、8个s启动文件,以及hex烧录文件、uvprojx工程与uvoptx配置等,涵盖GPIO初始化、启动与结束信号、段码位码传输、循环显示等驱动逻辑,并附功能说明文档。工程按User、Libraries、Doc、Listing等目录组织,便于理解固件库调用与程序编译细节。目前已有6050人学习下载,读者可参考其GPIO配置与通信时序,掌握亮度控制、消隐及滚动显示等扩展思路,快速移植到自己的嵌入式项目中。
1. 从两根线到四位数码管:STM32F103 驱动 TM1637 的实战拆解
手头有块 STM32F103 最小系统板,想接个四位数码管显示点东西,结果发现传统方案要占用 8 根 IO 甚至更多,引脚本来就紧张,再挂个数码管基本就没法扩展了。TM1637 这颗芯片就是冲着这个痛点来的——两根信号线,一根 CLK 一根 DIO,就能把四位数码管驱动起来,还自带亮度调节和按键扫描。这次拆的是一个基于 STM32F103 标准库的 TM1637 驱动资源包,里面包含底层时序模拟、显示刷新、亮度控制这些核心模块。如果你正在用 F103 做小项目,需要低成本、低引脚占用的显示方案,这份东西能直接拿来用,但前提是你得搞清楚它的时序逻辑和几个容易翻车的细节。
2. TM1637 的通信协议与 STM32 端选型:为什么不用硬件 I2C
2.1 TM1637 的类 I2C 时序到底特殊在哪
TM1637 用的是两线串行接口,看起来像 I2C,但严格来说不是标准 I2C。它没有从机地址概念,起始条件也不是 SCL 高时 SDA 拉低,而是CLK 为高时 DIO 从高变低作为起始,CLK 为高时 DIO 从低变高作为停止。数据在 CLK 上升沿被 TM1637 采样,所以 STM32 端要在 CLK 低电平期间把数据准备好,然后拉高 CLK 让芯片读走。这个时序如果用 STM32 硬件 I2C 外设去套,基本是自找麻烦——硬件 I2C 的起始条件、ACK 机制跟 TM1637 对不上,而且 F103 的硬件 I2C 本身就有不少玄学问题。所以这份资源里用的是GPIO 模拟时序,这也是绝大多数实战项目的选择。
具体到引脚配置,CLK 和 DIO 都配成开漏输出,外部上拉电阻一般 4.7k 到 10k 都行。开漏的好处是 DIO 线可以双向切换——STM32 要读 ACK 的时候把 DIO 切成输入,平时输出命令。如果你用推挽输出,读 ACK 那一步就会出问题,DIO 被强行拉高拉低,芯片可能直接不响应。
2.2 资源包里的文件结构与移植路径
拿到这个资源包,目录结构大致是这样的:
TM1637_STM32F103/ ├── CMSIS/ # 内核支持文件 ├── STM32F10x_StdPeriph_Driver/ # 标准外设库 ├── User/ │ ├── main.c # 主程序,显示测试 │ ├── tm1637.c # TM1637 驱动核心 │ ├── tm1637.h # 引脚宏定义与函数声明 │ └── stm32f10x_it.c # 中断向量 └── Project/ # Keil 工程文件移植的时候重点改tm1637.h里的引脚宏:
// tm1637.h 引脚配置区 #define TM1637_CLK_PORT GPIOB #define TM1637_CLK_PIN GPIO_Pin_6 #define TM1637_DIO_PORT GPIOB #define TM1637_DIO_PIN GPIO_Pin_7 // 引脚操作宏,方便移植时只改上面两行 #define CLK_H() GPIO_SetBits(TM1637_CLK_PORT, TM1637_CLK_PIN) #define CLK_L() GPIO_ResetBits(TM1637_CLK_PORT, TM1637_CLK_PIN) #define DIO_H() GPIO_SetBits(TM1637_DIO_PORT, TM1637_DIO_PIN) #define DIO_L() GPIO_ResetBits(TM1637_DIO_PORT, TM1637_DIO_PIN) #define DIO_READ() GPIO_ReadInputDataBit(TM1637_DIO_PORT, TM1637_DIO_PIN)这里的关键是DIO 引脚在输出和输入之间切换。资源包里用了一个DIO_MODE_OUT()和DIO_MODE_IN()的宏来切换 GPIO 方向,如果你只改端口和引脚号,这两个宏也得跟着改。我一般会把它们也放到头文件里,跟引脚定义放一起,移植的时候一眼就能看全。
2.3 起始、停止与 ACK 的代码实现
TM1637 的通信由起始条件开始,然后发命令字节,每个字节后跟一个 ACK 位。资源包里的起始函数长这样:
// 起始条件:CLK 高时 DIO 由高变低 void TM1637_Start(void) { CLK_H(); DIO_H(); delay_us(2); // 时序要求,保持稳定 DIO_L(); // CLK 高时拉低 DIO delay_us(2); CLK_L(); // 拉低 CLK 准备发数据 delay_us(2); }停止条件反过来,CLK 高时 DIO 由低变高:
void TM1637_Stop(void) { CLK_L(); DIO_L(); delay_us(2); CLK_H(); delay_us(2); DIO_H(); // CLK 高时拉高 DIO delay_us(2); }写一个字节的函数里,数据从低位开始发,每发一位拉高一次 CLK:
// 写一个字节,低位先发 void TM1637_WriteByte(uint8_t data) { uint8_t i; for(i = 0; i < 8; i++) { CLK_L(); delay_us(2); if(data & 0x01) DIO_H(); else DIO_L(); data >>= 1; delay_us(2); CLK_H(); // 上升沿 TM1637 采样 delay_us(2); } // 第 8 位后释放 DIO,准备读 ACK CLK_L(); DIO_H(); // 释放总线 delay_us(2); CLK_H(); // 第 9 个时钟读 ACK delay_us(2); // 这里可以读 DIO_READ() 判断 ACK,资源包里没做错误处理 CLK_L(); delay_us(2); }注意:
delay_us的精度直接影响通信稳定性。F103 主频 72MHz 时,一个简单循环延时大概几个微秒,资源包里用的是for循环空转。如果你改了主频或者开了中断,这个延时可能被拉长,导致时序错乱。常见做法是用 SysTick 或者定时器做微秒延时,但资源包里为了简化没这么做。
3. 显示寄存器映射与刷新逻辑:从段码到四位数码管
3.1 TM1637 的显示 RAM 地址与段码表
TM1637 内部有 6 个字节的显示 RAM,地址从 0xC0 到 0xC5。四位数码管只用前 4 个,对应关系是:
| RAM 地址 | 对应数码管位 | 备注 |
|---|---|---|
| 0xC0 | 第 1 位(最左) | 写段码 |
| 0xC1 | 第 2 位 | 写段码 |
| 0xC2 | 第 3 位 | 写段码 |
| 0xC3 | 第 4 位(最右) | 写段码 |
| 0xC4 | 第 5 位 | 四位数码管未用 |
| 0xC5 | 第 6 位 | 四位数码管未用 |
段码表是共阳数码管的编码,0 到 9 对应:
// 共阳数码管段码表,0~9 const uint8_t SegCode[] = { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F // 9 };如果你用的是共阴数码管,段码要取反,或者改 TM1637 的显示模式寄存器。资源包里默认是共阳,这个在tm1637.c的初始化里写死了。
3.2 写显示寄存器的完整流程
往 TM1637 写显示数据分三步:发写命令、发地址、发数据。资源包里的TM1637_Display函数封装了这个过程:
// 显示四位数,参数是四个数字 0~9 void TM1637_Display(uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4) { TM1637_Start(); TM1637_WriteByte(0x40); // 写数据命令,自动地址增加 TM1637_Stop(); TM1637_Start(); TM1637_WriteByte(0xC0); // 起始地址 0xC0 TM1637_WriteByte(SegCode[d1]); TM1637_WriteByte(SegCode[d2]); TM1637_WriteByte(SegCode[d3]); TM1637_WriteByte(SegCode[d4]); TM1637_Stop(); TM1637_Start(); TM1637_WriteByte(0x8F); // 显示控制命令,开显示,最亮 TM1637_Stop(); }命令字节 0x40 表示写数据到显示寄存器,地址自动加 1,所以后面连续写四个字节就填满了四位。0x8F 是显示控制命令,低三位控制亮度,0x8F 就是最亮。如果你要调暗,改成 0x88 到 0x8F 之间就行,0x88 最暗。
3.3 亮度调节与显示开关的寄存器操作
亮度调节就是改显示控制命令的低三位:
// 亮度等级 0~7,0 最暗,7 最亮 void TM1637_SetBrightness(uint8_t level) { uint8_t cmd = 0x88 | (level & 0x07); TM1637_Start(); TM1637_WriteByte(cmd); TM1637_Stop(); }关显示就是把命令改成 0x80,开显示是 0x88 起步。资源包里初始化的时候直接写了 0x8F,如果你要做呼吸灯效果,可以定时改亮度等级,但注意别改太快,TM1637 的通信速率有限,太频繁会闪。
提示:写显示数据的时候,如果只更新其中一位,可以用固定地址写命令 0x44,然后发地址和数据。但资源包里为了简单,每次都是全刷四位。如果你显示的内容变化不频繁,全刷没问题;如果要频繁更新某一位,建议改成固定地址写,减少通信量。
4. 避坑与常见问题排查:时序、供电与段码
4.1 上电后数码管全亮或全灭
现象:STM32 跑起来后,数码管要么所有段全亮,要么完全不亮,发什么数据都没反应。
原因:最常见的是 DIO 引脚方向没切换。TM1637 在读 ACK 的时候需要 STM32 释放 DIO 线,如果 DIO 一直配成推挽输出,芯片拉不低,通信直接失败。另一个可能是上拉电阻没接,开漏输出没有上拉,线永远是低电平。
解决:检查DIO_MODE_IN()和DIO_MODE_OUT()有没有在读写切换时调用。用万用表量 DIO 线在空闲时是不是高电平,如果不是,加上拉电阻。资源包里默认用了内部上拉,但内部上拉电阻太大,长线通信可能不够,建议外部加 4.7k。
4.2 显示的数字乱码或缺段
现象:数字能显示,但笔画不对,比如 8 缺中间一横,或者数字位置错乱。
原因:段码表跟数码管类型不匹配。资源包用的是共阳段码,如果你的数码管是共阴,显示就会乱。另外,如果地址写错了,比如从 0xC1 开始写四位,最左边那位就会没数据。
解决:确认数码管是共阳还是共阴。共阴的话把段码取反,或者改 TM1637 的显示模式。地址一定从 0xC0 开始写四位。如果只显示部分位,检查是不是只发了三个数据字节。
4.3 通信偶尔失败,重启后正常
现象:上电后有时候正常,有时候不亮,重新上电又好了。
原因:TM1637 上电需要一定时间稳定,如果 STM32 初始化太快,芯片还没准备好,第一条命令就丢了。另外,delay_us如果被中断打断,时序可能超出 TM1637 的容忍范围。
解决:在初始化 TM1637 之前加 100ms 以上的延时,等芯片上电稳定。delay_us尽量用定时器实现,或者在通信前后关中断。资源包里用的是空循环延时,如果你开了其他中断,建议把 TM1637 的通信放到中断优先级最低的地方,或者通信时临时关中断。
4.4 亮度调节没效果
现象:调用TM1637_SetBrightness后,亮度没变化。
原因:显示控制命令的低三位才是亮度,如果你发的命令是 0x8F,低三位是 7,最亮。改成 0x88 才是最暗。但有些 TM1637 模块的亮度调节范围很小,肉眼看不出来。
解决:确认命令字节的低三位有没有改对。如果模块本身亮度固定,可能是硬件上亮度引脚被拉死了,这种情况改软件没用。资源包里的亮度调节是标准做法,如果没效果,先量一下模块的亮度控制引脚。
5. 进阶用法:按键扫描与多模块级联的取舍
TM1637 除了驱动数码管,还支持按键扫描,不过这个功能在四位数码管模块上用得少,因为模块通常没引出按键引脚。如果你用的是裸芯片,可以把 KEY1 和 KEY2 接上按键,然后读按键寄存器。资源包里没做按键扫描,但原理不复杂:发读按键命令 0x42,然后读一个字节,低两位就是按键状态。我一般会在需要按键的项目里用这个功能,省一个 IO。
多模块级联的话,TM1637 不支持地址区分,两个模块只能共用 CLK,DIO 分开,或者用模拟开关切换。如果你要显示 8 位,更推荐用 TM1638 或者 MAX7219,TM1637 级联太麻烦,线也多。资源包里的驱动只针对单模块四位数码管,如果你要扩展,得自己改。
验证驱动是否正常,我习惯用这个步骤:先写一个固定值 0x3F 到地址 0xC0,看第一位是不是显示 0;然后依次写 0xC1、0xC2、0xC3,确认四位都能独立控制。如果这一步过了,再写动态变化的数字,看刷新有没有闪烁。从那以后我每次拿到新的 TM1637 模块,都先跑一遍这个固定值测试,确认时序和段码没问题再往下做。希望帮到你。
本文还有配套的精品资源,点击获取