ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TM1640驱动移植实战:从时序到段码表的数码管调试指南

TM1640驱动移植实战:从时序到段码表的数码管调试指南 简介TM1640驱动程序包面向需要快速实现LED数码管显示的单片机开发者以STC15系列为运行平台提供对TM1640芯片的完整控制方案。驱动源码包含两个头文件与两个源文件头文件完成寄存器地址及功能接口定义源文件实现初始化、工作模式设置、数据写入和动态扫描逻辑并附有延时函数模块便于工程直接移植与调用。压缩包共4个文件整体仅3KB结构非常精简适合初学者理解芯片时序也适合中小型项目快速集成。目前已有1078人学习下载常见应用包括数字时钟、仪表盘、计数器等显示场景。通过研读源码开发者可以掌握STC15与TM1640之间的通信过程学会将数字或字符编码为段码并动态刷新在实际制作中灵活调整显示亮度与扫描方式有效节省I/O资源并提升开发效率。 有些年头了我还在捣鼓温控器面板的时候从网上下载过一份“TM1640驱动程序.rar”。解压、改引脚、烧录一套操作下来数码管纹丝不动反而有一点微弱的光晕。后来耐着性子把时序、指令、显示缓冲挨个捋了一遍才发现这类驱动包的问题往往不在芯片本身而在“怎么把别人的代码搬到自己的板子上”。这篇文章就围绕TM1640这个LED驱动芯片聊聊它的驱动原理、代码移植过程以及实测中会遇到的几个真实坑给同样在调数码管和LED点阵的朋友做个参考。1. 先弄明白TM1640是干什么的一块能管128个LED点的芯片拿到“TM1640驱动程序.rar”之前得先认识TM1640这颗芯片。它是深圳天微电子出的一款LED驱动控制IC经常出现在冰箱面板、电磁炉显示板、温控器、小家电这类产品里主要场景就是驱动共阴数码管和LED点阵模块。芯片本身不算复杂但很多人把它当成I2C设备去用结果折腾半天通信对不上其实就是没搞清它的定位。1.1 引脚与内部资源16个SEG、8个GRID是什么意思TM1640比较常用的封装是SOP32引脚多但功能分类很清楚。核心资源是两组引脚SEG1到SEG16也就是段引脚一共16个GRID1到GRID8也就是位引脚一共8个。16乘8等于128也就是说它最多可以控制128个LED点这是它最基本的驱动力。如果驱动8位8段数码管常规接法是8个SEG对应数码管的a到g加dp8个GRID对应8个位选这样只用了64个点剩下的SEG引脚还能扩展一些状态指示灯。如果驱动16段米字管或者做LED点阵那SEG引脚多的优势就体现出来了。这里要记住一个关键点TM1640是共阴驱动GRID输出的是扫描位选SEG输出的是段电流连接数码管时公共端必须接到GRID段引脚接到SEG接反了不仅不显示还可能烧芯片。有一个容易忽略的地方TM1640虽然叫两线接口但它不是标准I2C。它只有CLK和DIN两根线没有设备地址没有ACK应答本质上是一种自定义的同步串行协议。很多人用I2C的思维去读它自然会卡住。1.2 为什么有595还要用TM1640有人会问几个74HC595级联不也能驱动数码管吗确实能595的方案更常见但有个痛点595本身没有显示存储器MCU要不停地把显示数据扫进去而且数据量一大CPU就被显示刷新占住了。TM1640内部自带显示寄存器数据写进去之后芯片自己负责扫描MCU写一次就不用管了。省下来的CPU时间对单片机项目来说非常宝贵。和MAX7219对比TM1640的优势是便宜、两线、引脚少缺点是内部没有BCD译码所有段码都要自己算。不过在工程里这反而不是坏事自定义符号和特殊段会更灵活。这块芯片不挑主控51、STM32、STM8、PIC、AVR都能驱动时序要求也不苛刻GPIO模拟完全够用。2. 解压后用三分钟看懂驱动源码时序与指令才是核心从压缩包里拿到的驱动通常就是TM1640.c、TM1640.h、一个main.c示例可能还有一份芯片手册。如果直接拿过来用多半会遇到不亮、乱码、亮度异常等问题。这时候不需要急着查硬件先把源码里的时序和指令搞懂问题基本就解决了一半。2.1 起始、结束和字节传输的时序细节TM1640的数据传输是LSB first也就是最低位在前。每一个CLK的上升沿DIN上的电平被锁存进去。起始信号和结束信号都发生在CLK为高的状态下起始是CLK高时DIN由高变低结束是CLK高时DIN由低变高。写代码的时候最常见的错误是把起始条件写成“先把DIN拉低再拉CLK高”这样芯片根本识别不到起始位后面发什么都不会有反应。标准写法应该是static void tm1640_start(void) { TM1640_DIN 1; TM1640_CLK 1; TM1640_DIN 0; } static void tm1640_stop(void) { TM1640_CLK 0; TM1640_DIN 0; TM1640_CLK 1; TM1640_DIN 1; }字节发送同样要注意低位在前static void tm1640_write_byte(uint8_t dat) { for (uint8_t i 0; i 8; i) { TM1640_CLK 0; if (dat 0x01) { TM1640_DIN 1; } else { TM1640_DIN 0; } dat 1; TM1640_CLK 1; } }如果驱动包里的代码是高位在前或者时序里没有先拉低CLK再写DIN那这块屏大概率点不亮。所以拿到源码的第一步就是对着芯片手册核对这两点。2.2 三条指令数据指令、地址指令、显示控制指令TM1640的指令分三类理解了这三条指令整个驱动就通透了。数据指令设置一般发0x40表示写显示寄存器时地址自动加一。也有驱动发0x44表示固定地址写。我习惯用0x40因为连续写16个字节缓冲的时候效率最高不需要每次都重新指定地址。地址指令起始地址是0xC0。TM1640内部显示寄存器有16个字节地址范围0xC0到0xCF对应16个SEG和8个GRID的LED矩阵。很多人不知道这个范围写数据超过0xCF之后地址会循环就会出现“第一位数码管跟着最后一位乱跳”的怪现象。显示控制指令范围是0x80到0x8F。这个指令里bit3控制显示开关低3位控制亮度等级。注意0x80到0x87都是关显示的从0x88开始才是开显示亮度是0x88、0x89一直到0x8F共8级。好多驱动包看起来代码没问题就是不亮最后排查发现是只写了数据忘了发0x88这条开显示指令。三条指令的用途整理成一张表比较直观指令类别常见数值作用数据指令0x40 / 0x44设置写数据模式自动地址或固定地址地址指令0xC0 - 0xCF指定显示寄存器起始地址显示控制0x88 - 0x8F开显示并设置亮度低3位为亮度等级2.3 驱动代码里为什么有一段“空 STOP”有的驱动源码会在写地址之前先执行一次START、发0x40、再STOP然后再执行一次START、发0xC0、连续写数据。很多人觉得多余其实这是协议要求0x40是设置数据模式0xC0是设置地址这是两条独立的指令中间必须用STOP分隔。如果直接把0x40和0xC0连着发芯片会把0x40C0当成一串数据自然就错乱了。我在移植的时候会特别留意这个细节。压缩包里的驱动如果少了中间那个STOP要么是作者笔误要么是芯片版本有差异稳妥的做法是自己补上。这个时序问题也是TM1640驱动最常见的Bug来源。3. 从一个最小驱动看移植思路拿到代码包后怎么改到自己的板子上把TM1640驱动从压缩包移植到自己的工程不会像改个引脚宏那么简单。尤其是单片机型号不同、编译环境不同、延时函数不一样的时候直接编译通常会报错或者表现怪异。这里我整理了一套自己的移植流程基本能一次调通。3.1 改引脚、改头文件别忽略三个隐藏点驱动包里一般会在头文件里定义CLK和DIN对应的IO口比如有的写成P1.0、P1.1这是51单片机的写法。换成STM32就要改成立即数或者通过一个引脚映射宏来切换。除了引脚定义还有三个隐藏点容易被忽略第一CLK和DIN的初始状态。初始化时CLK和DIN都应该拉低如果其中一个处于高电平后续的起始信号就会失灵。第二因为TM1640的信号线DIN是双向的虽然只用到写方向GPIO模式要配置成推挽输出不能配置成开漏输出否则高电平可能拉不上去。第三芯片手册对CLK最高频率有限制一般GPIO模拟不会有问题但如果用SPI硬件去控制时钟分频设置得太快芯片会收不到正确数据。3.2 一个能点亮8位数码管的最小实现下面这段是我调通过的最小代码在STM32上把PB0接CLK、PB1接DIN使用共阴数码管。先看初始化#include stm32f1xx_hal.h #define TM1640_CLK_PORT GPIOB #define TM1640_CLK_PIN GPIO_PIN_0 #define TM1640_DIN_PORT GPIOB #define TM1640_DIN_PIN GPIO_PIN_1 #define TM1640_CLK_L() HAL_GPIO_WritePin(TM1640_CLK_PORT, TM1640_CLK_PIN, GPIO_PIN_RESET) #define TM1640_CLK_H() HAL_GPIO_WritePin(TM1640_CLK_PORT, TM1640_CLK_PIN, GPIO_PIN_SET) #define TM1640_DIN_L() HAL_GPIO_WritePin(TM1640_DIN_PORT, TM1640_DIN_PIN, GPIO_PIN_RESET) #define TM1640_DIN_H() HAL_GPIO_WritePin(TM1640_DIN_PORT, TM1640_DIN_PIN, GPIO_PIN_SET) static void tm1640_start(void) { TM1640_DIN_H(); TM1640_CLK_H(); TM1640_DIN_L(); } static void tm1640_stop(void) { TM1640_CLK_L(); TM1640_DIN_L(); TM1640_CLK_H(); TM1640_DIN_H(); } static void tm1640_write_byte(uint8_t dat) { for (uint8_t i 0; i 8; i) { TM1640_CLK_L(); if (dat 0x01) { TM1640_DIN_H(); } else { TM1640_DIN_L(); } dat 1; TM1640_CLK_H(); } }然后是显示缓冲写入和开显示void tm1640_write_buf(uint8_t *buf, uint8_t len) { tm1640_start(); tm1640_write_byte(0x40); // 自动地址增加 tm1640_stop(); tm1640_start(); tm1640_write_byte(0xC0); // 起始地址 for (uint8_t i 0; i len; i) { tm1640_write_byte(buf[i]); } tm1640_stop(); } void tm1640_display_on(uint8_t brightness) { tm1640_start(); tm1640_write_byte(0x88 | (brightness 0x07)); tm1640_stop(); }3.3 段码表怎么填bit位与引脚的真实对应关系段码表是移植过程中最考验耐心的地方。TM1640的显示寄存器每个字节对应一个GRID上某几个SEG的状态代码里buf[0]、buf[1]这些下标和数码管哪一位、哪个段对应完全取决于PCB上怎么接线不同厂家的模块定义可能不一样。我习惯的做法是先用一个测试程序把buf[0]写成0xFF其他位清零看第一位数码管亮了哪些段。再分别把每个bit置1记录下a、b、c、d、e、f、g、dp分别对应buf[0]的bit0到bit7中的哪一位。测试完画一张映射表再把0到9的段码按这张表生成。网上有些段码表是0x3F、0x06这种标准共阴码但直接用往往不对就是这个原因。一个基础段码表假设bit0对应a段bit1对应b段以此类推长这样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 0x77, // A 0x7C, // b 0x39, // C 0x5E, // d 0x79, // E 0x71, // F };如果显示出来偏亮或者模糊也可以把段码逐位取反试试有些恒流驱动模块用反向逻辑驱动段选。4. 点亮之后才是麻烦的开始亮度、段码与电源这几个坑驱动代码跑通、数码管亮起来只是第一步。真正开发产品的时候亮度不均、显示乱码、上电暗亮等问题才是劝退新手的地方。这一部分我把实测中遇到过的典型问题按频率排个序逐个说说排查思路。4.1 上电不显示八成是忘了开显示或者地址越界上电不亮是最常见的现象。代码写完了数据也写了屏就是黑着。这时候先检查有没有调用tm1640_display_on(3)这类函数。TM1640上电默认状态是关显示的必须在初始化流程最后发一次0x88以上的显示控制指令。很多驱动包里没有这个函数需要自己补上。第二个高发原因是地址越界。显示寄存器只有16个字节如果写入的数据超过16字节地址会回绕后续数据会覆盖到前面的寄存器上表现就是某一位突然跟着另一位变化。排查方法很简单把写入长度固定为16不足的部分用0x00补齐看看现象是否消失。4.2 亮度不均和残影先查电源再查扫描时序TM1640本身带8级亮度调节如果所有位都偏亮或者偏暗调0x88到0x8F就行。但如果是“前几位亮、后几位暗”这种不均匀现象问题一般出在电源上。因为TM1640是扫描方式每一次只点亮一个GRID如果电源带载能力不足扫描到电流大的段时电压会跌落这段就显得暗。解决方法是在芯片电源引脚附近加100到220uF的电解电容再并联一个0.1uF的瓷片电容用于滤高频干扰。如果数码管段数多、亮度要求高建议GRID扫描的公共端不要直接接地而要采用独立供电或者加粗PCB走线降低压降。至于残影常见原因是在关显示之前清空了显示缓冲区但清空指令本身没有发送完整导致芯片内部还保留着上一帧数据。稳妥的关闭流程是先发0x80关显示再清空缓冲区数据并写入最后再开显示。顺序反了就会出现关灯后还能看到微弱笔画的现象。4.3 3.3V单片机和5V模块的通信问题不少成品TM1640模块是5V供电的而STM32、ESP32这类主控是3.3V电平。直接把IO连上去短时间能工作时间一长或者温度一高通信就可能不稳定。原因是3.3V高电平对5V芯片来说余量不足也容易受到电磁干扰。我试过在CLK和DIN上各串一个1k电阻然后接5V上拉虽然不算最优方案但能解决大部分不稳的问题。如果是批量产品建议加电平转换芯片或者直接选择3.3V供电的模块。时钟线尽量短不要在CLK线上并接长飞线否则毛刺会导致数据错位。4.4 段码反的、显示花屏映射关系和位顺序要实测确认显示花屏、数字不对大概率是段码表与引脚映射不匹配。TM1640的SEG引脚顺序和显示寄存器bit位之间不同的模块处理方式不同。有的模块把SEG1接到bit0有的模块内部做了翻转有的模块在PCB上重新排过线序。这时候不要猜直接用一盏一盏测的笨办法。把buf[0]写成0x01记录亮的是哪段再写0x02、0x04一直到0x80记录所有段的对应关系然后生成自己的段码表。整个过程十分钟不到却可以省掉后面几小时的调试时间。这个习惯我一直保留着几乎适用于所有LED驱动芯片。5. 别只盯着这一颗芯片从驱动到选型的几点参考把TM1640调通之后我反而更愿意谈选型。因为很多项目做到一半发现功能不够比如需要按键扫描、需要更多位数、需要更简单的接口这时候再换芯片驱动又得重写。提前想清楚需求能省很多返工时间。5.1 TM1640和TM1638、MAX7219怎么选经常有朋友问都叫天微的LED驱动TM1640和TM1638有什么区别。简单说TM1640偏纯显示适合数码管和点阵TL和DIN两线就够。TM1638带按键扫描支持8个SEG、8个GRID、3个按键扫描线一个芯片既能显示又能读按键面板类产品很常用。如果项目里既要有数码管显示又要按键选TM1638会更快。MAX7219则是SPI接口速度快、级联方便适合LED点阵数量多、需要高速刷新的场景。它的缺点是贵而且一颗只能驱动8位8段数码管大点阵要好几颗级联。价格敏感、显示内容固定、位数不超过8位TM1640是性价比之选。对比项TM1640TM1638MAX7219接口CLK DINCLK DINSPI最大LED点128点64点 按键扫描64点按键扫描不支持支持不支持显示缓冲有有有典型场景数码管、点阵显示显示 按键面板点阵、级联显示5.2 多片TM1640级联共享CLK单独控制DIN一个TM1640管8位数码管有时候项目要16位甚至更多。级联和I2C不一样TM1640没有设备地址所以不能像I2C设备那样挂在同一对线上靠地址区分。我用的方案是所有TM1640共用CLKDIN各自接一个IO。发送数据时芯片对DIN敏感所以一次只操作一片先把其他片的DIN拉低或拉高确保它们不会误收数据。这样写驱动也不复杂把显示函数扩展成带片选参数的版本就行。还有一种更快的做法是直接用两个独立的CLK和DIN各自驱动互不干扰。缺点是IO占用多一点。实际项目中如果位数超过16位我一般直接换TM1629这类更大点阵的芯片而不是无限制拼接TM1640。5.3 手头这份驱动包怎么变成自己的模板最后说点小经验。我建议拿到任何芯片的驱动包不只是TM1640不要直接用而是花一个小时做三件事第一把引脚相关的宏全部改成与硬件平台解耦的接口方便以后换板子第二整理一份段码表和寄存器映射说明存到项目文档里省得下次重新测第三把上电初始化、写缓冲、开显示、关显示、调亮度这几个API单独拆出来做成一个完整的上层接口。做完这三步以后再做带数码管显示的项目直接把这个模板复制过去改改引脚和段码表就能用。尤其是TM1640这类芯片市面上很多项目还在用手里有份靠谱的模板比临时去网上扒一个“TM1640驱动程序.rar”要省心得多。本文还有配套的精品资源点击获取
返回列表