news 2026/9/16 3:25:49

STM32驱动HT1621B段式LCD:GPIO模拟时序全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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_WriteBitHT1621B_WriteByteHT1621B_SendCmdHT1621B_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,发完再拉高 CS:

void 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 个地址写 0:

void 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_2BIAS_1_3两个命令。偏压比例决定 LCD 驱动波形的 VON/VOFF 比,1/3 偏压下 VON 约为 LCD 电压的 2/3,1/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 的整数倍,比如写数据是 3+6+4=13 位,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 自己也会进入低功耗态,整机待机电流能降一个数量级。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 3:25:37

校园学业预警系统设计与实现:Java规则计算与前端交互全解析

简介&#xff1a;基于Java、CSS和JavaScript构建的校园学生学业预警系统设计源码&#xff0c;面向需要开发教育管理系统或学习Web后端与前端整合的开发者。项目共39个文件&#xff0c;约55.1MB&#xff0c;包含12个Java源文件负责后端逻辑&#xff08;学生信息管理、成绩分析、…

作者头像 李华
网站建设 2026/9/16 3:24:06

从Modbus到物联网平台:工业设备接入与数据采集实践指南

1. 项目概述与核心思路这些年物联网平台聊得火热&#xff0c;平台侧动不动就是MQTT、HTTP、云边协同&#xff0c;但真正到了工厂车间、配电房、污水处理站&#xff0c;你面对的大概率不是一台自带云连接的智能设备&#xff0c;而是一堆挂着RS485总线、走Modbus协议的仪表和控制…

作者头像 李华
网站建设 2026/9/16 3:24:02

OSNet卷积设计原理与工业落地实践

1. 项目概述&#xff1a;为什么OSNet的卷积设计值得你花一整天细读OSNet不是又一个刷榜的“大模型”&#xff0c;它是一套在轻量级场景下真正跑得动、训得稳、部署得快的实用型网络架构。我第一次在行人重识别&#xff08;ReID&#xff09;项目里用上OSNet&#xff0c;是在一个…

作者头像 李华
网站建设 2026/9/16 3:23:58

Redis连接失败排查:include配置覆盖端口引发的三天故障实录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 3:22:03

OPC DA到MQTT的协议转换:工业数据采集与上云实战指南

OPC DA 到 MQTT 的路&#xff0c;我从第一次在客户现场被 DCOM 弹窗支配&#xff0c;到现在能十分钟排查完通信链路&#xff0c;中间踩了太多坑。这篇博文不聊虚的&#xff0c;就讲清楚为什么工业现场要把 OPC DA 的数据搬到 MQTT 上&#xff0c;以及协议转换到底怎么落地。如果…

作者头像 李华