段码LCD驱动芯片FZH1643实战:I2C通信、键盘扫描与低功耗设计
2026/9/8 15:25:25 网站建设 项目流程

做温控面板那会儿,我吃过一次亏。整块板子8个按键、一个段码LCD,主控用STM32F030,一开始图省事直接拿IO口推LCD,结果显示刷新和按键扫描抢时序,按一下键抖得跟拨浪鼓一样,最后没辙,把显示和输入拆成了两颗芯片分开跑,才勉强稳定。后来接触到FZH1643这种带键盘扫描的LCD驱动芯片,一根I2C总线把显示和按键全接管,才发现当初绕了多大一圈。这篇文章就围绕FZH1643的I2C接口、双模式切换和键盘扫描机制,讲讲这类"显示+输入合封"方案在实际项目里怎么用,以及哪些地方特别容易踩坑。适合正在做小家电、工控面板、仪器仪表显示交互的朋友参考,不管是换型选型还是调试排错,应该都能用得上。

1. 段码屏驱动的老问题,FZH1643是怎么一次解决的

1.1 段码LCD的驱动原理,为什么要专门的驱动芯片

先理清一个基本问题:段码LCD(就是那种显示数字和符号的液晶屏,不是TFT彩屏)本质上是一个电容性负载,每个显示段相当于一个微小电容。给某一段加上直流电压时间一长,液晶材料会极化,轻则显示重影、黑斑,重则直接把屏搞坏。所以驱动段码LCD必须用交流波形,通过COM口和SEG口之间的电压差来控制每个段是否点亮。

很多人第一次接触段码屏会问,为什么不能像驱动LED那样直接拉高拉低?因为LCD的"点亮"取决于COM和SEG之间的有效电压差,而且要考虑方向和时序。以常见的1/4占空比为例,4个COM轮流被选通,剩下的SEG引脚在对应时段输出段码信号,通过偏压配置(比如1/3偏压)来保证未选通段的电压低于阈值,不产生"半亮"或"影子"效果。

这就解释了为什么需要专门的LCD驱动芯片:它要在后台持续不断地生成多路COM扫描波形,同时把显存里的数据实时映射到SEG输出口。让主控直接干这个活,不仅IO口数量不够,CPU时间也被吃掉一大块。FZH1643这类芯片内部集成了振荡器、分频器、扫描时序发生器和显存,主控只需要告诉它"哪个段亮、哪个段灭",剩下的刷新工作它自己完成。

1.2 键盘扫描功能的加入,主控从"双任务"变成"单任务"

单独一颗LCD驱动芯片不难找,HT1621、HT1622这类都是老牌产品。但它们不管按键,面板上的矩阵键盘还得主控自己扫描。于是主控面临一个尴尬局面:一边是LCD驱动芯片通过串口接收显存数据,另一边是主控要周期性地给矩阵键盘的行线输出扫描电平、读回列线状态、做防抖处理。

矩阵键盘的扫描看着简单,写起来全是细节。行线什么时候输出高、什么时候输出低,列线要不要开内部上拉,按键按下的瞬间怎么去抖,长按、连击怎么判断,这些逻辑每写一次就要调试一次。特别是在主频不高的MCU上,还要兼顾显示刷新的实时性、通信协议的解析,代码稍微乱一点,按键就出现误触发或者丢键。

FZH1643把键盘扫描逻辑做进了芯片内部。主控设置好扫描使能和相关参数后,芯片会自动周期性地扫描矩阵键盘,把按键状态存到内部寄存器里。主控想查看有没有按键按下,直接通过I2C读几个字节就行。从软件层面看,原来是"负责显示刷新+负责输入采集"两个任务,现在变成了"查询按键结果+写显存数据"一个任务。任务少了,出bug的概率自然降下来。

1.3 I2C接口相比SPI和并口的实际意义

