STM32驱动HT1621B段式LCD:GPIO模拟时序全解析
简介这份资源是基于STM32 HAL库驱动HT1621B液晶显示模块的完整示例工程面向嵌入式初学者或需要快速实现段式LCD驱动的开发者解决STM32与HT1621B之间SPI通信配置及显示控制问题。压缩包仅3个文件分别为HT1621B的C源码、头文件及README说明整体大小仅3KB代码精简、结构清晰适合直接移植到自己的项目中。已有1391人学习下载。通过该示例可学习HAL库下SPI初始化、引脚复用配置、命令与数据发送函数编写以及清屏、字符显示等基本操作并了解HT1621B内部振荡器、电荷泵等特性。对照代码可快速掌握用STM32驱动128段点阵LCD的完整流程为后续开发类似显示应用提供可复用的底层驱动模板。1. 从一次显示异常说起为什么 HT1621B 不能用标准 SPI 硬核驱动手头有个表头项目主控是 STM32F103C8T6显示部分选了 HT1621B 驱动的段式 LCD 屏。最初想当然地认为芯片带串行接口就能直接用 SPI 外设结果初始化后屏幕要么全亮、要么乱码用逻辑分析仪抓波形才发现 HT1621B 虽然只有 CS、WR、DATA 三根线但它的时序和标准 SPI 的 CPOL/CPHA 组合对不上数据格式也不是 SPI 的 8 位连续帧而是芯片自定义的 3 线串行协议。这篇博文的内容全部基于HT1621B_STM32_HAL-master这个工程展开工程里 HT1621B.c/h 的写法很有代表性不依赖具体型号HAL 库的封装让 GPIO 模拟时序变得非常干净。适合正在用 STM32 驱动段式 LCD、或者想搞明白 HT1621B 时序细节的开发者通篇会给出可直接抄的代码和参数说明避免重走我当初对着手册干瞪眼的老路。2. HT1621B 的指令结构与 RAM 映射机制2.1 芯片选型依据128 段是怎么算出来的HT1621B 属于 Holtek 的 LCD 驱动器家族内部框图大致分三块振荡器与偏压发生器、指令解码器、显示 RAM。芯片最大支持 32x4 的扫描配置也就是最多 128 个段SEG和 4 个公共端COM。常见的段式屏型号定义成 8x8 点阵或 4 位数字加符号本质都是在这 128 个段里做分配。选型上需要注意HT1621B 和 HT1621 并不完全等同。HT1621B 工作电压范围更宽典型 2.4V~5.2V内部 RC 振荡器频率约 32kHz这决定了显示帧率。如果你的屏面积大、段数多32kHz 驱动 4 个 COM 口时刷新率大约 64Hz肉眼看不到闪烁。而用外部时钟模式时WDT 溢出时间会变化这一点在低功耗场景里要算清楚后面第 3 章会给出具体计算。工程里 README 提到通过 SPI 或 I2C 控制 HT1621B这个说法容易误导。HT1621B 硬件上并没有 I2C 模块它只有 CS、WR、DATA 三根线组成的专用 3 线接口。说 SPI 驱动只是因为它和 SPI 的 MOSI、SCK、CS 有物理上的对应关系但实际时序是 HT1621B 自己定义的。2.2 命令分类101 命令与 110 命令的差异HT1621B 的命令通过 DATA 线逐位发送所有命令都以一个识别码开头。命令识别码有两类这一点在工程代码里体现得非常明显识别码命令类型具体命令位数101系统命令SYS DIS、SYS EN、LCD OFF、LCD ON9 位101系统命令RC 256K、RC 32K、EXT 256K、EXT 32K9 位101系统命令偏压设置 BIAS 1/2、BIAS 1/39 位110显示命令清屏、当前地址、4 位数据11 位100读命令读取当前地址的 4 位数据11 位101 开头的命令只有 9 位前 3 位是识别码后 6 位是命令内容。110 开头的命令是写显示数据前 3 位识别码跟着 6 位 RAM 地址最后 4 位是数据。100 开头的读命令特殊一点需要 CS 拉低后先发识别码和地址再切换 DATA 方向为输入读取 4 位数据。工程里的 HT1621B.c 对命令发送的封装思路是统一的底层 write 函数加高层命令函数。比如HT1621B_WriteCmd内部就是按位操作 DATA 引脚先发 3 位识别码再发命令内容CS、WR 的拉高拉低顺序严格按照数据手册的时序图来。这里有个细节容易漏写命令和写数据在 WR 上升沿锁存而读数据是在 WR 下降沿之后读方向切换前必须把 DATA 引脚配置成输入模式。2.3 RAM 地址与段-COM 交叉关系HT1621B 的显示 RAM 有 32x4 位地址从 0x00 到 0x1F每个地址对应 4 个 COM 口上的段。注意地址的第 5 位A4决定访问的是普通显示 RAM 还是时间基准生成器如果写入地址大于 0x1F数据会落到时间基准模块而不是显示缓冲区这是新手容易踩的雷。段和 COM 的交叉关系是地址每增加 1对应到物理 SEG 引脚加 1同一个地址内的 4 位分别对应 COM0 到 COM3。所以当你要点亮一个特定位置的段时不能只发一个字节而要按 COM 维度分别写 4 个 bit。工程里如果实现了HT1621B_DisplayNumber这类函数它内部一定有一个把用户数据拆成 COM 位平面的步骤这一点后续驱动代码章节会具体展开。3. STM32 工程搭建与低层 GPIO 时序实现3.1 基于 CubeMX 的引脚初始化配置工程基于 STM32 HAL 库用 CubeMX 生成初始化代码。HT1621B 三根线推荐用普通 GPIO 输出模式多数人习惯选推挽输出、无上下拉、输出速度 Low因为 WR 和 DATA 的翻转频率通常只有几百 kHz不需要 High 档。引脚分配建议功能引脚GPIO 模式CSPB0推挽输出初始高WRPB1推挽输出初始高DATAPB2推挽输出初始高有人会把 DATA 配置成复用开漏模式以兼容读操作但 HT1621B 的读命令用得少而且切换方向需要重新配置 MODER 寄存器不如保持推挽输出读数据时动态改成输入模式更直观。在 CubeMX 里配置好后生成代码中会看到MX_GPIO_Init函数统一设置这三个引脚。初始化函数里需要特别注意三根线的初始电平。CS 在空闲时必须保持高电平WR 和 DATA 也建议初始化为高。如果 CS 上电后是低电平HT1621B 可能进入异常状态表现为屏幕乱码或功耗偏高。3.2 位级时序封装三种基础操作函数工程底层会封装三个基础函数HT1621B_WriteBit、HT1621B_WriteByte、HT1621B_SendCmd。HT1621B_WriteBit实现的是最底层的时序每次写一个 bit 需要 4 个动作DATA 置为对应电平、WR 拉低、延时、WR 拉高。代码如下void HT1621B_WriteBit(uint8_t bit_val) { HAL_GPIO_WritePin(HT1621B_DATA_PORT, HT1621B_DATA_PIN, bit_val); HAL_GPIO_WritePin(HT1621B_WR_PORT, HT1621B_WR_PIN, GPIO_PIN_RESET); HT1621B_Delay_us(1); // 数据建立时间手册要求 tSU 至少 0.1us HAL_GPIO_WritePin(HT1621B_WR_PORT, HT1621B_WR_PIN, GPIO_PIN_SET); HT1621B_Delay_us(1); // WR 高电平持续时间tHIGH 至少 0.6us }这里的HT1621B_Delay_us工程里一般用DWT-CYCCNT实现精确延时。如果不方便用 DWT可以用空循环for (volatile uint8_t i 0; i 10; i);代替但要注意不同主频下延时不一致影响极端时序下的可靠性。写命令和写数据的区别只是 bit 数不同命令是 9 位数据是 11 位3 位识别码加 8 位地址/数据组合发送前先拉低 CS发完再拉高 CSvoid HT1621B_SendCmd(uint8_t cmd) { HAL_GPIO_WritePin(HT1621B_CS_PORT, HT1621B_CS_PIN, GPIO_PIN_RESET); for (uint8_t i 0; i 8; i) { HT1621B_WriteBit((cmd (7 - i)) 0x01); } HT1621B_WriteBit(0); // 命令位比数据少末尾补齐一位 HAL_GPIO_WritePin(HT1621B_CS_PORT, HT1621B_CS_PIN, GPIO_PIN_SET); }注意命令发送结束要补一个 bit原因是 HT1621B 的命令时钟格式是固定长度的9 位命令识别码加命令内容总位数不足时芯片后续可能把下一个操作误认为是数据。实际上标准的命令定义是101 6 位命令共 9 位上面的函数发送的字节需要配合cmd的高 6 位有效。3.3 写数据与读回方向切换的注意事项写显示数据时格式是 3 位识别码110后面 6 位是 RAM 地址再后面 4 位是要写入的数据总共 13 位。工程代码里的实现void HT1621B_WriteData(uint8_t addr, uint8_t data) { HAL_GPIO_WritePin(HT1621B_CS_PORT, HT1621B_CS_PIN, GPIO_PIN_RESET); for (uint8_t i 0; i 3; i) { HT1621B_WriteBit(1); // 110 识别码前两位 } HT1621B_WriteBit(0); // 110 识别码第三位 for (uint8_t i 0; i 6; i) { HT1621B_WriteBit((addr (5 - i)) 0x01); } for (uint8_t i 0; i 4; i) { HT1621B_WriteBit((data (3 - i)) 0x01); } HAL_GPIO_WritePin(HT1621B_CS_PORT, HT1621B_CS_PIN, GPIO_PIN_SET); }这段代码有几个容易写错的地方。第一地址位顺序是高位在前如果从低位开始移位地址映射会颠倒第二data 只取低 4 位如果外部传入了高 4 位有值的内容显示会错乱第三CS 必须在整个传输期间保持低电平不能在每个字节之间拉高。读回操作用100识别码开头的命令。执行读命令时DATA 引脚要切换为输入模式代码里需要调用HAL_GPIO_ReadPin之前先修改 GPIO 的 MODER 寄存器。HAL 库没有直接提供切换方向的接口常见做法是GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin HT1621B_DATA_PIN; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(HT1621B_DATA_PORT, GPIO_InitStruct);读完之后还要切回输出模式。频繁切换会降低效率所以实际项目中读回功能一般只在自检时使用正常显示流程不会调用。4. 驱动层设计从初始化序列到显示缓冲4.1 初始化序列的完整顺序HT1621B 的初始化顺序是工程里 README 没有细说、但实际最容易出错的部分。按照数据手册的推荐顺序初始化序列如下void HT1621B_Init(void) { HT1621B_WriteCmd(HT1621B_CMD_SYS_DIS); // 先关闭系统 HT1621B_WriteCmd(HT1621B_CMD_RC_32K); // 选择内部 32kHz RC 振荡器 HT1621B_WriteCmd(HT1621B_CMD_BIAS_1_3); // 设置 1/3 偏压4 COM 模式 HT1621B_WriteCmd(HT1621B_CMD_LCD_ON); // 打开 LCD 驱动输出 HT1621B_Clear(); // 清屏并关闭看门狗 }HT1621B_CMD_SYS_DIS的值是 0x00注意它是 9 位命令里的高 6 位有效8 位变量发送时低位补零所以一个字节表示正好。先发 SYS DIS 的目的是让芯片在上电后处于完全关闭状态避免内部升压电路和偏压发生器的状态不确定。RC_32K命令选择内部振荡器对应命令字节的高 6 位是000000还是000001需要看数据手册的表不同版本手册命令码有差异。工程里HT1621B.h定义宏HT1621B_CMD_RC_32K 0x18换算成二进制是000110这个值对应该命令在数据位中的排列方式。使用 32kHz 内部振荡器时BIAS_1_3的偏压设置决定 LCD 的对比度1/3 偏压适合大多数 TN 屏1/2 偏压适合低电压场景但对比度会偏浅。初始化完成后的清屏操作需要遍历 32 个地址写 0void HT1621B_Clear(void) { for (uint8_t addr 0; addr 32; addr) { HT1621B_WriteData(addr, 0x00); } }清屏的另一层作用是关闭看门狗。HT1621B 内部有一个集成 WDT上电默认开启溢出时间取决于振荡器频率。如果不喂狗或关闭它芯片会周期性复位表现为屏幕每隔一段时间闪一下。工程里如果HT1621B_Clear里只写了数据没提 WDT需要补一条HT1621B_WriteCmd(HT1621B_CMD_WDT_DIS)命令值是 0x02 还是 0x03务必对照手上的原版头文件宏定义。4.2 显示缓冲区与段映射函数HT1621B 一次只能写 4 位数据但实际应用往往以字节为单位显示 ASCII 字符或自定义符号所以驱动层需要建一个 16 字节的显示缓冲把逻辑上的字符映射到物理段地址再刷新到芯片。这层映射关系建议这样设计uint8_t ht1621b_buffer[16]; // 高 4 位对应 COM3~COM0低 4 位对应另一组 void HT1621B_Refresh(void) { for (uint8_t addr 0; addr 16; addr) { HT1621B_WriteData(addr, ht1621b_buffer[addr] 0x0F); HT1621B_WriteData(addr 0x10, (ht1621b_buffer[addr] 4) 0x0F); } }这种双地址映射方式的依据是 HT1621B 的 RAM 地址 0x00 到 0x1F 直接对应 SEG 引脚顺序如果屏的引脚走线把高 4 位设计到了 SEG16 以后就要用地址 0x10 的方式把高位数据写到另一组地址。实际上 32x4 的 RAM 中地址 0x00~0x0F 和 0x10~0x1F 分别对应不同 SEG 段所以上面这个函数覆盖了全部 32 个地址。如果后续要更新数字显示直接改 buffer 再调用 Refresh 就行。这种方式有额外的好处统一了内部逻辑和硬件引脚之间的关系调整屏幕布局时只改映射函数上层业务代码不用动。4.3 字符取模与段码转换共阴极段式 LCD 的字符码与共阳极数码管类似但不完全相同。HT1621B 的段驱动输出是对称的没有共阴共阳之分只要给出 SEG 与 COM 的交叉矩阵就能确定字符形状。工程里通常会带一张段码表const uint8_t seg_code[10] { 0x7E, // 0 0x30, // 1 0x6D, // 2 0x79, // 3 0x33, // 4 0x5B, // 5 0x5F, // 6 0x70, // 7 0x7F, // 8 0x7B // 9 };上面这组段码假设数字的 7 段分别映射到 COM0~COM3 的某几位实际上要根据屏幕的引脚定义调整。具体做法是拿一颗万用表或直接用 HT1621B 的测试模式依次点亮每个地址的每个 bit在屏幕上记下对应的段位置再反推段码表。这个工作看起来很原始但比对着原理图猜要准确得多。显示一个两位数时先把数字转换成段码存进 buffer 的对应字节再统一刷新。这里要注意高位数字和低位数字的地址偏移量通常两个数字之间隔着 4 个 SEG 即一个字节如果布局中使用的是 COM0~COM3 中不同的位还需要做位拼接处理。5. 进阶功能与常见坑对比度、闪烁和命令时序5.1 对比度调节与偏压的选择误区HT1621B 的对比度主要受偏压设置和 LCD 供电电压影响软件层面能控制的只有BIAS_1_2和BIAS_1_3两个命令。偏压比例决定 LCD 驱动波形的 VON/VOFF 比1/3 偏压下 VON 约为 LCD 电压的 2/31/2 偏压下 VON 约为 1/2后者功耗更低但对比度弱。很多工程习惯把偏压设置放在初始化序列里写死这会导致一个问题3.3V 供电下 1/3 偏压可能显示过深5V 供电下 1/2 偏压又偏浅。我一般会在头文件里留一个宏定义不同的屏幕型号改一个宏即可。另外注意 LCD 偏压发生器的启动时间命令LCD_ON发出后大约需要 100~300ms 电压才能稳定此时立刻刷新显示数据会出现前几帧颜色偏淡。工程里如果有开机 logo 的显示流程最好在LCD_ON后加个 200ms 延时再刷内容。5.2 显示闪烁的两个隐藏来源WDT 与刷新频率前面提到看门狗会周期性复位芯片这是隐藏的闪烁来源之一。另一个来源是刷新频率不足或刷新间隙不均匀。HT1621B 的显示刷新是芯片自己循环扫描的外部只需要在数据变化时写入新内容理论上不会闪烁。但如果用了软件定时器刷新整个 buffer而刷新函数里带着长延时或复杂运算就会出现刷新间隙内某一帧数据被截断。解决方式是不要频繁全量刷新尽量在数据变化时只更新对应地址void HT1621B_UpdateDigit(uint8_t position, uint8_t number) { uint8_t addr position * 2; // 假设每个数字占 2 个地址 HT1621B_WriteData(addr, seg_code[number] 0x0F); HT1621B_WriteData(addr 1, (seg_code[number] 4) 0x0F); }如果一定要定时刷新比如实现闪烁效果用好的做法是写两个 buffer切换显示内容时先修改影子缓冲再一次性刷新避免单个字节写入时屏幕出现半更新状态。5.3 工程代码中 SPI 配置错误的分析不少初次接触 HT1621B 的开发者会在 CubeMX 里直接配置 SPI 外设接到 HT1621B 上后屏幕不工作。根本原因是 SPI 的数据帧和 HT1621B 的时序不兼容。HT1621B 要求 CS 在整个命令传输期间保持低电平而 SPI 外设每个字节传输之间会自动拉高 CS如果是硬件 NSS 管理或者需要软件控制拉高拉低这就破坏了命令的连续性。另一个问题是字节对齐。HT1621B 的命令和数据的位长不是 8 的整数倍比如写数据是 36413 位SPI 一次只能发 8 位或 16 位拆成 16 位发送时高位数据位和低位数据位会错位。也有一种做法是用 SPI 的 8 位模式连续发两个字节然后手动控制 CS但此时仍要对齐位流代码复杂度反而比 GPIO 模拟更高。工程采用 GPIO 模拟时序是更通用的方案因为它不依赖 SPI 外设的存在任何有 GPIO 的 MCU 都能移植。如果换用其他芯片只要把HT1621B_Delay_us改成对应平台的微秒延时函数即可。6. 验证技巧用逻辑分析仪和回读命令检查驱动状态手持逻辑分析仪是验证 HT1621B 驱动最直接的设备。接好 CS、WR、DATA 三根线后采样率设置 1MHz 以上触发条件选 CS 下降沿观察一次完整命令传输的波形检查各电平之间的建立时间是否满足手册要求。重点关注 CS 低电平期间是否存在毛刺WR 上升沿与 DATA 变化是否同时发生如果 WR 与 DATA 同时变化芯片会采到不确定电平。数据波形确认无误后可以用回读命令做 RAM 级别的验证。先写入已知数据到某个地址比如地址 0x00 写入 0x05再发100 000000的读命令把 DATA 切换成输入后用示波器看返回波形uint8_t HT1621B_ReadData(uint8_t addr) { uint8_t data 0; // 切 DATA 为输入 // 发 3 位识别码 100 和 6 位地址 // 随后 DATA 线上会返回 4 位数据 return data; }回读时地址还是高位在前。这个方法不仅能验证驱动正确性还能发现 PCB 上 SEG/COM 走线是否短路或断路。点亮屏幕所有段后再逐个回读数值和写入不一致的地址大概率就是硬件问题。硬件排障没有逻辑分析仪时用示波器单通道看 WR 引脚波形也能判断时序大致是否正确。如果 WR 频率明显偏低说明延时函数吃掉太多时间可以把微秒延时降到手册允许的下限附近提高整体刷新速度。另一个容易被忽略的验证点工作电流。HT1621B 在 LCD 全亮和非全亮时电流差异很小但 SYS DIS 与 SYS EN 状态下的电流差异很大。如果初始化后实测电流异常高优先怀疑偏压电路是否正常工作再看是否误开了外部时钟模式导致振荡器电流过大。工程如果带了低功耗模式SYS DIS 命令必须放在进入 STOP 模式之前这样 HT1621B 自己也会进入低功耗态整机待机电流能降一个数量级。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →