
简介一份基于HT1621芯片的段式LCD驱动代码面向需要低功耗、小体积显示模块的嵌入式开发者可快速解决HT1621与段式LCD之间的底层通信与显示控制问题。压缩包内仅包含1个C语言源文件HT1621.c文件体积约3KB代码精简无冗余便于直接阅读、修改和移植到AVR、ARM、PIC等常见平台。该驱动完整覆盖初始化、串行数据传输、段码转换、显示更新以及控制函数等关键模块开发者通过适配HT1621.c即可掌握HT1621的SPI/I2C通信时序和LCD驱动流程在嵌入式产品中实现文本与数字的清晰显示。资源已有137人学习小巧实用适合正在调试段码屏或希望理解HT1621驱动原理的开发者参考借鉴省去从零编写底层驱动的时间。1. 段式LCD与HT1621为什么这个驱动芯片值得拆家电面板、温控器、电表上的液晶数字大多由段式LCD构成。段式LCD每个段由一根COM和一根SEG控制驱动芯片负责扫描和偏压。HT1621是这类驱动里最常见的一颗内置振荡器、偏压电路和显示RAM几根IO线就能驱动几十个显示段在低成本嵌入式产品里几乎绕不开。这颗芯片的坑在于资料常把它当SPI或I2C设备实际是自定义三线串行时序命令结构、RAM寻址和段码映射都自成体系。直接套SPI外设能点亮但段码错位、亮度不匹配时还得回头查协议。下面这份HT1621.c驱动正好覆盖了从时序到段码的完整流程讲清怎么在新MCU上把段式LCD调通。适合正在做段式LCD样机、想把旧项目迁到新MCU、或想弄懂COM/SEG和段码表关系的开发者。下面按协议、代码、段码、优化四层拆。2. 三线串行时序与寄存器配置HT1621通信协议拆解2.1 引脚功能与接线选型先看硬件。HT1621引脚按功能分成三类电源、三线串口、LCD驱动矩阵。常见封装下COM引脚有COM0~COM3SEG引脚有SEG0~SEG31组合起来最多能驱动128个显示段。对4位数字加几个图标的场景通常用到2~3个COM就够多出来的COM悬空即可不影响整颗芯片的工作。引脚方向功能说明VDD电源2.4V~5.2V就近放0.1uF去耦电容GND电源地VLCD模拟LCD驱动电压决定对比度需电阻分压CS输入片选低有效一次传输期间保持低WR输入写时钟上升沿锁存DATA上的电平DATA输入/输出写命令和数据时输入读按键时输出COM0~COM3输出LCD公共端屏用几个就接几个SEG0~SEG31输出接LCD段电极按屏厂图纸连接接线时有两个容易犯的错。第一个是VLCD直接接VDD。部分屏在3.3V下确实能亮但对比度很低因为LCD偏压需要的是VDD和VLCD之间的压差不是单纯供电。常见做法是用两颗电阻分压或者用10k电位器先调出合适的对比度再把固定阻值留下来。第二个是DATA线在写模式下必须明确配置为输出读按键模式再切回输入GPIO初始化时就该想清楚免得按键读取时把总线拉死。2.2 命令格式与初始化序列HT1621的上行传输每次由CS拉低开始先发3位前导码再发命令或数据。前导码决定这次事务的类型101表示写命令110表示读按键数据写入时走另一组地址加数据的组合。这套结构和标准SPI的最大区别是命令和数据不在连续时钟流里中间可能夹着地址和片选切换所以现成驱动大多选择纯软件模拟时序而不是挂SPI外设。命令按功能分成系统配置和显示控制两类系统类负责开关芯片和选择振荡源显示类负责打开屏幕和设置亮度。直接用散装命令不好维护常见驱动里会用宏把固定字节封装成可读名称下面这组宏就是这类驱动里最常见的写法。#define HT1621_SYS_EN 0x03 /* 系统使能 */ #define HT1621_RC_OSC 0x29 /* 内部RC振荡器 */ #define HT1621_BIAS_DUTY 0x52 /* 1/3偏压, 4COM */ #define HT1621_DISP_ON 0x70 /* 显示开启 */ #define HT1621_DISP_OFF 0x60 /* 显示关闭 */ #define HT1621_SYS_DIS 0x02 /* 系统关闭 */初始化顺序一般是先使能系统再配置振荡器然后设置偏压和公共端数最后打开显示。RC振荡器这一步能省掉外部晶振但也意味着刷新频率会随温度和电源电压漂移对显示内容固定的产品影响不大。偏压和COM数的配置必须和屏一致4COM的屏写成3COM会出现某些段亮度不均甚至完全不显示。void ht1621_init(void) { ht1621_write_cmd(HT1621_SYS_EN); /* 打开芯片内部电源 */ ht1621_write_cmd(HT1621_RC_OSC); /* 使用内部RC振荡器 */ ht1621_write_cmd(HT1621_BIAS_DUTY); /* 偏压/公共端匹配屏 */ ht1621_write_cmd(HT1621_DISP_ON); /* 最后才开显示 */ }最后一个命令放在最后是有意的。如果先把显示打开再配置偏压屏幕上会闪一下不稳定的内容虽然不影响最终效果但在批量产测时容易被当成异常。命令值在不同来源的驱动里可能不同因为有的驱动把前导码拆开单独发有的把前导码拼进字节核心是这四步的顺序和语义不要直接复制别人的宏定义就完事。2.3 数据写入与RAM地址规律命令之外是数据写入。HT1621内部RAM按地址组织驱动函数需要同时给出地址和数据。标准的写数据流程是CS拉低发101前导码发6位RAM地址再发8位数据最后CS拉高。地址范围从0x00到0x1F共32个地址位每个地址对应8个显示段。在这里要注意地址连续并不等于显示位置连续。void ht1621_write_data(uint8_t addr, uint8_t dat) { uint8_t i; CS_LO(); ht1621_write_bits(0x05, 3); /* 101 写数据前导码 */ ht1621_write_bits(addr, 6); /* 6位地址 */ for (i 0; i 8; i) { if (dat 0x80) DATA_HI(); else DATA_LO(); WR_HI(); WR_LO(); dat 1; } CS_HI(); }地址和屏的SEG/COM交叉关系是一对多的4COM的屏中相邻地址可能分别对应不同的COM组。所以写一个数字后如果发现它串到了隔壁段位先怀疑段码表再怀疑地址间隔。一个经验是准备一张屏的COM/SEG交叉表调试时对照写能省下大量试错时间。读按键的时序类似前导码换成110然后按命令字节读取DATA引脚的返回电平。按键扫描不是这段驱动的核心但如果屏上带了触摸按键就会用到。读取时要注意先把DATA设为输入读完再恢复输出否则下一次写数据传输时总线电平会被拉乱。ht1621_write_bits函数负责把指定数量的位按时序发出MSB在前cnt参数就是位数。addr参数范围0~31超出部分写入会被芯片忽略。这个底层函数在新MCU上移植时只需要替换GPIO电平操作协议层不需要改动前提是时钟沿极性保持一致。3. HT1621.c代码结构初始化到显示刷新的实现拆解3.1 文件分层把平台相关和逻辑分开拿到的ht1621_lcd_code压缩包里核心文件是HT1621.c。这类驱动文件普遍的问题是把引脚操作、时序、段码转换全揉在一起在AVR上能跑换到STM32就要动十几处。建议拿到代码后先做三层拆分第一层是平台相关的GPIO电平读写第二层是HT1621协议的命令和数据传输第三层是显示API。三层之间用函数指针或宏隔离后面换MCU时只改第一层。分层关系可以用下面这张表看函数名以常见驱动风格为例。层次职责典型接口平台层GPIO电平、延时ht1621_cs_set / ht1621_data_set协议层前导码、地址、数据移位ht1621_write_cmd / ht1621_write_data应用层清屏、数字、图标、刷新策略ht1621_show_number / ht1621_clear很多HT1621.c实际上是两层结构把协议和GPIO写在一个函数里。还有的驱动用usleep做延时这在RTOS里会引入不确定性因为任务切换可能把半个时钟周期拉得很长。如果要在RTOS下用最好把位时序改成不带延时的忙等并且在位周期内关中断防止调度器打断导致时序撕裂。3.2 底层的位发送与接收函数协议层最基本的函数是ht1621_write_bits和ht1621_read_bits。前者负责把一个字节的高cnt位按MSB顺序发到DATA线后者在读到按键时使用。代码风格各家不同但核心都是对WR电平的控制。一个典型实现是static void ht1621_write_bits(uint8_t data, uint8_t cnt) { uint8_t mask 0x80; /* MSB先行 */ while (cnt--) { if (data mask) DATA_HI(); else DATA_LO(); WR_HI(); WR_LO(); data 1; } }mask初值是0x80每发送一位后左移一次这样无论cnt是3、6还是8都能从最高有效位开始取。WR先拉高再拉低形成上升沿锁存。很多MCU的GPIO翻转速度不同如果数据在WR上升沿出现时尚未稳定可以在WR_HI前加一个空循环或两个NOP反过来如果总是读到偏离一位的数据检查GPIO是不是配置成了开漏且没有上拉。3.3 显示缓冲区与批量刷新HT1621自身带显示RAM会持续扫描输出MCU不需要像老式动态刷新那样一直喂数据。这一点和不少网帖里“必须用定时器不断刷新显示”的说法相反。但实际驱动里维护一块镜像缓冲区仍然值得因为段式LCD的显示内容通常是一次更新一组多次直接写芯片RAM中间态会在屏上闪一下。镜像缓冲区先把所有修改合并好再一次刷进HT1621。static uint8_t display_buf[32]; void ht1621_update_display(void) { uint8_t i; for (i 0; i 32; i) ht1621_write_data(i, display_buf[i]); } void ht1621_clear(void) { memset(display_buf, 0x00, sizeof(display_buf)); ht1621_update_display(); }数组长度32对应0x00~0x1F地址范围。如果LCD只用了部分段仍然可以整块刷因为未使用的地址写入后只是不显示不影响其他功能。这块缓冲区也是段码和图标操作的入口后面所有显示API都先改buffer再决定是立刻刷还是等下一次统一刷新。3.4 批量刷新与单字节更新的取舍对于静态显示为主的应用整块刷新浪费的时间可以忽略。但带按键扫描和动态数字跳动的场景整块刷新会占用不少IO时间。更合理的做法是按需更新数字变化时只写那一个地址其他段不碰。实现上写一个单字节版本的更新函数即可。void ht1621_latch_byte(uint8_t addr, uint8_t dat) { display_buf[addr] dat; ht1621_write_data(addr, dat); }这个函数用于单个数字频繁更新不经过整体刷新减少中间态。调用方需要注意同步问题如果其他地方维护了同一地址的图标位更新数字前要把图标状态一并合并进来否则会覆盖。更进一步的方案是维护一个dirty数组每次修改只标记地址在统一flush时只写被标记的地址适合显示内容分布在不同地址区的产品。4. 段码表设计与字符显示实战数字、图标与自定义段4.1 七段数字的段码生成段式LCD屏幕上最常显示的是数字。一个七段数字由A到G七个段组成加一个小数点就是8个bit正好放进一个字节。写驱动时先把每个段的位置定义清楚然后用位或运算组合出数字的段码代码比直接写十六进制可读性高得多#define SEG_A (1 0) #define SEG_B (1 1) #define SEG_C (1 2) #define SEG_D (1 3) #define SEG_E (1 4) #define SEG_F (1 5) #define SEG_G (1 6) #define SEG_DP (1 7) const uint8_t seg_digits[] { SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F, /* 0 */ SEG_B|SEG_C, /* 1 */ SEG_A|SEG_B|SEG_D|SEG_E|SEG_G, /* 2 */ SEG_A|SEG_B|SEG_C|SEG_D|SEG_G, /* 3 */ SEG_B|SEG_C|SEG_F|SEG_G, /* 4 */ SEG_A|SEG_C|SEG_D|SEG_F|SEG_G, /* 5 */ SEG_A|SEG_C|SEG_D|SEG_E|SEG_F|SEG_G, /* 6 */ SEG_A|SEG_B|SEG_C, /* 7 */ SEG_A|SEG_B|SEG_C|SEG_D|SEG_E|SEG_F|SEG_G, /* 8 */ SEG_A|SEG_B|SEG_C|SEG_D|SEG_F|SEG_G, /* 9 */ };这个表的假设是bit0到bit6分别对应段A到段G。但HT1621输出到LCD引脚后段的位置取决于屏的走线不同屏厂定义可能完全不同。拿这个表点亮一块屏数字显示成镜像或乱跳是正常现象修正方式不是改代码而是重新排列数组顺序。4.2 用探针程序反查段位映射当屏的资料不全或跟样品不一致时最快的方法是让驱动依次点亮每个RAM位人眼记录对应显示段。这个方法不需要复杂工具适合现场调试。简单探针程序如下void seg_probe(void) { uint8_t addr, bit; ht1621_clear(); for (addr 0; addr 32; addr) { for (bit 0; bit 8; bit) { ht1621_write_data(addr, 1 bit); delay_ms(300); } } }跑一遍之后就能画出一张RAM位到屏上段的对应表然后按这张表重新生成段码。记录的表格可以这样组织方便后续写映射代码时直接查。RAM地址bit位屏上段名0x00bit0数字1的A段0x00bit1数字1的小数点0x01bit0电池图标左格比对原理图数引脚快得多尤其当屏的COM和SEG走线很绕时数错一个引脚就会导致一排段错乱。这个过程要在VLCD电压正常的前提下进行否则某些段亮度太低会被漏记。4.3 多位数字拆分与小数字段显示多位数字时需要把整数拆成各位再映射到对应地址。驱动一般提供类似下面的函数void ht1621_show_number(uint16_t val, uint8_t addr, uint8_t digits) { uint8_t i; for (i 0; i digits; i) { uint8_t code seg_digits[val % 10]; display_buf[addr i] code; ht1621_write_data(addr i, code); val / 10; } }参数含义val是待显示的无符号整数addr是数字位对应的起始RAM地址digits是位数。循环里先取个位显示在最低地址再整除10取下一位所以调用时addr要指向显示顺序上最靠右的那一位。如果屏的位顺序相反把addr i改成addr - i即可。超出digits的位不会显示因此这个函数天然防护了溢出显示。如果要在数字前面补零把取模结果等于0且已经进入前导零阶段时直接显示空白段码而不是0。小数点的处理方式是在段码上叠加SEG_DP。如果屏幕上小数点是独立的段需要知道它对应哪个RAM位。通常一个数字的小数点与它的某个段共用地址具体位置由屏厂决定。控制代码可以是void ht1621_show_decimal(uint8_t addr, uint8_t on) { uint8_t cur display_buf[addr]; if (on) cur | SEG_DP; else cur ~SEG_DP; display_buf[addr] cur; ht1621_write_data(addr, cur); }这里直接用缓冲区读写避免覆盖掉这个地址上已经在显示的数字段。执行顺序是先显示数字再开小数点或者反过来都行但每次操作后都要执行一次写RAM否则屏幕不会更新。4.4 图标段操作与读改写段式LCD上除了数字还有电池、单位、信号强度等图标。这些图标在RAM里和其他数字共用地址更新时不能整字节覆盖必须读改写。前面3.3节里维护的display_buf在这里是关键因为HT1621的RAM读出来不够可靠直接从镜像缓冲区读改写最安全。void ht1621_set_icon(uint8_t addr, uint8_t mask, uint8_t on) { if (on) display_buf[addr] | mask; else display_buf[addr] ~mask; ht1621_write_data(addr, display_buf[addr]); }调用方式举例ht1621_set_icon(0x0A, (1 4), 1);表示打开0x0A地址上的bit4图标。掩码中每一位对应屏上的一个图标段。把多个图标整理成枚举或宏定义比散落的魔法数更容易维护。实际项目里往往还要处理图标和数字共用同一个字节的情况这时先更新数字段码再调用set_icon叠加图标位顺序不能反。5. 亮度调节与低功耗VLCD电压与待机优化5.1 亮度等级与VLCD电压的配合HT1621命令里有亮度等级设置但段式LCD没有LED背光这个亮度实际是LCD偏压的强弱调的是对比度。命令能调的档位有限真正决定显示效果的是VLCD引脚电压。屏厂通常会给出VLCD参考值一般在3V到4.5V之间用电阻分压从VDD取电。经验做法是先接一颗10k电位器从最低电压开始慢慢往上调看到数字清晰、无黑影、无残影时固定阻值。如果要在产品里做多档亮度用PWM经过RC滤波接到VLCD是常见方案RC时间常数取屏幕刷新周期的5倍以上避免亮度跟着PWM频率抖动。5.2 待机电流先关显示再关系统电池供电产品对静态电流敏感HT1621进入低功耗的顺序有讲究。完整步骤是清屏、关显示、关振荡器最后把三根通信线拉低。清屏是为了防止某些段长期处于开启状态在LCD上留下残影。关显示和关系统的命令顺序不要反过来否则LCD会在停振前瞬间闪一下。驱动里加一个sleep函数void ht1621_sleep(void) { ht1621_clear(); ht1621_write_cmd(HT1621_DISP_OFF); ht1621_write_cmd(HT1621_SYS_DIS); DATA_LO(); WR_LO(); CS_HI(); }这里CS保持高、WR和DATA拉低是为了不悬空。HT1621在系统关闭后仍有微弱功耗但主要漏电路径在VLCD分压电阻上所以低功耗设计里VLCD经过一个MOS管或IO控制的开关需要显示时再接通。5.3 故障排查速查表现象优先检查项全屏无显示VDD、VLCD电压DISP_ON是否发出显示极淡VLCD偏低偏压设置和屏不匹配数字乱码段码表顺序、地址跨度、WR边沿极性出现鬼影亮段COM数配置错误偏压比错误内容闪烁电源纹波大VLCD或VDD加滤波电容待机电流超标VLCD分压电阻、CS/WR/DATA是否浮空HT1621驱动移植到新MCU上大部分问题不是芯片本身而是映射关系没对齐。抓时序用逻辑分析仪看CS、WR、DATA三根线的相对关系确认前导码是101、时钟沿和数据稳定时间是否满足芯片手册要求。软件模拟时序时把WR拉高前的两条NOP加上能解决不少跑得快就出错的问题。本文还有配套的精品资源点击获取