news 2026/9/9 17:04:43

STM32驱动TM1639数码管显示与ESP8266物联网远程控制完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32驱动TM1639数码管显示与ESP8266物联网远程控制完整实战

简介:面向STM32开发者与物联网入门者的TM1639共阴极数码管驱动代码,解决在STM32平台上通过专用驱动芯片TM1639高效控制共阴极数码管显示的问题。TM1639支持亮度调节,并通过I2C或SPI接口与MCU通信,可简化数码管硬件设计,示例代码也展示了完整的软件驱动流程。资源包含一个C源文件与一个H头文件,共2个文件,压缩包仅2KB,结构精简,便于直接嵌入现有工程。代码基于C语言编写,提供初始化、数字/字符显示、亮度控制等底层驱动接口,用户可快速调用并移植到不同STM32型号,从而缩短物联网终端、仪表盘等场景的显示模块开发周期;通过阅读代码还能理解TM1639的时序控制、寄存器操作以及共阴极数码管的段码映射等关键知识点。已有1086人学习下载,代码量小但覆盖驱动开发核心环节,适合正在学习单片机外设驱动或需要在项目中快速集成数码管显示的开发者参考。 这段时间帮朋友做了一块基于STM32的物联网数码管显示板,核心就是MCU通过TM1639驱动芯片点亮几块共阴极数码管,把手边的温湿度数据在本地显示出来,同时通过ESP8266同步到云端。项目本身不算复杂,但TM1639这颗芯片在淘宝上几块钱就能买到、驱动逻辑清晰,配上STM32之后无论拿来做课程设计、毕业设计还是智能家居小项目都非常合适。这篇文章就把整个项目的驱动原理、接线方式、代码实现和物联网接入思路完整拆一遍,目标是让看完的人能直接照着复现。

1. 项目整体设计:为什么选TM1639配共阴极数码管

1.1 硬件架构与选型思路

先交代一下整体硬件结构。主控用的是STM32F103C8T6,这是最常用的入门级Cortex-M3芯片,72MHz主频、64KB Flash、20KB RAM,驱动一个数码管芯片绰绰有余。数码管选的是6位共阴极红色数码管模块,驱动芯片就是TM1639。

整个系统分成三块:电源、显示单元、物联网通信单元。STM32负责控制逻辑,通过三根GPIO线驱动TM1639,TM1639再接管6位数码管的动态扫描。物联网这边用ESP8266模块,通过串口和STM32通信,ESP8266负责连WiFi、走MQTT协议,STM32不用操心网络协议栈,职责很干净。

刚开始我也纠结过另外一个方案:直接用STM32的GPIO加三极管/限流电阻去扫数码管。但算了一笔账,6位共阴极数码管,每位8个段,光GPIO就要占14个引脚,而且动态扫描刷新必须全程占用CPU,稍不注意就会出现亮度不均、程序卡死导致闪烁的问题。TM1639把动态扫描和恒流驱动全接管之后,MCU只需要在内容变化时更新一次显示寄存器,之后完全不用管,这也给后面的物联网逻辑腾出了大量CPU时间。

1.2 TM1639与TM1637该怎么选

做数码管的驱动芯片,很多人会纠结TM1639和TM1637。这两个芯片名字很像,但定位有明显差别:TM1637是两线接口(CLK+DIO),没有独立的片选脚,协议更像简化版I2C,适合驱动4位或6位共阴数码管;TM1639是三线接口(CLK+DIO+STB),有独立的片选脚,协议是类SPI的串行方式,支持6位共阴数码管,而且带按键扫描功能,最多可以扫64个按键。

如果你的项目只是显示几位数字、不接键盘,那TM1637完全够用,而且少一根线。但如果像我这个项目一样,除了显示还希望留按键接口做控制,或者想驱动6位以上显示,TM1639会更合适。另外TM1639内置恒流源,驱动大尺寸共阴极数码管时亮度一致性更好,这个特性在面板上比较重要。

还有一点要考虑:市面上很多“TM1639模块”是直接把芯片和数码管焊在一块的,买回来只需要接VCC、GND、DIO、CLK、STB五根线,非常省事。我的板子是自己画的,数码管和芯片分开,方便换不同尺寸的数码管,但调试的时候确实费了点劲。新手建议先买集成模块跑通一次,再考虑自由组合。

2. TM1639引脚与通信协议拆解

2.1 引脚功能与共阴极数码管接线

