多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

TM1650数码管驱动芯片:I2C接口与按键扫描实战解析

TM1650数码管驱动芯片:I2C接口与按键扫描实战解析 1. TM1650到底是个啥一颗芯片搞定数码管和按键先把这个芯片的定位说清楚。TM1650是一颗I2C接口的LED数码管驱动芯片市面上很多小家电、充电桩、仪表盘、温控器里都能看到它的影子。我最早接触它是在做一款小型计时器当时MCU的GPIO非常紧张但又要驱动4位数码管还要扫按键最后就选了这颗芯片。用下来最大的感受是它把两个最占I/O的活儿——段码刷新和按键扫描——全部收编成I2C通信主控只需要两根线就能控制4位数码管外加7个按键。从功能框图的角度看TM1650内部集成了显示驱动电路和键盘扫描电路两部分。显示驱动部分支持4个位选GRID1~GRID4和8个段选SEGA~SEGG、DP也就是能直接驱动4位共阴数码管每一位都能显示包括小数点在内的8个段。键盘扫描部分提供了7个按键检测通道K1~K7配合SGN引脚接地可以在不增加主控负担的情况下实现矩阵按键的扫描。适合什么人用如果你正在做需要1~4位数码管显示的产品同时又要处理少量按键输入而且主控的GPIO或者定时资源比较紧张这颗芯片就非常合适。STM32、AVR、PIC、Arduino甚至是ESP32这类带I2C外设的芯片都能轻松驱动它即使是纯软件模拟I2C也只有两根线随便找两个GPIO就能跑起来。我甚至见过有人用一颗8脚的MCU配合TM1650做了一整个电子钟主控只负责算时间和处理按键逻辑显示部分全部丢给TM1650整体方案非常精简。还有一个容易忽略的点TM1650的显示驱动是恒流源式的每个段的输出电流可以通过寄存器配置成8个档位这就意味着它不需要像传统三极管驱动方案那样精心计算限流电阻。不同亮度需求、不同尺寸的数码管只要改寄存器值就能适配。这一点在后面讲代码的时候会细说。要注意的是TM1650驱动的是共阴数码管不是共阳的。买数码管的时候别买错了共阳管子直接接上去会完全不亮或者显示错乱。这个坑我在帮朋友排查问题时遇到过他拿着共阳的数码管焊上去折腾半天以为芯片坏了其实是管子和驱动方式不匹配。2. 通信协议与寄存器I2C从机是怎么工作的2.1 I2C从机地址和读写方向TM1650作为I2C从机7位从机地址是0x24换算成8位写地址就是0x48读地址是0x49。这个地址是芯片出厂固定死的没有地址引脚所以一条I2C总线上只能挂一颗TM1650。如果你需要同时驱动两组数码管只能另外再想办法比如用两颗MCU或者换成带地址选择脚的驱动芯片。这个限制我在做双面显示面板的时候踩过当时想挂两颗TM1650共用总线的想法直接破产。I2C通信的时序跟标准I2C完全一致只是芯片内部没有复杂的寄存器映射表所有操作都是通过命令数据的流式写入来完成的。这一点跟EEPROM或者传感器那种先写寄存器地址再写数据的模式不太一样刚上手时容易搞混。2.2 显示寄存器段码数据怎么填写入显示数据的过程是这样的主控先发送I2C起始信号然后发送写地址0x48紧接着发送显示位置命令再发送该位置对应的段码数据。显示位置命令一共有4个0x48表示第1位数码管0x49表示第2位0x4A表示第3位0x4B表示第4位。注意这里0x48同时又是写地址容易造成误解。实际流程是先发0x48表示我要写数据再发一个0x48表示我要写第1位然后发段码字节。换句话说整个写入流程是START - 0x48从机地址写方向 - 0x48位置命令 - 段码数据 - STOP。段码数据对应关系是bit0对应SEGAbit1对应SEGB以此类推bit6对应SEGGbit7对应DP。对共阴数码管来说对应的段亮起来需要写1。所以数字0的段码就是0x3F也就是bit0到bit5全部置1g段不亮dp不亮。这里有一个很小的细节数据手册里有些地方用低电平有效来描述段选容易让人误解。实际用下来TM1650对共阴数码管段码位写1就是点亮对应的段跟74HC595直驱的逻辑是相同的。还有一个值得注意的点段码数据是每写一位就发一字节写完后芯片内部会自动锁存。也就是说你可以在任意时刻单独更新某一位的显示内容其他位不受影响。这在做倒计时、秒表这类需要动态更新某一位数字的场景非常方便不需要每次刷新都把4位全部重写一遍。2.3 控制寄存器亮度与开关除了显示数据TM1650还有一个控制寄存器地址也是0x48用不同的命令标识来区分。控制命令格式是这样主控发送0x48从机地址 - 0x48控制命令标识 - 控制字节。控制字节的高位控制显示开关和亮度。具体来说bit7是显示开关1为开显示0为关显示bit6到bit4是亮度档位000到111对应8级亮度0最暗7最亮低4位可以全部置0。这里有个常见的操作误区有人初始化的时候只发控制命令不发显示数据发现数码管不亮就以为芯片坏了。实际上TM1650上电后默认是显示关闭的状态必须先写控制字节把bit7置1然后写段码数据才能看到内容。我在第一次调试时就因为这个顺序问题折腾了半天一度怀疑是焊接虚接了。亮度的调节效果非常明显0档和7档的亮度差异在视觉上大概有三到四倍的差距。如果数码管放在强光环境下直接拉满7档如果是室内弱光环境3档左右就够了还能降低整机功耗。对于那些用电池供电的小设备来说这个亮度调节功能也是省电的利器白天亮、夜间暗代码里一条分支就能实现。2.4 键盘扫描寄存器键盘部分稍微特别一点。TM1650的键盘扫描结果不是通过写命令获取的而是通过I2C读操作读出。主控发送读地址0x49之后连续读取两个字节第一个字节的低7位对应K1到K7bit0对应K1bit6对应K7第二个字节通常保留不需要关心。需要说明的是这个按键检测结果是电平状态不是边沿触发。也就是说你读回来的数据反映的是此刻哪些按键被按下低电平表示按下高电平表示未按下。所以在软件里需要自己做消抖和边沿检测才能在按下和释放两个动作上精确响应。这个后面在代码部分会有更详细的说明。键盘扫描功能还有一个细节按键引脚内部没有上拉需要在硬件上给K1~K7接上拉电阻到VCC这样按键另一端接SGN地按下时才能把引脚拉低。有人直接用芯片内置的上拉结果发现按键全部失灵就是因为漏接了这颗上拉电阻。3. 硬件设计要点接线不复杂但有几个坑要避开3.1 典型接线方案先给一个最简单的接线案例4位共阴数码管1个按键接到TM1650主控用一片STM32。TM1650的SCL和SDA分别接到主控的I2C时钟和数据引脚VCC接3.3V或者5VGND共地。数码管的位选引脚通常标为 DIG1~DIG4 或者 COM1~COM4接到TM1650的GRID1~GRID4数码管的段选引脚A~G、DP对应接到SEGA~SEGG和SGP引脚。按键的一端接K1另一端接SGN同时在K1上接一颗10kΩ上拉电阻到VCC。市面上有一种集成好TM1650的数码管模块4位一体已经画好了PCB和限流电路使用起来更省事。不过对于量产产品还是建议自己把芯片和数码管布局在一块板上成本能低不少布线自由度也更大。我之前做的一款温控器就是直接把TM1650放在数码管正下方走线短显示稳定EMC表现也比较理想。3.2 上拉电阻和电平匹配I2C总线的SCL和SDA都需要上拉电阻这个是一定不能省的。阻值选多少取决于总线长度和通信速率。短距离板内走线4.7kΩ是安全选择如果总线长度超过20cm或者并联了多个I2C从设备可以换成2.2kΩ。有人图省事直接用MCU内部上拉结果在快速模式400kHz下波形边沿变缓偶尔出现通信错误。我的习惯是无论是I2C还是其他总线该用外部上拉就用外部上拉内部上拉只是应急方案。电源电压方面TM1650的工作电压范围大概在2.0V到5.5V之间3.3V和5V系统都能用。如果你的主控是5VTM1650也接5VI2C电平都是5V自然兼容如果主控是3.3VTM1650也接3.3V同样没问题。怕的是主控3.3V而TM1650接5V这时候SDA和SCL的电平就不匹配了需要加电平转换电路或者用开漏配合外部上拉到3.3V的方式处理。我一般避免这种不对称供电因为省事永远是第一位的。3.3 电源和去耦TM1650的峰值电流不容小觑。4位8段全亮时每位电流按20mA算总共需要80mA以上这还不包括点亮瞬间的浪涌。如果主控板上3.3V的LDO本来余量就不大加入TM1650之后很可能出现电压跌落导致MCU复位或者数码管亮度不均匀。建议在芯片电源引脚附近放一颗10μF电解电容和一颗0.1μF陶瓷电容并联去耦并且从电源入口单独拉一条粗一点的走线给数码管供电。还有一个在批量生产中很关键的点数码管的段和位之间有一个最大耐压的限制不要超过手册给的绝对最大值。如果是12V供电的系统数码管驱动部分需要做额外的电平转换不能直接把TM1650悬在12V上。绝大多数情况下TM1650都是低压系统里的器件涉及高压就要换思路了。4. 驱动代码实现从初始化到动态显示4.1 初始化流程初始化TM1650的流程非常简单无非就是往控制寄存器写一个字节。以STM32的HAL库为例uint8_t tm1650_ctrl 0x81; // 显示开亮度1档 uint8_t set_addr 0x48; HAL_I2C_Master_Transmit(hi2c1, 0x48, set_addr, 1, 100); HAL_I2C_Master_Transmit(hi2c1, 0x48, tm1650_ctrl, 1, 100);第一句先发送地址字节0x48表示要操作控制寄存器第二句发送控制字0x81表示开显示、亮度1档。注意这里两次都用了0x48作为从机地址因为HAL库的地址参数需要包含写标志位而0x48本身就是0x24写位的结果。如果你用的是Arduino代码会更简洁#include Wire.h void setup() { Wire.begin(); Wire.beginTransmission(0x48); // 写地址0x48 Wire.write(0x48); // 控制命令 Wire.write(0x81); // 开显示亮度1 Wire.endTransmission(); }如果连初始化这一步都省了直接写显示数据大概率会看到数码管毫无反应。所以无论用什么平台记得先把显示开关打开。4.2 段码表的建立段码表是驱动数码管绕不开的东西。我一般建一个const数组把所有需要显示的字符的段码放进去。下面是常见的共阴数码管段码表字符段码共阴备注00x3F全部点亮a~f10x06b、c点亮20x5Ba、b、d、e、g30x4Fa、b、c、d、g40x66b、c、f、g50x6Da、c、d、f、g60x7Da、c、d、e、f、g70x07a、b、c80x7F全部点亮90x6Fa、b、c、d、f、gA0x77a、b、c、e、f、gb0x7Cc、d、e、f、gC0x39a、d、e、fd0x5Eb、c、d、e、gE0x79a、d、e、f、gF0x71a、e、f、g全灭0x00所有段熄灭横杠0x40g段点亮如果你不清楚某个字符长什么样最好的办法是照着数码管的段布局图慢慢推。我还习惯把带小数点的段码做一份比如0.就是0x3F | 0x80 0xBF这样在显示温度小数位的时候就不用单独处理小数点标志位了。4.3 显示一个数字的完整过程假设要在4位数码管上显示12.34拆解出来的4个字符分别是1、2、3、4其中第二位后面带小数点。显示过程如下uint8_t display(uint8_t pos, uint8_t seg_data) { uint8_t addr 0x48 pos; // pos从0开始对应0x48~0x4B HAL_I2C_Master_Transmit(hi2c1, 0x48, addr, 1, 100); HAL_I2C_Master_Transmit(hi2c1, 0x48, seg_data, 1, 100); return 0; } // 显示12.34 display(0, code_1); // 第1位显示1 display(1, code_2 | 0x80); // 第2位显示2带小数点 display(2, code_3); // 第3位显示3 display(3, code_4); // 第4位显示4整个过程中TM1650内部会自动完成位选和段选的控制主控不需要做动态扫描。这意味着CPU的负担极轻刷新几百次都不会有闪烁感因为每次刷新都是直接把段码数据锁存到芯片内部的显示寄存器里由芯片硬件驱动数码管持续点亮。4.4 键盘读取与消抖读键盘的代码也不复杂只是需要连续读两个字节。以HAL库为例uint8_t keys[2]; HAL_I2C_Master_Receive(hi2c1, 0x49, keys, 2, 100); uint8_t key_state ~keys[0] 0x7F; // 取低7位高电平表示按下取反是为了把逻辑统一成bit为1表示按下方便后续处理。如果你在硬件上已经做了取反逻辑这里就可以省掉取反操作具体看个人习惯。消抖我一般放在定时器中断里做每5ms读一次连续读到两次相同状态才算数。边沿检测则用上次状态和本次状态比较实现这样既能识别按下事件也能识别释放事件。一个简单有效的实现思路把key_state和old_state做异或结果中为1的位就是状态发生变化的引脚再和当前状态做与操作就能得到刚刚按下的引脚掩码。如果你不想占用定时器资源也可以把按键扫描放在主循环里用软件延时做消抖。但对于多任务系统我强烈建议用定时器扫描否则很容易出现按键响应卡顿。5. 常见问题排查显示乱码、不亮、按键误触怎么办5.1 数码管完全不亮数码管完全不亮首先检查的是控制寄存器是否写对了。很多芯片上电默认是显示关闭状态TM1650也一样必须把控制字节的bit7置1。如果你初始化时写的是0x01而不是0x81那就是只开了亮度没开显示。这类问题在代码审查时最容易被忽略因为编译器不会报错逻辑上也说得通。其次检查I2C地址。注意区分0x24和0x480x24是7位地址0x48是带写标志的8位地址。在不同厂商的库函数接口中两种写法都有可能出现。如果你在STM32的HAL接口里写0x24实际发送的是0x48从机地址会完全错位。用示波器或者逻辑分析仪看I2C波形一抓就能确认。最后检查硬件连线。TM1650的GRID接到数码管的位选SEG接到段选接反了会导致亮灯位置错乱但不会完全不亮。完全不亮大概率是电源问题用万用表量一下芯片VCC引脚有没有电压排除焊接短路和虚焊。5.2 显示乱码或者段错位如果数码管亮了但显示的是乱码基本可以锁定是段码表的问题。比如把共阴数码管当成共阳来编码显示0就变成了全灭显示1变成了除了b、c之外全亮看起来很像是乱码。这时候对照段码表逐位验证不要瞎猜。还有一种情况是段选引脚和数码管引脚顺序不对应。不同厂家的数码管段排列顺序可能不同A段不一定在物理引脚1上。仔细看数码管的数据手册一一对应好再焊板子。动态刷新时序问题也会导致显示异常比如在写段码数据时被中断打断导致数据字节错位。不过TM1650是I2C接口每次传输都有起始位和停止位保护只要协议上完整被中断打断的概率比 GPIO 模拟扫描的74HC595方案低得多。真的遇到传输被打断的情况可以试试用硬件I2C而不是模拟I2C或者把I2C中断优先级调低避免关键传输被打断。5.3 按键误触或者无响应按键误触大概率是上拉电阻没接或者接得太大。TM1650的K1~K7引脚内部没有上拉如果不外部接上拉引脚会浮空按键按下和释放的状态都会被噪声干扰表现出来就是按键乱跳。我一般用10kΩ上拉最大不要超过47kΩ否则引脚上的电容效应会让电平跳变变慢消抖时间得加长。无响应的问题除了上拉电阻还有一个容易被忽略的点SGN引脚必须接到系统地。SGN是按键扫描的参考地如果不接地按键回路就不完整自然读不到状态。有些模块把SGN引出来了需要自己在主板上接GND别漏了。软件上如果按键读回来的值总是0xFF大概率是I2C读时序不对。读操作时主控发完从机地址和读位后需要连续读两个字节第一个字节才是有效按键状态。有些参考代码只读一个字节就停止了这在某些芯片上可以工作但在TM1650上就会导致数据错位。5.4 和TM1637、MAX7219怎么选很多人会把TM1650和TM1637搞混。TM1637也是I2C接口的数码管驱动芯片能驱动4位8段数码管也带按键扫描但它的I2C是类I2C并不是标准的I2C时序在某些平台上模拟时序需要注意。TM1650使用的是标准I2C协议接口更通用。功能上两家的显示驱动能力接近日常使用差异不大选谁主要看你手里模块的货源和价格。MAX7219则是SPI接口驱动位数更多最多8位而且支持级联适合做多位大屏显示。但MAX7219的段电流控制是固定的亮度调节不如TM1650灵活价格也高不少。我的选型建议很实在做4位以内小尺寸显示TM1650性价比最高需要级联驱动多位大屏就上MAX7219如果要完整的标准I2C同时要兼顾按键TM1650依然是最优选。5.5 一个关于I2C时钟速率的提醒TM1650虽然支持到400kHz的快速模式但我在实测中建议用100kHz标准模式跑。原因是TM1650内部没有复杂的FIFO缓冲主控发送数据的间隔如果太短芯片内部的锁存逻辑偶尔跟不上会出现个别位显示刷新不及时的问题。把速率降到100kHz之后这类问题几乎绝迹。反正显示数据本身量很小每次只发几个字节100kHz的带宽完全够用没必要为了这点速度去挑战稳定性。5.6 常见问题速查表现象可能原因排查方法完全不亮控制寄存器未开显示检查Ctrl字节bit7是否为1完全不亮I2C地址错误确认0x24/0x48的区别乱码段码表不符合共阴共阳逐段验证段码乱码SEG顺序接错对照数码管数据手册亮度太暗亮度档位太低尝试把亮度档提到7亮度不均匀某位限流异常测量每位电流按键乱跳缺少上拉电阻加上拉10kΩ按键无响应SGN未接地将SGN接GND按键误触发消抖不充分增加消抖时间或滤波6. 几个实战中的小技巧最后聊几个我在实际项目中总结的细节这些在数据手册里很难找到但遇到了确实能省不少时间。第一个技巧是批量显示前先做一个全亮自检。在初始化之后把所有位的段码都写成0xFF让数码管全亮3秒钟。这一步看起来简单但在硬件调试时特别有用能快速确认每颗数码管的段和位是否都接触良好也能直观看到亮度档位是否符合预期。如果全亮时某一段不亮直接就能定位是硬件问题还是驱动问题不用对着逻辑分析仪猜半天。第二个技巧是关于小数点处理的。如果产品需要显示小数而你又不想每次手动给段码加0x80可以在封装显示函数的时候把小数点和数字分离开。比如函数原型定义为display_digit(pos, digit, int dot)内部自动完成带小数点段码的计算。这样调用层只关心业务逻辑不用关心段码细节代码可读性提升非常多。第三个技巧是用亮度变化替代闪烁来做报警指示。很多设计会用数码管闪烁来提示状态但MCU闪烁数码管要么需要定时刷新要么需要额外控制逻辑。TM1650支持随时修改控制寄存器可以每隔一段时间把显示开关切一次实现闪烁效果。这种方式的好处是段码数据不需要重写闪烁的稳定性也好主控的负担基本可以忽略。甚至可以让不同位在不同时间点亮做出位扫式的装饰效果花不了多少资源。第四个技巧是键盘扫描结果和显示数据之间的时序关联。TM1650的键盘扫描是独立的和显示更新互不干扰所以在设计软件架构时可以把显示刷新和按键检测分成两个独立的模块分别驱动。如果放在同一个任务里注意不要在一次I2C操作中间穿插另一个I2C操作否则总线时序会乱。我给每个I2C操作加一个互斥锁或者简单的忙标志就能完全避免这个问题。7. 写在最后还是老规矩做了这么多年嵌入式各种显示驱动芯片也用了几十种TM1650属于那种朴实无华但真管用的器件。它的技术指标不算惊艳功能也不算花哨但胜在把数码管和按键两个最常用的外围整合到了一颗芯片里通信又用的是标准I2C几乎任何主控都能轻松驱动。对于小尺寸显示产品来说这套方案成本低、稳定、开发周期短是我个人非常偏爱的一种组合。如果你正在做一个需要数码管显示的小项目我建议直接拿TM1650起步先把点亮和显示跑通再逐步加按键、调亮度、做动画整个过程会非常顺畅。踩过几个坑之后你就会发现这颗芯片其实没有那么多隐藏的雷大部分问题都出在基础细节上——地址有没有写对、共阴还是共阳、上拉有没有接。把这些基本功打牢TM1650能陪你走很远。
返回列表