从接口选型的角度,I2C的意义在于能用最少的两根线(SCL和SDA)完成所有通信。实际项目中,主控的GPIO资源往往比Flash容量还紧张,尤其在做小家电、智能插座、温控面板这类产品时,一颗8脚或者20脚的MCU要把每个引脚都精打细算:几个ADC通道采集传感器、几个IO口控制继电器、几个口接蜂鸣器和LED。能省出5到8个IO口,整个BOM和布线难度完全不一样。

当然有人会提SPI,SPI速度更快,但标准的SPI最少也要三根线(SCK、MOSI、MISO),而且很多MCU的SPI外设要复用,引脚冲突经常需要跳线解决。I2C还有一个好处,就是同一根总线上可以挂多颗从设备——显示屏挂一颗0x70地址的芯片,温湿度传感器挂一颗0x44地址的芯片,只要地址不冲突,就都能访问。这在做多模块集成的产品时,对PCB布局非常友好。

FZH1643选择I2C作为通信口,还有一个很实际的原因:I2C是半双工、带应答机制的协议,适合这种"主控问一句、芯片答一句"的交互模型。写显存数据时,主控连续发送字节,芯片收到后回ACK;读按键状态时,主控先写寄存器地址,再切换为读模式,芯片把数据送上总线。整个过程逻辑清晰,即使通信出错,主控也能通过ACK状态和超时机制判断出来。

2. 双模式切换的实际形态:显示扫描与低功耗之间的取舍

2.1 说的"双模式"是哪两个模式

"双模式"这个说法容易让人迷糊,有人理解为两种显示模式,有人理解成两种通信模式。以这类芯片的常见设计来看,实际指的是两种工作状态:正常扫描模式和低功耗待机模式。正常扫描模式下,芯片内部振荡器持续运行,LCD扫描波形持续输出,键盘扫描也同步工作,所有的显示数据和按键状态都能实时反映;而低功耗待机模式下,芯片会关掉LCD扫描输出、暂停键盘扫描,只保留I2C接口的寄存器读写能力,让整个芯片的耗电降到微安级别。

为什么需要这种切换?因为段码LCD的扫描驱动本质上是一个持续消耗能量的过程——COM和SEG之间反复充放电,加上偏压产生电路的静态功耗,让显示驱动成为整机功耗的大头。很多产品要求待机功耗极低,比如电池供电的遥控器、温控面板、智能电表,几毫安的显示扫描电流都是不能接受的。所以FZH1643这类芯片会把"关显示、停扫描"做成一个明确的模式,而不是让用户简单地写一个空显存来"假装"关闭。

2.2 模式切换的内部状态变化:振荡器、扫描时钟、显存保持

模式切换并不是简单地把某个GPIO输出关掉,芯片内部是一整套联动动作。正常扫描模式下,内部RC振荡器(或者外部晶振)持续运行,分频电路产生LCD扫描用的帧时钟和键盘扫描用的行扫描时钟,这些时钟分别送到COM扫描发生器和键盘扫描状态机。

切换到低功耗待机模式时,振荡器会停掉或者降频,分频链路的输出被切断,COM口和SEG口进入高阻或者固定电平状态,键盘扫描状态机复位到空闲状态。值得注意的是,显存里的数据并不会因为模式切换而清零——它只是不再被映射到输出引脚上而已。这意味着主控在待机前写入的显示数据,唤醒后会原样恢复,不需要重新初始化一遍。

这个特性在实战中很有用。比如温控面板从待机唤醒,用户希望看到的是进入待机前的温度数值,而不是一个空屏幕再重新刷一遍。我实际测试过好几次,发现只要在写显存后、进入待机前做一个写命令同步操作,唤醒后显示内容就能无缝恢复,不会出现花屏或闪烁。

2.3 一次完整模式切换的操作流程

以FZH1643的典型指令结构为例,切换到待机模式需要使用系统命令,常见的做法是向命令寄存器写入特定的模式控制字。以下是一段参考流程代码,具体命令字以实际数据手册为准:

