☰
TM1624数码管驱动实战:基于HK32F030M的时序与移植指南
2026/10/15 11:34:35 网站建设 项目流程

简介:这是一份面向嵌入式开发者的HK32F030M微控制器与TM1624数码管驱动芯片的完整源码工程,适用于需要实现多位数码管显示的控制系统,如工业仪表、智能家电等场景。驱动基于SPI接口通信,覆盖了外设初始化、显示数据编码、动态扫描刷新等核心环节,可直接参考或移植到实际项目中。压缩包共包含380个文件,以C语言源码和头文件为主,附带编译生成的hex固件、工程配置及调试输出文件,整体大小约7.3MB,目录结构便于按模块查看。目前已有481人学习下载。对理解ARM Cortex-M0内核的SPI编程、数码管译码驱动机制以及底层时序控制有直观的代码示例,适合正在学习MCU驱动开发或需要快速实现数码管显示的工程师参考。

1. 数码管驱动代码拿到手:先搞清这套源码解决什么问题

搞单片机显示方案的人,多半都遇到过这种局面:产品原型已经跑起来了,MCU 选型也定了,结果卡在驱动 TM1624 这颗 LED 驱动芯片上。手册里的时序图倒是画得清清楚楚,真写起代码来,上升沿、下降沿、建立时间、保持时间,一个对不上就是花屏。HK32F030M-TM1624 数码管驱动代码,就是一套已经调通的参考实现,把 GPIO 模拟时序、指令封装、段码映射全部打通,适用于小家电控制板、温控器、定时器这类需要数码管显示的场景,拿过来改改引脚就能跑。

这套资源的价值在于:HK32F030M 是基于 Cortex-M0 内核的国产 MCU,库函数风格和主流 M0 系列接近,但寄存器细节和时钟配置有差异,直接抄网上 STM32 的 TM1624 驱动往往要改不少地方。源码已经替你处理好了端口初始化、位操作时序、亮度控制和段码刷新这几块最容易出问题的环节。适合手里有 HK32F030M 开发板或者正在基于这颗芯片做产品的开发者,也适合想拿一套干净代码做参考、不想从零抠数据手册时序的人。

2. TM1624 驱动原理:时序图里的四个关键点,先看懂再动手

2.1 数据帧格式:一条完整的控制指令由哪几个字节组成

TM1624 的控制接口本质上就是一个带使能引脚的移位寄存器,由 STB(片选)、CLK(时钟)、DIO(数据)三根线组成,所有通信都以 STB 拉低为开始,STB 拉高为结束。这一点和 TM1628、TM1629 系列是同源的,命令格式也基本一致:先是模式设置指令(选择写数据模式还是读键扫模式),再是地址设置指令(指定从哪个显示地址开始写),最后是连续的数据字节。整套驱动代码里最核心的逻辑就是把这三段拼成一组完整事务。

需要注意,TM1624 的地址范围是 00H 到 0DH,共 14 个字节,对应 14 个显示寄存器。每个寄存器的位定义和数码管段位是一一对应的,比如地址 00H 的第 0 位控制第一位数码管的 A 段,第 1 位控制 B 段,以此类推。这里有个容易踩的坑:数据手册上通常画的是“第 0 位”对应段码的最低位,但实际上段码表需要按位反转,否则显示出来的字符会左右镜像。

指令字节的具体格式如下表:

指令类型二进制格式十六进制范围说明
数据模式设置0100 0XXX0x40~0x47选择写显示模式、自动地址增加或固定地址
显示控制1000 XXXX0x80~0x8F高 4 位固定 1000,低 4 位控制亮度和消隐
地址设置1100 0XXX0xC0~0xC7高 4 位固定 1100,低 3 位选择起始地址

驱动代码里默认用的是自动地址增加模式,也就是发完地址指令后,后续写入的每个数据字节自动写入下一个地址,不需要每写一个地址都重新发一次地址指令。适合连续刷新一组数码管的情况。如果是离散地更新单个显示位,建议改用固定地址模式,省去来回切换缓冲区的操作。

