
前阵子在帮朋友做一块电梯楼层广告屏的显示板需求比较头疼一屏里要同时显示数字、英文字母还要能播几个简单动画帧。7段数码管放不下字母8×8点阵又太小单颗MCU直接扫16×16点阵还要兼顾灰度刷帧和定时器直接把人干废。后来把目光放到16段×9位的驱动方案上找到了TM2666这颗芯片共阴极、16段9位、144点阵列、每点独立8bit灰度还带恒流源基本把传统方案里最占资源的活全包了。这篇文章就当是这颗芯片的摸底笔记从原理到寄存器再到硬件布线和实测踩坑一次说清楚。这颗芯片适合谁看如果你正在做数码管段码显示、LED点阵屏、仪表刻度显示或者想用单片机做一块小尺寸灰度点阵屏又不想在74HC595三极管限流电阻的堆料方案里挣扎那这篇内容应该能帮你省下不少折腾时间。我会尽量把驱动原理、代码结构、硬件细节和排障过程都讲透最后也会聊聊它在实际项目中到底值不值得用。1. 这个尺寸到底意味着什么16段、9位、144点不是随便写的1.1 先看16段×9位能显示什么传统7段数码管大家都熟0-9、小数点再加几个字母都勉强。但16段数码管的结构完全不同除了横竖笔段还多了4个斜角段总共16个段。这意味着它可以完整显示26个英文字母的大小写、数字以及大部分常用符号显示效果比7段管接近点阵屏。9位就是9个这样的16段字符位一排排开做英文广告词、电梯楼层加提示字母、仪表状态字都非常合适。如果换个角度不把它当数码管而是把16个SEG当作16列、9个GRID当作9行那就是一块9×16144点的LED点阵。这个点阵做不了复杂中文单字16×16需要两片级联但显示数字、字母、简单图形、音符、电池电量、音量柱、跳动的波形绰绰有余。这就是这个尺寸两头通吃的价值硬件不用改软件里切换字模映射就行。很多人一开始盯着144点阵觉得小实际上把它当9位16段字符屏来用正好覆盖了7段和点阵之间的空白地带。1.2 传统方案要堆多少料TM2666替我省了什么先算一笔老方案的账。要驱动9×16144个LED并且每个LED都能独立控制亮度传统做法大概是2颗74HC595级联做16路列输出9颗三极管或MOS管做行扫描144颗限流电阻一个LED一颗贴片0402/0603能堆到怀疑人生MCU定时器不停刷新行扫描灰度PWM靠软件在中断里一个个点处理这套方案在8×8点阵上还勉强能玩一旦到了144点还要灰度MCU的刷新负担会非常重。更别提限流电阻带来的亮度不一致问题——同一批LED都有Vf差异电阻限流下每个点电流不同亮度就是花的。TM2666的方案一下子把这些全收进去了项目传统方案TM2666方案列驱动74HC595×2芯片内置16路恒流SEG行扫描三极管/MOS×9芯片内置9路GRID扫描限流电阻144颗仅1颗Rext设定基准电流灰度控制软件定时器PWM每点8bit灰度RAM芯片内部PWM动态扫描MCU中断不断刷新写入灰度数据后芯片自动扫描PCB面积大非常小这颗芯片本质上是一个显示协处理器MCU只需要把一帧144字节的灰度数据通过3线串口灌进去剩下的动态扫描、恒流控制、灰度脉宽调制全部由芯片自己完成。省下来的MCU资源可以去跑协议栈、按键、传感器这才是它最大的价值。1.3 单点辉度不是一个营销词单点辉度可控是这个芯片和普通段码驱动芯片最核心的区别。老一代LED驱动芯片如TM1628、TM1629虽然也能控制整体亮度等级但那是整屏一起变亮变暗所有点只有亮/灭两种状态加几档整体亮度做不了逐点灰度。TM2666是给每个点分配一个8bit灰度值0x00到0xFF共256级。有了这个能力显示效果就完全不一样了。数字时钟的秒点可以平滑呼吸音量柱可以像流水一样渐变绘制曲线时可以做到抗锯齿效果甚至可以直接显示一个小型LOGO的灰阶渐变。这一点在后续2.3章节里会详细展开原理。2. 共阴极144点阵的扫描和灰度是怎么工作的2.1 共阴极的驱动方向SEG负责给GRID负责放LED点阵驱动有两种基本接法共阳极和共阴极。很多人玩数码管时纠结过共阴段码表和共阳段码表不一样这个差异在驱动芯片上同样关键。共阳极点阵里所有LED的阳极接在一起驱动芯片的GRID输出高电平提供电流SEG做灌电流吸收共阴极点阵则是所有LED的阴极接在GRID上SEG输出高电平电流GRID接地导通。TM2666是共阴极结构也就是说SEG16路恒流源输出点亮时向LED注入恒定电流GRID9路扫描输出选中某一行时将该行公共阴极接通到地这里有个容易犯的错误网上很多现成的点阵驱动代码是按共阳极设计的如果照搬到共阴极芯片上会发现显示完全反向或者根本不亮。硬件接法决定了软件高低电平逻辑务必先确认自己的LED阵列是共阴极接法再动手写代码。2.2 一帧画面怎么扫出来144个点如果同时独立控制需要144根独立驱动线这在封装和成本上都不现实。所以几乎所有的LED驱动芯片都采用动态扫描复用方式。TM2666内部是这样工作的一个扫描周期内GRID1到GRID9依次导通同一时刻只有一行GRID被接通而该行对应的16个SEG根据灰度数据决定是否点亮以及亮多长时间。一行扫完切下一行9行全部扫完就是一帧画面。因为人眼有视觉暂留效应只要帧率够高看到的画面就是完整的不会感觉到行与行之间的切换。这里有个数据量的计算对后面评估通信负载很有用显示数据144点 × 8bit 1152bit ≈ 144字节如果按60帧/秒刷新144 × 8 × 60 69120bit/s约67.5kbps即便按120帧/秒加倍也才135kbps这个速率对3线串口来说非常轻松跑个1MHz的SPI都有将近十倍的余量。所以MCU完全不需要为刷屏发愁一帧数据一次性发完就完事芯片自己会滚动刷新GRID。2.3 单点辉度恒流电流×PWM占空比亮度控制的基本公式是LED亮度 驱动电流 × 点亮时间。TM2666的灰度实现就是把这两个维度都控制住。电流维度16路SEG都是恒流源电流大小由外部电阻Rext统一设定比如设定10mA那每个点亮时的电流就是10mA不会因为LED的Vf差异而波动。这一点保证了整屏的亮度一致性。时间维度芯片内部为每个点维护一个8bit灰度值在扫描周期内灰度值决定这个点的有效点亮时间。灰度0x00表示整个扫描时间内不亮0xFF表示全程满亮0x80就是半亮。这种恒流占空比的组合是LED灰度显示的标准做法。恒流保证单点色彩和亮度准确PWM保证灰度等级细腻。和通过改变电流大小来调光相比PWM在低亮度区间的线性度更好也不会出现低电流下LED色温漂移的问题。从驱动代码的角度看你只需要把每个点的灰度字节写进芯片的显示RAM剩下的PWM生成和扫描都是芯片内部电路完成。这也是TM2666和直接用595搭的方案的体验差异用595做灰度PWMMCU要在中断里按位刷新每一行一个定时器中断9行就是9个刷新点用TM2666你只写144字节写完了该干嘛干嘛。2.4 为什么恒流源能让亮度更均匀用限流电阻的老方案有个物理上的硬伤LED的正向压降Vf随批次、温度、电流变化而限流电阻的方案本质是电压源限流电阻电阻上的压降 电源电压 - LED的Vf。Vf不同电阻上的压降就不同电流自然不同亮度就散。恒流源不关心LED的Vf是多少只要供电电压足够余量它就在内部通过反馈环路强制输出设定好的电流。这使得即使144颗LED的Vf分布有差异流过每颗LED的电流依然一致亮度均匀性在肉眼观感上会好很多。这也是为什么恒流两个字在LED驱动领域的分量这么重。3. 寄存器映射和通信时序驱动代码的地基3.1 引脚定义与最小系统连接这里以TM系列产品的常见引脚定义做参考具体命名和引脚号务必以官方数据手册为准。最小系统大概是这样的引脚功能连接建议VDD逻辑电源3.3V或5V就近100nF退耦GND地主地平面STB片选/锁存MCU GPIO空闲时高电平数据操作期间拉低CLK时钟MCU GPIO或SPI_SCKDIO串行数据MCU GPIO或SPI_MOSIREXT恒流基准电阻接一颗精密电阻到GNDSEG1-SEG16段驱动输出接LED阳极GRID1-GRID9位扫描输出接LED阴极注意这个DIO在级联的时候会变成双向DIN/DOUT的结构。级联时前一片的DOUT接后一片的DIN主机发出的数据像一串珠子一样流经所有芯片这个在4.5里再细说。3.2 命令系统三类指令都要分清从TM16xx系列产品的命令设计习惯来看TM2666大概率也是三个层次的命令结构数据命令、地址命令、显示控制命令。注意以下命令字节的具体数值仅为TM系列惯例用于说明结构实际以你拿到的芯片手册为准。数据命令设置数据传输方向和模式。比如0x40表示写显示寄存器地址自动递增通常在开始写灰度数据前先发一次。地址命令设置起始地址。比如0xC0表示从显示RAM地址00h开始写后面的数据会按地址自动递增依次填充。显示控制命令控制显示开关和亮度控制。比如0x80~0x8F区间高字节部分决定显示开/关低字节部分控制整体显示亮度等级或软件消隐。理解了这三类命令写驱动就变成一件很简单的事先数据命令再地址命令然后连续写144字节灰度数据最后发显示控制命令开显示。3.3 写一帧灰度数据的完整流程我用一个伪代码级的C语言示例把整个写帧流程串起来以STM32的GPIO模拟3线时序为例#define STB_HIGH() HAL_GPIO_WritePin(STB_GPIO_Port, STB_Pin, GPIO_PIN_SET) #define STB_LOW() HAL_GPIO_WritePin(STB_GPIO_Port, STB_Pin, GPIO_PIN_RESET) #define CLK_HIGH() HAL_GPIO_WritePin(CLK_GPIO_Port, CLK_Pin, GPIO_PIN_SET) #define CLK_LOW() HAL_GPIO_WritePin(CLK_GPIO_Port, CLK_Pin, GPIO_PIN_RESET) #define DIO_HIGH() HAL_GPIO_WritePin(DIO_GPIO_Port, DIO_Pin, GPIO_PIN_SET) #define DIO_LOW() HAL_GPIO_WritePin(DIO_GPIO_Port, DIO_Pin, GPIO_PIN_RESET) // 发送一个字节低位先出TM系列常见为LSB first以手册为准 static void tm2666_send_byte(uint8_t dat) { for (uint8_t i 0; i 8; i) { CLK_LOW(); if (dat 0x01) DIO_HIGH(); else DIO_LOW(); CLK_HIGH(); dat 1; } } // 写入一帧灰度数据buf为144字节对应144个点 void tm2666_write_frame(uint8_t *buf) { // 第1步数据命令写显示寄存器、地址自动递增 STB_LOW(); tm2666_send_byte(0x40); STB_HIGH(); // 第2步地址命令从RAM起始地址开始 STB_LOW(); tm2666_send_byte(0xC0); // 第3步连续写144字节灰度数据 for (uint16_t i 0; i 144; i) { tm2666_send_byte(buf[i]); } STB_HIGH(); // 第4步显示控制命令开显示并设置整体亮度 STB_LOW(); tm2666_send_byte(0x8F); // 开显示最高显示等级具体值以手册为准 STB_HIGH(); }看到没有整个写帧过程就是拉低STB、发数据、拉高STB。STB低电平期间数据必须在CLK的边沿被芯片采样STB的上升沿是命令/数据被锁存生效的时刻。很多通信异常都出在这个STB的时序上后面踩坑部分会专门讲。3.4 上电初始化顺序和时序坑上电初始化不能一上来就开显示顺序错了容易出现花屏亮一下或者乱码。我通常这样做上电后先把STB、CLK、DIO三个引脚初始化为确定的GPIO状态STB高、CLK低、DIO低延时10ms左右等电源稳定发一条显示控制命令先把显示关掉0x80这个级别的命令写144字节的灰度数据全部填充0x00清空显示RAM再发显示控制命令开显示正常运行后每帧更新时只发数据不需要每次都开关显示这个顺序是为了避免上电瞬间芯片内部RAM里是随机数据如果显示已经打开了RAM里的乱数会全部显示出来肉眼看就是雪花闪烁一下。时序方面容易踩的坑主要是CLK极性。有的芯片在CLK上升沿采样数据有的在下降沿如果拿通用SPI模块硬发很可能方向搞反。我建议第一版先用GPIO模拟方式调通把时序确定无误后再换成硬件SPI提速这样出问题了好排查。4. 硬件设计里最容易被忽略的六个细节4.1 恒流设定电阻Rext是整个亮度系统的基准TM2666的16路SEG恒流源共用一个基准电阻Rext这个电阻的精度直接影响所有LED的电流一致性。典型架构里恒流值 I K × Vref / RextK是芯片内部的电流镜像倍数Vref是内部基准电压。举个例子如果芯片手册给出 Vref 1.2VK 10你希望每路恒流20mA那么Rext K × Vref / I 10 × 1.2 / 0.02 600Ω实际取标称值620Ω电流略小一点点但误差在可接受范围。如果你希望精确一点可以用两个电阻并联凑出目标值。这里的关键是Rext必须用高精度低温漂电阻1%甚至0.1%精度普通5%碳膜电阻会让同一批板子的亮度都不一致。4.2 峰值电流与电源去耦这个芯片静态看似不起眼但动态电流峰值比想象中大。假设每路SEG恒流20mA某一行16路同时全亮时瞬时电流是16 × 20mA 320mA而这320mA还带着陡峭的开关沿因为灰度PWM和GRID扫描切换都在不断开关电流。这是典型的脉冲负载如果电源去耦不到位VDD电压会被拉出明显纹波表现出来就是显示闪烁或者灰度不均匀。我建议的电容配置是VDD引脚就近放一个100nF陶瓷电容再加一个10µF到22µF的陶瓷或钽电容LED供电入口再放一个100µF铝电解电容做蓄能。电源走线尽量短粗不要在芯片附近绕圈。4.3 GRID回流走线的压降会直接搞垮亮度动态扫描结构里GRID这一路承担的是该行所有LED的回路电流也就是说GRID1到GRID9的走线会周期性地流过0到320mA的电流。如果走线太长太细线路电阻带来的压降会让恒流源进入输出电压不够的状态。我在测试板里曾经把GRID走线拉得很长绕过一堆过孔结果那一行的亮度明显比其他行暗。用万用表一量GRID路径上的压降有将近0.4V而其他行只有0.1V。恒流源为了维持设定电流会尽量抬高输出电压但一旦超过它的输出能力电流就维持不住了亮度必然下降。解决思路很简单GRID走线加宽、过孔数量减少、尽量让LED阵列的阴极公共端靠近芯片的GRID引脚。如果布线实在绕不开可以考虑在LED阵列旁边加一层铺铜做回流。4.4 LED正向压降与恒流源的输出电压余量恒流源有一个合规电压的概念。它要正常工作输出端必须留出足够的电压余量。例如芯片手册可能会写SEG输出电压范围为0.7V到VLED-0.5V意思是LED的正向压降不能太高否则恒流源没有足够空间去稳压。不同颜色LED的Vf差异很大红色LED大约1.8-2.2V绿色大约2.8-3.2V蓝色和白色大约3.0-3.6V。如果你在同一块板上混用不同颜色LED要特别留意它们的Vf是否都在芯片的合规电压范围内。一块点阵屏如果全用蓝光LEDVf普遍偏高供电电压不够的话恒流源会顶到天花板电流掉下来亮度就不对了。4.5 级联扩展TM2666支持多片级联做更大的显示阵列。级联时从前一片的DOUT接到后一片的DIN主机一次性把2片甚至多片的灰度数据全部发出去最后再拉一次STB让所有芯片同时锁存。级联有个数量限制要留意芯片手册会规定最大级联数这个限制通常是通信线上的总负载电容决定的。级联太多片以后CLK/DIO信号会变差建议在CLK和DIO线上各加一个33Ω到100Ω的串联电阻做阻尼减少过冲。多片级联时每片芯片的Rext可以独立设置这样不同屏幕区域可以有不同的最大亮度这个灵活性在一些特殊显示需求里非常有用。4.6 IO状态与上电时序MCU刚上电时IO口经常处于不确定状态可能是高阻、可能是弱上拉、也可能随机翻转。如果这个阶段STB、CLK、DIO在乱跳TM2666会误收随机数据导致上电瞬间出现乱码显示。推荐做法是STB引脚加一个10kΩ上拉电阻默认高电平不选通CLK加一个10kΩ下拉电阻默认低电平时钟不动作DIO也加一个上拉或下拉固定死空闲电平。MCU固件启动后第一步就初始化这三个引脚为输出状态然后再开始正常通信。这个小成本能避免大量上电花屏问题。5. 实测踩坑亮度不均、残影、乱码的排查过程5.1 问题一某一位整体偏暗怎么排查现象9个GRID中第5行整体亮度明显低于其他行其他行完全正常。我一开始怀疑是芯片GRID5引脚虚焊但补焊后故障依旧。接着用示波器量GRID5引脚波形发现波形形状正常但高电平期间有将近0.5V的电压抬升。顺着PCB走线一查GRID5从芯片出来以后绕了两个过孔走线长度比其他GRID长了一倍线路电阻大概0.6Ω320mA峰值电流一过压降接近0.2V再加上LED阵列端连接器的接触电阻最终压降到了0.4V以上。这种情况下恒流源已经超出了工作电压范围电流维持在设定值的80%左右亮度差就出来了。解决方法是把GRID5的走线重新加宽并增加铺铜实测压降降到0.1V以下亮度恢复一致。从这个案例得到的教训是动态扫描电路的GRID走线不能只当信号线处理它本质上是一条周期性大电流的电源线必须按功率线标准来设计。5.2 问题二低灰度区域明显跳变还有条纹闪烁现象显示渐变色的时候低灰度部分灰度值0x10以下会出现肉眼可见的跳变感某些灰度值下整屏出现水波纹一样的条纹。这个问题的根源比较复杂按我的理解分两个层面第一是灰度PWM和扫描刷新之间的拍频。灰度PWM本身有一个调制频率GRID扫描也有一个切换频率当两个频率的差落在人眼敏感区间就会产生可见的明暗条纹。解决思路是提高刷新率到120Hz以上或者在灰度调制频率和扫描频率之间拉开差距。第二是低灰度下LED的发光占空比太窄。比如扫描一行只占1/9周期灰度值又只有十几级实际点亮时间只有几微秒LED刚导通还没稳定就切走了。这个状态下人眼对亮度的感知是非线性的会觉得这个低灰度档位凹下去。我的做法是做一个软件gamma校正表在低灰度区间多分配一些档位。5.3 问题三切换画面时出现明显残影现象从全亮画面切换到全灭画面时画面消失的瞬间能看到上一帧的影子缓慢变暗大约几十毫秒才完全消失。残影的核心原因是LED结电容存储的电荷没有路径释放。GRID切换那一瞬间SEG已经停止供流但LED两端还残留电压余辉被视觉暂留捕捉到看起来就是拖影。排查时我先确认了刷新率足够高100Hz排除扫描慢的因素。然后重点看切换时序发送新帧数据前先发一个显示关命令等数据写完再发开显示命令让LED有明确的放电窗口。半帧数据被拉高STB锁存导致的半截帧也会产生类似残影的异常如果是这个原因需要保证144字节一次性发完再拉STB。软件上处理完后我把GRID输出端到地之间加了一颗1kΩ到4.7kΩ的泄放电阻残影进一步减轻。共阴极结构里这个电阻还有一个附加好处GRID悬空时不会被耦合噪声误触发。5.4 问题四上电瞬间花屏闪烁持续不到100ms现象每次给板上电屏幕会先闪烁出一堆随机亮点的图案然后才进入正常显示。这个就是3.4提到过的IO默认状态问题。我用逻辑分析仪抓了上电瞬间STB/CLK/DIO三根线的波形看到MCU内核还没跑起来时三个引脚在电源上升过程中来回抖动芯片被这些毛刺当成了有效命令内部RAM被写入了随机值显示RAM内容起来以后自然是一片花。处理办法分两层硬件上STB上拉10kΩ到VDDCLK下拉10kΩ到GNDDIO下拉10kΩ到GND软件上复位后第一时间配置GPIO先关显示再清RAM最后开显示改完以后上电花屏问题彻底消失。这个问题的排查链路比较简单但很多人会习惯性地去怀疑芯片复位电路或者电源时序绕一大圈才发现是IO电平问题。5.5 关于gamma校正的经验补充8bit灰度看起来256级很充裕但人眼对暗部亮度的分辨率远高于亮部。如果直接把灰度值线性映射到PWM占空比实测低亮度区间会出现明显的台阶感暗部过渡不平滑。标准做法是做一个gamma查找表把线性目标亮度映射到实际灰度值实际灰度 255 × pow(目标亮度 / 255, 1 / gamma)其中gamma通常取2.2到2.8具体数值要看你的LED亮度和使用环境。我的习惯是先跑一个线性扫描灰阶条肉眼观察暗部跳变的位置再调整gamma值做参数扫描。这个gamma表在MCU里就是一个256字节的const数组每帧数据经过去查表转换再写入TM2666。对144点的小屏来说这个转换运算量几乎可以忽略但对显示效果的提升非常明显。6. 选型建议什么项目适合用TM2666什么情况还是用老方案聊了这么多原理和踩坑最后回到一个很实在的问题这东西到底值不值得用我个人觉得如果你的项目满足以下任意一个条件TM2666这类芯片很值得考虑需要显示英文字母和符号但7段管不够用16×9的阵列正好合适需要做9×16或16×9的小尺寸灰度点阵希望MCU不被刷新逻辑占满项目对PCB面积敏感不想堆144颗限流电阻和9颗三极管多个同规格显示单元需要级联且希望各单元独立控制亮度反过来也有几个场景我觉得没必要用只是做最简单的时钟数字显示7段管加一片TM1628就绰绰有余用不上灰度点阵尺寸很大比如64×64TM2666的144点容量太小级联数量又会受限制这时更适合用专用大点阵驱动架构项目极度够用主义、对成本极度敏感且显示内容完全不需要灰度那传统595方案确实能省一两块钱从我实际测试的感受来说这颗芯片解决的是中小尺寸灰度显示这个细分场景的痛点。它把MCU从繁琐的扫描刷新里解放出来把硬件复杂度压缩到极致得到的是快速开发、稳定显示和一致的亮度。如果你正在做相类似的产品找一个下午把驱动跑通应该会有一种这活终于不用自己扛了的感觉。最后说一个小技巧调试这种带灰度驱动的芯片先用灰度0xFF和0x00交替的测试图案确认所有SEG和GRID都能正常点亮和熄灭再用0x80确认灰度方向是否正确最后再跑真正的显示内容。分步验证出问题的时候能省下大量的排查时间。