void FZH1643_EnterStandby(void) { uint8_t cmd = 0x02; // 示例:系统命令,进入待机模式 I2C_Start(); I2C_WriteByte(0xE0); // 器件写地址,示例值 I2C_WriteByte(cmd); // 发出模式切换命令 I2C_Stop(); } void FZH1643_ExitStandby(void) { uint8_t cmd = 0x01; // 示例:系统命令,进入正常模式 I2C_Start(); I2C_WriteByte(0xE0); I2C_WriteByte(cmd); I2C_Stop(); }

这段代码看着简单,但里面有两点容易忽略。第一,模式切换命令发出后,芯片内部有一定响应时间,即使数据手册没有明说,也建议在切换后延时1到2毫秒再继续后续操作,尤其是从待机唤醒后立刻写显存的情况,太急了可能丢数据。第二,如果总线上还挂了其他设备,发完模式切换命令之后最好等一下,让芯片内部状态稳定,不要立刻发起下一笔通信,否则可能因为从机正在切换内部时钟而收不到ACK。

实际产品中,我通常会设计一个"电源状态机":上电初始化为正常模式 → 工作若干秒无操作 → 进入待机模式 → 检测外部按键或RTC中断后唤醒。FZH1643的待机模式功耗本身已经很低,但要注意主控侧的唤醒来源,如果按键扫描也被关掉了,那唤醒就不能依赖读键值,通常要预留一个独立的按键中断脚,或者用定时器周期性唤醒主控后再由主控唤醒显示芯片。

3. 键盘扫描的硬件与软件配合:矩阵结构、防抖、读键流程

3.1 矩阵键盘的检测原理,以及芯片内部做了什么

矩阵键盘的基本原理是用较少的IO口检测较多的按键。以4×4矩阵为例,4根行线和4根列线交叉,每个交叉点放一个按键,共16个按键。检测时,扫描方让行线逐行输出低电平或高电平,然后读列线状态;某一行被驱动为低时,如果该行与某列交叉的按键被按下,对应的列线就会被拉低(或拉高,视电路极性而定),由此确定按键位置。

FZH1643做扫描时,内部状态机自动完成这个"行驱动+列读取"的过程,扫描频率通常在几十到几百赫兹之间,取决于芯片配置的分频系数。主控完全不需要介入每一步的行线切换和列线采样,只需要最后读扫描结果即可。这对主控的一个隐藏好处是:按键检测的时间抖动大大降低,因为硬件扫描是固定节奏运行的,不像软件扫描那样容易受中断优先级、任务调度影响。

3.2 扫描结果寄存器的组织方式

键盘扫描的结果会以位图(bitmap)形式存放在芯片的按键寄存器区。每一位对应一个按键位置,值为1表示按下,为0表示未按下。比如8个按键的键盘,用1个字节就能表示全部状态;如果是16个按键,就占2个字节。FZH1643的寄存器地址空间里,一般会给按键状态分配连续的地址区域,主控通过I2C读命令一次性读回。

这里有一个实际经验值得分享:很多新手读按键时,喜欢把整个寄存器区都读回来,然后逐位判断。但如果按键数量不多,建议只读取涉及到的寄存器字节,减少I2C通信时间。I2C速度在100kHz或400kHz时,多读一个字节看起来不慢,但在低功耗场景下,总线活动时间直接关系到平均功耗。只读必要的字节,不仅通信更快,还能让MCU更快进入sleep状态。

3.3 防抖的边界问题:芯片自带的防抖够不够

机械按键按下和释放的瞬间,触点会经历一个几毫秒到十几毫秒的抖动过程。直接读取电平会因为抖动产生多次跳变,软件上通常采用延时采样或者连续数次采样一致才判定有效。FZH1643这类带硬件扫描的芯片,在扫描结果里已经内置了防抖设计——扫描周期本身构成了时间窗口,只有持续被扫描到一定次数的状态变化才会更新到按键寄存器。

但千万不要以为芯片自带防抖就可以完全丢掉软件防抖。以我实际调试的经验,芯片的硬件防抖解决的是"同一时刻电平跳变"的物理抖动,而软件层面的"功能防抖"(比如按键松开后必须间隔多少秒才能再次触发)是需要主控自己实现的。例如一个"加温"按键,用户长按时希望持续加温,就需要主控自己判断按下持续时间;用户短按时希望单次触发动作,就需要主控监测"从按下到松开"的完整事件。这些业务逻辑芯片不可能替你完成,必须在主控的按键任务中处理。

按键读回的推荐流程可以这样组织:

uint8_t FZH1643_GetKey(void) { uint8_t key_sts[2]; FZH1643_ReadKeys(key_sts, 2); // 通过I2C读取2字节按键寄存器 if(key_sts[0] == 0 && key_sts[1] == 0) return 0; // 没有按键 for(uint8_t i = 0; i < 16; i++) { uint8_t byte_idx = i / 8; uint8_t bit_pos = i % 8; if(key_sts[byte_idx] & (1 << bit_pos)) { return (i + 1); // 返回按键编号1~16 } } return 0; }

在主控代码里,这个函数周期性调用(比如每隔5到10ms),再配合上一次的状态做边沿检测,就能区分"按下事件""松开事件"和"持续按住"。我一般会再套一层简单的状态机,用于区分短按和长按,这是产品交互层面的常用需求,和芯片硬件扫描没有冲突。

4. I2C接口实操:从从机地址到显存和按键寄存器的读写

4.1 从机地址与命令字节的结构,先看懂芯片怎么组织通信

I2C通信的第一步是从机地址。FZH1643作为从设备,会有一个固定的7位地址或者支持地址引脚配置。以示例地址0x70为例,对应的写地址就是0xE0,读地址就是0xE1。这类地址在数据手册的"Slave Address"章节一定会写明,设计PCB的时候如果芯片有地址配置脚,提前规划好是接高还是接低,避免后面多颗同型号芯片挂同一条总线时地址冲突。

FZH1643的命令结构通常会区分两种类型:一种是写入命令寄存器(控制芯片工作模式、显示配置、偏压设置等),另一种是访问显示寄存器和按键寄存器。有的芯片通过第一个字节(控制字节)来区分接下来是命令还是数据,有的则通过寄存器地址的高位来区分。不管哪种方式,掌握一个原则就行:先发控制/地址信息,再发数据;读操作先写寄存器地址,再切换读方向。

4.2 显示数据写入的完整时序

段码LCD的每个显示段在芯片内部都对应显存中的一个bit。上位机要显示某个数字,实际上是把一个位图数据写入到显存对应的地址里。下面是一个典型的写显示数据流程:

void FZH1643_WriteDisplayRAM(uint8_t addr, uint8_t *data, uint8_t len) { I2C_Start(); I2C_WriteByte(0xE0); // 写地址 I2C_WriteByte(addr | 0x80); // 寄存器地址,带写标志 for(uint8_t i = 0; i < len; i++) { I2C_WriteByte(data[i]); // 连续写入显存数据 } I2C_Stop(); }

这里的addr和len取决于屏的段位分布。比如一个8位数字加若干符号的屏,如果芯片显存按8bit一个字节组织,那么一次写8个字节到显存起始地址,就能一次性刷完整个屏幕。实际项目中,我习惯在程序里建一个与显存大小对应的缓存数组,需要更新显示内容时先改缓存数组,再把整个数组通过I2C写进芯片。这样做的原因是,改任何一位都需要完整刷新才能保证一致性,而且从缓存到芯片的映射关系一目了然,方便排查问题。

4.3 读取键盘扫描结果,以及I2C读操作的方向切换

I2C的读操作比写操作多一个方向切换的步骤。主控需要先发出从机地址的写方向,把要读取的寄存器地址告诉芯片,然后重新发出起始条件(Restart)或者停止后重新开始,再发从机地址的读方向,最后读回数据并发出NACK结束。下面是一个参考实现:

void FZH1643_ReadKeys(uint8_t *buf, uint8_t len) { I2C_Start(); I2C_WriteByte(0xE0); // 写地址 I2C_WriteByte(0x40); // 示例:键盘扫描结果寄存器起始地址 I2C_Start(); // 重新发送起始条件 I2C_WriteByte(0xE1); // 读地址 for(uint8_t i = 0; i < len; i++) { buf[i] = I2C_ReadByte(i == (len - 1)); // 最后一字节返回NACK } I2C_Stop(); }

这里最后返回NACK是一个关键细节。I2C协议规定,主控读最后一个字节之前和之后要发送NACK来通知从机"读完了",如果全部都用ACK应答,从机会一直往总线上送数据,导致多余字节,甚至因为总线状态混乱引发错误。很多刚开始调I2C读操作的人卡在这一步,总线波形看着乱,其实就是应答位没处理好。

4.4 初始化驱动的完整代码参考

结合前面的内容,给一个完整的初始化流程参考,包含I2C配置、进入正常模式、设置显示参数、清空显存:

void FZH1643_Init(void) { uint8_t clear_data[8] = {0}; I2C_Init(400000); // 配置I2C时钟为400kHz FZH1643_ExitStandby(); // 从待机或默认状态进入正常模式 delay_ms(2); // 等待内部振荡器稳定 // 发送显示配置命令(示例:使能显示、设置1/4占空比、1/3偏压) uint8_t cfg = 0x28; // 示例配置字,实际按手册 FZH1643_WriteCommand(cfg); // 清一次显存,避免上电显示乱码 FZH1643_WriteDisplayRAM(0x00, clear_data, 8); }

初始化顺序我建议固定为:供电稳定 → I2C通信测试(读一个固定寄存器校验)→ 退出待机模式 → 配置显示参数 → 清显存 → 正常显示。为什么要把"I2C通信测试"放在前面?因为在批量生产中,芯片焊接不良或者总线不通是常见故障,如果一上电就初始化显示,很可能屏幕不亮但主控代码还在跑,等发现时已经过了大半天。先读一个芯片版本寄存器或者复位标志寄存器,确认通信正常再继续,能省很多排查时间。

5. 项目实测中遇到的坑:偏压、上拉、扫描干扰与上电时序

5.1 偏压和占空比没配对,显示重影或发暗

这是段码LCD项目里最经典的坑。偏压(bias)和占空比(duty)必须和屏厂的规格书严格对应。屏厂会说这块屏是"1/4 duty、1/3 bias",那芯片就必须配置成相同的组合。如果把1/3偏压的屏配成1/2偏压,会出现不该亮的段微亮、显示重影;反过来如果把1/2偏压的屏配成1/3偏压,显示对比度就会变差,笔画颜色发浅。

我遇到过一块屏,刚开始怎么调都是"8"字显示的时候某些段亮但不完整,排查了很久才发现是芯片配置里把占空比设置成了1/3,而屏实际是1/4的。这种问题在软件层面看没有任何报错,因为寄存器写入都正常,但显示效果就是不对。所以做样机阶段,第一件事就是跟屏厂确认duty和bias参数,然后写进配置注释里,防止后面换人维护时改错。

5.2 I2C上拉电阻和总线电容的实测取舍

I2C总线需要上拉电阻,这是常识,但具体选多大阻值很多人拿不准。标准模式下总线电容通常是几pF到几十pF,阻值太小(比如1K)会加大灌电流,阻值太大(比如100K)会拉长信号边沿,导致波形上升沿太缓,在高速模式下产生通信错误。比较常用的区间是2.2K到4.7K,具体取决于总线长度和挂载设备数量。

当FZH1643和主控距离比较远,或者中间加过ESD保护器件时,总线电容会明显增加。此时如果系统频率用400kHz,建议用逻辑分析仪看下SCL和SDA的上升沿时间,如果超过300ns,要么换小一点的上拉电阻,要么把I2C频率降到100kHz。实测中,4.7K在短距离单设备场景非常好用,但多设备并连、走线较长时,2.2K更稳妥。还要注意SDA上的上拉电阻大小会影响按键读取的稳定性——总线边沿太缓,数据采样会发生位移,偶尔会出现读到0xFF或0x00这种异常值。

5.3 键盘扫描与显示扫描的时间窗冲突,以及芯片的仲裁机制

FZH1643内部同时运行LCD扫描和键盘扫描,很多用户担心两个扫描会不会互相干扰。实际上芯片设计时已经做了时间片仲裁,LCD扫描和键盘扫描复用内部时序,不会出现同时占用的冲突。但有一个特殊情况:I2C访问显存或按键寄存器的瞬间,如果正好撞上内部扫描读取显存的周期,就可能出现短暂的busy状态,表现为I2C通信偶尔延迟一个ACK。

这种延迟不是错误,但主控代码要有超时容错。我的处理方式是给I2C通信加一个简单的重试机制:发送地址后如果没有收到ACK,延时1ms再重试,连续3次失败才报错误。这个机制能吸收芯片内部的忙碌周期,又不至于掩盖真正的硬件故障。另外,I2C时钟频率越高,单次通信时间越短,撞上内部扫描窗口的概率越低,所以条件允许时用400kHz而不是100kHz,反而更稳。

5.4 上电复位时序和看门狗协作

FZH1643上电后需要一定时间的内部复位过程,典型值是几百微秒到几毫秒。如果主控在芯片还没准备好时就发起I2C通信,第一个字节基本拿不到ACK。有些主控的看门狗周期很短,如果启动代码里I2C通信失败导致看门狗复位,就会形成"复位-通信失败-再复位"的死循环。

解决方法是把初始化做的"温柔"一点:上电后延时50到100毫秒,等电源稳定,再发起第一次I2C通信。同时把看门狗的喂狗位置放在通信重试循环之外,避免重试期间看门狗溢出。这个经验来自一次真实的事故:现场有一批设备偶发启动黑屏,抓日志发现是电源上电波形有轻微抖动,芯片复位不完整,而主控复位太勤快,两个复位相互打架。加长了初始延时就再没复发过。

6. 最后聊点选型和设计的个人看法

FZH1643这类合封方案,最打动我的地方不是某一项指标多突出,而是它把显示驱动和键盘扫描两个"脏活累活"一起打包了,让主控代码可以保持干净。在实际项目选型时,我通常会列一个简单的对照表来判断合不合适:需要驱动的段码屏有多少段、按键数量有多少、I2C总线上还挂了哪些设备、待机功耗要求是多少、工作温度范围是否覆盖产品应用场景。这些参数和FZH1643的规格一对,基本就能判断方向对不对。

如果你的产品只需要显示、没有按键,那选一颗纯LCD驱动芯片就够了,没必要为用不上的键盘扫描功能买单。如果按键数量特别多(超过8×8矩阵),或者需要支持一些特殊按键行为(比如长按连续触发、组合键检测),那可能要考虑按键扫描交给主控做、显示单独用驱动芯片的方案。选型不是越集成越好,而是看合封的这部分功能是不是正好落在你的需求范围内。

按照我个人的调试习惯,第一次拿到FZH1643样片,不会急着写完整应用,而是先用逻辑分析仪把I2C通信抓一遍,确认芯片能正常应答、显存读写能回读一致,再做显示和按键联调。这一步看起来慢,实际上是最快的路径。等这些基础操作都验证过,再进到具体的双模式切换、低功耗调优,心里就有底了。最后再提醒一句:写代码时记得在显存更新函数前后加上合适的延时或者使用Busy标志位判断,别太相信瞬间完成,I2C从机不是寄存器数组,它是带内部时序的物理器件。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询