2.2 从零实现 16 位移位输出:GPIO 模拟时序的核心逻辑

TM1624 对时序的要求其实不算苛刻,CLK 高电平最短维持时间大约 200ns,普通的 GPIO 翻转配合几个空指令延时就能满足。整套驱动里最关键的是三件事:起始条件、字节移位、结束条件。起始条件是 STB 拉低后保持一段时间再开始送 CLK;结束条件是所有字节发完后,先把 CLK 和 DIO 拉低,再拉高 STB,形成一个完整的上升沿锁定。

下面是驱动代码里最基本的字节发送函数:

void TM1624_WriteByte(uint8_t dat) { uint8_t i; for (i = 0; i < 8; i++) { if (dat & 0x01) { TM1624_DIO_HIGH; // DIO 置高 } else { TM1624_DIO_LOW; // DIO 置低 } dat >>= 1; // 低位在前,先发 LSB TM1624_DELAY_US(1); // 数据建立时间 TM1624_CLK_HIGH; // CLK 拉高,上升沿采样 TM1624_DELAY_US(1); // 时钟高电平维持时间 TM1624_CLK_LOW; // CLK 拉低,准备下一个 bit TM1624_DELAY_US(1); // 时钟低电平维持时间 } }

这里用的是先发低位(LSB first)的方式,和 TM1624 数据手册的时序图保持一致。每次进入循环前先置 DIO 的电平,再拉高 CLK,这样能保证数据在 CLK 上升沿到来之前已经稳定。TM1624_DELAY_US(1)在 24MHz 系统主频下,实际延时大约 1~2 微秒,远大于芯片要求的建立时间,但绝对值不算大,刷新 8 位数码管也不会觉得闪。

参数说明:这段代码里延时用的是宏定义,换到不同主频的 MCU 上,只需要调整这个宏的循环次数即可。如果显示出现偶发乱码,先把延时时间拉长到 3~5 微秒试试,多数情况下是建立时间不够,而不是逻辑错误。之后可以逐步缩短,直到临界点再留出 50% 余量,这样既能保证稳定,又不拖慢刷新速度。

整段通信事务的封装代码如下:

void TM1624_SetAddress(uint8_t addr) { TM1624_STB_LOW; // 片选拉低,通信开始 TM1624_DELAY_US(5); // 起始建立时间 TM1624_WriteByte(0x44); // 固定地址模式,写显示数据 TM1624_WriteByte(0xC0 | addr); // 指定起始地址 TM1624_STB_HIGH; // 片选拉高,结束本次事务 TM1624_DELAY_US(5); // 片选恢复时间 }

这段代码在每次写入地址时都会同时把地址指令和数据模式指令一起发出去,也就是完整的一次通信事务。注意这里用的是固定地址模式(0x44),而不是自动地址增加模式(0x40)。原因是固定地址模式下每写一个字节只需要一次事务,逻辑更简单,出错时也容易定位是哪个地址的哪一次写入出现了问题。

2.3 段码映射机制:从显示缓冲区到数码管段位的完整链路

数码管显示并不是直接把要显示的数字发给 TM1624,而是先把数字映射成段码,再写入显示寄存器。比如要显示数字 0,需要让 A、B、C、D、E、F 六段亮,G 段灭,对应的段码就是 0x3F(共阳数码管)或者 0xC0(共阴数码管)。全套驱动代码里附了一张标准的 0~9 段码表,同时也支持小数点扩展。

static const uint8_t seg_code[] = { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71 };

数组的下标对应要显示的数字,值就是该数字在共阳数码管上的段码。前十个是 0~9,后面六个是 A、b、C、d、E、F,方便做十六进制显示。小数点一般单独用一个 bit 处理,比如在段码的最高位(0x80)加一个小数点亮起的标志,写入时做一次按位或操作。

这里有一段容易混淆的地方:TM1624 的寄存器位序和段码的位序不一定一致。有的芯片把段码的最高位对应 A 段,有的对应 G 段,两者相差一位甚至更多。如果显示出来的字符看起来“左右翻转”或者“上下颠倒”,多半就是位序映射反了。解决办法是把一个已知的段码(比如 0x06 对应数字 1)写入地址 0x00,然后用万用表量数码管各引脚的电位,逐个确认 A~G 分别落在哪一位上。

3. HK32F030M 工程落地:从引脚配置到点亮第一个数字

3.1 新建工程与最小系统初始化:时钟和 GPIO 必须这样配

HK32F030M 这颗芯片的库函数风格和主流 M0 系列非常接近,但有几个寄存器默认值和其它厂商不同,最明显的是 GPIO 的默认速度和上下拉配置。初始化 TM1624 需要的三个引脚时,必须显式地把模式配成推挽输出,否则 DIO 引脚在输入模式和高阻态之间切换时,会产生毛刺,导致 TM1624 误判数据。

void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUT_PP; GPIO_InitStructure.GPIO_Speed = GPIO_SPEED_HIGH; GPIO_Init(GPIOA, &GPIO_InitStructure); }

这段代码把 PA5、PA6、PA7 都配置成推挽输出,分别对应 STB、CLK、DIO。注意三根线必须一次性配置,不能分开初始化,否则在两条初始化语句之间的瞬间,未初始化的引脚处于浮空输入状态,可能被外部噪声拉到错误电平,导致 TM1624 在开机瞬间误收到一组乱码数据。

参数说明:GPIO_SPEED_HIGH 在 HK32F030M 上对应的翻转速率足够支撑 TM1624 的通信频率,不需要配置成更高速率。实际上如果配置成极高速率,反而可能因为信号边沿过陡,在杜邦线上产生振铃,造成误码。中速或高速即可,不建议用低速模式,低速模式下 GPIO 翻转时间不稳定,影响 CLK 的占空比。

时钟配置方面,HK32F030M 默认使用内部 8MHz 振荡器,经过锁相环倍频到最高 24MHz。TM1624 驱动对系统主频不敏感,只要延时宏的循环次数跟着主频调整就行。这部分比较简单,在 main 函数开头调用 SystemInit 即可,不需要额外配置。

3.2 驱动代码移植:把延时和端口映射改成可裁剪的接口

拿到源码后第一件要做的事,是检查延时函数和端口映射是否和当前板子匹配。源码里用宏定义的方式统一管理引脚和延时,换板子时只需要修改头文件里对应的宏即可,不需要动驱动逻辑。但实际移植时大概率会遇到一个问题:原工程用的引脚和你的板子不一样,或者延时函数是基于 HK32F030M 的 systick 写的,而你想用纯延时循环。

#define TM1624_STB_PORT GPIOA #define TM1624_STB_PIN GPIO_PIN_5 #define TM1624_CLK_PORT GPIOA #define TM1624_CLK_PIN GPIO_PIN_6 #define TM1624_DIO_PORT GPIOA #define TM1624_DIO_PIN GPIO_PIN_7 #define TM1624_DELAY_US(us) delay_us(us) #define TM1624_STB_LOW TM1624_STB_PORT->BSRR = (uint32_t)TM1624_STB_PIN << 16 #define TM1624_STB_HIGH TM1624_STB_PORT->BSRR = (uint32_t)TM1624_STB_PIN

BSRR 寄存器的特性是低 16 位写 1 置高电平,高 16 位写 1 置低电平,这样一个寄存器就能同时实现拉高和拉低两项操作,不需要分别操作 ODR 寄存器。这种写法在 M0 内核上比读改写 BRR 或 ODR 更快,因为 BRR 和 BSRR 都是单周期写操作,不涉及读回。迁移到 STM32F0 系列也能直接编译通过,因为寄存器布局是兼容的。

延时函数这里用的是微秒级颗粒度。如果在调试中发现显示刷新明显变慢,可以把固定的 DELAY_US 调用改成自适应延时,即每次延时前先读取当前系统时钟计数,再计算出准确的循环次数。不过这套代码默认的延时值已经留了很大余量,实际使用时不需要过度优化。

3.3 显示缓冲区设计与刷新逻辑:亮度和消隐的工程化处理

TM1624 内部有 14 个显示寄存器,但并不是所有寄存器都必须用来存段码。工程上常见的做法是维护一个同样长度为 14 的本地缓冲区,每次要更新显示内容时,先把数据写入这个缓冲区,再一次性刷新到 TM1624。这样做的好处是避免多次通信之间出现显示撕裂,也就是第 N 个字节已经更新、第 N+1 个字节还没来得及写时,数码管上会短暂出现一段旧一段新的画面。

static uint8_t display_buf[14]; void TM1624_DisplayString(const uint8_t *str, uint8_t len) { uint8_t i; for (i = 0; i < len && i < 14; i++) { display_buf[i] = str[i]; } for (i = len; i < 14; i++) { display_buf[i] = 0x00; // 多余位熄灭 } TM1624_Commit(); }

刷新函数 Commit 内部要完成一段完整的事务:STB 拉低,依次发送数据模式指令 0x40、地址指令 0xC0,然后连续发送 14 个数据字节,最后 STB 拉高。整个过程需要占用大约 14 乘 8 个时钟周期,加上延时代码,在 24MHz 主频下大约耗时 100~200 微秒。放在主循环里做定时刷新完全没有压力,放在定时器中断里也行,只要中断间隔不小于这个耗时。

void TM1624_Commit(void) { TM1624_STB_LOW; TM1624_DELAY_US(5); TM1624_WriteByte(0x40); // 自动地址增加模式 TM1624_WriteByte(0xC0); // 从地址 0 开始写 for (uint8_t i = 0; i < 14; i++) { TM1624_WriteByte(display_buf[i]); } TM1624_STB_HIGH; TM1624_DELAY_US(5); }

亮度控制方面,TM1624 支持 8 级灰度调节,通过显示控制指令 0x88 加低 3 位实现,0x88 到 0x8F 对应从最暗到最亮的 8 个等级。工程上不建议把亮度调到最高一档,长时间满亮度工作会导致驱动芯片发热,缩短数码管寿命。源码里默认用的是中间档位,也就是 0x88 加 0x03,如果产品在强光环境下看不清,再往上调。

4. 数码管驱动避坑指南:4 个高频翻车点与排查路径

4.1 现象:显示内容乱码、闪烁不定,且无规律

原因分析:乱码的根源大多数是时序不满足 TM1624 的建立时间和保持时间要求,尤其是 GPIO 翻转速度过快或过慢导致 CLK 高电平时间不稳定。另一种可能是 DIO 数据在 CLK 上升沿到来时还没稳定,也就是数据建立时间不足。

排查与解决:先用逻辑分析仪或示波器抓一下 CLK 和 DIO 的波形,确认 CLK 上升沿时 DIO 电平已经稳定。如果波形显示数据翻转和时钟边沿几乎同时发生,说明延时时间太短,把 TM1624_DELAY_US 的时长加大到 5 微秒再测。另外检查一下 STB 拉低和第一路 CLK 之间的间隔,这个间隔至少要保证 5 微秒以上,否则 TM1624 可能会把第一路 CLK 当作起始信号漏掉。

// 修改后的起始延时 #define TM1624_START_DELAY 6 // 单位:微秒,留足裕量

4.2 现象:段码正确但显示的数字不对,比如 0 显示成 8

原因分析:这是段码位序映射不一致导致的。TM1624 显示寄存器的位定义是 A 段对应第 0 位,G 段对应第 6 位,但不同厂家的数码管内部引脚排列不同,可能把 A 段接到了寄存器第 6 位,导致段码表整体错位。还有一种情况是代码里用了共阴段码,但板上实际是共阳数码管,这样所有段全部反转。

排查与解决:先把显示内容固定为 0x06(数字 1),然后逐个测量数码管的段引脚电平,看实际点亮的是哪一段。如果点亮的不是 B、C 两段,而是别的段,说明位序映射有问题。解决办法不是改段码表,而是在写寄存器时做位反转映射,这样才能在不改动层叠模块代码的前提下适配不同数码管。

4.3 现象:亮度不可调或调高后显示异常

原因分析:显示控制指令的发送时机不对。TM1624 要求显示控制指令单独占用一次完整通信事务,不能在写数据的过程中穿插发送。如果代码里把亮度设置放在了数据字节的中间,TM1624 会把亮度指令当成显示数据写入当前地址,导致亮度没有改变,同时还污染了显示缓冲区。

排查与解决:检查代码中亮度和数据的写入逻辑,必须确保两者分开。先发送显示控制指令完成亮度设置,再进入数据写入流程。如果发现亮度只能两档切换,说明低 3 位比特在传递过程中被丢弃,检查 WriteByte 函数是否完整发送了 8 个 bit,个别精简代码会只发 5 位就提前结束了循环。

4.4 现象:程序偶发死机,复位后恢复正常

原因分析:TM1624 通信过程中如果 STB 引脚受到干扰提前拉高,TM1624 内部状态机会进入未知状态,后续所有指令都会被忽略,同时 MCU 侧因为等待应答超时,陷入死循环。这类问题在电快速瞬变脉冲群测试中尤其常见,因为 STB 线对噪声敏感。

排查与解决:在 STB、CLK、DIO 三根线上各加一个 100 欧姆串联电阻,并各接一个 10nF 电容对地滤波,可以有效抑制高频噪声。软件层面,在通信开始前先把 STB 拉高再拉低,主动复位 TM1624 的内部状态机,同时在主循环加一个通信超时保护,超过 10 毫秒没有完成一次事务就强制复位所有引脚重新初始化。从那以后我每次移植 TM1624 驱动都会强制检查这三根线的滤波电容是否焊接到位,几乎成了肌肉记忆。

5. 进阶验证手法:用逻辑分析仪抓完整时序,一次调通不再盲试

驱动代码写完后,很多人直接上电看现象,数码管亮了对就完事了。但实际产品开发里,现象正确不代表时序达标,可能刚好工作在那个临界状态下,温度一变或者电压稍有波动,显示就崩了。建议大家养成一个习惯:连接逻辑分析仪抓一次完整的通信波形,用数据说话。

逻辑分析仪的采样率不需要很高,4MHz 就够用了。把三根线分别接到分析仪的通道 0、1、2,触发模式设置为 STB 下降沿触发,抓一段包含完整事务的波形。重点看三个参数:CLK 高电平时间是否大于 200ns、最后一个数据位到 STB 拉高之间的时间是否足够、以及 DIO 数据在 CLK 上升沿前是否稳定了至少一个采样点。

# 用 Python 解析逻辑分析仪导出的 CSV 波形数据 # 检查每个上升沿处的 DIO 电平是否稳定 import csv clk_last = 0 rising_edges = [] with open('tm1624_wave.csv', 'r') as f: reader = csv.DictReader(f) for row in reader: clk = int(row['CLK']) if clk == 1 and clk_last == 0: rising_edges.append((row['TIME'], row['DIO'])) clk_last = clk for t, dio in rising_edges[:8]: print(f"上升沿时刻 {t}us,DIO 电平 {dio}")

上面这段 Python 脚本从逻辑分析仪导出的 CSV 里提取所有 CLK 上升沿对应的 DIO 电平,用来快速验证写入的第一个字节是不是预期的值。跑完脚本后,把打印出来的电平和手动算出的段码做对比,如果每一位都一致,说明时序和数据链路是通的。这样能定位到底层驱动,不用在显示异常时拿万用表一根根量段引脚。

多次实践下来,这套验证流程确实能避免不少玄学问题。有一次项目在低温环境测试时显示偶发跳变,我抓完波形才发现 CLK 高电平时间在 -10 度时被拉长了近一倍,原因是代码里用了循环计数的软延时,低温下指令执行变慢。换成定时器硬延时后问题消失。从那以后我每次改驱动代码都会把时序抓下来存档,作为后续问题的对照基线,希望你也能少走这些弯路。

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

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

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

立即咨询