1. 段式LCD方案还在大量出货HT1621就是那颗最常用的驱动器段式液晶屏在很多人印象里是老古董但实际上你去看看热水器、电表、血氧仪、遥控器、电子体温计这些产品里面绝大多数用的还是段码屏配HT1621这颗驱动芯片。原因很简单显示内容固定数量也固定不需要点阵那种像素级刷新段码屏成本低、阳光下清晰、静态功耗几乎可以忽略不计。用LuatOS做设备端开发时这颗芯片被做成了核心库ht1621开发者不需要自己拿GPIO一根一根去抠时序。但问题也出在这里如果完全不懂底层协议屏幕不亮或者显示乱码时排查起来会很痛苦。这篇内容就把HT1621/HT1621B的驱动细节和LuatOS里ht1621库的用法一起讲透适合正在用LuatOS做低功耗显示设备的开发者参考。1.1 段式屏的优势为什么它还是工程首选段式LCD的显示单元是固定的段比如数字8的七段、几个图标、小数点、单位符号出厂时玻璃上就已经刻好了。点亮某个段只需要让某个SEG和某个COM夹出一定电压差即可。相比点阵屏段式屏不需要持续刷新图像MCU不需要时刻送数据也不存在烧屏问题。从成本角度算一块段式VA屏的价格通常只有同尺寸点阵LCD的零头驱动芯片也比带控制器的小屏便宜得多。更关键的是功耗屏幕本身不耗电HT1621内部自带RC振荡器和偏压发生器整个驱动电路的静态电流可以做到微安级别这对电池供电的物联网终端非常重要。1.2 HT1621和HT1621B驱动上有没有区别HT1621B是HT1621的改进版本主要是工作电压范围、功耗、封装形式上做了优化命令字和通信时序完全兼容。市面上能买到的模块丝印标HT1621B的反而更多见。实际驱动代码两边通用不用区分对待。这颗芯片最多驱动32个SEG和4个COM也就是128个显示段。对绝大多数段式屏场景这个规模足够了。通信只需要三根线CS、WR、DATA另外还有一个可选的RD脚用于读RAM但正常显示业务根本用不到保持悬空即可。2. 128个显示点的存储逻辑先弄懂SEG和COM怎么映射拿到一块段式屏第一件事不是写代码而是看清它的引脚和内部走线。HT1621的128个显示点本质是32个SEG引脚和4个COM引脚交叉出来的矩阵每个交叉点对应RAM里的1个bit。2.1 SEG和COM构成的最小显示单元你可以把SEG想象成一个纵向的电线组COM想象成横向的公共线每个显示段就挂在某一条SEG和某一条COM之间。想要点亮这个段只需要让这两个引脚之间产生足够的电压差HT1621的偏压发生器会按扫描顺序在COM上轮换驱动。所以一个段在代码里的坐标是固定的它必须落在某个SEG地址和某个COM编号的交叉点上。比如屏幕丝印上数字8的左上竖线接的是SEG2和COM0那你要操作的就是RAM地址2的第0个数据位。2.2 RAM地址和COM数据位的对应关系HT1621内部RAM的地址编号直接对应SEG0到SEG31每个地址存4个bit这4个bit分别对应4个COM。需要提醒的是这4个bit和COM的对应方向不同厂家的数据手册标注可能相反有的芯片bit0对应COM0有的屏实际效果是bit0对应COM3。这不是芯片的bug而是屏幕厂家在设计玻璃引脚时可能做了镜像或者旋转。所以第一次调板子时不要盲目相信SDK里的段码示例最好自己挨个点亮SEG把实际对应关系画出来再写段码表。2.3 从点亮某一段倒推RAM值应该怎么写假设你确认了某个段位于SEG5、COM2那在LuatOS的ht1621 API下只需要这样ht1621.set(5, 2, 1) -- 点亮SEG5、COM2这个点 ht1621.refresh() -- 把显存数据真正刷到芯片如果自己写底层驱动那操作的就是一个32字节的数组local seg5 ram[5] -- 先读出当前值 seg5 seg5 | (0x01 2) -- 把第2位置1表示COM2点亮 ram[5] seg5反过来熄灭这个段就是seg5 ~(0x01 2)。理解了这一步后面的段码表、温度显示、局部刷新都是顺理成章的事。3. 三线串口不是标准SPI命令、地址、数据的准确时序HT1621的通信方式是三线串行很多人想当然地拿SPI去套结果输在时序细节上。它跟标准SPI最大的区别是没有标准的片选时序协议命令和数据都由CS低电平期间的位流决定起始标志固定为101。3.1 引脚和典型接线最小系统只需要三个GPIO引脚作用说明CS片选低电平有效整个操作期间保持低WR写时钟上升沿锁存DATA上的数据DATA数据线发送命令/地址/数据内容实际接线时CS对应模组一个普通GPIOWR是时钟脚DATA是数据脚。电源部分注意VDD和VLCD如果屏的VLCD引脚需要外部供电还要按数据手册接好电容或分压电阻。三根信号线尽量短尤其是WR和DATA飞线过长容易出现花屏。3.2 命令模式101加命令码任何一次完整的写操作CS拉低后先发3bit的101表示后面是命令或数据。通信协议就是按bit一位一位发MTD操作顺序不能反。常用命令码如下HT1621和HT1621B通用命令码功能0x00关闭系统振荡器0x01开启系统振荡器0x02关闭LCD偏压/显示0x03开启LCD偏压/显示0x04偏压1/20x05偏压1/30x06占空比1/40x07占空比1/30x08使用内部RC振荡器0x09使用外部时钟标准的初始化序列是0x01开启系统振荡器0x05设置1/3偏压0x06设置1/4占空比最后0x03打开显示。如果你的屏幕是3COM的占空比就要选0x07而不是0x06。3.3 数据模式101加地址加4位数据写RAM和写命令不一样。数据模式的格式是CS拉低 - 发送101 - 发送6位SEG地址 - 发送4位COM数据 - CS拉高每写完一个地址的4bit内部地址会自动加1所以可以连续刷多个SEG。很多驱动库优化刷新速度就是在一次CS低电平时把首地址0x00发出去然后连续送128bit数据把整片RAM刷完。这里的位发送方向需要注意发送地址时先发最高位数据也是从高位到低位逐位发送。WR上升沿采数据所以常规做法是先拉低WR设置DATA电平再拉高WR完成一个bit的写入。3.4 时钟频率和时序余量HT1621手册给出的时钟上限一般是几百千赫兹到一兆赫兹GPIO模拟时序时完全不用担心超频反而要注意别太快。有些MCU的GPIO翻转速度非常快信号建立时间不够屏幕就会显示乱码。我见过有人在LuatOS里用gpio.set连续翻转不加任何延时也能正常工作因为Lua解释执行本身有开销翻转速度已经被限制住了。如果换成性能更强的环境建议每个bit之间至少留几百纳秒的建立时间必要时用一个短延时函数兜底。没有逻辑分析仪时可以靠示波器看WR上升沿偏移但更快的办法是直接试能稳定显示就说明时序OK。4. LuatOS里的ht1621库初始化参数、调用方式和最小示例LuatOS把HT1621做成了核心库require(ht1621)之后就能用。开发者不需要自己拼命令码库内部已经把协议封装好了。下面这些接口是以我常用的固件版本来写的不同版本可能略有出入但核心思路一样。4.1 初始化参数偏压、占空比和引脚绑定初始化时通常要传入CS、WR、DATA三个脚以及屏幕的SEG数、COM数、偏压和占空比。我习惯用表参数可读性更好local ht1621 require(ht1621) ht1621.init({ cs pin.PB03, wr pin.PB04, dat pin.PB05, seg 32, -- SEG总数HT1621最多32 com 4, -- COM数4 bias 2, -- 2表示1/3偏压1表示1/2偏压 duty 4 -- 4表示1/4占空比3表示1/3占空比 })引脚一定要确认是普通GPIO不能是模组里被默认占用的特殊功能脚。不同模组的默认引脚功能不同拿到板子先查引脚复用表。4.2 核心APIset、clear、refresh实际业务里用得最多的是这几个-- 清空全部显示内容 ht1621.clear() -- 点亮/熄灭指定段seg范围0~31com范围0~3 ht1621.set(seg, com, 1) -- 把缓冲区数据刷新到屏幕 ht1621.refresh()set只是修改内置显存数组不会立刻作用到物理屏幕所有修改要等refresh之后才一起送出去。这样设计的好处是刷屏时可以一次性连续发送避免频繁CS拉高拉低产生毛刺。如果库没有refresh而是set直接写屏那就要自己控制刷新节奏不要在循环里反复调用否则屏幕会闪烁。4.3 最小点亮示例点亮第一个显示点完整流程是这样的local ht1621 require(ht1621) ht1621.init({ cs pin.PB03, wr pin.PB04, dat pin.PB05, seg 32, com 4, bias 2, duty 4 }) ht1621.clear() ht1621.set(0, 0, 1) ht1621.refresh()这段代码跑起来屏幕上对应SEG0和COM0交叉的那个段就会亮。如果你不知道哪个段是哪个就一个个递增SEG和COM记录下实际点亮的位置这也是所有段式屏项目的第一步。5. 实战一个温度显示段码表构建和刷新策略段式屏最经典的场景就是显示温度。假设屏上有两个数字位、一个小数点和一个℃符号我们要显示23.5℃这样的内容。难点不在于刷屏而在于把数字映射到正确的段坐标。5.1 段码表不是查表值是坐标集合很多人一上来就说段码表直接拿单片机八段数码管那套0x3F编码来用这在HT1621上是行不通的。因为HT1621的每个显示段坐标是(seg, com)不是一个字节能表达的。正确做法是先把屏上每个数字的a、b、c、d、e、f、g、dp八段对应到具体的SEG和COM坐标然后按数字查表取出一组坐标。标准七段数字编码如下供参考数字点亮的段0a b c d e f1b c2a b d e g3a b c d g4b c f g5a c d f g6a c d e f g7a b c8a b c d e f g9a b c d f g5.2 把温度值拆成位并写入显存温度23.5℃要显示成三个字符加一个小数点。计算拆位local temp 23.5 local show_value math.floor(temp * 10) -- 235 local d1 show_value // 100 -- 百位2 local d2 (show_value // 10) % 10 -- 十位3 local d3 show_value % 10 -- 个位5即小数点后一位然后按屏上每个字符的SEG基地址把对应段点亮local function show_digit(value, seg_base, com) -- 这里digit_map存的是每个数字7个段的亮灭状态顺序是a,b,c,d,e,f,g local bit_map digit_map[value] for i 1, 7 do ht1621.set(seg_base i - 1, com, bit_map[i]) end end show_digit(d1, 0, 0) -- 十位SEG0-6 show_digit(d2, 8, 0) -- 个位SEG8-14 show_digit(d3, 16, 0) -- 小数位SEG16-22 -- 额外点亮小数点和℃符号坐标按实物丝印确认 ht1621.set(23, 0, 1) ht1621.set(24, 0, 1) ht1621.refresh()这里的SEG基地址完全取决于屏幕设计不同厂商的屏差异很大。我习惯在每个字符区域之间空一个SEG方便后续加图标或者特殊符号。5.3 刷新机制全量刷新和局部刷新怎么选数据变化不频繁时直接clear再全部重新set再refresh是最省事的。温度显示哪怕一秒刷新一次也完全没有性能问题。如果屏幕上有动态闪烁的秒表或者需要频繁刷新的部分全局清屏加全量刷新会造成可见闪烁这时候应该改成局部刷新只修改变化的段坐标不调用clear最后统一refresh一次。库内部维护的显存数组天然支持局部刷新你只需要控制自己不乱调clear即可。实测下来局部刷新不仅能减少闪烁还能降低GPIO翻转次数对功耗也有好处。6. 实测翻车点从完全不亮到乱码、闪烁的完整排查链路我在几个项目里踩过不少HT1621相关的坑这里把最典型的问题和排查思路完整列出来。如果你照着上面的步骤做还是不显示按这个链路走一遍基本能定位。6.1 屏幕完全不亮优先怀疑偏压和占空比完全不亮最常见的原因是初始化序列不对。很多屏不是1/3偏压而是1/2偏压这时把命令码0x05换成0x04试试。同样地如果屏是3COM你却配了1/4占空比屏上所有段都不会正常显示。快速验证方法用LuatOS的ht1621库把所有段全部点亮也就是循环set(seg, com, 1)然后刷新。如果屏全亮说明GPIO和RAM读写没问题如果只有一半亮很可能是偏压/占空比匹配问题如果完全不亮先查电源和CS电平。6.2 显示乱码检查段坐标和起始标志乱码和点亮了错误段是两回事。点亮了错误段多半是段码表映射写错把SEG/COM坐标弄反了。真正意义上的乱码是写命令时起始标志或位长度错了芯片解析到了错误数据屏幕上会出现多个无规律亮点。排查办法是找一个屏上绝对不会亮的角落段对它执行ht1621.set(seg, com, 1)并刷新。如果它旁边的段也莫名其妙亮了基本可以断定是时钟采样或位序问题而不是代码逻辑。6.3 刷新太快导致闪烁库的set只是改内存真正刷屏靠refresh。但如果你在一个频繁触发的回调里每次都refresh屏幕上就会出现闪烁。段式LCD本身是靠扫描维持显示的刷新次数一旦超过视觉暂留就会看到抽丝般的闪烁。正确做法是加节流用一个状态标志只在数据变化时刷新。比如定时1秒刷新一次或者通过sys.timerLoopStart控制不要在GPIO中断里刷新。6.4 引脚复用和飞线干扰有一块板子屏幕数据发出去就是花的排查了很久发现WR脚和模组的下载调试口复用了调试器在线时屏幕正常断开调试器屏幕就乱。换到普通GPIO后解决。所以初始化之前一定对照当前板子的引脚复用表确认CS/WR/DATA都不是默认被占用的脚。飞线过长也会出问题尤其是靠近射频天线或者电机驱动线的位置。三根线尽量并排短走DATA和WR不要跟电源线绑在一起否则串扰会造成偶发抖动。实测中把屏线从20cm缩短到5cm后问题直接消失。6.5 低功耗场景的额外注意段式屏虽然省电但HT1621的偏压发生器一旦开启会持续消耗电流。设备进入休眠模式前最好先调用命令关闭显示和系统振荡器唤醒后再重新初始化。否则即使MCU休眠了驱动芯片还在空转。我自己习惯封装一个sleep函数-- 进入休眠前调用关闭振荡器和LCD偏压 ht1621.write_cmd(0x02) ht1621.write_cmd(0x00)唤醒后再走一遍初始化序列重新刷新一次显示内容。这里要注意如果库没有暴露write_cmd也可以通过init重新初始化来恢复。个人经验是第一次做段式屏一定要耐心把每个段的映射关系测出来画成一张表。虽然多花了半小时但后面写段码表、调试显示逻辑都很顺少走很多弯路。