
简介面向STM32单片机开发与物联网入门的TM1639共阴极数码管驱动资源适合正在学习裸机外设驱动、希望快速上手数码管显示的开发者。RAR压缩包约2KB内含2个文件分别是1个.h头文件和1个.c源文件头文件负责驱动接口声明与显示参数定义C文件实现TM1639的初始化、段码输出与显示刷新代码体量小便于直接移植到Keil或STM32CubeIDE工程中使用。已有1086人学习下载。透过这套驱动代码读者可以理解TM1639与单片机之间的串行通信时序、共阴极数码管的段码映射方式以及用C语言组织底层硬件驱动的思路同时还能结合物联网应用场景把温湿度、时间或传感器数值发送到数码管实时显示。整体定位明确适合物联网毕设、智能仪表或DIY项目在前期的显示方案验证。 做物联网项目时数码管显示一直是最朴素但也最可靠的需求。这个项目看起来很简单STM32 主控、TM1639 驱动芯片、共阴极数码管做显示再挂一个网络模块就组成了一套能实时显示云端数据的物联网终端。但真正把这套方案从选型到落地完整走一遍你会发现里面有大量数据手册不会告诉你的细节。这篇文章就把我实际调通的过程、关键代码、以及踩过的坑全部摊开讲给正在做物联网项目、单片机驱动或者毕业设计的你一个可以直接参考的完整链路。1. 为什么是 TM1639物联网显示方案的取舍复盘做显示方案时很多人第一反应是用 0.96 寸 OLED 或者 LCD1602。OLED 确实显示内容丰富但在只需要显示几个数字的场景下它有三个让我不想用的缺点贵、耗电、还得频繁刷新。而数码管加驱动芯片的方案在物联网终端上反而有独特优势——尤其是 TM1639 这颗芯片专门为用最少引脚驱动最多数码管设计。1.1 几套主流数码管方案的横向对比我实际对比过三套显示方案分别是 GPIO 直接驱动、74HC595 移位寄存器级联、TM1639 专用驱动芯片。方案占用引脚显示位数硬件复杂度驱动能力代码量GPIO 直接驱动8~9 个/位受引脚数量限制极低单片机灌电流有限亮度差动态扫描代码较复杂74HC595 级联3 个可任意扩展中需加限流电阻共阴共阳选择多需外部三极管否则段电流不足中TM16392 个8 位 × 8 段低内置扫描和恒流内置驱动管单段电流可到 10mA 以上简单从表格能直观看到TM1639 用 2 个引脚就能驱动 8 位数码管而且段驱动能力足够不需要额外扩流。这对 STM32 这种引脚资源紧张、还要接网络模块和传感器的场景来说省下的引脚价值很高。1.2 TM1639 的硬指标和适用边界TM1639 是一颗 LED 驱动控制芯片核心参数是 8 段 × 8 位也就是能驱动 8 位共阴极数码管或者 64 颗独立 LED。它还内置了 RC 振荡器、扫描电路和 8 级亮度调节所以单片机写入一次数据后芯片会自己维持扫描显示不占用 MCU 时间。这颗芯片还带键盘扫描功能最多能读 8×3 个按键。我做显示功能时基本用不到但如果你的物联网设备需要两三个按键输入这个接口还能省掉一片独立按键扫描芯片。它的适用边界也要说清楚只能驱动共阴极数码管不能直接驱动共阳极显示内容只能是段码组合不适合显示复杂图形。如果你需要显示汉字、曲线或图标应该考虑点阵屏或 OLED。但如果需求就是显示温度、湿度、时间、计数器这类数字TM1639 在性能、成本和代码维护性之间几乎是这几年的最优解。2. TM1639 通信机制双线时序、命令帧和共阴极的关系TM1639 的通信接口是两根线CLK 和 DIO。很多新手一看两根线就以为是 I2C这是第一个大坑。它的协议更像类 SPI没有器件地址、没有 ACK 应答、时序是自定义的。如果照搬 I2C 的读写逻辑一定调不通。2.1 时序核心起始、停止、数据采样TM1639 的起始条件CLK 为高电平期间DIO 从高电平跳变到低电平。停止条件CLK 为高电平期间DIO 从低电平跳变到高电平。这和 I2C 的起始停止很像但数据位规则不同低位先发也就是 LSB first。CLK 低电平时 DIO 设置数据CLK 上升沿时芯片采样数据。很多人在 STM32 上模拟这个时序时习惯用 SPI 或 串口 的高位先发思维写出来的代码从 bit7 开始移位结果上电后数码管显示乱码。正确写法是从 bit0 开始static void TM1639_WriteByte(uint8_t dat) { for (uint8_t i 0; i 8; i) { TM1639_CLK_L(); if (dat 0x01) TM1639_DIO_H(); else TM1639_DIO_L(); dat 1; TM1639_CLK_H(); } }注意 CLK 低电平期间改 DIO 状态、CLK 上升沿锁存数据这个顺序不能乱。2.2 三条命令搞定一次显示TM1639 的指令系统只有三条命令核心逻辑非常清晰。命令类型命令字节功能说明数据命令设置0x40写数据到显示寄存器自动地址递增地址命令设置0xC0~0xC7设置显示寄存器的起始地址显示控制命令0x88~0x8F控制显示开关和亮度0x8F 为最亮一次完整显示流程分三步先发 0x40 告诉芯片我要写数据地址自动递增再发起始地址 0xC0然后连续写入 8 个段码字节最后发显示控制命令打开显示并设置亮度。三步之间都要有起始停止条件不能省。void TM1639_Display(uint8_t *segData, uint8_t len) { TM1639_Start(); TM1639_WriteByte(0x40); TM1639_Stop(); TM1639_Start(); TM1639_WriteByte(0xC0); for (uint8_t i 0; i len; i) { TM1639_WriteByte(segData[i]); } TM1639_Stop(); TM1639_Start(); TM1639_WriteByte(0x8F); TM1639_Stop(); }这里有个细节0x8F 的低三位控制亮度范围从 0 到 7第三位是显示开关。如果写成 0x88等于关闭显示。调亮度时要保留 bit3void TM1639_SetBrightness(uint8_t brightness) { TM1639_Start(); TM1639_WriteByte(0x88 | (brightness 0x07)); TM1639_Stop(); }2.3 共阴极数码管与段码表的关系共阴极数码管的公共端接 GND段选端由 TM1639 输出高电平点亮。所以段码表是高电平有效这一点和共阳极完全相反。8 个数码管在 TM1639 内部对应地址 0xC0~0xC7每个地址对应一个 8 位段码寄存器bit0~bit7 分别对应 a、b、c、d、e、f、g、dp 段。常用的 0~9 段码表如下const uint8_t SEG_CODE[] { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71 };这个表里 0x3F 对应数字 00x06 对应数字 1以此类推。如果要用到大写字母比如显示温度单位 C 就是 0x39E 是 0x79。带小数点时只需把对应的段码最高位置 1例如 3. 就是 SEG_CODE[3] | 0x80。动手做之前最好用万用表确认一下手上模块的共阴共阳类型。很多淘宝模块标注不清买到共阳极会直接导致全灭或乱码。3. STM32 上的驱动落地从时序函数到 8 位显示缓冲理解了协议之后代码其实就是把时序用 STM32 的标准外设库或 HAL 库翻译一遍。我用的是 STM32F103C8T6选择 PB0 作为 CLK、PB1 作为 DIO两个引脚都配置为推挽输出。3.1 硬件接线和初始化接线非常宽松CLK 接 PB0DIO 接 PB1VCC 接 3.3V 或 5VGND 共地。如果模块上有 10k 上拉电阻建议保留DIO 在键盘扫描场景下需要作为输入读取上拉电阻是必须的。初始化代码就两条GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure);注意GPIO 速度不要设太高50MHz 档位下翻转速度已经远超 TM1639 的承受能力后面会讲到时序太快怎么处理。3.2 起始停止时序和完整驱动起始停止时序用宏定义加简单函数实现#define TM1639_CLK_H() GPIO_SetBits(GPIOB, GPIO_Pin_0) #define TM1639_CLK_L() GPIO_ResetBits(GPIOB, GPIO_Pin_0) #define TM1639_DIO_H() GPIO_SetBits(GPIOB, GPIO_Pin_1) #define TM1639_DIO_L() GPIO_ResetBits(GPIOB, GPIO_Pin_1) void TM1639_Start(void) { TM1639_DIO_H(); TM1639_CLK_H(); TM1639_DIO_L(); TM1639_CLK_L(); } void TM1639_Stop(void) { TM1639_CLK_L(); TM1639_DIO_L(); TM1639_CLK_H(); TM1639_DIO_H(); }3.3 显示函数设计缓冲区、消隐与小数点为了和业务逻辑解耦我建议维护一个 8 字节的显示缓冲区数组主程序只需要把要显示的数字转换成段码填入缓冲区TM1639_Display 一次性写出去。uint8_t dispBuf[8] {0}; void Disp_ShowNumber(uint32_t num) { dispBuf[0] SEG_CODE[num % 10]; dispBuf[1] SEG_CODE[num / 10 % 10]; if (num 100) dispBuf[2] SEG_CODE[num / 100 % 10]; else dispBuf[2] 0x00; // 消隐高位不显示 TM1639_Display(dispBuf, 8); }消隐的关键是用 0x00 而不是段码 0。如果你把未用到的位写 0x00数码管对应的位就不会点亮视觉效果干净很多。而因为 TM1639 内部会持续扫描写入一次后显示不会消失所以不需要像 GPIO 动态扫描那样频繁刷新。4. 接上物联网之后ESP8266 数据链路与显示策略本地显示跑通只是第一步物联网场景下 TM1639 的价值才真正体现出来。我给这套方案加了一个 ESP8266-01S 作为网络协处理器整体数据链路是STM32 通过 USART1 连接 ESP8266ESP8266 连接物联网平台云端下发的数据通过串口以字符串形式送到 STM32STM32 解析后更新 TM1639 显示。4.1 系统架构与串口数据格式数据链路图用文字描述就是这样云端物联网平台 - Wi-Fi - ESP8266-01S - USART1 - STM32F103 - TM1639 - 共阴极数码管为了保证解析简单可靠ESP8266 透传模式下云端下发的数据统一使用 JSON 格式比如{temp:26,humi:58}STM32 串口中断接收完整字符串后按引号提取字段。这种格式的好处是易于扩展以后要加 CO2、PM2.5 之类的传感器只需在 JSON 里加字段解析函数相应增多即可。4.2 8 位数码管的显示分区策略8 位数码管怎么显示温湿度我推荐两种方案。第一种是左右分区前 4 位显示温度保留一位小数后 4 位显示湿度中间用小圆点或灭掉一位做视觉分隔。显示函数里维护一个 index 指针按位填充缓冲区。第二种是轮询切换同一屏先显示 C 25.6隔几秒切换到 H 58.0。这种方案显示内容更集中单位标识更清晰我用的是这种。需要特别注意小数点的处理。温度 25.6 度如果只显示 C 25.6小数点应该放在个位 5 的后面也就是把显示 5 的那一位段码或上 0x80。实现时不要直接改段码表而是显示前临时处理void Disp_ShowTemp(float temp) { int t (int)(temp * 10); dispBuf[0] SEG_CODE[10]; // C dispBuf[1] SEG_CODE[t / 100 % 10]; dispBuf[2] SEG_CODE[t / 10 % 10] | 0x80; // 带上小数点 dispBuf[3] SEG_CODE[t % 10]; TM1639_Display(dispBuf, 8); }4.3 网络阻塞时的刷新策略物联网设备常遇到一个问题主循环里等待网络响应时如果显示代码也放在主循环里屏幕就会出现卡顿或闪烁。TM1639 的好处是内部扫描写一次数据就一直显示所以只要在收到数据后更新一次缓冲区即可。真正要注意的是不要让业务逻辑一直阻塞显示更新函数。我的做法是串口接收用 DMA 空闲中断接收完成后通过标志位通知主循环解析。主循环里在没有新数据时不调用 TM1639_Display避免无意义的重复写入一旦新数据到来立即解析并刷新显示。这样既节省 MCU 时间显示也始终是最新状态。5. 实测中翻车最多的三个地方共阴共阳、时序过快、电源跌落这章节是我最想写的。前面代码调通只是第一步真正让整个系统稳定运行靠的是解决下面这几个幽灵问题。5.1 共阳共阴混淆段码表方向反了我第一次踩这个坑是把一个共阳极数码管模块直接接到 TM1639 上。现象是上电后数码管亮了几段但完全不按段码表走有的段亮有的段灭看起来像随机乱码。排查了半天最后万用表一量发现模块是共阳极。共阳极数码管的公共端接正极段选端要靠低电平点亮和 TM1639 的高电平点亮输出完全不匹配。解决方案有两个一是换共阴极数码管模块二是在硬件上加反相三极管阵列。对于大多数项目直接换模块最省事。买模块时一定要看商品页描述的 共阴 字样最好问清楚卖家芯片型号和数码管类型。5.2 CLK 时序过快导致的偶发错位STM32 的 GPIO 翻转速度很快50MHz 输出模式下一个上升沿可能只要十几纳秒。TM1639 内部采样电路跟不上这么快的沿就会出现偶发性错位显示的数字偶尔跳动或错位。这个坑在 STM32F1 上还不太明显换到 F4 或 H7 系列 CPU 后特别容易出现。解决办法是在 CLK 高低电平之间加一点延时。实测下来CLK 低电平保持 1~2 微秒CLK 高电平保持 1~2 微秒显示就很稳定。如果使用 HAL 库加几个for空循环或调用DWT_Delay都可以。重点是保证 TM1639 在上升沿前有足够时间稳定读取 DIO。5.3 电源跌落导致的复位与闪烁数码管全亮时电流很大8 位数码管最多 64 段每段按 5mA 算峰值电流超过 300mA。如果用 3.3V 稳压芯片从 5V 输入转出芯片瞬间输出大电流时电压跌落可能引发 STM32 或 ESP8266 复位表现就是数码管闪一下、系统重启。我的解决做法是数码管供电和 MCU 供电分开。如果模块支持独立 VCC直接从电源输入端取电如果只能共用 3.3V就在模块电源引脚旁边并一个 100uF 电解电容和一个 100nF 陶瓷电容增加瞬态电流供给能力。同时把亮度等级降低比如设置 0x8C 或 0x8D实际亮度没有明显下降但电流显著减小。5.4 排查显示问题的小技巧最后分享一个我非常依赖的自检方法写一个点亮测试函数让所有段的显示缓冲区全部填充 0xFF然后循环向左移位。这样能很快确认 8 位数码管的位和段映射是否正确问题在硬件还是软件也能一目了然。void TM1639_Test(void) { uint8_t test[8]; for (uint8_t i 0; i 8; i) test[i] 0xFF; TM1639_Display(test, 8); delay_ms(500); for (uint8_t i 0; i 8; i) test[i] 0x00; TM1639_Display(test, 8); delay_ms(500); }如果在测试阶段就能跑通这个函数后面的业务层问题基本可以定位在逻辑而不是驱动。这套 TM1639 驱动方案我已经在两个物联网项目和一次毕业设计上反复用过了整体感受就是踏实引脚省、代码简单、稳定性高把显示这件事从需要操心的部分里彻底扔了出去。最后再说一个细节如果未来可能扩展按键功能设计 PCB 时给 DIO 预留一个上拉电阻位改板成本几乎为零却能省掉后续很大的麻烦。数码管的魅力就在于它不像屏幕那样花哨但在工业、家居、教育这些真实场景里从来没有让人失望过。本文还有配套的精品资源点击获取