
简介面向嵌入式开发者的HK32F030M数码管显示驱动工程资源以HK32F030M微控制器为主控通过SPI接口控制TM1624驱动芯片实现四位数码管的动态扫描显示。适合学习MCU编程、SPI协议及数码管驱动应用的中高级嵌入式爱好者及相关项目工程师。资源包共159个文件容量约4.61MB包含完整Keil工程文件uvprojx/uvoptx、C语言源码及头文件、编译生成的hex/axf文件另有pdf数据手册和htm、txt说明文档覆盖从源码到烧录验证的全流程便于直接查阅和二次开发。已有331人学习下载资源中提供了SPI初始化、定时器中断更新显示等关键驱动代码配合数据手册可快速掌握TM1624段码/位选控制逻辑与低功耗动态扫描设计思路对完善嵌入式显示项目或课程设计很有参考价值。 最近在调一块由 HK32F030M 和 TM1624 组成的四位数码管显示驱动板简单说就是让 MCU 通过三根 IO 把要显示的数字交给 TM1624剩下的段扫描、位扫描和刷新全部由这颗 LED 驱动芯片自己完成。项目本身不复杂但如果你直接搜 TM1624会发现问题很多资料经常跟 TM1628、TM1629 混在一起引脚定义各说各话命令字也容易对不上。这篇文章把我实际搭这套方案的过程整理出来从选型原因、硬件连接、协议时序、代码实现一路讲到调试时踩过的坑给要做数码管显示驱动的人一个可以照着做的参考。光看标题可能有人会问四位数码管而已MCU 引脚直接灌电流不也能点吗或者用 74HC595 串转并也行。我最早也这么想直到把亮度、引脚占用、刷新占用和量产一致性放在一起比才发现专用 LED 驱动芯片在这里确实更合适。下面不绕弯子直接讲这套方案。1. 为什么我放弃了595和MAX7219最终选了TM16241.1 HK32F030M的资源底子HK32F030M 是 Cortex-M0 内核我用的这颗是宽压供电版本2.4~5.5V。这听起来只是电源范围问题实际对驱动数码管影响很大TM1624 在 5V 供电时输入高电平门槛大约是 0.7×VDD也就是 3.5V 左右而如果 MCU 只有 3.3V推挽输出约 3.3V就差那么零点几伏容易时好时坏。让 HK32F030M 直接 5V 供电IO 高电平接近 5VTM1624 这边就完全不用考虑电平转换。这是我这套方案能省掉一堆麻烦的前提。至于主频、Flash、RAMHK32F030M 完全够用因为显示刷新根本不占用它MCU 只在数字变化时写几个字节。1.2 为什么不选74HC59574HC595 是很常见的串转并方案三根线也能驱动四位数码管但它的本质只是扩展 IO不是显示驱动。四位数码管要稳定显示必须动态扫描MCU 开定时器中断每 1~2ms 切换一次位选同时往段选送码还要处理切换瞬间的拖影消隐。这套逻辑写起来不难难在它跟主程序其他中断抢 CPU一旦主程序里有耗时操作或者更高级的中断数码管就会闪、亮度下降。595 输出的是普通推挽电平每个段还需要串限流电阻不同批次 LED 的压降差异会让亮度一致性一般。TM1624 自带显示 RAMMCU 把数据写进去之后扫描、刷新都由芯片自己完成不占用 CPU 时间这是最大的区别。BOM 也更省SEG 那边基本不用外接限流电阻。1.3 为什么不选MAX7219MAX7219 大家更熟悉SPI 接口自带 BCD 解码接法也简单。但对这个项目来说它有几个让我不舒服的地方BCD 解码只方便显示 0~9 数字想显示字母、负号、自定义符号就不够灵活必须绕回全段码控制MAX7219 的段电流靠一个外接电阻设定量产时电阻精度的差异会直接体现在亮度差异上它还需要处理 5V 供电和 MCU 电平匹配的问题。TM1624 这边段码表完全由软件定义驱动输出接近恒流结构不用每个段都放限流电阻亮度一致性更可控。做产品而不是做实验板的话这个差异很实际。当然 MAX7219 不是不能用只是在这个“四位数码管显示内容自定义”的场景里TM1624 的贴合度更高。同样是一个四位数码管的显示任务我用下面这张表记录了几种方案的差异。这里的“占用MCU资源”指的是长跑任务比如定时刷新和扫描切换。表里的结论基于我实际调试的主观判断具体到你的产品还要结合成本、供货和你熟悉的方案来权衡。不过有一点我可以肯定如果你想要的是“写一次数据芯片自己把显示维持住”TM1624 这类带显示 RAM 的 LED 驱动芯片是比 595 更省心的选择。方案占用IO是否自带扫描限流电阻自定义符号典型问题GPIO直接驱动12个左右否需要可以占用IO太多只能静态74HC5953个否需要需要软件转换刷新占用MCU有拖影风险MAX72193~4个是一个电阻BCD不便字母类显示麻烦亮度靠外置电阻TM16243个是基本不用方便资料少命令字要仔细看手册2. HK32F030M和TM1624之间的硬件连接细节比想象中多2.1 引脚分配TM1624 常用引脚包括 VDD、GND、STB、CLK、DIO、SEG1~SEG8、GRID1~GRID4。我的板子上 CLK 接 PB1DIO 接 PB0STB 接 PB10。选这三个引脚没有特别高深的理由主要是布线方便PB0/PB1 离 MCU 电源脚近而且默认不带特殊复用功能不占调试串口和烧录口。要提醒的是不要一上来就把 CLK 接到 PA9/PA10 这类串口脚或者 PB2 这类 BOOT 脚后面调试或量产烧录时容易互相干扰。如果你用的封装引脚更少引脚分配要重新核一遍以原理图和封装图为准。显示部分用的是四位共阴数码管公共端接 GRID1~GRID4段引脚接 SEG1~SEG8。注意这里“共阴”是我手头这颗 TM1624 的典型接法有些型号可能设计成共阳结构换芯片时不要把公共端接反否则显示乱且可能过流。2.2 电平匹配与IO模式这颗 TM1624 的输入高电平门槛约 0.7×VDD所以当 VDD5V 时控制信号高电平最好能在 3.5V 以上。我直接让 HK32F030M 和 TM1624 都用 5V 供电MCU 推挽输出高电平约为 VDD 减去很小压降识别没有问题。如果你的 MCU 必须工作在 3.3VCLK、DIO、STB 这三根线一定要做电平转换不要指望在 5V 芯片输入端串个电阻或者只靠上拉就能稳定工作。DIO 这根线尤其要注意有些 TM16xx 系列芯片的 DIO 是双向口用于读按键或读状态。如果只做显示可以把 DIO 一直配置成推挽输出如果以后要扩展按键扫描就得随时切换方向PCB 上不要在 DIO 上并太大电容否则方向切换瞬间电平跟不上。我在这版只做显示所以 DIO 始终输出。2.3 电源与去耦的实测经验硬件上最容易忽略的是电源。TM1624 驱动数码管时段电流是阶梯变化的几位同时点亮瞬间电流可能到几十毫安甚至上百毫安如果电源和地线太细VDD 会被拉出明显毛刺。我在这块板上给 TM1624 的 VDD 和 GND 之间放了 10uF 电解电容加 0.1uF 陶瓷电容并且都靠近芯片电源脚HK32F030M 的电源也单独加 0.1uF 去耦。第一次画板时我把地线走得很细结果一显示带小数的“8.8.8.8.”就偶发重启后来把地线加宽、电源走线缩短问题才消失。如果测试中遇到复位、乱码先怀疑电源再怀疑时序这个排查顺序能省很多时间。3. TM1624的三线协议拆解命令字、地址和段码到底怎么对应3.1 时序基础首先明确一点TM1624 的三线口不是标准 SPI别直接用 MCU 的 SPI 外设去接。它的名字虽然叫 CLK/DIO/STB但时序更像“GPIO 模拟串行总线”。一次传输流程是这样的先把 STB 拉低表示开始CLK 上升沿时DIO 上的电平被锁存进芯片每 8 个 CLK 周期形成一个字节数据先发低位最后把 STB 拉高表示结束。因为不是标准 SPI也没有从机的片选自动控制所以 STB 的拉低拉高必须由程序严格管理不能省。网上有些代码把 CLK 的极性搞反了现象就是显示完全错乱或者干脆不亮。判断标准很简单用示波器看DIO 上的数据必须在 CLK 上升沿之前稳定这就是“上升沿锁存”的含义。3.2 命令字和地址布局关于 TM1624 的命令字网上资料少且乱跟 TM1628、TM1629 混着出现我一开始也抄错过。我手里这颗 TM1624一次完整的写显示数据过程是这样STB 拉低发一个数据命令字节表示接下来写显示寄存器、地址自动加一再发一个起始地址然后连续发送 4 个字节的段码数据最后 STB 拉高。显示开关和亮度控制是单独的显示控制命令我在代码里用一个宏定义方便根据手册调整。我这里的记录是数据命令 0x44起始地址 0xC0显示控制命令基础值 0x88低三位是亮度占空比bit3 是显示开关。需要强调不同批次的 TM1624 命令字布局可能有差异如果你手里的手册和我下面的定义不一致务必按手册改不要照抄网上 TM1628 的代码。这个“命令字不确定”是 TM1624 最大的门槛但也别怕只要 STB 时序正确命令字试错成本很低。3.3 段码映射前面说过TM1624 的 RAM 地址对应 GRID也就是数码管的位。段码的 bit 位对应的是 SEG 引脚通常 bit0 对应 a 段bit1 对应 b 段一直到 bit7 对应 dp。共阴数码管常用的数字 0~9 的段码我从 0x3F 开始排0x3F、0x06、0x5B、0x4F、0x66、0x6D、0x7D、0x07、0x7F、0x6F。这个表后面代码里会直接用。在这里要特别提醒段码表是针对“共阴bit0a”的约定来的如果你的数码管是共阳段码要全部取反如果 PCB 上 SEG 线序被打乱代码里看到的数字就会错乱这时不要怀疑协议去对照原理图重排段码表的位序。显示小数点是 bit7 置 1显示负号就用 0x40显示字母 C 用 0x39这些都是自定义显示时的灵活之处。4. 基于HK32F030M的驱动代码初始化、段码表与显示函数4.1 GPIO初始化代码使用航顺固件库接口和 STM32F0 的标准外设库很像。初始化时要把 PB0/PB1/PB10 都设成推挽输出速率 2MHz 就够了TM1624 不是高速器件拉得太快反而容易产生振铃。另外我习惯把三根线初始化为高尤其是 STB防止 MCU 复位期间引脚高阻导致 TM1624 收到半截命令。具体代码#include hk32f030m.h #define TM1624_STB_PORT GPIOB #define TM1624_STB_PIN GPIO_PIN_10 #define TM1624_CLK_PORT GPIOB #define TM1624_CLK_PIN GPIO_PIN_1 #define TM1624_DIO_PORT GPIOB #define TM1624_DIO_PIN GPIO_PIN_0 static void TM1624_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin TM1624_STB_PIN | TM1624_CLK_PIN | TM1624_DIO_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_2MHz; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_SetBits(GPIOB, TM1624_STB_PIN | TM1624_CLK_PIN | TM1624_DIO_PIN); }4.2 写字节和命令帧接下来是底层时序。写字节时要注意按位判断 DIO 电平然后在 CLK 上升沿锁存。我用的延时函数很简陋就是一个计数器目的是把 CLK 周期压到 1us 以上如果你把主频调过这个延时时间会变最好用定时器或者 SysTick 做精确延时。数据先发低位很多移植代码翻车就是这里没改。代码如下static void TM1624_Delay(void) { volatile uint32_t i 20; while (i--); } static void TM1624_WriteByte(uint8_t dat) { uint8_t i; for (i 0; i 8; i) { if (dat 0x01) { GPIO_SetBits(TM1624_DIO_PORT, TM1624_DIO_PIN); } else { GPIO_ResetBits(TM1624_DIO_PORT, TM1624_DIO_PIN); } GPIO_SetBits(TM1624_CLK_PORT, TM1624_CLK_PIN); TM1624_Delay(); GPIO_ResetBits(TM1624_CLK_PORT, TM1624_CLK_PIN); TM1624_Delay(); dat 1; } }地址自动递增意味着只要在起始地址后连续发字节TM1624 会自己把后面的数据填到后续地址不用每个地址都发一次命令。这既省时间也能避免中途 STB 抖动造成的地址错乱。写数据帧函数如下static void TM1624_WriteData(uint8_t addr, uint8_t *buf, uint8_t len) { uint8_t i; GPIO_ResetBits(TM1624_STB_PORT, TM1624_STB_PIN); TM1624_WriteByte(0x44); // 数据命令写显示寄存器地址自动加一 TM1624_WriteByte(addr); // 起始地址 for (i 0; i len; i) { TM1624_WriteByte(buf[i]); } GPIO_SetBits(TM1624_STB_PORT, TM1624_STB_PIN); }4.3 段码表与显示函数段码表我直接放到数组里从 0 到 9 的顺序排列后面要显示字母或者负号再往里面补。四个数字按照千位、百位、十位、个位放进 buf[0] 到 buf[3]然后一次写进 TM1624。这里我加了首消隐参数让显示 123 的时候不要出现 0123产品界面上会好看很多如果想让数值右对齐也可以只消隐不右移。下面这段是核心static const uint8_t FontTable[] { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; static void TM1624_DisplayOn(uint8_t brightness) { uint8_t cmd 0x88; // 显示控制命令基础值 if (brightness 7) brightness 7; cmd | brightness; GPIO_ResetBits(TM1624_STB_PORT, TM1624_STB_PIN); TM1624_WriteByte(cmd); GPIO_SetBits(TM1624_STB_PORT, TM1624_STB_PIN); } void TM1624_ShowNumber(uint16_t value, uint8_t blankLeading) { uint8_t buf[4]; buf[0] FontTable[(value / 1000) % 10]; buf[1] FontTable[(value / 100) % 10]; buf[2] FontTable[(value / 10) % 10]; buf[3] FontTable[value % 10]; if (blankLeading) { if (value 1000) buf[0] 0x00; if (value 100) buf[1] 0x00; if (value 10) buf[2] 0x00; } TM1624_WriteData(0xC0, buf, 4); }DisplayOn 的亮度参数命令我用了基础值 0x88低三位就是亮度挡位。你拿到的 TM1624 如果低三位含义不同直接改这个函数里的 cmd。调用顺序上main 函数里先做 GPIO 初始化再关显示、清显示 RAM然后开显示最后才显示想要的内容。上电清屏这一步不能省因为 TM1624 的显示 RAM 上电内容是随机的不清屏的话数码管可能闪一下乱码。下面的 main 片段就是标准流程int main(void) { uint8_t clear[4] {0x00, 0x00, 0x00, 0x00}; TM1624_GPIO_Init(); TM1624_WriteData(0xC0, clear, 4); // 清屏 TM1624_DisplayOn(5); TM1624_ShowNumber(1234, 1); while (1) { // 这里写业务逻辑显示不会闪 } }4.4 为什么这里不需要自己写动态刷新很多第一次用 TM1624 的人都会不放心我是不是还要在定时器里刷新不用。TM1624 内部有显示 RAM 和扫描电路你只要把段码写进 RAM它会自动轮询 GRID 并输出对应的 SEG 信号。这意味着当你把 1234 四个字节写进去之后CPU 就可以去干别的活了。这个特性在小系统里非常舒服尤其 HK32F030M 本身资源不多省掉一个定时中断对系统实时性很有帮助。如果显示内容不更新数码管也会一直稳定显示不会像 595 动态扫描那样一停就闪。我在产品里让 MCU 长时间进入低功耗数码管依然保持显示这个效果用 GPIO595 很难做到。5. 实测排错记录四个最容易翻车的地方5.1 上电瞬间数码管乱闪第一个坑是上电瞬间乱码。原因是 TM1624 先于 MCU 完成复位此时显示 RAM 随机SEG/GRID 可能已经输出数码管会闪一下。软件上我在 main 开头的 GPIO 初始化之后立刻清显示 RAM再开显示能压住大部分乱码硬件上把 MCU 的复位上电时间稍微拉长一点或者给 STB 加一个 10k 下拉电阻避免复位期间 STB 悬空误触发。如果对时序要求很严格可以用一颗带 RC 延时的复位芯片控制 MCU 启动顺序。5.2 数字错位和段乱第二个坑是显示错位。可能是地址方向搞反如果你的板子 GRID1 在右边而我代码里 buf[0] 对应最左边那么 1234 会显示成 4321。改法很简单把 buf 数组倒过来填。也可能是 SEG 顺序乱。我建议先写一个自检程序依次点亮 SEG1~SEG8然后对着数码管记录哪一段亮再生成新的段码表。这个表一旦确认就不要再动 PCB 走线了。还有一次我把数据发成高位在前整个数码管显示的字符完全不对把 WriteByte 里的 dat 1 改成 dat 1 就正常。排查顺序建议是先确认位序再确认段序最后才怀疑命令字。5.3 长时间运行后偶发花屏第三个坑是花屏。当 MCU 主频高、GPIO 翻转快又没有加延时杜邦线一长CLK/DIO 上的振铃会让 TM1624 收到错误数据。表现为运行几分钟突然显示一个乱码下一次写数据又恢复正常。我解决的方法是加大 TM1624_Delay把 CLK 频率控制在 500kHz 以下同时在 STB 拉低前让 DIO 先稳定。另一个原因是中断里写显示如果在定时器中断或串口中断里调用 TM1624_WriteData一旦主程序同时在写两次 STB 帧可能互相穿插协议就乱了。建议所有 TM1624 操作都放在同一个任务里必要时加个互斥标志。5.4 亮度不均和干扰处理第四个坑是亮度不均。TM1624 输出接近恒流驱动但 GRID 公共端的走线压降、数码管本身每段 LED 的压差都会造成位与位之间的亮度差。如果只是某一位暗查公共端走线和插接件接触如果是整片亮度太低查 VDD 电压。还有一点不要把亮度命令开到最高挡。我试过在最高挡位下个别段明显偏亮降低一挡之后均匀度好了很多。如果你的设备周围有电机、继电器这类干扰源信号线上串 100 欧姆电阻、TM1624 电源脚加强去耦会有帮助。最后量产前记得把命令字定义和段码表固化成正式头文件并标注手册版本不然三个月后你自己都分不清当前这颗料该用哪套命令。本文还有配套的精品资源点击获取