1. 项目概述与驱动芯片选型思路1.1 TM1622/HT1622是什么能做什么我做单片机开发这些年段码液晶屏一直是绕不开的一个东西。电饭煲上的定时显示、温控器上的温度数字、血压计上的数值、电动车仪表上的电量条这些看起来不起眼的小屏幕背后基本都是这类段码液晶驱动芯片在工作。TM1622和HT1622就是这类芯片里最常用的两个型号它们做的事情本质上是一样的用少量的MCU引脚通过串行协议控制一块液晶屏上的几十上百个显示段让数字、字母、图标按需要亮起来。这次要聊的项目就是把TM1622/HT1622驱动完整移植到STM32平台上的全过程包括代码架构怎么搭、底层时序怎么写、硬件电路怎么设计、PCB布局要注意什么以及在调试过程中我实打实踩过的一些坑。不管你是刚接触段码屏的新手还是需要在项目里快速用上这类芯片的工程师这篇文章应该都能给你省下不少时间。以TM1622为例它内部集成了显示RAM、偏压发生电路、振荡器和按键扫描逻辑只需要4根线——CS、WR写时钟、DATA数据和KEY按键输入就能驱动最多128个点即32个8段数字或者更复杂的自定义符号。也就是说MCU这边只需要3个GPIO做输出、1个GPIO做输入就能控制一整块液晶屏。相比直接驱动点阵屏占用的引脚资源少了一个数量级。1.2 为什么在项目里选段码屏而不是点阵屏很多新手会有疑问现在TFT彩屏、OLED屏这么便宜为什么还要用这种又老又土的段码屏这里我得说句公道话。段码屏能在家电和仪表领域活了几十年靠的是三个点显示功耗极低、阳光下可读性好、成本便宜到令人发指。段码液晶本身是反射式显示靠环境光就能看清内容不需要背光也能用整机功耗可以压到微安级别。这对电池供电的设备来说非常关键。我做过一个温湿度计用HT1622驱动段码屏整机待机电流不到 15μA一节CR2032电池能用一年多。要是换成了OLED或者TFT光是屏幕功耗就顶不住了。另外段码屏的显示内容是固定设计好的你把数字位、单位符号、电池图标、WiFi图标都做进玻璃里显示效果非常稳定不存在刷新率、残影、烧屏这些幺蛾子。程序侧也不需要维护复杂的字库和画点函数只需要往显示缓存里填0和1就行。所以在做量产产品时段码屏加这类驱动芯片的组合至今仍是性价比最高的方案之一。1.3 TM1622与HT1622的选型差异这两个芯片在功能上可以说是一对孪生兄弟引脚定义、指令格式、时序参数几乎一致硬件上大部分时候可以互换。我最早接触的是HT1622后来缺货直接换成了TM1622代码改动量很小。如果要细抠区别主要在这几个地方对比项TM1622HT1622显示点阵最大128点32×4最大128点32×4工作电压2.4V~5.2V2.4V~5.2V接口方式3线/4线串行3线/4线串行按键扫描支持最多8×2支持最多8×2指令格式基本相同基本相同备注国产供货稳定台系老牌实际选型时我主要看供货和价格。TM1622因为是国产芯片交期和价格通常更友好。HT1622则胜在资料多、案例多遇到问题容易搜到答案。两个芯片可以共用一套驱动代码只要把寄存器地址映射确认好就行。这个特点意味着你可以先用手头有的芯片调通软件再根据量产情况灵活切换对供应链来说是个不小的优势。2. 移植前的准备工作接口、时序与代码架构2.1 硬件连接与引脚规划在写代码之前先把硬件连接理清楚。TM1622/HT1622和STM32之间的连接非常简单典型接法如下芯片引脚功能STM32引脚说明CS片选任意GPIO推挽输出低电平有效WR写时钟任意GPIO推挽输出上升沿锁存数据DATA数据输入/输出任意GPIO推挽输出读按键时切输入双向引脚KEY按键输出任意GPIO上拉输入有按键按下时输出低电平这里有个容易忽视的点DATA引脚是双向的。写显示数据时它是输入方向对芯片而言读按键时它变成输出方向。所以在STM32侧这个引脚需要配置为开漏输出或者推挽输出并且能够在运行时切换方向。我最开始直接用了推挽输出读按键时忘了切换模式结果按键数据全是乱的。后来改成在每次操作前根据操作类型重新配置GPIO方向问题就解决了。如果你用的STM32型号支持开漏输出模式建议DATA引脚用开漏加外部上拉这样读按键时天然就是输入态代码可以少处理一些方向切换的细节。没有内部上拉的芯片记得在外部加一个10kΩ上拉电阻到VDD。2.2 时序分析与接口模式理解TM1622/HT1622支持两种串行接口模式3线去掉KEY引脚和4线带KEY引脚。这里说的3线、4线指的都是MCU到芯片的控制线数量不包括电源和地。4线模式比3线多了一个KEY引脚用来做按键唤醒和扫描。写时序的核心是CS拉低后WR的每个上升沿把DATA上的电平锁存进芯片内部移位寄存器。整个过程是高位先出MSB First每写一个字节芯片内部会把数据按位移动。这里我必须强调一个很多教程都没讲清楚的点TM1622/HT1622的数据写入不是简单按字节打包而是分成“命令字”和“显示数据”两种帧格式。命令字的特征是前3位是101后面跟9位命令参数总共12位。显示数据的特征是前3位是101中间6位是显示RAM地址后面跟4位数据总共13位。也就是说虽然芯片叫4线接口但每次操作传输的位数不是固定的8的倍数。写成代码时你要自己控制传输位数。我刚从I2C设备转过来写这个驱动时习惯性地按字节去拼数据结果发现第13位怎么都对不上。后来才反应过来必须用位操作逐位发送。这个细节如果不能理解后面调试起来会非常痛苦。2.3 驱动代码整体架构设计在动手写代码之前我建议先想清楚驱动要分层。我这次项目的代码结构分了四层硬件抽象层封装GPIO的拉高拉低、延时函数方便以后换MCU芯片底层驱动实现CS、WR、DATA的时序操作以及命令发送、数据发送显示管理层维护显示缓存数组提供段码数据的读写接口业务应用层把温湿度、时间、电量这些业务数据翻译成段码对应的位这样分层的最大好处是芯片底层驱动和显示管理是通用的以后如果从HT1622切到TM1622或者把STM32换成GD32、APM32只需要改硬件抽象层和少量底层配置。显示缓存的设计也非常重要。我把128个点按字节组织成16个字节的数组每个字节对应芯片显示RAM中的8个点。这样在业务层只需要操作数组元素完全不关心物理引脚的电平变化。3. 驱动移植核心实现与代码解析3.1 底层GPIO模拟时序的实现这块是整个驱动移植的核心基础。虽然STM32也有硬件SPI理论上可以用SPI外设来驱动TM1622/HT1622但我强烈建议先用GPIO模拟时序把整个流程跑通。原因有两个第一模拟时序的代码可读性好出了问题容易定位第二GPIO引脚选择更灵活不占用SPI外设资源。先看最基础的操作函数。这里以STM32的HAL库为例#define LCD_CS_LOW() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET) #define LCD_CS_HIGH() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET) #define LCD_WR_LOW() HAL_GPIO_WritePin(LCD_WR_GPIO_Port, LCD_WR_Pin, GPIO_PIN_RESET) #define LCD_WR_HIGH() HAL_GPIO_WritePin(LCD_WR_GPIO_Port, LCD_WR_Pin, GPIO_PIN_SET) #define LCD_DATA_LOW() HAL_GPIO_WritePin(LCD_DATA_GPIO_Port, LCD_DATA_Pin, GPIO_PIN_RESET) #define LCD_DATA_HIGH() HAL_GPIO_WritePin(LCD_DATA_GPIO_Port, LCD_DATA_Pin, GPIO_PIN_SET)然后是逐位发送的函数这里要注意先发高位static void LCD_SendBit(uint8_t bit) { if (bit) { LCD_DATA_HIGH(); } else { LCD_DATA_LOW(); } LCD_WR_HIGH(); LCD_WR_LOW(); }有了这个基础发送任意位宽的数据就很直接了static void LCD_SendData(uint16_t data, uint8_t bits) { for (uint8_t i bits; i 0; i--) { LCD_SendBit((data (i - 1)) 0x01); } }这段代码有一个值得注意的地方延时在哪里加TM1622/HT1622的WR时钟频率上限是几百kHz对于STM32跑72MHz甚至更高的主频来说GPIO翻转速度太快超出了芯片的承受范围。所以必须在每次电平翻转后加一个小的延时。我实测下来在STM32F103 72MHz下不加延时也能工作但波形质量很差抗干扰能力弱。加了几个空循环或者微妙级延时后稳定性明显提升。3.2 寄存器写入与显示缓存设计芯片内部有16×4共64个4位寄存器单元对应位寻址的显示RAM。每次显示数据写入需要先发送101命令头再发送6位地址最后发送4位数据总共13位。这里有个容易让人绕晕的地方芯片的RAM地址是从低到高排列的但我们的显示缓存数组是从左到右排列的两者之间要做一次映射。我设计了一个显示缓存数组长度为16字节uint8_t lcd_cache[16]; // 每字节对应芯片RAM的一个4位单元实际只用低4位写入一个单元的函数如下void LCD_WriteData(uint8_t addr, uint8_t data) { uint16_t frame 0x000; frame | (0x05 10); // 命令头101 frame | ((addr 0x3F) 4); // 6位RAM地址 frame | (data 0x0F); // 4位显示数据 LCD_CS_LOW(); LCD_SendData(frame, 13); LCD_CS_HIGH(); }这里我用了13位的帧结构把命令头、地址和数据一次性拼好然后用一个循环发送出去。相比逐段发送这样做代码更简洁也不容易漏位。3.3 按键扫描与闪烁功能的扩展段码屏驱动不仅仅要显示按键扫描也是这类芯片的重要功能。TM1622/HT1622的按键扫描逻辑是芯片内部周期性地对KEY线进行扫描当检测到按键动作时KEY引脚输出一个低电平脉冲。MCU这边的处理方式有两种轮询和外部中断。轮询实现比较简单在while循环里定期检查KEY引脚如果检测到低电平就进入读按键流程输出14位的按键扫描命令然后读取数据线上的按键值。外部中断方式更省CPU但要注意KEY引脚上的低脉冲持续时间很短中断处理函数里要及时读数据。闪烁功能是段码屏产品中另一个高频需求。比如温控器上“正在加热”的指示图标要每秒闪烁一次。实现方案有两种一种是在MCU侧定时让对应缓存位置0或置1然后刷新到芯片另一种是利用芯片自身的闪烁功能如果支持的话。TM1622的指令里没有独立的闪烁控制所以我在MCU侧用定时器实现void LCD_BlinkTask(void) { static uint8_t blink_cnt 0; if (blink_cnt 100) { // 100ms翻转一次 blink_cnt 0; blink_state !blink_state; if (blink_state) { lcd_cache[BLINK_ADDR] | BLINK_MASK; } else { lcd_cache[BLINK_ADDR] ~BLINK_MASK; } LCD_RefreshAll(); } }刷新整个屏幕的函数建议只在必要的时候调用比如数据变化或者闪烁切换时而不是在while循环里无脑刷。原因很简单虽然LCD_SendData写一个RAM单元只需要13个时钟周期但128个点全部刷新一遍也需要一定时间频繁刷新会增加功耗还会在电源线上引入不必要的噪声。3.4 从STM32移植到其他MCU的注意点这次项目虽然主要在STM32上调试但我也把代码移植到了GD32和APM32上验证过给准备做芯片替换的朋友一个参考。这三个MCU的HAL库和GPIO操作风格很接近大部分代码可以直接复用主要注意以下几点。时钟频率不同会影响延时函数的参数。GD32运行在108MHz、APM32运行在96MHz时同样的空循环延时时间会变短需要重新标定。引脚复用功能配置不同。STM32的GPIO初始化和GD32类似但APM32的某些引脚默认状态不同要仔细看数据手册。中断优先级分组方式有差异。如果按键用了外部中断切换到不同MCU时要重新配置NVIC。另外还有个良心建议把底层时序函数封装好后在第一次上电调试时不要急着接液晶屏。先用示波器或者逻辑分析仪抓一下CS、WR、DATA三个引脚的波形确认帧格式和时序参数完全正确后再接上屏幕调试。这一步能帮你快速隔离问题如果波形不对肯定是代码问题如果波形对了屏幕不亮才有可能是硬件或液晶配置的问题。我吃过好几次直接接屏调试的亏效率太低了。4. 硬件设计要点与PCB布局经验4.1 电源与偏压电路设计TM1622/HT1622的工作电压范围是2.4V到5.2V可以直接用3.3V或5V供电。但这里有个隐藏的坑液晶显示的对比度是由芯片内部的偏压电路决定的偏压的大小和VDD电压直接相关。如果VDD电压纹波太大屏幕上的显示内容会出现明显的深浅不均现象。我在一个项目里用DC-DC给系统供电输出3.3V纹波有大约80mV。上电后液晶屏显示内容的边缘明显发虚。后来在VDD引脚旁边加了一个10μF陶瓷电容和一个4.7μF钽电容纹波降到了20mV以内显示效果立刻正常了。所以屏幕供电这块我建议至少在芯片的VDD引脚附近放一个10μF以上的电容并且尽量靠近引脚。还有一个负责产生液晶驱动电压的引脚通常叫V1或VLCD不同厂商命名略有差异。这个引脚和VDD之间需要接一个电容典型值是0.1μF到1μF。这个电容不能省也不能随便用大容值的。我试过用10μF电容结果液晶偏压建立时间变长上电瞬间屏幕会短暂出现全显的残影。4.2 液晶屏供电与对比度调节段码液晶屏本身需要的驱动电压比逻辑电压高TM1622/HT1622内部集成电荷泵可以自己产生液晶驱动电压。在大多数应用里你不需要外接负压电路只需要在外部接好电容就行。但要注意的是液晶驱动电压的幅值直接影响显示对比度。如果你的屏幕显示很淡不要急着改程序先查一下偏压电容选得对不对。还有一个影响对比度的因素温度。液晶材料在低温下响应变慢、在高温下对比度下降这是物理特性不是芯片的问题。如果你的产品工作在宽温范围可能需要根据温度调整偏压设置。TM1622提供了偏压选择命令通过命令字可以调整偏压等级。在低温环境测试时我更倾向于把偏压稍微调高一点让显示更清晰。当然具体数值要结合屏幕的参数来不能瞎调。我做过一个户外仪表夏天暴晒后屏幕对比度明显变浅后来在固件里加了一个NTC温度检测根据温度自动调整偏压等级问题就解决了。这个方案虽然增加了几个元件但对用户体验的提升非常明显。4.3 背光电路与时序配合虽然段码屏反射式显示不需要背光但很多产品为了夜间使用还是会加背光。常见的做法是在液晶屏背面贴一片EL冷光片或者LED导光板。LED背光的设计相对简单用一颗三极管驱动LED灯串。我常用的电路是STM32的PWM引脚通过1kΩ电阻接NPN三极管基极三极管集电极接LED灯串负极LED灯串正极接VDD发射极接地。通过调整PWM占空比可以改变背光亮度。背光的控制有个时序上的细节在系统刚上电时MCU引脚默认状态不确定可能瞬间拉高三极管基极导致背光闪亮一下。为规避这个问题我在三极管基极加了一个10kΩ下拉电阻确保MCU引脚未初始化时背光处于关闭状态。另外系统进入低功耗模式前记得先把背光关闭再切睡眠否则背光电流会把你的待机功耗拉高一大截。4.4 PCB布局与走线经验段码屏的PCB布局需要注意的地方不多但每一条都挺关键。首先是芯片位置。TM1622/HT1622一般放在液晶屏的排线附近缩短屏幕玻璃和芯片之间的走线长度。走线过长会引入噪声导致显示内容闪动或出现鬼影。其次是地线布置。液晶驱动是典型的模拟电路芯片的VSS引脚应该有独立的过孔直接连接到主地平面而不是和数字电路共用一小段走线。我遇到过一个问题芯片VSS走线和LED驱动的回流地共用了一段细走线结果LED开启的瞬间屏幕上会出现明显的横条纹干扰。后来把VSS单独打孔到地平面干扰就消失了。最后是晶振布局。TM1622内置振荡器一般不需要外部晶振但如果你用外部晶振给MCU提供时钟晶振的位置要尽量远离液晶排线。晶振产生的辐射干扰会被排线耦合进液晶驱动信号导致显示内容不稳定。我一般会把晶振放在PCB的另一侧或者用地线包围起来。5. 常见问题与排查技巧实录5.1 液晶屏完全不显示或显示白屏这个问题是最常见的也是排查起来最让人抓狂的。我跟很多同行交流过大家的排查路径基本一致按顺序检查基本都能定位到问题第一步用万用表量芯片VDD和VSS之间的电压确认供电正常。不少新手做了板子发现屏幕不亮量了半天才发现是芯片虚焊或者电源没接上。第二步用示波器或者逻辑分析仪抓CS、WR、DATA三根线的波形确认芯片确实收到了正确的命令和数据。没有示波器的话可以用GPIO翻转加LED的方式来验证代码有没有执行到发送函数。第三步确认芯片进入了正常显示模式。TI/HT系列芯片上电后默认可能是关闭显示的需要发送系统使能System Enable和显示开启Display ON命令。很多人在移植时忘了这一步导致屏幕一直白屏。第四步检查液晶屏本身的排线连接。段码屏的排线是热压到玻璃上的非常娇贵稍微弯折一下就可能断。排查时注意轻拿轻放。如果以上四步都检查过了还是白屏我建议你直接换一片芯片试一下。TM1622这类芯片虽然便宜但市面上的货源质量参差不齐有些拆机片或者散新片可能存在内部不良。5.2 显示乱码或内容错位显示内容和预期不符这是代码问题高发的区域。最常见的两个原因一是RAM地址映射错误二是数据位顺序弄反。地址映射错误的典型表现是你往第0个单元写了数字1结果屏幕的第3个位置显示了1。解决方法是仔细阅读芯片手册里的RAM地址表确认物理COM和SEG的对应关系。我建议你在调试阶段写一个测试程序按顺序把所有RAM单元全部点亮一次然后用照片记录下来这样就能直观地看到实际的映射关系。数据位顺序弄反的表现是写了一个数字8结果屏幕上显示的是数字1。这是因为段码屏的每个数字由7个段组成a到g这7个段对应RAM里的7个bit你的代码如果按错误的顺序填充这些bit就会出现“字形错乱”。调试方法是用单个段的点亮测试逐个确认每个bit对应的物理段。这里我想分享一个经验技巧软件调试段码屏时最好做一个“段扫描”功能。定义好所有段的名字然后每500ms点亮一个段同时在上位机打印当前是哪个段。这样一轮跑下来你就能画出完整的段映射表。我每次接到新屏幕项目第一件事就是做段扫描比对着手册猜快得多。5.3 显示闪烁或出现鬼影显示内容稳定看着没问题但是亮度会轻微闪烁或者本应熄灭的段隐约有残影。这类问题多半出在硬件而不是软件上。闪烁的常见原因是刷新频率设置不当。TM1622/HT1622的显示扫描频率由芯片内部振荡器决定刷新率通常在几十到几百赫兹之间。如果VDD电压不稳扫描频率也会出现抖动视觉上就是闪烁。处理方法是把VDD的滤波电容加大或者在电源输入端加LC滤波器。鬼影的出现通常是因为液晶驱动电压过高。驱动电压偏高时本来不该点亮的段也会有微弱响应。遇到这种情况调整偏压等级参数即可。另外也可能是液晶屏本身质量不好这个问题在低价屏上比较常见调试时可以让供应商提供同一规格的其他批次屏幕做交叉验证。5.4 按键失灵或误触发按键扫描涉及模拟信号和数字逻辑的结合问题排查起来也比较繁琐。我碰到过一个典型现象按一个键结果显示两个键同时被触发。后来检查发现是PCB布局问题——按键走线和DATA线的距离太近产生串扰。如果是单纯按键没反应先检查KEY引脚的初始化是否正确GPIO是否配置为上拉输入。其次检查读按键命令的时序特别是帧头101和命令格式是否和芯片手册严格一致。最后看按键的硬件设计TM1622的按键扫描支持组合键如果你没在程序里做去抖和防连击实际使用中很容易出现误触发。我自己的经验是按键去抖一定要做不能依赖芯片自带的硬件去抖。用定时器做10ms到20ms的软件去抖效果最稳定。另外如果产品的按键使用频率很高建议在KEY引脚对地加一个100nF电容可以有效滤除高频干扰。关于这次移植的个人收尾这次把TM1622/HT1622驱动移植到STM32平台上的经历整体收获还是挺多的。回头总结一下最值得记住的经验有三条。第一先理解帧格式再写代码命令头、地址、数据三者的位宽一定要烂熟于心这能省掉大量调试时间。第二硬件设计上电源滤波和走线布局优先搞定别让硬件问题去干扰软件调试。第三调试工具要备齐逻辑分析仪在这个项目里的价值甚至超过示波器它能清晰展现位级别的时序细节。如果你正在做类似的项目或者准备把段码屏方案用到自己的产品里建议先把这篇文章里的代码框架和排查思路吃透然后在自己板子上跑一遍段扫描和闪烁测试。这个芯片的上手门槛其实很低但只有真正动手做一遍你才会理解那些看起来琐碎的硬件细节到底对显示效果有多大的影响。