STM32+TM1639数码管驱动:物联网显示方案实战解析
2026/9/7 3:01:10 网站建设 项目流程

简介:面向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,等于关闭显示。调亮度时要保留 bit3:

void 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 对应数字 0,0x06 对应数字 1,以此类推。如果要用到大写字母,比如显示温度单位 "C" 就是 0x39,"E" 是 0x79。带小数点时,只需把对应的段码最高位置 1,例如 "3." 就是 SEG_CODE[3] | 0x80。

动手做之前,最好用万用表确认一下手上模块的共阴共阳类型。很多淘宝模块标注不清,买到共阳极会直接导致全灭或乱码。

3. STM32 上的驱动落地:从时序函数到 8 位显示缓冲

理解了协议之后,代码其实就是把时序用 STM32 的标准外设库或 HAL 库翻译一遍。我用的是 STM32F103C8T6,选择 PB0 作为 CLK、PB1 作为 DIO,两个引脚都配置为推挽输出。

3.1 硬件接线和初始化

接线非常宽松:CLK 接 PB0,DIO 接 PB1,VCC 接 3.3V 或 5V,GND 共地。如果模块上有 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 连接 ESP8266,ESP8266 连接物联网平台,云端下发的数据通过串口以字符串形式送到 STM32,STM32 解析后更新 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 预留一个上拉电阻位,改板成本几乎为零,却能省掉后续很大的麻烦。数码管的魅力就在于,它不像屏幕那样花哨,但在工业、家居、教育这些真实场景里,从来没有让人失望过。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询