拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于STM32F103的TM1628数码管驱动工程详解与排坑指南

简介基于STM32F103系列单片机的TM1628数码管驱动完整工程源码面向嵌入式初学者与开发者解决低成本环境下多位数码管显示与按键扫描的快速实现需求。源码核心为TM1628.c/TM1628.h采用标准GPIO模拟时序完成通信不依赖硬件SPI兼容Keil MDK开发环境并已通过实际硬件验证稳定运行。压缩包共6个文件涵盖C源码、头文件及demo示例工程整体大小约8KB结构精简便于直接移植到最小系统板或开发板。目前已有35人学习浏览。读者拿到后只需配置STB、CLK、DIO三根引脚即可实现多位共阴/共阳数码管动态扫描显示数字、字母或自定义段码同时读取连接在TM1628上的独立按键状态该方案能直接应用于电子钟、工业仪表、简易人机界面等嵌入式交互项目有效缩短开发周期。 拿到一套源码包最怕的就是“能编译、烧进去、屏不亮”然后对着数据手册和原理图干瞪眼。我前前后后帮人调过不少TM1628的板子也见过太多人在STB时钟时序和段码映射上翻车。这篇就把我整理的一套基于STM32F103标准库的TM1628数码管驱动工程从硬件选型、通信协议、代码框架到排坑思路完整拆开讲清楚。不管你是刚接触单片机的小白还是想快速给现有项目加个显示模块的老手这套工程和思路都能让你少走弯路。1. 项目整体设计与选型思路1.1 为什么选STM32F103驱动TM1628STM32F103这颗芯片在国产和开发板市场里的保有量极大Cortex-M3内核72MHz主频GPIO翻转速度足够模拟出TM1628所需的通信时序。更关键的是它的5V容忍引脚可以直接对接TM1628的逻辑电平不需要额外做电平转换这在小板子上省了不少事。TM1628本身是一颗集成度很高的LED驱动芯片内部集成了显示RAM、键扫电路和辉度调节。它最大的特点就是“省引脚”用3根线STB、CLK、DIO就能驱动最多16个7段数码管或者128个LED点。相比直接用74HC595级联TM1628的优势在于自带键扫做微波炉、电磁炉、热水器这类需要按键和显示同时存在的面板控制板一颗芯片全搞定。这套工程的适用面很广从简单的温控仪表面板、倒计时器到需要按键交互的小家电控制板都可以直接套用。源码包里包含完整的标准库工程用Keil MDK打开就能编译下载。1.2 工程源码包的分层结构这个工程我按照“驱动层-应用层”的思想拆成了四个模块目的就是让显示逻辑和业务逻辑解耦。你可以直接拿来就用也可以把TM1628驱动文件拖到你自己的工程里。Project ├── User │ ├── main.c // 主函数演示如何使用驱动接口 │ ├── stm32f10x_it.c // 中断服务函数本工程未用中断 │ └── system_stm32f10x.c ├── Hardware │ ├── TM1628_drv.c/.h // TM1628底层驱动时序模拟、命令封装 │ ├── TM1628_disp.c/.h // 显示应用层段码表、数字/小数点显示封装 │ └── BSP_GPIO.c/.h // 引脚初始化 ├── Libraries │ └── (标准外设库) └── Project.uvprojx驱动层负责最底层的时序和控制字发送应用层关心的是“显示什么内容”两者之间通过接口函数对接。这样分层的好处很直接以后你要是把TM1628换成TM1629或者别的驱动芯片只改驱动层应用层的显示逻辑不用动。2. TM1628核心细节与通信协议解析2.1 TM1628引脚功能与典型硬件电路TM1628常见的封装是SOP32引脚不算少但真正需要单片机控制的只有3根线STB片选、CLK时钟、DIO数据。剩下的都是驱动输出口和按键扫描口。引脚名功能说明接法建议STB片选信号低电平有效接STM32任意GPIO推挽输出CLK时钟信号接STM32任意GPIO推挽输出DIO数据输入/输出接STM32 GPIO注意设置成开漏或推挽VDD电源正极5V或3.3V取决于模块设计GND电源地共地GRID1-GRID7位选输出接数码管公共端共阴/共阳看模块SEG1-SEG13段选输出接数码管段引脚硬件上有一个容易忽略的细节TM1628的DIO是双向口在读取按键状态时需要切换方向。如果用推挽输出模式直接干读的时候容易出问题。我的做法是把它配置成开漏输出外部加上拉电阻到VDD这样既能拉低总线又能释放总线读取按键电平。还有一个常见坑是复位电路。TM1628要求上电时STB保持高电平VDD上升到稳定后至少等待1ms再开始通信。很多人的板子一上电就立刻发数据结果第一个字节经常丢失。这个时序在驱动代码里必须做处理否则就会出现“第一次显示乱码复位后正常”的怪现象。2.2 通信协议三线串行接口的时序拆解TM1628的通信协议本质上是一种类SPI的串行协议但比标准SPI要简单没有时钟极性和相位的概念也不涉及片选的自动控制一切靠软件模拟。一次完整的通信过程是这样的STB拉低表示传输开始。在CLK上升沿DIO上的数据被移入芯片内部先发低位LSB first。一个字节发送完毕后根据后续命令决定是继续发数据还是结束。数据传输完成STB拉高结束本次通信。时序上有个参数需要特别注意CLK高电平和低电平的最短持续时间。TM1628数据手册上标注的是CLK周期不能小于2.5us具体看手册版本表格5.1中有tCLK参数。我之前在F103主频72MHz下不加延时直接翻转GPIO时钟周期大概只有0.2us结果就是显示内容随机乱跳。后来在每次翻转CLK之后加了个短暂延时问题就消失了。代码中我用了一个辅助延时函数static void TM1628_DelayUs(volatile uint32_t us) { us * 72; // 72MHz主频下粗略延时1us while (us--) { __NOP(); } }这个延时函数只是简单的软件延时在72MHz主频下一个循环大概能撑住1us左右。实际测试时用示波器看波形微调一下循环次数即可不需要精确到时钟周期。2.3 三种命令类型与显示寄存器的关系TM1628的命令分为三种数据命令、显示控制命令、地址命令。每一种命令的格式不同作用也不同搞清楚它们之间的配合关系是驱动调试的关键。命令类型命令字格式作用数据命令0x00~0x03设置数据写入/读取的模式显示控制命令0x80~0x8F控制显示开关、辉度亮度地址命令0xC0~0xC7设置写入显示RAM的起始地址数据命令的三种模式里最常用的是“写数据到显示寄存器”模式命令字为0x00。发送完数据命令后紧接着发送地址命令0xC0起始地址然后连续发送要写入的数据字节。写入过程中地址会自动递增不需要每次重新指定地址。显示RAM总共16个字节地址0x00-0x0F但本工程只用到了前8个字节。每个字节对应一个GRID的8个SEG段。比如GRID1地址0xC0对应第1个数码管的段码GRID2地址0xC1对应第2个数码管以此类推。显示控制命令就简单了0x88是“开显示、辉度1/16”0x89是“开显示、辉度2/16”一直到0x8F是“开显示、辉度14/16”。0x80关闭显示。调试时建议先用最低辉度测试电流小、发热低也方便观察。3. 硬件接线与CubeMX配置实操3.1 引脚分配与接线对照表我这次用的是STM32F103C8T6最小系统板TM1628模块是某宝上最常见的数码管显示模块8位数码管8个按键那种。GPIO分配如下STM32引脚功能连接目标PA0STBTM1628模块 STBPA1CLKTM1628模块 CLKPA2DIOTM1628模块 DIO3.3VVCCTM1628模块 VCC部分模块需5V请看模块说明GNDGNDTM1628模块 GND3.3V和5V的供电问题值得单独说一句。TM1628的VDD范围是3.0V-5.5V3.3V供电完全没问题但此时SEG/GRID输出的高电平也是3.3V驱动红色数码管的亮度会稍微暗一点。想要高亮度就用5V给模块供电但STM32的GPIO输出高电平是3.3V对TM1628来说逻辑高电平阈值VIH最高是0.7VDD5V供电时大概是3.5V3.3V电平就够不着了。这时候有两个方案一是用三极管/电平转换芯片把单片机的3.3V信号升到5V再去驱动TM1628二是直接3.3V供电凑合用。我的建议是对亮度没有极致要求的话统一3.3V供电最稳妥不用纠结电平匹配问题。源码包里默认按3.3V供电设计。3.2 标准库的GPIO初始化配置GPIO初始化这部分看似简单但角色得提前想清楚——STB和CLK是纯输出DIO是双向口。DIO我用了开漏输出模式外部接了4.7kΩ上拉电阻这样在走读键扫流程时可以直接把GPIO配置切成输入模式不用重新初始化外设。void BSP_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // STB、CLK、DIO 全部配置为推挽输出速度50MHz GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 初始状态STB和CLK都拉高DIO也拉高 GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2); }DIO配置成推挽输出也能读写但切换方向时要小心。如果输出寄存器里的值恰好是低电平切到输入模式时GPIO内部的上拉/下拉可能把总线钳位住读回来全是0。开漏输出加外部上拉能完全避开这个问题这是我踩过坑之后的深刻体会。3.3 TM1628底层驱动的核心实现底层驱动最关键的是把时序写对。我封装了三个静态函数发送一个字节、接收一个字节、发送命令字。在此基础上再封装了写显存和读按键的API。static void TM1628_WriteByte(uint8_t dat) { uint8_t i; for (i 0; i 8; i) { if (dat 0x01) { GPIO_SetBits(GPIOA, GPIO_Pin_2); } else { GPIO_ResetBits(GPIOA, GPIO_Pin_2); } TM1628_DelayUs(1); GPIO_SetBits(GPIOA, GPIO_Pin_1); // CLK上升沿 TM1628_DelayUs(1); GPIO_ResetBits(GPIOA, GPIO_Pin_1); // CLK拉低 dat 1; // LSB first } }注意这里的移位方向。TM1628是低位在前很多从51单片机转过来的朋友习惯高位先出一上来就写MSB first结果整个显示全乱。这是协议层面最容易出错的一个点。发送命令字的函数也很直观void TM1628_WriteCmd(uint8_t cmd) { GPIO_ResetBits(GPIOA, GPIO_Pin_0); // STB拉低开始传输 TM1628_DelayUs(1); TM1628_WriteByte(cmd); GPIO_SetBits(GPIOA, GPIO_Pin_0); // STB拉高结束传输 TM1628_DelayUs(1); }写显存时先发数据命令0x00再发地址命令0xC0随后连续写入若干字节。一次写完所有8个GRID的段码然后STB拉高结束。void TM1628_SetDisplayBuffer(uint8_t *buf, uint8_t len) { uint8_t i; GPIO_ResetBits(GPIOA, GPIO_Pin_0); TM1628_WriteByte(0x00); // 数据命令写显示寄存器 TM1628_WriteByte(0xC0); // 地址命令从0x00地址开始 for (i 0; i len; i) { TM1628_WriteByte(buf[i]); } GPIO_SetBits(GPIOA, GPIO_Pin_0); }写完之后调用TM1628_SetDisplayMode(0x8F)打开显示并把辉度调到最高档。这里有个细节每次写显存后不需要重发显示控制命令只要芯片不断电显示模式不会自己变。但如果你写了显存却不显示大概率就是忘了发0x8B这些控制字。4. 数码管显示应用层封装与段码处理4.1 共阴共阳与段码表的设计数码管有共阴和共阳之分但TM1628内部是恒流源结构它不关心外面接的数码管是共阴还是共阳关键是段码表要跟硬件匹配。你买的模块大概率是一体的模块厂已经把数码管接好了你需要做的只是把段码表测出来。段码表其实很好理解一个字节的bit0对应SEG1bit1对应SEG2以此类推。如果某个段亮了对应的比特就置1。我工程里的段码表是我自己按“共阴”方式定义的也就是段点亮时写1熄灭时写0const uint8_t SEG_CODE[] { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F, // 9 0x77, // A 0x7C, // b 0x39, // C 0x5E, // d 0x79, // E 0x71, // F 0x00, // 熄灭 0x80, // 小数点DP };这组码是标准的7段码但如果你是照着网上的电路图自己画的板子SEG引脚接的数码管段位可能跟我这张表不一样。遇到这种情况最简单的办法就是写一个“扫段测试程序”把0x01到0x80逐个写进去肉眼记录每段对应的bit位然后重新映射段码表。这个过程虽然原始但一劳永逸以后再也不用猜段码了。4.2 动态显示与数据刷新策略8位数码管如果直接并行驱动需要很多引脚TM1628的GRID是扫描方式也就是“动态显示”。TM1628内部自动完成GRID扫描不需要主控干预主控只需要在显存里维护好每一GRID要显示的段码数据即可。但是主控往TM1628写显存的速度不能太慢。因为TM1628内部扫描一旦开始它就会周期性地从RAM里取数据。如果你写RAM的速度太慢中间会有一段肉眼可见的闪烁——尤其是在多位数码管都亮着的情况下。实测下来我用72MHz主频、纯软件模拟时序刷新一次8字节显存只需要不到20us远小于人眼的闪烁感知完全没问题。应用层的显示接口我封装了三个常用函数TM1628_DisplayNumber(uint32_t num)显示一个整数自动左对齐右对齐TM1628_DisplayFloat(float val, uint8_t decimal)显示带小数的数值TM1628_DisplayString(const char *str)显示字符串支持0-9、A-F、一些符号void TM1628_DisplayNumber(int32_t num) { uint8_t i, digit, buf[8]; // 从个位开始取数字 for (i 8; i 0; i--) { digit num % 10; buf[i - 1] SEG_CODE[digit]; num / 10; if (num 0) break; } // 剩余高位显示空格熄灭 while (i 1) { buf[i - 2] SEG_CODE[16]; // 熄灭 i--; } TM1628_SetDisplayBuffer(buf, 8); }显示小数点时注意不能把DP位的0x80直接加到段码上就完事因为DP位在硬件上对应的是SEG13而数字段码用的是SEG1-SEG7两者需要按位或合并。我的段码表里最后一个0x80就是DP的段位码实际显示时用SEG_CODE[num] | 0x80即可。5. 常见问题排查与实测心得5.1 显示乱码、缺段、闪烁的排查顺序这些症状背后对应的原因往往不同按以下顺序排查效率最高故障现象可能原因排查方法完全无显示供电不对、STB一直拉低万用表测量VDD和GND检查STB电平静态值显示乱码、内容错乱通信时序太快、命令字发错用示波器看CLK周期降低通信速率或加长延时闪烁刷新太慢、VDD电压不稳检查刷新频率在VDD引脚旁边加大电容10410uF某一位不亮GRID引脚虚焊或接触不良用扫段测试程序检查该位补焊或更换数码管段缺笔画段码表不匹配重做扫段测试修正段码表遇到“第一次上电乱码”的情况第一时间检查初始化代码里有没有留够上电稳定时间。TM1628在上电时STB必须保持高电平至少1ms我用的是// 在 main() 开头 BSP_GPIO_Init(); delay_ms(5); // 确保上电稳定 TM1628_Init(); // 发送显示控制命令 0x8F 等这5ms的延时看起来不痛不痒但在工业环境里如果电源本身纹波大留多点时间更稳妥。有条件的可以改成检测VDD电压但普通小板子软件延时够用了。5.2 按键读取与DIO方向切换的注意事项TM1628的按键扫描功能在这颗芯片里是免费的不用白不用。读按键的流程是先发数据命令0x42读键扫数据然后STB拉低连续读入N个字节取决于扫描组数最后STB拉高。关键在DIO方向切换。我一般在读之前把GPIO配置成输入模式读完再切回开漏输出。标准库下切换要小心GPIO_Init整个结构体重新初始化一遍最保险uint8_t TM1628_ReadKey(void) { uint8_t i, key_byte 0; GPIO_InitTypeDef GPIO_InitStructure; // 发送读命令 GPIO_ResetBits(GPIOA, GPIO_Pin_0); TM1628_WriteByte(0x42); // DIO切为输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); for (i 0; i 8; i) { if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2)) { key_byte | (0x01 i); } GPIO_SetBits(GPIOA, GPIO_Pin_1); // CLK上升沿 TM1628_DelayUs(1); GPIO_ResetBits(GPIOA, GPIO_Pin_1); TM1628_DelayUs(1); } // 切回开漏输出模式 GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_2); GPIO_SetBits(GPIOA, GPIO_Pin_0); // STB拉高结束 return key_byte; }还有一个细节读键的时候DIO在CLK下降沿时芯片输出数据在CLK上升沿时主控去采样比较好。我在循环里先把引脚读到变量里再拉高CLK顺序就是这个考虑。不少参考代码是先拉CLK再读数据结果读到的总是上一位的数据按键值就会错位。5.3 移植到其他工程的几个坑最后说说把这套驱动往别的工程里搬会遇到的几个典型问题。第一个是引脚定义。工程里直接把GPIO写死在BSP_GPIO.c里了换芯片或者换引脚别只改宏定义要确保三处一起改GPIO时钟使能、GPIO_Init的引脚配置、以及驱动函数里所有用到GPIO_SetBits/GPIO_ResetBits的地方。我的建议是学我在头文件里把端口、引脚、时钟都用宏集中定义#define TM1628_GPIO_PORT GPIOA #define TM1628_STB_PIN GPIO_Pin_0 #define TM1628_CLK_PIN GPIO_Pin_1 #define TM1628_DIO_PIN GPIO_Pin_2 #define TM1628_GPIO_CLK RCC_APB2Periph_GPIOA这样移植的时候只需要改头文件里的宏驱动函数里的代码一个字都不用动。第二个是延时函数。我这个工程用的软件延时跟主频强相关。如果换到F407168MHz或者F103倍频到72MHz延时函数的时间都会变TM1628通信的CLK周期可能会超速。移植时用Systick或者定时器计时更通用或者每次写完驱动后用示波器实测CLK频率保证周期在2.5us以上。第三个是5V电平问题。前面已经说过了如果模块是5V供电而MCU是3.3V务必查一下TM1628的原理图看模块上有没有电平转换。很多现成模块是兼容3.3V和5V的但如果DIO线上有上拉电阻上拉到5V的话MCU的引脚会被钳位到5V长期运行有烧引脚的风险。我一般看到这种情况会串一个1kΩ电阻再接到MCU既保护引脚又能正常通信。6. 从源码包到项目的落地总结这套工程我用在过两个实际项目上一个是小家电的定时控制面板一个是实验室的温控仪表。两个项目都遇到过不同的坑但把底层时序、段码表、按键扫描这几块理顺之后剩下的业务逻辑开发就顺畅多了。如果你拿到这个源码包我建议的先做一件事不要急着改功能先把“扫段测试”程序烧进去确认每一段数码管都能按预期点亮。这一步确认了后面的所有显示问题都不用再怀疑硬件。然后再烧一个显示“8.8.8.8.8.8.8.8”的程序确认所有位都能正常显示。后续扩展方向上这套驱动配合TM1628的键扫能力可以轻松实现“显示按键”组合。我甚至在一款产品上同时用了两个TM1628一个管显示一个管按键矩阵总共也就花了6个GPIO。如果你打算做低功耗设备F103的停机模式加TM1628的显示关闭命令也能配合得很好需要的时候唤醒刷新一下就行。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门