简介:HT1621.rar提供专用于HT1621液晶驱动芯片的C语言底层驱动源码,面向嵌入式开发者、单片机工程师以及正在调试点阵LCD显示模块的电子爱好者。HT1621芯片内置RAM存储区、8位数据总线,支持128/192点单色液晶驱动和多种显示模式,这套源码正是围绕上述特性给出工程可用的驱动实现。资源聚焦ht1621.c单文件,包体仅2KB,代码精简,便于快速阅读与工程移植。驱动函数完整覆盖初始化、数据写入、显示区域控制、错误处理等环节,并可通过修改I/O配置适配不同主控引脚,帮助读者理解内部寄存器操作、指令时序以及低功耗节能模式的设计思路。对于基于51、STM32等主流微控制器接入HT1621、驱动点阵液晶屏的开发者,这是一份可直接参考的底层代码范例。当前已有158人学习下载,适合需要对照芯片手册逐行学习驱动编写、或在此基础上二次开发显示效果并优化功耗的工程师使用。
1. HT1621 段式 LCD 驱动,为什么还要翻出一份 rar 里的老代码
拿到HT1621.rar_HT1621_HT1621产品_ht1621.c这类压缩包,第一反应往往是里面躺着一个十几年前的ht1621.c和一份扫描版数据手册。段式 LCD 本身不复杂,但 HT1621 的坑在于它既不是标准的 I2C,也不是标准 SPI,而是厂家自定义的三线串口,时序参数藏在寄存器说明里,稍不注意就出现显示乱码、偏暗、闪烁甚至完全不亮。压包里那份 C 源码,往往就是前人在某个产品项目里调通的参考实现,直接覆盖到新工程里通常能跑,但真正要做得稳,还得把那三条信号线、内部寄存器、段码映射和刷新方式全部理顺。这篇文章就按 HT1621 最常见的落地路径讲:先拆时序和寄存器,再给一份可抄的ht1621.c驱动骨架,最后说调试和边界问题。适合刚接手带段式 LCD 的嵌入式工程师,也适合想用 HT1621 替代传统 74HC595 方案但不想走弯路的人。
2. HT1621 内部结构与三线时序,决定代码能不能跑起来
2.1 HT1621 的引脚定义与接口模式选择
HT1621 是 Holtek 的 128 点(32×4)LCD 驱动器,最多直接驱动 32 个 SEG 和 4 个 COM,内部集成 RC 振荡器和 LCD 偏压发生器。关键引脚只有 5 个:CS、WR、DATA、VDD、GND,外加 VLCD 用于调节 LCD 工作电压。很多国产兼容型号引脚兼容,但偏压和命令字略有差异,拿到ht1621.c后先核对型号再改代码。
接口上 HT1621 支持两种模式:一种是三线串行接口(CS/WR/DATA),通常接单片机 GPIO 模拟;另一种是接内部 RC 或外部时钟,通过 CLK 引脚提供移位时钟。大多数产品用三线模式,因为 GPIO 模拟最灵活,不占硬件 SPI 外设。硬件 SPI 也能驱动,但要小心 HT1621 的时序要求与 SPI 模式 0/1/3 的匹配,实际项目中我一般不用硬件 SPI,因为 WR 和 DATA 的建立时间、保持时间在极端温度下会漂,GPIO 模拟反而可控。
引脚电平要注意:HT1621 的逻辑高电平最低是 0.7×VDD,如果单片机是 3.3V、HT1621 用 5V,那 DATA/WR/CS 必须加上拉或者用电平转换,否则在 3.3V 驱动 5V 器件时,逻辑高电平刚好卡在阈值边缘,温度一变化就出随机乱码。压包里那份代码如果只在 3.3V 下测过,移植到 5V 系统里要补上拉电阻或改用开漏输出。
2.2 读写时序:命令字与数据位的编排规则
HT1621 的操作套路很简单:CS 拉低使能,WR 作为写时钟,DATA 作为数据线,低位先出。写命令时,前 3 位是命令标识,后面跟具体内容;写数据时,前 3 位是数据标志 101,紧接着是高 4 位或 6 位地址,然后再是 4 位数据。区分命令和数据的关键就在前 3 位:
- 101:写数据到显示 RAM
- 100:读按键扫描值
- 110:写命令
- 100:读命令(部分型号)
实际项目里最常用的只有两类:写命令(110)和写数据(101)。命令模式下,0x04是系统使能,0x02是关闭,0x03是 LCD 偏压选择,这些值要按数据手册的位定义拼装。常见的错误是把命令字直接当作一个字节通过ht1621_write_cmd(0x01)发送,但 HT1621 的命令格式要求先发 3 位110,然后才发命令内容,所以底层函数要区分“命令前缀”和“命令内容”两部分。
下面给出一段 GPIO 模拟的三线写时序,这份代码是 HT1621 驱动里最底层的部分:
// 定义三个控制引脚 #define HT1621_CS_H() GPIO_SetBits(GPIOB, GPIO_Pin_12) #define HT1621_CS_L() GPIO_ResetBits(GPIOB, GPIO_Pin_12) #define HT1621_WR_H() GPIO_SetBits(GPIOB, GPIO_Pin_13) #define HT1621_WR_L() GPIO_ResetBits(GPIOB, GPIO_Pin_13) #define HT1621_DATA_H() GPIO_SetBits(GPIOB, GPIO_Pin_14) #define HT1621_DATA_L() GPIO_ResetBits(GPIOB, GPIO_Pin_14) // 发送1位数据,低位在前 void ht1621_send_bit(uint8_t bit_val) { if (bit_val) { HT1621_DATA_H(); } else { HT1621_DATA_L(); } // 先拉高WR,再拉低,形成一个下降沿 HT1621_WR_H(); HT1621_WR_L(); } // 发送count位数据,从最低位开始 void ht1621_send_bits(uint32_t data, uint8_t count) { for (uint8_t i = 0; i < count; i++) { ht1621_send_bit((data >> i) & 0x01); } } // 写命令:前3位固定为110,后面跟命令内容 void ht1621_write_cmd(uint8_t cmd) { HT1621_CS_L(); ht1621_send_bits(0x06, 3); // 110:命令前缀 ht1621_send_bits(cmd, 8); // 命令内容,8位 HT1621_CS_H(); }这段代码的关键在于ht1621_send_bits里先发低位,和很多 SPI 外设默认先发高位不同。HT1621 数据手册的时序图里,D0 在最前面,所以这个顺序不能错,否则命令字和数据的每一位都会对不上。ht1621_write_cmd里 CS 拉低后先发0x06的 3 位(即二进制 110),然后发命令内容。注意0x04是系统使能、0x02是系统关闭,而0x03用于选择 1/2 偏压还是 1/3 偏压,具体看手册第几页的寄存器表,不同批次兼容芯片对命令的接受范围略有差异。
ht1621_send_bit里 WR 先拉高再拉低的顺序,看起来是反直觉的,但 HT1621 是在 WR 下降沿锁存数据。如果反过来先拉低再拉高,就会变成上升沿锁存,同一块板子可能初始正常,跑偏后出现偶发错位。所以写底层时序时,要严格按“设数据→WR 高→WR 低”的顺序,不建议用把 WR 当作时钟源、数据在上升沿更新的写法去改顺序。
2.3 显示 RAM 的地址映射与写入策略
HT1621 内部有 128 位显示 RAM,按 4 个 COM 和 32 个 SEG 组织,但 RAM 地址并不是按 SEG 0~31 连续排列的,而是按 COM 0~3 分页:地址 0x00~0x1F 对应 COM0,0x20~0x3F 对应 COM1,以此类推。也就是说,写地址时低位地址是 SEG 编号,高位地址的低两位是 COM 编号。这个映射关系是新手最容易栽的地方:按 SEG 顺序刷新显存,屏幕却按列跳着亮。
写入 RAM 的数据格式是:先发101数据标志,再发 6 位地址(从最高位开始),最后发 4 位数据。地址范围 0x00~0x3F,共 64 个地址,每个地址存 4 位。但 HT1621 只用到其中 128 位,所以地址 0x20~0x3F 里有一部分是无效的,不同的兼容芯片对无效地址的处理不同,有的会忽略,有的会映射到别的段上。稳妥的做法是只写实际用到的段位,不用的段保持默认值,避免显示残留。
数据写入的 4 位对应的是 COM0~COM3 在某一个 SEG 上的点亮状态。如果想显示一个常见的“8”字形数字,需要把数字段码拆到 4 个 COM 上,提前用查表法把段码算好。压包里的ht1621.c通常已经有一张段码表,但那份表往往是为特定产品的 PCB 走线顺序做的,换一块板子就要重新映射,不能直接拿来用。
3. ht1621.c 最小驱动框架:从 GPIO 模拟到显存刷新
3.1 显存组织:用一个 u16 数组模拟 128 位 RAM
HT1621 的显示 RAM 是 128 位,最直观的建模方式是用 8 个uint16_t变量,每个变量代表 4 个 COM 在某个 SEG 上的状态,但这样按 SEG 遍历时逻辑比较绕。更常见的做法是直接用 8 个字节按地址顺序排布,每个字节的高 4 位和低 4 位分别对应两个连续的 4 位地址,这样刷显存时只需要顺序调用写数据函数即可,不用每个地址单独处理地址编码。
我一般定义一个 64 字节的数组来对应 HT1621 的全部 RAM 空间,其中前 32 字节实际有效:
uint8_t ht1621_ram[64]; // 对应地址0x00~0x3F,高32字节多数为空 // 刷新某一段显示区域:从addr开始连续写len个4位数据 void ht1621_update_display(uint8_t addr, uint8_t len) { HT1621_CS_L(); ht1621_send_bits(0x05, 3); // 101:写数据标志 ht1621_send_bits(addr, 6); // 6位地址 for (uint8_t i = 0; i < len; i++) { // 每个地址写4位数据,低位在前 uint8_t d = ht1621_ram[addr + i]; ht1621_send_bits(d, 4); } HT1621_CS_H(); }这里ht1621_send_bits(0x05, 3)发送的二进制是101,对应数据手册里的写数据命令。ht1621_send_bits(addr, 6)发送的是 6 位地址,先发最高位,这和命令内容先发低位不一样,属于 HT1621 特有的混搭格式。如果整帧刷新,addr从 0 开始,len设 32,顺序写完所有有效 RAM 地址。如果只更新某个数字位,可以只写那 4 位所在地址,减少通信量。
这个显存数组要与实际显示的段码建立映射。假设产品面板上第一位数字的 a~g 七段接在 SEG0~SEG6 上,那ht1621_ram[0]的低 4 位对应 COM0~COM3 的 SEG0 状态,高 4 位对应 SEG1 的四个 COM。想让某一笔点亮,需要对这个字节做按位或,逐位操作容易出错,最好用一张映射表。
3.2 初始化寄存器序列:偏压、使能与清除显存
HT1621 上电后默认是关闭状态,必须依次写入系统使能、LCD 偏压选择和 LCD 开启三条命令,否则显示屏不会有任何反应。顺序没有严格要求,但偏压必须在开启 LCD 之前设好,否则会出现开启瞬间电压不对、然后恢复正常的情况,在电池供电设备上表现为瞬间闪一下。
常见的初始化序列如下:
void ht1621_init(void) { // 1. 设置偏压,0x03对应1/3偏压,4个COM常用 ht1621_write_cmd(0x03); // 2. 系统使能 ht1621_write_cmd(0x04); // 3. 打开LCD输出 ht1621_write_cmd(0x06); // 4. 清除显存 uint8_t clear_buf[32] = {0}; ht1621_write_display(clear_buf, 32); }三条命令分别是0x03(偏压配置)、0x04(系统使能)、0x06(打开 LCD)。但要注意,0x06的二进制是00000110,它的前三位是命令前缀 110,后面 5 位才是命令内容。所以写ht1621_write_cmd(0x06)会得到 8 位数据:110前缀 +00110命令,正好是打开 LCD。0x03的二进制是00000011,同样前三位 110,后五位 00011,其中 bit0 和 bit1 控制偏压选择:00是 1/2 偏压,01是 1/3 偏压。4 个 COM 场景下,推荐用 1/3 偏压,因为 1/2 偏压在低温下对比度会掉得比较快。
如果ht1621_init执行完屏幕还是全黑,用万用表量 VLCD 引脚电压。HT1621 内部有升压电路,VLCD 应该是 VDD 的 2 倍或 3 倍(取决于偏压配置和内部倍压设置),如果 VLCD 等于 VDD,说明偏压电路没起来,多半是命令字拼装位数不对,把 3 位命令前缀和 5 位内容写成了 8 位内容。
3.3 显示刷新方式:全量刷新与增量刷新
段式 LCD 的特点是刷新频率不需要高,一般 50~100Hz 就足够,但通信接口速度慢(GPIO 模拟一位通常要几个微秒),全量刷 32 个地址、每个地址 4 位,总共要发 128 位,再加上地址和命令开销,一次完整刷新大约需要几百微秒到 1ms。在低速 MCU 上,如果每毫秒全量刷一次,CPU 占用率就显出来了。
所以实际工程里区分两种刷新策略:
- 全量刷新:用于初始化、清屏、切换完整画面时,一次写完 32 个有效地址。
- 增量刷新:用于某个数字或图标变化时,只更新变化的地址。例如时钟的秒位,只需要写秒位数字所在的那一个 SEG 组,4 位数据,总共 13 位通信量,比全量快一个数量级。
增量刷新的前提是显存数组保持正确,也就是先把要显示的内容更新到ht1621_ram,再调用更新函数。这个顺序不能反,否则会出现短暂的花屏。在实时性要求高的系统里,可以做一个脏标记:主逻辑只改显存数组,定时器中断里检测到脏标记再执行刷新,刷新完清除标记。这样既保证显示一致性,又不让主循环被通信耗时阻塞。
4. 段码表设计与 HT1621 产品级工程化封装
4.1 数字段码与 COM/SEG 的映射推导
压包里的ht1621.c通常自带一个const uint8_t code[]段码表,但那个表是给特定 PCB 的。段码表的本质是:把 0~9 的数字形状,拆成 a~g 七个段,再把每个段对应到某个 SEG 的某个 COM 位,最后合成一个 4 位值。如果 PCB 的走线是标准顺序(SEG0 接 a 段、SEG1 接 b 段……),段码表可以直接按七段码推算,但实际产品往往为了布板方便,把 SEG 顺序打乱,甚至把 COM 也重新映射了。
最稳妥的推导方式是用一个二维数组描述笔画位置。例如数字 8 的 a~g 全部点亮,数字 1 只有 b 和 c 点亮。把每个笔画对应的 SEG 编号和 COM 编号记下来,手动或写脚本生成段码。对 4 位数据来说,bit0 到 bit3 分别对应 COM0~COM3,点亮为 1,熄灭为 0,所以某个数字的段码是多个笔画的按位或。
这里给出一份标准顺序的段码表,当作参考起点:
// 标准七段码表,COM0~COM3 对应 bit0~bit3 // 依次为 0,1,2,3,4,5,6,7,8,9 const uint8_t ht1621_digit_std[10] = { 0x0F, // 0: a b c d e f on, g off 0x03, // 1: b c on 0x2D, // 2: a b g e d on 0x33, // 3: a b g c d on 0x27, // 4: f g b c on 0x3A, // 5: a f g c d on 0x1E, // 6: a f e d c g on 0x21, // 7: a b c on 0x3F, // 8: all on 0x37 // 9: a b c d f g on };这张表假设 a~g 七段的 SEG 编号是连续的,且每个 SEG 的 4 位里恰好只有一位有效。实际产品中如果某个笔画的 COM 位不在同一字节的低四位,就不能直接照抄这张表。拿到新板子时,建议先用代码把所有 SEG 和 COM 的组合轮流点亮,做一个“点亮全扫”测试,记录每个笔画的真实位置,再反推段码表,这样一小时内就能完成映射,比对着原理图猜省事得多。
4.2 多字节缓冲与按位写入的边界处理
HT1621 每个地址写 4 位,但 C 语言里最小的数据单位是字节。如果用ht1621_send_bits(d, 4)写数据,d的高 4 位会被忽略,所以显存数组里每个字节只存低 4 位有效值。但在增量更新时,这个限制会带来麻烦:如果某个字节同时被两个数字的逻辑共用(比如地址 0x00 的低 4 位是第一个数字的 COM0~3,高 4 位是第二个数字的 SEG0 部分),只更新低 4 位时,不能把整个字节重新写入,否则会覆盖高 4 位原本的状态。
处理方式有两种:
- 用位掩码合并:先读旧值,把要改的低 4 位替换进去,再写回整个地址。
- 按 SEG 分组:把整个显存按物理地址重新组织,每个地址只归属一个逻辑显示单元,避免交叉覆盖。
第二种方式在功能简单、显示元素固定时更实用。比如一个带小数点的温湿度计,小数点和小数点前一位共用一个 SEG 组,但它们的 COM 位不同,可以拆到两个地址里,互不干扰。
增量写入时还要注意地址的 6 位发送顺序。HT1621 的地址发送是先发高位的,和 SPI 的习惯一致,而数据是先发低位。所以ht1621_send_bits(addr, 6)和ht1621_send_bits(data, 4)的位序是反的,同一个函数里两个参数用了不同的位序逻辑,这是个隐蔽的坑。如果显存刷新时地址位发送顺序错了,会出现一个地址上的数据整块平移,看起来像乱码,但单看某一字节又是对的。
4.3 与主循环和定时器中断的集成方式
在裸机或 RTOS 环境里,HT1621 驱动不能直接在中断服务函数里做完整刷新,因为 GPIO 模拟时序耗时较长,会阻塞中断响应。推荐的做法是:
- 主逻辑修改
ht1621_ram数组,不做通信。 - 设置一个
display_dirty标志,由定时器(比如 10ms 周期)检测。 - 定时器里只负责标志置位,实际的
ht1621_update_display放在主循环或低优先级任务中执行。
这样 HT1621 的通信不会打断其他中断,也不会被其他更快的外设挤掉导致时序中断。如果需要极端稳定,可以在刷新 HT1621 期间关闭更高优先级的中断,但全量刷新 1ms 内不要随便关中断,否则对 RTC、串口等实时性外设有影响。
RTOS 环境里还可以用互斥量保护ht1621_ram,避免一个任务在更新数组中间被另一个任务抢先刷新,导致显示数据是两帧混合。对于小型嵌入式项目,一个简单的临界区保护(如关中断或使用taskENTER_CRITICAL)就够了。
5. 三招快速验证 HT1621 驱动:点亮全扫、单笔翻转与功耗定位
拿到一个陌生板子时,不要直接写完整应用逻辑,先用三个小实验把驱动基座验证稳,再往上叠业务代码。
第一招是“点亮全扫”:写一版临时代码,把 128 个显示位逐个点亮并延时 500ms,观察屏幕上的状态。这一步能同时验证三件事——通信方向是否反了、地址映射是否正确、COM/SEG 是否有短路。如果扫描时多点同时亮,大概率是 PCB 上有桥连;如果亮的顺序和代码预期完全无关,优先怀疑ht1621_send_bits的位序问题,把低 4 位发送改成高 4 位再试。
第二招是“单笔翻转”:选中一个特定的段,从灭到亮,再用万用表量该段对应的 SEG 引脚电压。HT1621 的 SEG 输出不是纯数字高/低,而是在 LCD 驱动波形里交替出现正负电压,量到的是占空比波形。如果引脚有波形但屏幕不显示,说明 LCD 本身的阈值电压和 VLCD 配置不匹配,需要调偏压命令或 VLCD 的外接分压电阻。这一招能快速区分是驱动代码问题还是面板问题。
第三招是功耗定位:HT1621 的静态功耗通常在微安级,如果整机待机电流偏高,用电流钳或万用表串入 VLCD 回路。常见原因是初始化时打开了不需要的段,或者 LCD 使能命令里包含了不必要的倍压设置。HT1621 有0x02系统关闭命令,进入睡眠前先把 LCD 关闭、系统关闭,再切到低功耗模式。如果关掉 LCD 后电流还是高,检查 SEG/COM 输出是否处于浮空状态,浮空输出会经 LCD 面板形成漏电路径,这时把所有 SEG 输出设为固定电平再测,电流往往能降下来。
本文还有配套的精品资源,点击获取