资讯动态

HK32F030M驱动TM1624数码管:GPIO模拟时序与避坑指南

发布时间:2026/10/19 13:08:00 来源:尧图企业网站定制
简介这份源码资源面向嵌入式开发初学者与需要快速搭建数码管显示方案的工程师提供基于HK32F030M微控制器驱动TM1624数码管芯片的完整工程代码。HK32F030M基于ARM Cortex-M0内核具备SPI、GPIO等外设TM1624则集成译码、驱动与动态扫描功能二者通过SPI接口通信。代码涵盖SPI初始化配置、数据编码转换、动态扫描刷新、亮度与消隐控制以及通信错误处理等关键环节可直接用于工业控制、智能家居、仪器仪表等显示场景。资源包共380个文件约7.3MB包含94个h头文件、62个c源文件以及Keil工程文件、编译中间产物、hex烧录文件与说明文档目录结构完整便于对照学习与二次开发。目前已有484人学习下载适合希望掌握MCU与数码管驱动技术的读者参考借鉴。1. 从一块“点不亮”的板子说起HK32F030M 驱动 TM1624 到底难在哪如果你手头正好有一块 HK32F030M 的最小系统板又碰上一块 TM1624 数码管驱动模块大概率会遇到一个很反直觉的现象代码编译通过、下载成功、引脚也接对了但数码管就是全黑或者只亮一位、亮度忽明忽暗。这不是玄学而是 TM1624 这类“类 I2C 两线串行接口”芯片的时序容错窗口比标准 I2C 窄得多而 HK32F030M 在默认时钟配置下GPIO 翻转速度、延时精度和中断响应都会直接影响通信成败。这份源码包解决的正是这个落地问题——它把 HK32F030M 的底层 GPIO 模拟时序、TM1624 的命令字封装、显存映射和亮度调节都写成了可直接调用的驱动层适合正在做小家电面板、仪器仪表显示或低成本数码管方案的嵌入式从业者。你不需要从零啃 TM1624 手册的时序图但需要知道哪些参数不能照抄、哪些延时必须按主频重算。2. 先搞懂 TM1624 的通信模型为什么它不是标准 I2C2.1 两线接口的电气本质与命令结构TM1624 的 SCL 和 SDA 是两根普通 GPIO但它并不是标准 I2C 协议。标准 I2C 有 7 位地址、ACK/NACK 机制和重复起始条件而 TM1624 用的是“起始条件 命令字节 数据字节 停止条件”的简化帧格式。命令字节的高两位决定操作类型0b01是写显存0b10是写系统设置亮度、显示开关0b11是读按键。这意味着你不能直接拿硬件 I2C 外设去驱动它必须用 GPIO 模拟时序。源码包里最核心的一个文件是tm1624_gpio.c它把起始、停止、字节写入和 ACK 等待都拆成了独立函数。我一般会先看TM1624_Start()和TM1624_Stop()的实现因为这两个函数的延时参数决定了整个通信的稳定性。常见做法是SCL 高电平期间 SDA 从高拉低作为起始SCL 高电平期间 SDA 从低拉高作为停止。但 TM1624 对建立时间和保持时间的要求是微秒级HK32F030M 在 48MHz 主频下一个__NOP()大约 20ns所以你需要根据实际主频算出一个TM1624_DELAY()的循环次数。/* tm1624_gpio.c 中的起始条件实现片段 */ void TM1624_Start(void) { TM1624_SDA_OUT(); /* 切换 SDA 为输出模式 */ TM1624_SDA_H(); /* SDA 拉高 */ TM1624_SCL_H(); /* SCL 拉高 */ TM1624_DELAY(); /* 延时约 5us保证建立时间 */ TM1624_SDA_L(); /* SCL 高期间 SDA 拉低 起始 */ TM1624_DELAY(); TM1624_SCL_L(); /* 钳住 SCL准备发数据 */ TM1624_DELAY(); }这段代码的逻辑说明TM1624_SDA_OUT()把 GPIO 切到推挽输出因为读 ACK 时需要切回输入TM1624_DELAY()的宏定义在tm1624_config.h里默认是按 48MHz 调的如果你把主频降到 8MHz这个延时必须乘以 6否则时序会太快TM1624 根本来不及采样。参数说明TM1624_DELAY()不是随便写的for循环源码里用的是__NOP()内联汇编避免编译器优化把延时循环删掉。2.2 显存映射与数码管段码的对应关系TM1624 的显存是 14 字节对应 14 个字节的显示寄存器。每个字节的 8 个 bit 对应一个数码管的 8 个段包括小数点。源码包里有一个tm1624_seg_map.h里面用宏定义把“第几位数码管、哪一段”映射到了具体的字节地址和 bit 位。这个映射表是硬件布线决定的不能随便改。我见过不少人直接抄网上的 TM1624 驱动结果数码管显示乱码原因就是显存映射和实际 PCB 走线不一致。这份源码的做法是在tm1624_config.h里留了一个TM1624_SEG_MAP数组你可以根据自己板子的实际连接顺序重新排列。比如你的数码管 a 段接在 TM1624 的 SEG0但源码默认是 SEG7那就把数组对应位置改掉。/* tm1624_seg_map.h 中的段码映射示例 */ const uint8_t TM1624_SEG_MAP[8] { 0x01, /* a 段 - bit0 */ 0x02, /* b 段 - bit1 */ 0x04, /* c 段 - bit2 */ 0x08, /* d 段 - bit3 */ 0x10, /* e 段 - bit4 */ 0x20, /* f 段 - bit5 */ 0x40, /* g 段 - bit6 */ 0x80 /* dp 段 - bit7 */ };逻辑说明这个数组定义了“段码字节的每一位对应哪个物理段”。如果你用的是共阴数码管段码为 1 表示点亮共阳则相反需要在TM1624_WriteSeg()里取反。参数说明数组长度固定为 8对应一个字节的 8 个 bit不要改成 7否则小数点会丢。2.3 命令字封装亮度调节与显示开关TM1624 的系统设置命令是0b10开头后面跟 3 位亮度0~7和 1 位显示开关。源码里把这两个操作封装成了TM1624_SetBrightness(uint8_t level)和TM1624_DisplayOn(void)。亮度等级不是线性的0 最暗、7 最亮但实际电流还取决于你串的限流电阻。我一般会在初始化时先设亮度为 3再根据实测电流调整。/* tm1624_cmd.c 中的亮度设置 */ void TM1624_SetBrightness(uint8_t level) { uint8_t cmd 0x80 | (level 0x07) | 0x08; /* 0b10 亮度 显示开 */ TM1624_Start(); TM1624_WriteByte(cmd); TM1624_WaitAck(); TM1624_Stop(); }逻辑说明0x80是命令前缀0b10左移 6 位的结果level 0x07取低 3 位0x08是显示开标志。参数说明level范围 0~7超过 7 会被截断。如果你发现设了亮度但没变化先检查TM1624_DisplayOn()是否被调用过因为显示关的时候亮度设置不生效。3. 把源码跑起来HK32F030M 工程配置与移植步骤3.1 工程目录结构与文件依赖这份源码包不是一个大杂烩它按驱动层、硬件抽象层和应用层分了三层。根目录下有tm1624_driver/、hk32f030m_hal/和app_demo/三个文件夹。tm1624_driver/里是纯逻辑代码不依赖任何特定 MCU 的库hk32f030m_hal/里是 GPIO 初始化和延时函数app_demo/里是一个每秒刷新一次数码管的示例。移植到你的工程时只需要把tm1624_driver/和hk32f030m_hal/下的.c和.h文件加入编译然后在你的main.c里调用TM1624_Init()和TM1624_Display()。注意tm1624_config.h里有两个宏必须改——TM1624_SCL_PIN和TM1624_SDA_PIN它们对应你实际用的 GPIO 端口和引脚号。/* tm1624_config.h 中需要根据硬件修改的部分 */ #define TM1624_SCL_PORT GPIOA #define TM1624_SCL_PIN GPIO_PIN_1 #define TM1624_SDA_PORT GPIOA #define TM1624_SDA_PIN GPIO_PIN_2 #define TM1624_DELAY() { for(uint8_t i0;i10;i) __NOP(); }逻辑说明TM1624_DELAY()里的循环次数 10 是按 48MHz 主频、约 5us 延时估算的。如果你用的是内部 8MHz RC 振荡器这个值要改成 60 左右。参数说明GPIO_PIN_1和GPIO_PIN_2是 HK32F030M 标准库的宏如果你用的是寄存器操作直接写0x02和0x04也行。3.2 GPIO 初始化与推挽输出配置HK32F030M 的 GPIO 配置和 STM32F0 系列很像但有个坑它的 GPIO 速度寄存器默认是低速度如果你不改成高速SCL 和 SDA 的上升沿会变缓导致 TM1624 采样出错。源码里的TM1624_GPIO_Init()做了三件事使能 GPIO 时钟、配置为推挽输出、设置速度为高速。/* hk32f030m_hal/tm1624_hal.c 中的 GPIO 初始化 */ void TM1624_GPIO_Init(void) { GPIO_InitTypeDef gpio {0}; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); gpio.GPIO_Pin TM1624_SCL_PIN | TM1624_SDA_PIN; gpio.GPIO_Mode GPIO_Mode_OUT; gpio.GPIO_OType GPIO_OType_PP; gpio.GPIO_Speed GPIO_Speed_Level_3; /* 高速 */ gpio.GPIO_PuPd GPIO_PuPd_UP; GPIO_Init(TM1624_SCL_PORT, gpio); TM1624_SCL_H(); TM1624_SDA_H(); }逻辑说明GPIO_Speed_Level_3是 HK32F030M 标准库里的最高速度档对应 50MHz 翻转率。GPIO_PuPd_UP是内部上拉因为 TM1624 的 SDA 是开漏输出需要上拉电阻才能读到高电平。参数说明如果你外部已经有 4.7k 上拉电阻内部上拉可以关掉改成GPIO_PuPd_NOPULL否则并联后阻值变小功耗会增加。3.3 主循环刷新与显存更新策略数码管刷新不能太慢否则会闪也不能太快否则 TM1624 的通信还没完成就被下一次刷新打断。源码里的做法是用一个 10ms 的定时器中断置标志位主循环检测到标志位后调用TM1624_Update()把显存数组一次性写进 TM1624。这样刷新率稳定在 100Hz肉眼看不到闪烁。/* app_demo/main.c 中的刷新逻辑 */ uint8_t disp_buf[14] {0}; /* 显存缓冲区 */ void TM1624_Update(void) { TM1624_Start(); TM1624_WriteByte(0x40); /* 写显存命令地址自动递增 */ TM1624_WaitAck(); TM1624_Stop(); TM1624_Start(); TM1624_WriteByte(0xC0); /* 从地址 0 开始写 */ TM1624_WaitAck(); for(uint8_t i0; i14; i) { TM1624_WriteByte(disp_buf[i]); TM1624_WaitAck(); } TM1624_Stop(); }逻辑说明0x40是写显存命令0xC0是起始地址 0。TM1624 支持地址自动递增所以连续写 14 个字节后会自动停到最后一个地址。参数说明disp_buf的长度必须是 14如果你只用了 4 位数码管也要保证数组长度是 14否则写越界会导致通信错位。4. 避坑与排查TM1624 驱动最常见的 5 个翻车现场4.1 现象数码管全黑但用逻辑分析仪能看到波形原因TM1624 的 SDA 线没有上拉电阻或者上拉电阻太大比如 10k 以上导致高电平建立不起来。HK32F030M 的内部上拉只有 40k 左右如果外部没有 4.7k 上拉SDA 在高电平期间会被 TM1624 的开漏输出拉低。解决在 SDA 和 VCC 之间并一个 4.7k 电阻同时把 GPIO 配置里的内部上拉关掉避免并联后阻值变成 4.3k 左右虽然能用但功耗偏大。4.2 现象只有第一位数码管亮其他位全黑原因显存地址写错了。TM1624 的显存是 14 字节但如果你只写了 1 个字节就发停止条件后面的地址不会被更新。源码里的TM1624_Update()是连续写 14 个字节如果你自己改成了只写 4 个那第 5 位以后就不会亮。解决检查TM1624_Update()里的循环次数确保等于 14。如果你只用了 4 位数码管也要把剩下的 10 个字节填 0不能跳过。4.3 现象亮度调到 7 还是很暗或者调到 0 反而最亮原因亮度等级和实际电流不是线性关系而且 TM1624 的亮度控制是 PWM 方式0 最暗、7 最亮。如果你发现反了说明你的数码管是共阳的而源码默认是共阴。共阳数码管的段码需要取反亮度等级也要反过来理解。解决在tm1624_config.h里把TM1624_COMMON_CATHODE改成 0或者在TM1624_WriteSeg()里对段码取反。4.4 现象通信偶尔失败重启后又正常原因HK32F030M 在默认时钟配置下如果主频是 48MHzGPIO 翻转速度很快但TM1624_DELAY()的循环次数是按 48MHz 调的。如果你在初始化时先切到 8MHz 再切回 48MHz延时函数的实际延时就会变。另外如果主循环里有中断打断 GPIO 翻转也会导致时序错乱。解决在TM1624_Start()和TM1624_Stop()前后关中断或者把TM1624_DELAY()改成基于 SysTick 的微秒级延时不依赖主频。4.5 现象数码管显示的数字和预期不符比如 1 显示成 7原因段码映射错了。TM1624 的 SEG0~SEG7 对应数码管的 a~dp但你的 PCB 可能把 SEG0 接在了 g 段上。源码里的TM1624_SEG_MAP数组默认是按 a~dp 顺序排的如果你的板子不是这个顺序就会显示乱码。解决拿万用表测一下数码管每个段对应 TM1624 的哪个 SEG 引脚然后重新排列TM1624_SEG_MAP数组。不要靠猜这是血泪经验。5. 进阶技巧用显存缓冲实现滚动显示与按键读取5.1 显存缓冲的二次封装源码里的disp_buf[14]是直接映射到 TM1624 显存的但如果你想做滚动显示直接改disp_buf会有一帧撕裂。我一般会在应用层再开一个frame_buf[14]先在frame_buf里把要显示的内容排好然后一次性memcpy到disp_buf再调用TM1624_Update()。这样滚动的时候不会看到中间状态。/* 滚动显示示例每 200ms 左移一位 */ void ScrollDisplay(void) { static uint32_t last_tick 0; if (HAL_GetTick() - last_tick 200) return; last_tick HAL_GetTick(); for(uint8_t i0; i13; i) { frame_buf[i] frame_buf[i1]; } frame_buf[13] 0x00; /* 末尾补空 */ memcpy(disp_buf, frame_buf, 14); TM1624_Update(); }逻辑说明frame_buf是应用层缓冲disp_buf是驱动层缓冲。每次滚动先移动frame_buf再整体拷贝保证 TM1624 收到的是一帧完整的数据。参数说明200是滚动速度单位毫秒改小会更快但低于 100ms 会看到拖影。5.2 按键读取与去抖TM1624 支持 10 个按键输入命令是0b11开头。源码里有一个TM1624_ReadKeys()函数返回一个 16 位的按键状态。但 TM1624 的按键读取需要先发命令再读两个字节而且读的时候 SDA 要切回输入模式。我一般会在 10ms 定时器里调用一次连续读到 3 次相同值才认为是有效按键。/* 按键读取与去抖 */ uint16_t TM1624_ReadKeys(void) { uint16_t keys 0; TM1624_Start(); TM1624_WriteByte(0x42); /* 读按键命令 */ TM1624_WaitAck(); TM1624_SDA_IN(); /* 切输入 */ keys TM1624_ReadByte() 8; TM1624_WaitAck(); keys | TM1624_ReadByte(); TM1624_WaitAck(); TM1624_SDA_OUT(); TM1624_Stop(); return keys; }逻辑说明0x42是读按键命令读两个字节后组合成 16 位。TM1624_SDA_IN()把 SDA 切回输入因为 TM1624 要驱动 SDA 线。参数说明读到的按键值需要根据你的 PCB 按键映射表来解析源码里有一个key_map.h可以参考。5.3 用宏定义切换共阴/共阳避免改代码最后分享一个我自己的习惯在tm1624_config.h里用宏定义区分共阴和共阳而不是在代码里写死。这样同一份驱动可以同时支持两种数码管只需要改一个宏。/* tm1624_config.h 中的共阴/共阳切换 */ #define TM1624_COMMON_CATHODE 1 /* 1共阴0共阳 */ #if TM1624_COMMON_CATHODE #define TM1624_SEG_ON 1 #define TM1624_SEG_OFF 0 #else #define TM1624_SEG_ON 0 #define TM1624_SEG_OFF 1 #endif逻辑说明TM1624_SEG_ON和TM1624_SEG_OFF在写段码时使用这样上层应用不需要关心硬件是共阴还是共阳。参数说明改这个宏之后TM1624_WriteSeg()里的段码会自动取反不需要手动改数组。从那以后我每次拿到新的数码管板子都强制先跑一遍TM1624_ReadKeys()和TM1624_SetBrightness(7)确认通信和亮度都正常再开始写业务逻辑。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