TM1639常用封装是SOP24,引脚不算多。最关键的是DIO(数据输入输出)、CLK(时钟输入)、STB(片选/使能)。供电范围标准是3V到5.5V,和STM32共用一个3.3V供电完全没有问题。

接线非常简单,以我用的PA4/PA5/PA6为例:

TM1639引脚功能连接到STM32
VCC电源正3.3V
GND电源地GND
STB片选/使能PA6
CLK串行时钟PA4
DIO串行数据PA5
SEG1~SEG8段驱动输出数码管a~g、dp
GRID1~GRID6位驱动输出数码管COM1~COM6

共阴极数码管接法的核心是:公共阴极COM脚接TM1639的GRID端,段脚a、b、c、d、e、f、g、dp依次接SEG1到SEG8。TM1639内部会轮流把GRID拉低,同时SEG输出高电平,点亮对应位上的对应段。人眼看到的就是6位数字同时稳定显示,但实际上芯片内部是在高速扫描,每位显示时间极短,整个刷新周期在几毫秒内完成,所以没有任何闪烁感。

接线时注意,如果使用的是集成模块,模块上通常已经把数码管和TM1639连好了,你只需要关心DIO/CLK/STB三根控制线。如果是自己画板子,一定要确认数码管封装图里a段对应的是哪个引脚,不同厂家的数码管脚序经常不一样,接错之后段码显示会错乱。

2.2 指令格式与时序关键点

TM1639的通信协议是典型的“片选+时钟+数据”三线时序,和SPI有点像,但不是标准SPI。数据从最低位开始发送,每个时钟上升沿锁存一位。整个传输以STB拉低为起始条件,以STB拉高为结束条件。注意,在整个数据传输过程中STB必须保持低电平,只有在一个完整命令或者一条完整数据发送完成后才能拉高。

芯片的指令分三类:数据命令设置、地址命令、显示控制命令。

数据命令设置最常见的是0x40,含义是“写数据到显示寄存器,地址自动递增”。这样一次可以连续写入6个显示字节,效率高。如果你只想更新某一位,可以用0x44固定地址模式,后面跟地址和单个数据。

显示控制命令是0x80到0x8F这一组。网上很多资料说0x88是关闭显示,这个说法有误导。实际拆开二进制看:0x80=1000_0000,其中bit3是显示开关位,0为灭、1为亮,bit2~bit0是亮度等级000~111。0x88=1000_1000,bit3是1,表示开启显示、亮度最低;0x8F=1000_1111,bit3是1,表示开启显示、亮度最高。真正的关显示命令是0x80。项目里我直接写0x8F,开机就是最高亮度。

地址方面,起始地址是0xC0,对应的不是芯片引脚号,而是显示寄存器的首地址。0xC0对应第1位(GRID1),往后每加1就是下一位,0xC5对应第6位。每个显示寄存器存一个字节,这个字节的bit0对应SEG1也就是a段,bit7对应SEG8也就是dp段。

时序上还有个容易忽略的点:每发完一个字节后,芯片会在CLK的第9个脉冲附近把DIO拉低作为应答信号。如果你只做写操作、不读按键,这个应答可以不理会,直接继续发或拉高STB结束。但如果你的DIO引脚在发完数据后没有释放(还是推挽输出高),可能会顶住芯片的应答信号。稳妥的做法是在发送完一个字节后把DIO配置成输入模式,或者至少保证DIO输出低电平后不再干预,直到STB拉高。我这个项目里没有用到应答检测,实测不影响稳定工作。

3. STM32驱动代码实现

3.1 GPIO初始化与宏定义

我用的是STM32标准库配合寄存器操作,因为TM1639的时序对时间精度有要求,HAL库调用GPIO_WritePin的开销比较大,在72MHz主频下虽然也能用,但寄存器操作明显更干脆。下面的代码基于STM32F103系列,PB用来举例,你自己接哪几个引脚就改对应宏。

#include "stm32f10x.h" // 根据实际接线修改引脚:PA4-CLK, PA5-DIO, PA6-STB #define TM1639_CLK_H() GPIOA->BSRR = GPIO_Pin_4 #define TM1639_CLK_L() GPIOA->BRR = GPIO_Pin_4 #define TM1639_DIO_H() GPIOA->BSRR = GPIO_Pin_5 #define TM1639_DIO_L() GPIOA->BRR = GPIO_Pin_5 #define TM1639_STB_H() GPIOA->BSRR = GPIO_Pin_6 #define TM1639_STB_L() GPIOA->BRR = GPIO_Pin_6

