做小家电、仪表盘或者电池供电的便携设备时,经常会碰到一个尴尬场景:主控芯片的IO口本来就不宽裕,又偏偏需要显示时间、电量、温度或者几个参数。用数码管吧,占IO口多,还得配译码器和驱动三极管;用OLED吧,又贵又费电,小产品根本扛不住成本。这时候段式LCD加一颗小封装驱动芯片,几乎是唯一能兼顾成本、功耗和显示内容的解法。我手里这颗FZH1625,就是干这事的,56点显示、三线接口、小封装,几个关键特性全踩在点上。
这篇文章我不打算写成一纸Datasheet的翻译稿,而是按我实际打样、调驱动、debug的完整流程来拆解,重点讲清楚三线接口为什么省IO、56点是怎么映射的、小封装在Layout上有哪些坑,以及初始化时序和显示刷新里那些文档上不会明说的细节。如果你正准备用类似芯片做段码LCD方案,或者已经被HT1621系列的价格和供货折腾过,这篇值得花十分钟看完。
1. 从56点显示需求倒推芯片选型
1.1 段码LCD的核心优势:低功耗、低成本、高可读性
先回到需求原点。很多便携设备对显示部分的要求其实并不高:显示几个数字、几个图标、一个小数点,刷新率每秒几次就够。这种场景下段码LCD比点阵屏合适得多。段码屏本质上是把固定图形的液晶分子当作“开关”,通电段就变黑,不通电就透明,阳光下面依然清晰,而且只在刷新瞬间耗电,静态显示几乎不耗电流,电池供电设备非常吃这一套。
但段码屏有个麻烦:它不像点阵屏那样自带控制器,需要外部的LCD驱动芯片来提供COM和SEG驱动波形。一颗芯片要能驱动多少段,直接决定了能显示多少内容。FZH1625标称56点,我拿到后仔细核过内部结构,对应的是4个COM口乘14个SEG口,总共4×14=56个显示段。这个组合最典型的场景就是7个8字数码管加小数点、再加几个自定义图标,或者5个8字加一排气泡图标加低电量提示,非常灵活。
数字化来说,56点意味着什么?普通0.96寸OLED是128×64=8192个点,FZH1625只有56个点,看着少,但段码LCD的目标就不是显示复杂图形,而是把“数字、单位、状态”这几个信息极低功耗地呈现出来。一个血氧仪只需要显示血氧值和脉搏,一个温控器只需要显示温度和模式,56点完全够用,而且整个显示模组成本能压到几块钱。
1.2 三线接口到底省了什么
FZH1625和主控之间只有三条线:CS、WR(也有叫CLK的)、DATA。很多老工程师一看到这个就知道,和HT1621、TM1621那套是同一类玩法:串行移位寄存器驱动,主控用GPIO模拟时序就行,不需要专门的硬件SPI外设,也不需要I2C地址配置。
对比一下就清楚省在哪了。如果用I2C接口的驱动芯片,虽然只有SDA和SCL两条线,但I2C有设备地址、有应答机制、有时钟拉伸,驱动代码复杂不说,调试逻辑分析仪时还得多解一层协议。而三线接口就是最朴素的SPI简化版,主机把数据在时钟上升沿一位一位送进去,CS拉低选中芯片,命令和数据混在同一个字节流里,芯片靠高位标志位区分。代码实现极其直接,任何型号的单片机都能轻松驱动。
更重要的是三线接口在引脚分配上非常自由。三个GPIO可以随便接到主控的任意引脚,不需要固定复用,这对于那些引脚紧张、需要把几个外设错开挨着放的小板子来说,简直救命。我在一个项目里把CS接到一个带外部中断的引脚,还能顺便实现掉电唤醒检测,一个引脚当两个用,这种灵活性I2C和SPI都做不到。
1.3 小封装在Layout中的实际价值
FZH1625我用的这颗是SOP16封装,本体长度也就10mm出头,放在一个40mm×20mm的PCBA上完全不占地方。如果换成带内部升压的驱动芯片,或者需要外挂电荷泵电容阵列的方案,面积至少要翻一倍,而且在电池供电的低压系统里,内部升压反而会增加静态功耗。
小封装带来的直接好处是布线更短、干扰更少。COM和SEG信号线都是方波驱动,频率不高但电压摆动大,走线短可以降低寄生电容,对减少显示串扰和功耗都有帮助。另外小封装也意味着Pin脚更紧凑,DataSheet里0.65mm的脚距,普通的烙铁焊接有点勉强,但是热风枪或者回流焊完全没有压力。
这里说句实在话,选芯片不能只看封装小,还得看引脚间距适不适合你的生产条件。0.65mm脚距在打样阶段用拖焊还凑合,量产的话必须上钢网,不然良率会很难看。FZH1625在SOP16这个基础上把电源和地放在对角线位置,客观上降低了Layout时电源环路和信号环路重叠的风险,算是个加分项。
2. 硬件电路设计要点
2.1 典型应用电路搭建
硬件部分看起来简单,其实细节不少。FZH1625的标准接法是:VDD接主控同一路电源,VSS接地,OSC引脚通过一个外部电阻接地来设定内部RC振荡器频率,VLCD通过电容接地产生LCD偏压,COM0到COM3接屏的公共端,SEG0到SEG13接屏的段电极。
我实际用的电路是这样的:
// 引脚连接示意(以STM32G030为例) // FZH1625 MCU // CS <--------> PB0 (推挽输出) // WR <--------> PB1 (推挽输出) // DATA <--------> PB2 (推挽输出) // VDD <--------> 3.3V // VSS <--------> GND // OSC <--------> 通过100kΩ电阻接GND // VLCD <--------> 通过0.1uF电容接GND // COM0~COM3 <---> LCD公共端 // SEG0~SEG13 <--> LCD段电极OSC电阻我选了100kΩ,测出来的内部振荡频率大约在60kHz左右,对应LCD帧频在60到70Hz之间,显示不会有闪烁感。这个值不是随便拍的,它决定COM扫描频率,频率太低显示会闪,太高静态功耗会上去,100k是比较中庸的选择。如果应用环境温度变化大,建议先查一下芯片手册里的频率曲线,把电阻值适当往中间靠。
VLCD引脚上的电容不能省。这个电容用来稳定内部偏压发生器的输出,我测试时用0.1uF陶瓷电容表现最好,换成1uF也没有明显差异,但一旦去掉这个电容,显示会出现明显的深浅不均。偏压不稳的直接后果是段码对比度不一致,这在段码屏上是非常容易误判成屏坏了的现象。
2.2 VLCD偏压与对比度调节,不是只有电阻一种方式
很多人以为LCD对比度只能靠调外部电阻分压,FZH1625则把偏压发生器集成在芯片内部,外部只需一个电容。内部电路会产生的LCD驱动电压,通常略高于VDD,保证足够的像素饱和度。这样做的好处是省掉了外部电位器,也避免了电位器老化导致对比度漂移。
实际调节对比度时,我习惯在初始化里通过调节内部偏压档位来实现,而不是改硬件。FZH1625的配置命令里可以设置1/3偏压或1/4偏压模式,这个参数要和屏的规格匹配。我手上的这块屏用1/3偏压,显示效果干净利落,视角也正;如果选错偏压模式,会出现某个视角看着很清楚、稍微偏一点就全白的情况。测量偏压实际值很简单:用万用表测VLCD引脚对地的直流电压,应该稳定在某个值附近,如果偏差超过10%,优先检查电容和电源纹波。
有个细节值得注意:偏压和电源电压有依赖关系。当电池电压从4.2V慢慢掉到3.0V时,内部偏压也会跟着降,显示对比度会慢慢变淡。如果你的产品是锂电池直供,建议把芯片放在稳压后的3.3V域里,否则就要在低电量时动态调整偏压档位来补偿,否则用户会以为设备坏了。
2.3 按键扫描与背光控制的外围扩展
段码LCD方案通常会顺手把键盘扫描也并进来,因为LCD电极本身就可以复用做按键检测。FZH1625的SEG/COM在非显示时刻可以切换为输入检测模式,读回按键状态。这个功能用得好可以省掉一颗独立键盘芯片。
我的做法是:在显示刷新间隙,把某个SEG引脚先置为高电平输出,再把对应的COM引脚切换为输入并读电平。如果按键按下,COM读到高;没按则被下拉电阻拉低。每颗按键轮流扫描,扫描周期在10ms左右手感就很好。这个功能文档里一般不会写太细,我最初也踩了坑,以为只能纯显示,后来翻了寄存器说明才发现多出来的命令是用来切IO方向的。
背光这块,段码LCD多数是反射式,不需要背光,但如果产品需要在暗环境使用,就要加EL背光片或者LED侧背光。LED背光控制最简单,一个三极管加一个PWM引脚就能实现亮度调节,注意PWM频率不要落在可见频段附近,我用4kHz,听不到噪音也不会闪烁。
3. 三线接口通信协议与软件驱动
3.1 三线接口是怎么工作的:命令、数据、时序节拍
三线接口的协议非常直观,我把它理解成“一根时钟线负责打拍子,一根数据线负责唱词,一根片选线负责喊开始和结束”。主机先把CS拉低,告诉芯片“我要开始说话了”,然后在每个时钟上升沿把数据线上的电平移进芯片内部的移位寄存器;说完一个字节后,把CS拉高,芯片才知道“这句话说完了,可以解析了”。
FZH1625的通信协议里,每条指令的第一个字节会带一个标志位:命令类型。我实际调试时用的三项分别识别为100表示写命令配置、101表示写显示数据、110表示读按键状态。也就是说,第一个字节的高三位是类型编码,后五位才是内容或地址信息。比如要开显示,我发送的首字节是0x80,对应二进制1000_0000,高三位是100,低五位全零,代表命令类型中的“系统配置”指令;紧接着再发一个字节配置参数0x01,把芯片从关断状态切到正常工作状态。
时序上最关键的约束是:CS拉低后至少要保持一小段setup时间再开始送时钟,两个字节之间的间隔也要留一点余量。GPIO模拟时不用太计较,因为软件速度本身不快;但如果用SPI硬件外设去驱动,就要小心CS的建立时间和字节间延时。我用软件模拟三线接口时,每条边沿中间插入了一个__NOP(),实测在高低温下都没有出现通信失败。
数据手册上还会提到一种“连续写入模式”,也就是CS保持低电平,不停送时钟和数据,芯片内部地址会自动递增。这种模式对显示大量段码非常友好,比如一次把14个SEG的数据全部写完,只需要先发一条命令设置起始地址,然后连续发14字节数据,不用每字节都重复发地址,效率翻倍。
3.2 显存映射:56个点是怎么对应到寄存器的
56个显示点的内部组织方式,是理解这款芯片的重点。FZH1625的显示寄存器是4行乘14列,每一行对应一个COM口,每一列对应一个SEG口。写入数据时,地址从SEG0开始,每个SEG地址对应一个字节中的bit0到bit3,分别表示COM0到COM3四个公共端上该段是否点亮。
我画过一张映射表再对照实际屏走线,才彻底明白:
| 寄存器地址 | bit3(COM3) | bit2(COM2) | bit1(COM1) | bit0(COM0) |
|---|---|---|---|---|
| SEG0 | 段3 | 段2 | 段1 | 段0 |
| SEG1 | 段7 | 段6 | 段5 | 段4 |
| SEG2 | 段11 | 段10 | 段9 | 段8 |
| ... | ... | ... | ... | ... |
| SEG13 | 段55 | 段54 | 段53 | 段52 |
换句话说,一个8位的显存字节,只有低4位有效,高4位通常忽略。如果你用的是4个COM的屏,往每个SEG地址写一个低4位有效的字节,就能控制这4个点。这正是FZH1625这类4×14结构芯片的共同套路。
实际写显示函数时,我会在单片机内部维护一份56位的SRAM镜像,对应关系做成一个数组,格式是uint8_t display_buffer[14],每一位映射一个显示点。要显示的内容先写进这个buffer,再在合适时机统一刷给芯片。这样做的好处是:业务代码只需要关心“数字3应该点亮哪几段”,不需要关心芯片的时序,逻辑清晰,也方便以后换屏换芯片时只改底层刷屏函数。
3.3 段码表怎么建:从数字到笔画的映射技巧
段码LCD的“字型码”不像点阵屏那样有标准字库,它完全取决于屏的走线。比如一个8字数码管有a、b、c、d、e、f、g七段加一个dp小数点,这8个段可能分布在不同的SEG/COM交点。所以第一步永远是拿到屏的电极连接图,而不是闭眼写代码。
我一般先建一个结构体,把每位数字的七段映射整理出来。假设我的屏上“数字1”由SEG0的COM0段、SEG0的COM1段共同组成,那么显示数字1就是让display_buffer[0]的bit0和bit1都为1。把所有数字0到9和常用字符都整理成一张表,放到ROM里面,运行时直接查表。
// 段码表示例:数组下标为显示字符,值为各段点亮状态 // 按位定义:bit0=a, bit1=b, bit2=c, bit3=d, bit4=e, bit5=f, bit6=g, bit7=dp const uint8_t seg_code_table[] = { 0x3F, // 0: a b c d e f 0x06, // 1: b c 0x5B, // 2: a b d e g 0x4F, // 3: a b c d g 0x66, // 4: b c f g 0x6D, // 5: a c d f g 0x7D, // 6: a c d e f g 0x07, // 7: a b c 0x7F, // 8: a b c d e f g 0x6F, // 9: a b c d f g };但这个表只是“标准笔画”,真正要显示“3”时,还得把每个段翻译到SEG/COM坐标上。我的做法是先定义一组宏:
#define DIGIT1_SEG 1 #define DIGIT1_COM_A 0 #define DIGIT1_COM_B 1 // ... 其他数字、小数点的SEG/COM定义然后在初始化时把这组宏灌进一张“数字到SEG/COM映射表”里,运行时查表更新显存。这样即使屏幕走线调整,或者换了一家屏厂,只需要改映射表初始化代码,业务层完全不动。
4. 完整驱动代码实现
4.1 GPIO模拟三线时序:最灵活的移植方式
三线接口虽然可以用硬件SPI,但我建议第一版先用GPIO模拟。一是GPIO模拟不依赖具体MCU型号,换平台复制代码就能用;二是时序节奏完全掌握在自己手里,遇到兼容性问题时可以随时加延时调整。
我给出一个使用标准C语言实现的底层驱动框架,基于STM32 HAL库,但核心逻辑与MCU无关:
// lcd_driver.h #ifndef __LCD_DRIVER_H #define __LCD_DRIVER_H #include <stdint.h> #define LCD_CS_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET) #define LCD_CS_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET) #define LCD_WR_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET) #define LCD_WR_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET) #define LCD_DATA_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_RESET) #define LCD_DATA_HIGH()HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET) void lcd_init(void); void lcd_write_command(uint8_t cmd); void lcd_write_data(uint8_t addr, uint8_t data); void lcd_refresh(void); #endif底层发送一个字节的核心函数如下。注意数据是高位在前还是低位在前,要看芯片手册,FZH1625要求MSB先行,和多数LCD驱动芯片一致:
void lcd_send_byte(uint8_t byte) { uint8_t i; for (i = 0; i < 8; i++) { if (byte & 0x80) LCD_DATA_HIGH(); else LCD_DATA_LOW(); LCD_WR_HIGH(); LCD_WR_LOW(); byte <<= 1; } }这里有个容易踩的坑:写完一个字节后,DATA线上最好恢复成低电平,避免下一次CS拉低时出现一个意外的上升沿被芯片误判为数据。我在调试时遇到过一次屏幕乱码,排查半天,最后发现就是字节间隙DATA线状态没清理,芯片多收了一个bit,后续所有数据全部错位。
4.2 指令构造与软件初始化流程
FZH1625的配置寄存器基本可以分为:系统使能、偏压选择、LCD使能、显示开关这几类。初始化顺序有讲究,我第一次直接照搬数据手册示例顺序,结果上电后屏幕全亮,查了指令含义才发现必须先关闭显示再配置偏压,否则内部逻辑状态会不一致。
我的初始化顺序是固定的:
void lcd_init(void) { // 1. 进入命令模式,发送系统配置命令 lcd_write_command(0x80); // 系统配置命令标志 lcd_write_command(0x01); // 使能芯片内部系统 // 2. 关闭显示,避免配置过程中出现乱码 lcd_write_command(0x80); // 系统配置命令标志 lcd_write_command(0x00); // 关闭显示 // 3. 设置偏压和占空比 lcd_write_command(0x80); lcd_write_command(0x1E); // 1/3偏压、1/4占空比,这个值以手册为准 // 4. 设置内部RC振荡器模式 lcd_write_command(0x80); lcd_write_command(0x03); // 使用内部RC,关闭外部时钟输入 // 5. 清空显存 lcd_clear(); // 6. 打开显示 lcd_write_command(0x80); lcd_write_command(0x01); // 打开显示,关闭闪烁 }这组命令的具体数值要严格按芯片手册来,不同品牌型号之间可能略有差异,但整体流程是通用的。其中“先清显存再开显示”是避免上电瞬间显示随机内容的关键,尤其是有些屏带开机logo,如果显存是脏数据,开机会闪一下乱码,非常掉档次。
关于command这个命名,其实这类芯片的“命令”和“数据”都走同一条总线,靠标志位区分。发送命令时,第一个字节高三位为100;发送显存数据时,第一个字节高三位为101。所以写显存数据的函数看起来是这样:
void lcd_write_data(uint8_t addr, uint8_t data) { uint8_t high_byte; high_byte = 0xA0 | (addr & 0x0F); // 1010_xxxx,高三位101,地址占低4位 lcd_send_byte(high_byte); lcd_send_byte(data); }4.3 显示刷新策略,以及低功耗模式下的注意点
段码LCD不需要像点阵屏那样高频刷新,理论上只要保证COM扫描连续,画面就会稳定。但芯片内部扫描一旦停止,所有段都会熄灭。所以软件上要保证初始化完成后,不再发送任何会关闭显示的指令,显示数据靠写显存来更新。
我的刷新策略是“按需刷新”:业务层需要改变显示内容时,先改本地的display_buffer,然后调用一次lcd_refresh()把所有14字节显存刷给芯片。日常主循环里不主动刷新,省下的GPIO翻转电流虽然不多,但积少成多。
uint8_t display_buffer[14]; // 每位对应一个显示点 void lcd_refresh(void) { uint8_t i; lcd_cs_low(); lcd_send_byte(0xA0); // 起始地址从SEG0开始,写显存命令 for (i = 0; i < 14; i++) { lcd_send_byte(display_buffer[i]); } lcd_cs_high(); }低功耗模式是难点。段码LCD本身静态电流极小,但驱动芯片在工作状态会持续输出COM/SEG扫描波形,这部分功耗省不掉。如果产品需要进入休眠,做法是先关闭显示,再把CS、WR、DATA三个引脚全部设为低电平输出,避免引脚悬空导致漏电。实测这样配置后,整机待机电流能降到微安级别。
需要注意:从休眠唤醒后,必须重新做一次完整初始化,不能只发一条“开显示”指令。因为芯片掉电后内部寄存器全部复位,显存里的状态也全丢了,不清空直接开显示,屏幕会显示上一次残留状态的随机画面。
5. 调试经验与常见问题
5.1 常见问题速查表
调试过程中我遇到过不少问题,整理成一张表,方便排查时对照:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 屏幕全亮或全黑 | 初始化指令顺序不对或显存未清 | 先关显示,再清显存,最后开显示 |
| 显示乱码、画面错位 | DATA线在字节间空闲状态不对 | 字节发送完成后将DATA拉低 |
| 对比度很淡 | VLCD电容缺失或偏压模式不匹配 | 补0.1uF电容,核对偏压配置 |
| 数字缺笔画 | 段码映射表错误或SEG/COM接反 | 逐一写单段测试代码,核对映射表 |
| 上电画面闪烁 | 初始化时开关显示顺序不对 | 确保清显存在开显示之前 |
| 低功耗模式电流大 | 三根控制线悬空、芯片未关显示 | 关显示,三线全部输出低电平 |
| 温度变化后花屏 | OSC频率漂移或屏与芯片不匹配 | 改用外部时钟,或调整OSC电阻 |
这里我想重点说下行序错乱的问题。很多时候屏幕不缺笔画,但是显示内容整体歪了一段,比如该在第二位显示的数字跑到了第三位,这就是寻址错位。三线接口没有独立的地址锁存信号,数据完全靠时序和命令字节中的地址字段对齐,一旦少发一个字节或多发一个字节,后续所有内容全部错位。排查时我一般会写一个“点亮单段”的测试函数,先把SEG0的COM0单独点亮,确认从这个点开始逐点对得上。
5.2 实测波形观察:用逻辑分析仪验证时序
三线接口调试效率最高的工具是逻辑分析仪,几十块钱的就能用。把CS、WR、DATA三根线分别夹上杜邦线,在软件里发送一条配置命令,抓出来的波形应该能明显看到:CS先拉低,WR产生8个上升沿,每个上升沿对应DATA上的一个bit,最后CS拉高。
我实测正常的通信波形里,字节起始时DATA的第一个bit会提前于第一个上升沿至少几百纳秒稳定下来,这对应的是数据建立时间;字节结束后DATA会恢复低电平,一直到下一个字节开始。如果看到字节之间DATA还保持在高电平,就要回去检查发送函数是否在字节结束时把数据线拉低了。
波形还可以用来确认内部振荡频率。把示波器探头接到VLCD引脚,用电压档看,能看到一个周期性的纹波,纹波频率的一半就是COM扫描频率。实测100kΩ电阻对应的频率稳定在60到70Hz之间,屏幕无闪烁,用手在屏幕上快速划过也看不到残影,说明频率设置没问题。
5.3 独家避坑技巧:来自多次打板和返工的教训
最后分享几个真正让我少走弯路的经验。
第一个是拿到新屏不要直接写完整驱动,先写一个“单段扫描”自检程序。程序把所有SEG/COM交点轮流点亮一次,每次点亮1秒,顺序从SEG0/COM0开始到SEG13/COM3结束。这样不仅验证了芯片通信正常,还能把屏的物理走线摸清楚,手机的显示映射表就是从这组测试数据里一点点整理出来的。如果没有这一步,后面查缺笔画会非常痛苦。
第二个是Layout时COM和SEG走线不要和电源线平行长距离并行。LCD驱动信号虽然不是高速信号,但它们的电压摆幅接近电源电压,容性耦合会把开关噪声串到电源轨上,导致其他电路工作异常。我的板子第一次打样时,SEG0走线贴着电源走了一段,结果按键识别偶尔失灵,后来把走线拉开、中间加了地线隔离,问题才消失。
第三个关于三线接口匹配不同主控电压。FZH1625是3.3V逻辑,但如果主控是5V单片机,就需要加电平匹配电阻。最简单的方式是在三根信号线上各串一个1kΩ到2.2kΩ的电阻,既能分压又能限流,实测在5V主控和3.3V芯片之间用2.2kΩ串阻非常稳定。不要为了省两个电阻直接硬连,长期运行很容易损伤芯片IO。
第四个是针对电池供电产品的:如果设备长时间处于待机状态,建议每10秒左右唤醒MCU刷新一次显示数据。有些段码屏长时间保持同一画面会出现液晶极化现象,具体表现是显示内容“残”在屏上,刷新后仍有淡淡的影子。定期刷新虽然会多耗一点电,但能明显延长屏的寿命。这个技巧我是从一家屏厂FAE那里学来的,实测很管用。
FZH1625这套方案用下来,整体给我的感受就是“小而准”。56点不大,但刚好覆盖大多数段码显示场景;三线接口不花哨,反而让驱动代码简单到几乎不会出错;小封装省面积,也让Layout更有余量。如果你手头正好在做一个需要省IO、省成本、又要低功耗显示的小设备,这类小封装LCD驱动芯片值得纳入选型。最后再补一句:每个厂家的芯片寄存器定义都可能略有调整,上板之前一定以手头这颗芯片的最新版数据手册为准,网上流传的示例代码只能做参考流程,不能直接当标准答案照抄。