
简介这份源码资源面向嵌入式开发初学者与需要快速搭建数码管显示方案的工程师提供基于HK32F030M微控制器驱动TM1624数码管的完整工程代码。HK32F030M基于ARM Cortex-M0内核具备SPI、GPIO等外设TM1624则集成译码、驱动与动态扫描功能二者通过SPI通信实现多位数码管显示。代码涵盖SPI接口初始化、数据编码、动态扫描切换、亮度与消隐控制及通信错误处理等关键环节可直接用于工业控制、智能家居、仪器仪表等场景的显示模块开发。资源包共380个文件约7.3MB以94个h头文件、62个c源文件为核心辅以Keil工程文件、编译中间产物、hex烧录文件及说明文档工程结构完整便于直接编译与二次修改。目前已有484人学习下载适合希望理解SPI时序、掌握数码管驱动逻辑并快速移植到实际项目的开发者参考。1. 从一块 TM1624 驱动板说起为什么这个 HK32F030M 驱动代码值得拆前阵子帮朋友调一块 6 位 TM1624 数码管驱动板主控用的是 HK32F030M现象很典型上电后数码管全亮一下然后熄灭按键无响应示波器抓 SCL 和 SDA 只有零星毛刺。翻遍手头资料才发现问题不在硬件而在驱动代码里 GPIO 模式配置和 TM1624 的时序延时对不上。这类“代码看着对、跑起来就是不动”的场景在国产 MCU 加专用驱动芯片的组合里太常见了。这份 HK32F030M-TM1624 数码管驱动代码就是围绕这个组合整理出来的一套可直接编译、可二次修改的工程源码覆盖 GPIO 模拟时序、TM1624 命令字封装、显存映射和按键扫描几个核心部分。适合正在用 HK32F030M 做小家电面板、仪器仪表显示、工控按键板的开发者也适合刚接触国产 MCU 想找一个完整外设驱动范例的新手。它不依赖复杂中间件纯裸机 C 代码拿到手改几个宏定义就能跑。2. 工程结构与技术选型裸机 C 加 GPIO 模拟时序的取舍2.1 目录组织与文件职责这份代码没有上 RTOS也没有用厂商的图形库走的是最朴素的裸机分层硬件抽象层、驱动层、应用层各管一摊。目录结构大致如下不同版本可能略有出入但核心文件基本固定。HK32F030M_TM1624/ ├── CMSIS/ # 内核头文件与启动文件 ├── Device/ # HK32F030M 外设寄存器定义 ├── Driver/ │ ├── hk32f030m_gpio.c # GPIO 初始化与位操作封装 │ ├── hk32f030m_delay.c # 微秒级延时时序基础 │ └── tm1624.c # TM1624 命令、显存、刷新逻辑 ├── App/ │ ├── main.c # 主循环、按键扫描、显示更新 │ └── display.c # 段码表与业务显示映射 └── Project/ # 工程文件与编译配置tm1624.c是核心里面把 TM1624 的启动命令、地址命令、数据命令都封装成独立函数上层只调TM1624_Display和TM1624_ScanKey。hk32f030m_delay.c提供delay_us因为 TM1624 是两线串行接口时序靠软件延时撑延时不准后面全崩。display.c放段码表和业务映射改显示内容基本只动这个文件。2.2 为什么选 GPIO 模拟而不是硬件外设HK32F030M 本身有 SPI但 TM1624 的时序并不是标准 SPI它在命令字节之间需要特定的建立和保持时间时钟极性也不完全匹配。用硬件 SPI 反而要额外处理片选和命令间隔代码更绕。常见做法是直接用两个普通 GPIO 模拟 SCL 和 SDA配合delay_us控制节奏逻辑直观移植到别的 MCU 也只需改 GPIO 宏。/* tm1624.h 中的引脚宏定义移植时只改这里 */ #define TM1624_SCL_PORT GPIOC #define TM1624_SCL_PIN GPIO_PIN_4 #define TM1624_SDA_PORT GPIOC #define TM1624_SDA_PIN GPIO_PIN_5 #define TM1624_SCL_H() GPIO_SetBits(TM1624_SCL_PORT, TM1624_SCL_PIN) #define TM1624_SCL_L() GPIO_ResetBits(TM1624_SCL_PORT, TM1624_SCL_PIN) #define TM1624_SDA_H() GPIO_SetBits(TM1624_SDA_PORT, TM1624_SDA_PIN) #define TM1624_SDA_L() GPIO_ResetBits(TM1624_SDA_PORT, TM1624_SDA_PIN) #define TM1624_SDA_READ() GPIO_ReadInputDataBit(TM1624_SDA_PORT, TM1624_SDA_PIN)这里把端口和引脚抽成宏换板子时只改这六行。注意 SDA 在读取按键时要切换成输入模式所以TM1624_SDA_READ之前必须先调GPIO_Init把 SDA 配成上拉输入读完再切回输出。这个切换如果漏了按键永远读不到是新手最容易翻车的地方。2.3 启动文件与时钟配置HK32F030M 默认跑内部 8MHz RCTM1624 对时钟频率不敏感但delay_us的循环次数依赖系统时钟。工程里SystemInit一般把时钟配到 48MHz 或保持 8MHz两者延时系数不同。如果直接拿别人的延时函数不改系数SCL 频率会差好几倍TM1624 可能完全不响应。建议在main开头打印或翻转一个 IO 测一下实际延时确认delay_us(1)真的接近 1 微秒再往下调。3. 核心驱动拆解TM1624 命令字、显存与刷新链路3.1 TM1624 命令字封装TM1624 的通信靠三条命令启动命令、地址命令、数据命令。启动命令告诉芯片准备接收地址命令指定从哪个显存地址开始写数据命令决定写入模式和地址是否自动递增。代码里通常这样封装/* 向 TM1624 写入一个字节LSB 在前 */ static void TM1624_WriteByte(uint8_t data) { uint8_t i; for (i 0; i 8; i) { TM1624_SCL_L(); delay_us(2); if (data 0x01) TM1624_SDA_H(); else TM1624_SDA_L(); delay_us(2); TM1624_SCL_H(); /* 上升沿采样 */ delay_us(2); data 1; } } /* 启动命令0x03 为固定值部分批次需 0x02以实测为准 */ void TM1624_Start(void) { TM1624_SDA_H(); TM1624_SCL_H(); delay_us(2); TM1624_SDA_L(); delay_us(2); TM1624_WriteByte(0x03); TM1624_SDA_H(); /* 释放总线 */ delay_us(2); }TM1624_WriteByte里每个 bit 都配了 2 微秒延时这是为了满足 TM1624 对时钟高低电平最小宽度的要求。延时太短芯片采样不到太长刷新率下降但不会出错。TM1624_Start里的 0x03 是启动命令如果换批次芯片发现不响应可以试 0x02这是血泪经验不同封装厂家的 TM1624 启动字偶有差异。3.2 显存映射与段码表TM1624 内部有 14 字节显存对应 6 位 8 段数码管加按键状态。代码里用一个uint8_t disp_buf[14]做影子寄存器所有显示更新先写缓冲区再统一刷新到芯片。段码表放在display.c/* 共阴数码管段码表0-9 加空白和横线 */ const uint8_t seg_code[] { 0x3F, /* 0 */ 0x06, /* 1 */ 0x5B, /* 2 */ 0x4F, /* 3 */ 0x66, /* 4 */ 0x6D, /* 5 */ 0x7D, /* 6 */ 0x07, /* 7 */ 0x7F, /* 8 */ 0x6F, /* 9 */ 0x00, /* 空白 */ 0x40 /* 横线 */ }; /* 把数字映射到显存pos 为 0-5 位 */ void Display_SetDigit(uint8_t pos, uint8_t num) { if (pos 5) return; disp_buf[pos * 2] seg_code[num]; }disp_buf的索引是pos * 2因为 TM1624 每个数码管占两个字节低位在前。如果显示乱码或错位先检查这个乘 2 有没有写错再检查段码表是共阴还是共阳。共阳段码要取反这个在换数码管型号时必查。3.3 刷新链路与按键扫描刷新函数把disp_buf按地址命令写入 TM1624按键扫描则读回按键状态字节。两者共用 SDA 线所以刷新和扫描不能同时进行主循环里要错开。/* 刷新全部显存到 TM1624 */ void TM1624_Refresh(void) { uint8_t i; TM1624_Start(); TM1624_WriteByte(0x40); /* 数据命令写显存地址自动递增 */ TM1624_SDA_H(); TM1624_Start(); TM1624_WriteByte(0xC0); /* 地址命令从 0x00 开始 */ for (i 0; i 14; i) { TM1624_WriteByte(disp_buf[i]); } TM1624_SDA_H(); TM1624_Start(); TM1624_WriteByte(0x8F); /* 显示控制开显示最大亮度 */ TM1624_SDA_H(); } /* 读按键返回按键码0 表示无按键 */ uint8_t TM1624_ScanKey(void) { uint8_t key 0; GPIO_InitTypeDef gpio; /* SDA 切输入上拉 */ gpio.GPIO_Pin TM1624_SDA_PIN; gpio.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(TM1624_SDA_PORT, gpio); TM1624_Start(); TM1624_WriteByte(0x42); /* 读按键命令 */ delay_us(2); key TM1624_ReadByte(); /* 读一个字节位对应按键 */ /* SDA 切回输出 */ gpio.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(TM1624_SDA_PORT, gpio); return key; }TM1624_Refresh里连续发了三次启动命令分别对应数据命令、地址命令、显示控制这是 TM1624 的标准流程少一步显示就不对。TM1624_ScanKey里 SDA 模式切换是关键读之前切输入读之后切回输出否则下一次刷新写不出去。按键码的位定义要看具体 PCB 走线代码里返回的是原始字节业务层再映射成具体按键。4. 编译与烧录从零跑通一块板子的完整步骤4.1 工具链准备与工程打开这份代码一般配 Keil MDK 或 IAR 工程也有 GCC Makefile 版本。以 Keil 为例需要先装 HK32F030M 的器件支持包否则打开工程会提示找不到 device。装完包后打开Project目录下的.uvprojx检查 Options 里 Target 选的是不是 HK32F030M晶振频率和实际板子是否一致。如果用的是内部 RCSystemInit 里要确认时钟源配置。# 如果用 GCC 工具链编译命令大致如下 make clean make all # 生成的 hex 或 bin 在 build/ 目录下编译前重点看两个地方一是tm1624.h里的引脚宏和实际 PCB 是否对应二是hk32f030m_delay.c里的延时系数是否匹配当前主频。这两处不对编译能过但跑不起来。4.2 烧录与上电检查烧录用 SWD 接口常见烧录器都支持。烧录后先别急着看数码管用万用表量一下 TM1624 的 VDD 和 VSS确认供电正常。然后上电正常现象是数码管先全亮再按disp_buf初始值显示。如果全亮后熄灭说明启动命令发了但数据没写进去重点查TM1624_Refresh里的三次启动命令是否完整。如果完全不亮查 GPIO 初始化有没有把 SCL 和 SDA 配成推挽输出。提示第一次上电建议在main里先只调TM1624_Refresh把disp_buf填成固定值排除按键扫描干扰确认显示链路通了再加按键逻辑。4.3 验证显示与按键显示验证用Display_SetDigit逐位写 0-9看每一位是否对应。如果某一位不亮查该位对应的显存字节和段码表。按键验证在main循环里打印TM1624_ScanKey的返回值按不同键看返回值变化。如果按键无反应先确认 SDA 模式切换有没有执行再量按键对地电阻排除硬件问题。/* main 循环里的典型验证代码 */ while (1) { Display_SetDigit(0, 1); Display_SetDigit(1, 2); Display_SetDigit(2, 3); TM1624_Refresh(); delay_ms(500); uint8_t key TM1624_ScanKey(); if (key ! 0) { /* 按键有反应可以在这里翻转某个显示位做指示 */ Display_SetDigit(5, 8); } }这段代码跑通说明显示和按键两条链路都正常可以开始接业务逻辑。5. 避坑与常见问题时序、供电和模式切换的排查记录5.1 数码管全亮后熄灭按键无响应现象上电瞬间数码管全亮随后熄灭按任何键无反应。原因通常是TM1624_Refresh里三次启动命令只发了第一次或者TM1624_Start里的启动字不对。解决用示波器抓 SCL 和 SDA确认每次TM1624_Start后都有完整的命令字节。如果启动字 0x03 不响应换 0x02 试。另外检查disp_buf是否在刷新前被清零。5.2 显示乱码或段位错位现象数字显示成乱码或者第 1 位显示到第 2 位。原因一般是段码表共阴共阳搞反或者disp_buf索引没乘 2。解决先确认数码管是共阴还是共阳共阳段码取反。再检查Display_SetDigit里pos * 2有没有写错。如果只有某一位错查该位对应的显存地址是否被按键状态字节覆盖。5.3 按键读不到或时好时坏现象按键偶尔能读到时读不到。原因多半是 SDA 模式切换不干净或者读按键时没有先发启动命令。解决在TM1624_ScanKey里确认 SDA 切输入上拉后延时足够读完后立即切回输出。如果还是不稳在TM1624_ReadByte的每个 bit 后加 2 微秒延时给芯片反应时间。5.4 换 MCU 后延时不准导致通信失败现象代码移植到同系列但不同主频的 MCU 后TM1624 完全不响应。原因delay_us的循环次数是按原主频算的主频变了延时就不对。解决重新校准delay_us用 IO 翻转加示波器测实际延时调整循环系数。或者改用 SysTick 做微秒延时不依赖循环次数。5.5 供电不足导致显示闪烁现象数码管亮度不均或整体闪烁。原因TM1624 驱动 6 位数码管时电流较大如果 VDD 走线细或滤波电容小电压会被拉低。解决在 TM1624 的 VDD 和 VSS 之间并一个 100nF 加一个 10uF 电容尽量靠近芯片引脚。如果还闪查限流电阻是否合适必要时降低显示亮度。6. 二次开发把驱动接进自己的业务逻辑拿到这份代码后最常见的二次开发是替换显示内容和增加按键业务。显示部分display.c里的seg_code和Display_SetDigit是入口如果要显示小数点或特殊符号在段码表里加对应编码再写一个Display_SetDot函数操作显存的最高位。按键部分TM1624_ScanKey返回原始字节业务层用一个映射表把位转成键值再加消抖。/* 按键映射与消抖示例 */ #define KEY_NONE 0 #define KEY_UP 1 #define KEY_DOWN 2 static uint8_t key_map(uint8_t raw) { if (raw 0x01) return KEY_UP; if (raw 0x02) return KEY_DOWN; return KEY_NONE; } /* 在主循环里每 10ms 调一次 */ uint8_t key_scan_debounce(void) { static uint8_t last 0, count 0; uint8_t cur key_map(TM1624_ScanKey()); if (cur last) { if (count 3) count; } else { count 0; last cur; } if (count 3) return cur; return KEY_NONE; }消抖用连续三次相同读数确认简单有效。如果要加长按和双击在key_scan_debounce基础上加计时器状态机别在TM1624_ScanKey里做那里只管读原始值。改造时我一般会先建一个app_display.c和app_key.c把业务逻辑和驱动层隔开这样换显示芯片或换按键芯片时只改驱动层业务代码不动。另外disp_buf的刷新频率别太高主循环里 20ms 刷一次足够刷太快反而影响按键读取。从那以后我每次改显示驱动都强制先用固定值验证刷新链路再接业务数据这个习惯帮我省了不少返工时间。希望帮到你。本文还有配套的精品资源点击获取