资讯动态

HK32F030M 驱动 TM1624 四位数码管显示与 LED 控制实战

发布时间:2026/10/15 16:15:46 来源:尧图企业网站定制
简介这份资源面向嵌入式开发初学者与需要快速验证显示方案的工程师聚焦HK32F030M微控制器通过SPI接口驱动TM1624芯片、点亮四位数码管的完整工程。它解决的是从零搭建数码管显示驱动时SPI时序配置、段码与位选控制、动态扫描与功耗管理缺乏可参考实例的问题适合作为MCU外设编程与数字电路结合的练手项目。压缩包共159个文件约4.61MB以42个h头文件、25个c源文件为核心涵盖SPI、定时器、USART、I2C、ADC等外设驱动另有23个d依赖文件、23个o目标文件、22个crf编译中间文件以及uvprojx、uvoptx工程配置、pdf数据手册与hex烧录文件构成可直接编译的Keil工程。目前已有338人学习下载。读者可从中获得初始化SPI、编写TM1624显示函数、定时中断刷新显示内容的具体实现并借助原理图与手册理解引脚连接和芯片参数掌握动态扫描降低功耗的排错思路。1. 从一颗 HK32F030M 说起为什么四位数码管驱动值得单独写一篇手里拿到一块 HK32F030M 最小系统板再配一个 TM1624 驱动的四位数码管模块很多人第一反应是「这不就是点亮几个数字的事」。真上手才发现TM1624 不是普通数码管驱动它自带键盘扫描和显示寄存器走的是类 I2C 的两线协议时序和标准 I2C 有微妙差别而 HK32F030M 这颗国产 Cortex-M0 芯片GPIO 翻转速度和标准库习惯又和常见的 STM32 不完全一样。标题里的「HK32F030M-TM1624 四位数码管显示驱动 LED」本质是讲清楚一颗低成本 MCU 如何用两根线稳定驱动一颗带显示缓存的驱动芯片把四位数字和若干 LED 指示一起管起来。适合正在做小家电面板、仪器仪表显示、工控按键板的嵌入式从业者也适合想从「直接 IO 扫数码管」升级到「专用驱动芯片」的开发者。下面按选型、协议、代码、避坑、进阶的顺序讲透。2. TM1624 与 HK32F030M 的搭配逻辑为什么不用 IO 直驱2.1 直驱四位数码管的三个硬伤四位数码管如果直接用 MCU 的 IO 做动态扫描至少需要 4 位段选加 4 位位选共 8 根线如果还要带小数点、冒号、单位指示灯线数继续涨。HK32F030M 的引脚本来就不宽裕全砸在显示上不划算。更麻烦的是动态扫描要靠定时器中断不断刷新一旦主循环里有阻塞操作显示就会闪或者暗。第三个硬伤是亮度一致性不同批次的数码管正向压降有差异限流电阻要一个个调量产时很头疼。TM1624 这类专用驱动把这些问题接过去了。它内部有显示 RAMMCU 只要把要显示的内容写进去芯片自己负责扫描和亮度控制。MCU 侧只需要两根信号线一根时钟一根数据再加一根可能用到的使能或片选。对 HK32F030M 来说省下来的 IO 可以去做按键、传感器、通信整体 BOM 也更可控。2.2 TM1624 的显示 RAM 映射与地址规划TM1624 的显示缓存按「位」和「段」组织常见四位数码管对应四个 GRID每个 GRID 下有若干 SEG。写显示时不是直接写「数字 3」而是写某个 GRID 下哪些 SEG 亮。所以第一步是建立一张段码表把 0 到 9、A 到 F、以及小数点、负号这些字符对应的字节值算出来。下面这张表是我在 HK32F030M 上实际用的段码映射假设 SEG 顺序是 a b c d e f g dp共阳极数码管接法位选由 TM1624 内部完成字符段码二进制十六进制00011 11110x3F10000 01100x0620101 10110x5B30100 11110x4F40110 01100x6650110 11010x6D60111 11010x7D70000 01110x0780111 11110x7F90110 11110x6F小数点1000 00000x80这张表不是死的如果你的硬件把 SEG 顺序接反了或者用的是共阴极数码管整张表都要重新算。我一般会在代码里把段码表定义成const uint8_t seg_code[10]再单独处理小数点和负号这样改起来只动一个地方。2.3 两线协议的时序特征和标准 I2C 差在哪TM1624 的两线协议看起来像 I2C但不要直接拿硬件 I2C 去套。它的数据在时钟上升沿被采样但起始条件和停止条件的定义、以及 ACK 的处理和标准 I2C 不完全一致。最稳妥的做法是用 GPIO 模拟时序把延时控制在微秒级。HK32F030M 主频常见配置在 48MHz 左右一条__NOP()大约几十纳秒所以几个 NOP 就能凑出几百纳秒的延时。下面这段是起始和停止条件的模拟代码实际项目里我会把它放在tm1624.c里// HK32F030M GPIO 模拟 TM1624 起始条件 // SCL 和 SDA 分别接在 GPIOB 的 PIN6 和 PIN7 void TM1624_Start(void) { SDA_HIGH(); // 先拉高数据线 SCL_HIGH(); // 时钟线也拉高 delay_us(2); // 保持一段时间具体值看芯片手册 SDA_LOW(); // 数据线在时钟高电平期间拉低形成起始 delay_us(2); SCL_LOW(); // 时钟拉低准备发送第一位 delay_us(1); } void TM1624_Stop(void) { SDA_LOW(); // 数据线先拉低 SCL_HIGH(); // 时钟拉高 delay_us(2); SDA_HIGH(); // 数据线在时钟高电平期间拉高形成停止 delay_us(2); }逻辑说明起始条件的关键是「时钟高电平期间数据线从高变低」停止条件是「时钟高电平期间数据线从低变高」。这两个动作和标准 I2C 一致但 TM1624 对保持时间的要求更宽松2 微秒的延时在 48MHz 的 HK32F030M 上足够稳。参数说明delay_us可以用简单的循环实现也可以用定时器但不要用系统滴答中断因为中断延迟会破坏时序。如果发现通信不稳定先把延时加大到 5 微秒试再逐步往下压。3. 在 HK32F030M 上把 TM1624 跑起来从 GPIO 初始化到显示刷新3.1 GPIO 初始化与方向切换的细节HK32F030M 的 GPIO 配置和常见 STM32 类似但寄存器名和库函数有差异。我一般直接用寄存器操作减少对库的依赖。SCL 和 SDA 都要配置成开漏输出并且使能内部上拉或者外部接 4.7k 上拉电阻。如果只做单向写显示SDA 可以一直输出但如果要读 TM1624 的按键扫描结果SDA 需要在读的时候切成输入。// HK32F030M GPIO 初始化SCLPB6SDAPB7 void TM1624_GPIO_Init(void) { // 使能 GPIOB 时钟具体寄存器名参考芯片头文件 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); GPIO_InitTypeDef gpio; gpio.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; gpio.GPIO_Mode GPIO_Mode_OUT; // 开漏输出模式 gpio.GPIO_OType GPIO_OType_OD; // 开漏 gpio.GPIO_PuPd GPIO_PuPd_UP; // 内部上拉 gpio.GPIO_Speed GPIO_Speed_Level_2; GPIO_Init(GPIOB, gpio); SDA_HIGH(); SCL_HIGH(); }逻辑说明开漏加内部上拉是最省外围器件的做法但如果总线走线较长或者旁边有干扰源内部上拉可能不够需要外部加 4.7k 到 10k 的上拉。参数说明GPIO_Speed_Level_2对应中等翻转速度对 TM1624 这种几百 kHz 的时钟足够速度设太高反而容易引入振铃。3.2 写一个字节与命令字的发送流程TM1624 的通信以「命令字 数据」的形式进行。命令字包括设置显示模式、设置地址、设置亮度等。每次写操作都是先发起始发命令字等 ACK再发数据最后停止。下面是一个写字节的通用函数// 向 TM1624 写一个字节低位先发 void TM1624_WriteByte(uint8_t data) { for (uint8_t i 0; i 8; i) { SCL_LOW(); delay_us(1); if (data 0x01) { SDA_HIGH(); } else { SDA_LOW(); } delay_us(1); SCL_HIGH(); // 上升沿采样 delay_us(2); data 1; } SCL_LOW(); delay_us(1); // 这里可以读 ACK但很多简单应用直接跳过 }逻辑说明TM1624 是低位先发所以每次取data 0x01后右移。时钟上升沿采样所以数据要在 SCL 拉高之前稳定。参数说明delay_us(1)和delay_us(2)是经验值如果通信失败先把所有延时翻倍确认能通后再优化。ACK 读取在只写显示的应用里可以省略但如果你要读按键就必须处理 ACK 和后续的读时序。3.3 显示刷新把四位数字和 LED 状态写进显存假设我们要显示「12:34」这样的四位数字并且冒号对应的 LED 要亮。TM1624 的显示地址通常从 0x00 开始每个地址对应一个 GRID。下面是一个刷新函数// 段码表0-9 const uint8_t seg_table[10] {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; // 显示缓存4 位 uint8_t display_buf[4] {0}; // 把数字拆到缓存里并处理冒号 LED void Update_Display(uint16_t value, uint8_t colon_on) { display_buf[0] seg_table[value / 1000]; display_buf[1] seg_table[(value / 100) % 10]; display_buf[2] seg_table[(value / 10) % 10]; display_buf[3] seg_table[value % 10]; if (colon_on) { display_buf[1] | 0x80; // 假设冒号接在第二位的小数点段 } TM1624_Start(); TM1624_WriteByte(0x40); // 写显示命令具体值看手册 TM1624_Stop(); TM1624_Start(); TM1624_WriteByte(0xC0); // 地址命令从 0x00 开始 for (uint8_t i 0; i 4; i) { TM1624_WriteByte(display_buf[i]); } TM1624_Stop(); TM1624_Start(); TM1624_WriteByte(0x8F); // 显示开亮度最大 TM1624_Stop(); }逻辑说明先发写显示命令告诉芯片接下来要写显存再发地址命令指定从哪个地址开始然后连续写四个字节最后发显示控制命令打开显示并设置亮度。参数说明0x40、0xC0、0x8F这些命令字是 TM1624 常见值但不同厂家可能略有差异以手头芯片手册为准。亮度一般有 8 级0x88到0x8F对应从暗到亮室内应用0x8A左右就够太亮反而刺眼。4. 避坑与排查TM1624 驱动四位数码管最常见的五个翻车点4.1 上电后数码管全亮或全灭现象给 HK32F030M 上电TM1624 驱动的四位数码管要么所有段全亮要么完全不亮写显示数据没反应。原因TM1624 上电后显示控制寄存器默认可能是关闭状态或者亮度为 0。另外如果起始条件没发对芯片根本没进入接收状态后续数据全被忽略。解决先单独发一条显示开命令再发亮度命令最后才写显存。用逻辑分析仪抓 SCL 和 SDA确认起始条件里 SDA 是在 SCL 高电平期间拉低的。如果没有逻辑分析仪用示波器看 SCL 是否有 8 个以上的脉冲。4.2 显示的数字缺段或段位错乱现象显示「8」的时候少一段或者本该第一位亮的段跑到第三位去了。原因段码表和硬件接线不匹配。SEG 顺序接反、共阳共阴搞混、或者 GRID 顺序和代码里的缓存顺序不一致都会导致这种问题。解决写一个测试模式让每个 GRID 轮流显示全段观察哪一位亮、哪些段亮。然后对照实际接线调整段码表。我一般会在代码里留一个#define SEG_REVERSE之类的宏方便快速切换。4.3 通信偶尔失败显示闪烁现象大部分时间正常但偶尔某次刷新后显示乱一下或者亮度跳变。原因延时不够或者主循环里有中断打断了 GPIO 模拟时序。HK32F030M 如果开了系统滴答中断而delay_us又是靠循环实现的中断一来时序就拉长了。解决在 TM1624 的起始、写字节、停止这几个函数里先关全局中断操作完再开。或者把延时改成基于定时器的精确延时。另外SCL 和 SDA 的走线尽量短远离电机、继电器这类干扰源。4.4 亮度不均匀某一位偏暗现象四位数码管里某一位明显比其它位暗换数码管也没用。原因TM1624 的扫描是分时复用的如果某一位的限流电阻偏大或者该位的 SEG 线走线电阻偏大就会偏暗。另外如果显示缓存里该位的段码有多个段同时亮总电流被拉低也会显得暗。解决先检查限流电阻是否一致再检查 PCB 走线。软件上可以尝试提高整体亮度等级但根本办法还是硬件上保证每位的驱动能力一致。如果只是个别段偏暗查该段的 LED 正向压降。4.5 按键读取和显示刷新互相干扰现象TM1624 同时接按键和数码管时按下按键显示会闪或者按键读取不灵敏。原因TM1624 的按键扫描和显示扫描共用内部时序如果 MCU 在按键读取过程中频繁写显示可能打断芯片的扫描周期。解决把显示刷新和按键读取分时处理比如每 10ms 刷新一次显示每 20ms 读一次按键两者不要在同一时刻发起通信。读按键时严格按照读时序先发读命令再切换 SDA 为输入读回数据后发停止。5. 进阶技巧用 TM1624 的显存做滚动显示与亮度自适应5.1 滚动显示的缓存搬运策略四位数码管做滚动显示比如「1234」向左滚出「2345」核心是维护一个比 4 位更长的显示缓存然后每次刷新时从缓存里取连续 4 个字节写入 TM1624。下面是一个简单的滚动实现#define BUF_LEN 16 uint8_t scroll_buf[BUF_LEN]; uint8_t scroll_pos 0; // 每次调用把显示窗口向右移动一位 void Scroll_Step(void) { uint8_t window[4]; for (uint8_t i 0; i 4; i) { window[i] scroll_buf[(scroll_pos i) % BUF_LEN]; } // 把 window 写入 TM1624复用前面的写显存流程 TM1624_WriteDisplay(window); scroll_pos (scroll_pos 1) % BUF_LEN; }逻辑说明scroll_buf里预先放好要滚动的内容的段码scroll_pos指向当前窗口的起始位置每次移动一位就加一到末尾回绕。参数说明BUF_LEN取 16 是方便用位运算取模实际内容长度可以小于它多余位置填 0。滚动速度由调用Scroll_Step的间隔决定一般 200ms 到 500ms 一步比较舒服。5.2 亮度自适应根据环境光调显示等级如果产品有外壳白天和晚上对亮度的需求完全不同。可以在 HK32F030M 上接一个光敏电阻用 ADC 读电压然后映射到 TM1624 的 8 级亮度。下面是一个简单的映射表ADC 读数范围亮度命令适用场景0 - 8000x88夜间或暗室801 - 16000x8A室内弱光1601 - 24000x8C室内正常光2401 - 32000x8E明亮环境3201 - 40950x8F强光下实现时每 500ms 读一次 ADC做几次平均滤波再查表发亮度命令。注意不要在显示刷新过程中插入 ADC 读取避免时序冲突。5.3 用显存做简单的呼吸灯效果TM1624 的亮度等级是全局的不能单独调某一位。但可以利用刷新频率做视觉上的呼吸效果在短时间内交替使用高亮度和低亮度命令通过改变两者的停留时间比例让人眼感觉亮度在渐变。这个技巧在按键背光或者状态指示 LED 上比较有用但要注意刷新频率不能太低否则会看到闪烁。我一般把整个呼吸周期控制在 1 秒左右分 20 步每步 50ms亮度命令在 0x88 和 0x8F 之间跳变。5.4 一个我踩过的坑不要频繁发显示开命令早期我在每次刷新显存后都发一遍显示开命令结果发现数码管偶尔会闪一下。后来用逻辑分析仪抓波形发现显示开命令会重置芯片的内部扫描周期发得太勤反而打断扫描。正确的做法是初始化时发一次显示开和亮度设置之后只发写显存命令。这个习惯我保留到现在凡是带显示缓存的驱动芯片都先确认哪些命令是一次性配置哪些是每次刷新都要发的。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