GPIO初始化和普通LED控制一模一样,三个引脚全部配置为推挽输出、50MHz速度。如果之后要做按键扫描,DIO需要改成开漏输出加外部上拉,或者动态切换输入输出模式。我们这个项目只写不读,保持推挽输出即可。

void TM1639_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TM1639_CLK_H(); TM1639_DIO_H(); TM1639_STB_H(); }

3.2 核心通信函数

通信函数是整个驱动的灵魂,原理就三步:STB拉低表示开始,逐位发送数据,最后STB拉高表示结束。每个字节都是LSB first,也就是最低位先发。

我写了一个通用的发送字节函数,循环8次,每次先把CLK拉低,然后在CLK低电平期间修改DIO的电平,再拉高CLK产生上升沿锁存数据。注意修改DIO一定要在CLK为低的时候做,否则芯片可能采到不稳定的数据。

void TM1639_Start(void) { TM1639_STB_H(); TM1639_CLK_H(); TM1639_DIO_H(); TM1639_STB_L(); // STB从高到低,一帧开始 } void TM1639_Stop(void) { TM1639_CLK_L(); TM1639_DIO_H(); TM1639_STB_H(); // STB拉高,一帧结束 } 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(); } TM1639_CLK_H(); dat >>= 1; } }

写完字节要不要加延时?我自己测试下来,在72MHz主频下不加额外延时也能正常工作,因为GPIO翻转本身就有几十纳秒的建立时间。如果换成超频到128MHz或者系统时钟特别快,建议在每次CLK拉低后加几个空指令的延时,这个后面排查问题时再展开。

3.3 显示函数与段码表

段码表是共阴极数码管标准编码,记住一个规律:段码的bit0到bit7对应a到g到dp,点亮就是1。比如数字0,a、b、c、d、e、f亮,g不亮,段码就是0x3F。

const uint8_t segTab[] = { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F, // 9 0x77, // A 0x7C, // b 0x39, // C 0x5E, // d 0x79, // E 0x71, // F 0x00, // 熄灭 };

下面是完整的显示函数。我习惯把“写显示数据”和“开显示”分开,这样初始化时可以先关显示,避免上电瞬间一些随机数据被显示出来。往显示寄存器写数据时,发送0x40然后连续写6个字节,最后发0x8F开显示并设置最高亮度。

void TM1639_Display(uint8_t *buf, uint8_t len) { TM1639_Start(); TM1639_WriteByte(0x40); // 数据命令:写显示寄存器,地址自动递增 TM1639_Stop(); TM1639_Start(); TM1639_WriteByte(0xC0); // 起始地址:第1位 for (uint8_t i = 0; i < len; i++) { TM1639_WriteByte(buf[i]); } TM1639_Stop(); TM1639_Start(); TM1639_WriteByte(0x8F); // 显示控制:开启显示,最高亮度 TM1639_Stop(); }

调用方式也很简单。假如要显示“123456”,先把数字转成段码数组,再传给TM1639_Display。如果要在某一位点亮小数点,直接在该位段码上或上0x80。

uint8_t dispBuf[6] = { segTab[1], segTab[2], segTab[3], segTab[4], segTab[5], segTab[6] }; TM1639_Display(dispBuf, 6);

实际使用中,数码管只有6位,要显示温湿度这种带符号的数值,可以把温度和湿度分开显示,或者用冒号位做隔断。比如“25.5”显示5位,第2位的小数点亮起来,效果很直观。

4. 物联网接入:本地显示加云端远程

4.1 接入方案选型

物联网接入的方案有好多条路,最常见的是STM32外挂ESP8266,走MQTT协议。ESP8266本身可以刷AT固件,也可以刷NodeMCU的Lua固件,甚至直接刷Arduino固件。我的做法是保留原厂AT固件,STM32通过串口发AT指令控制ESP8266联网。这个方案的好处是STM32代码不依赖第三方协议栈,所有网络逻辑都在ESP8266里,出了问题可以单独用串口助手调试ESP8266,定位很快。

MQTT云平台选型上,我试过自建Mosquitto和现成的云平台。自建Mosquitto适合深度定制,但服务器维护成本高,适合长期项目;现成云平台对小项目更友好,注册完之后设备侧连上就能收发消息,手机端有配套的App或小程序。我的做法是先用云平台跑通整个链路,后面有精力再迁到自建服务器。

整个数据链路是这样的:STM32采集传感器数据,一方面在TM1639数码管上本地显示,另一方面把数据按约定的帧格式通过串口发给ESP8266,ESP8266将数据通过MQTT发布到云平台。远程控制则反过来,手机App发消息到云平台,ESP8266订阅到消息后通过串口把数据帧发给STM32,STM32解析之后改变数码管显示内容。

4.2 STM32与ESP8266串口协议设计

STM32与ESP8266之间只通过USART通信,所以Serial协议一定要约定清楚。我定义了一个非常轻量的帧格式,头部是0xAA,接着是命令字,然后是数据长度、数据区和结束符0x55。这样设计的好处是简单可靠,MCU解析代码只有几十行。

我举一个实际的例子,远程下发“HELLO”到数码管显示:

帧字段数值说明
帧头0xAA固定帧头
命令字0x010x01表示显示字符串
数据长度0x055个字符
数据区'H' 'E' 'L' 'L' 'O'ASCII数据
帧尾0x55固定帧尾

STM32收到帧后,校验帧头帧尾和长度,如果命令字是0x01,就把数据区的ASCII字符逐个映射到段码表,再调用TM1639_Display更新数码管。这里有个细节值得注意:一个ASCII字符对应一个段码,但并不是所有字符都能在7段数码管上完美显示。比如字母“K”就没法用标准7段表示,我的做法是做一张字符映射表,只保留能显示的字符,遇到不认识的字符统一显示成横杠0x40。

4.3 完整数据流与显示联动

整个项目做出来后,实际运行场景是这样的:STM32每隔2秒读一次DHT11温湿度传感器,温度值亮在第1和第2位数码管,小数点常亮,湿度值亮在第4和第5位,第3位用来显示一个小横杠做分隔。数码管上直接读就是“25.5 - 60.0”这样的效果,在本地一眼就能看到环境状态。

同时STM32把温湿度打包成一条JSON,通过串口帧发给ESP8266,ESP8266再通过MQTT publish到主题“sensor/temp_hum”。手机端App订阅这个主题,就能实时看到数据,而且历史数据全部落在云端,随时可以追溯。

反过来,我在手机App上发一个消息到主题“device/display”,内容是一个字符串“HELLO”,消息经云平台流转,ESP8266收到后立刻转发给STM32,数码管马上显示“HELLO”。这个场景看起来简单,但背后打通了“端-云-端”的完整链路,智能家居里面的远程状态显示、公告屏、倒计时牌,本质上都是这个套路。

有一点开发上的建议:STM32里不要把所有逻辑都堆在主循环里。我是用一个状态机来管理显示逻辑的,主循环只负责轮询状态,串口接收用中断加环形缓冲区,一旦收到完整帧就置标志位,主循环解析并更新显示。这套结构写起来会多点代码,但后面要加按键、加传感器、加其他外设时会非常感谢当初的自己。

5. 常见问题排查与经验心得

5.1 问题速查表

项目调试过程中遇到的坑,大多集中在时序、供电和段码映射这几个方面。我把典型问题整理成了速查表,方便以后直接对照排查:

现象可能原因排查方法
数码管完全不亮显示控制命令写成了0x80(关显示);STB接线错误确认命令为0x8F;量STB电平是否有跳变
某一位永远不亮GRID位选线虚焊;地址超出范围用固定地址命令单独测试每一位
显示乱码段码表与SEG顺序不匹配;共阴极共阳极搞反对照模块原理图确认SEG1到SEG8对应哪个段
显示亮度低供电电压不足;模块供电线过长独立3.3V或5V供电,注意共地
偶发性花屏CLK时序太快;主循环发送时被中断打断降低发送速度;发送期间屏蔽不必要的中断
串口乱码STM32与ESP8266波特率不一致;未共地统一波特率,两根GND连在一起
上电显示随机值上电瞬间未关闭显示初始化时先发0x80关显示,清空寄存器后再开

5.2 实战心得与三个关键建议

第一,TM1639的资料很杂,网上有些说法互相矛盾,尤其关于0x88和0x8F的含义,一定要以数据手册的二进制位定义为准。遇到数码管不亮先别怀疑硬件,先用逻辑分析仪或者示波器抓一下STB、CLK、DIO三根线的波形,看看一个完整的数据帧是不是发出去了。没有示波器的时候,可以把CLK频率降到很低,把CLK和STB分别接两个LED,肉眼能看到规律闪动,说明时序在跑。

第二,TM1639内部有恒流源,正常情况下不需要在段引脚额外串限流电阻。但如果你用的是自己画板的方案,数码管尺寸比较大、段电流需求较高,要注意供电电压不能太低。我试过用3.3V供电驱动1.2英寸大数码管,亮度稍微偏暗,后来把模块供电改成5V、逻辑部分保持3.3V,亮度问题就解决了。这种“供电分开、逻辑共地”的做法,在各种驱动板里都很常见。

第三,如果要在这个项目上加按键扫描功能,DIO的GPIO模式需要动态切换。读按键时发送完读命令后,必须先把DIO配置成输入上拉模式,否则推挽输出会和外部的应答信号冲突。我当时没注意这一点,读按键老是不稳定,改成开漏输出加外部10K上拉后,问题一次性解决。

最后说点题外话。这个项目做完后,我最大的感受是:TM1639这类专用显示驱动芯片,把数码管最麻烦的动态扫描、恒流驱动、位选分配全部封装好了,MCU要做的只是按时序吐数据,非常省心。如果你想再往深走,可以把6位数码管拆成两组独立显示内容,或者在上面加一个光敏电阻根据环境亮度动态调整显示亮度。

我自己的板子后来又扩展了一下:把TM1639空余的按键扫描能力用起来,接了4个按键,用来切换显示模式和手动设置上限阈值,触发之后数码管会闪烁提示。整个项目从最开始的“点亮数码管”一步步变成了一个带云远程控制的智能环境显示器,过程很有意思。手边有STM32开发板和TM1639模块的话,非常建议你照着上文自己焊一块试试。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 17:04:04

Redis List与Set选型实战:底层原理、命令对比与应用场景

我需要先说明一个情况&#xff1a;这篇文章的价值不在命令行背诵&#xff0c;而在“为什么同样一个需求&#xff0c;有人用List有人用Set&#xff0c;还有人两种组合着用”。先说个我自己的经历。前年做一个电商后台的运营看板&#xff0c;需求很朴素&#xff1a;运营人员要在大…

作者头像 李华
网站建设 2026/9/9 17:03:03

芯片制造企业CAD图纸嵌入TinyMCE的SVG矢量输出方案

芯片制造企业的工程师&#xff0c;在TinyMCE里写设备异常报告或者工程设计变更单时&#xff0c;总会遇到同一个难题&#xff1a;图纸怎么贴进去&#xff1f;直接复制CAD图元再粘贴&#xff0c;编辑器里只剩一张模糊的位图&#xff0c;放大看全是锯齿&#xff1b;先导出PNG再上传…

作者头像 李华
网站建设 2026/9/9 17:02:01

推理GPU告别HBM:480GB大显存如何破解大模型部署难题

推理服务器的选型&#xff0c;这几年我一直有个很深的体会&#xff1a;在GPU集群里&#xff0c;真正让人头疼的不是算力不够&#xff0c;而是显存不够。70B模型量化到FP8还要70GB&#xff0c;叠上KV Cache&#xff0c;单卡想舒舒服服跑起来几乎是奢望。HBM带宽确实猛&#xff0…

作者头像 李华
网站建设 2026/9/9 17:01:42

Qt项目发布实战:依赖收集、平台插件与交叉编译避坑指南

写Qt项目很多年&#xff0c;发布这件事几乎每次都要跟人解释一遍&#xff1a;不是把 exe 拷给客户就完事了。我见过太多“在我电脑上能跑&#xff0c;到你那里就崩”的例子&#xff0c;而 Qt 项目的发布&#xff0c;坑恰恰集中在平台插件、运行时依赖、编译器 ABI 这些看不见的…

作者头像 李华
网站建设 2026/9/9 17:01:29

Claude Code直连国产大模型完全指南:从安装到切换DeepSeek

最近后台收到最多的私信&#xff0c;不是“Claude Code怎么用”&#xff0c;而是“Claude Code装好了&#xff0c;但怎么直连国产大模型”。说实话这个需求太真实了。Claude Code是Anthropic推出的命令行AI编程助手&#xff0c;能在终端里直接帮你读代码、改文件、跑命令&#…

作者头像 李华