一、简介
1.1 什么是段码液晶屏
详细的介绍可以看我的这篇帖子xxx。
以下是某段码屏的走线参考: 

可以看到其实与数码管的驱动方式比较类似,但是实际上完全不同,码液晶屏,需要交流电来驱动,有些 MCU 会自带 LCD 液晶驱动,而通常我们则会采用像 HT1621B 这样的 LCD 驱动器来控制段码屏。
仔细观察走线会发现,每一条控制线上固定有最多四颗灯,这是由于C0-C3共四条线构成矩阵,后面将通过这个特性来进行驱动。
1.2 HT1621B

HT1621B 的显示矩阵为 32×4 其中 4 代表上面说的 C0-C3 四条,在此基础上共可以驱动 32 位,共 128 点。

通过数据手册中的管脚定义,实际我们在驱动屏幕时 HT1621B 要与 MCU 连接三条线,分别为:CS 片选线(低电平有效)、WR 写时钟线(上升沿写入)、DAT 线用于串行输入/输出*。
二、HT1621B 驱动电路
驱动电路非常简单,只需要将对应的 COM 和 SEG 相连即可。 这里的 COM 可以随意互换,但是如果调换 SEG 可能会大幅提高软件的驱动难度,这是由于单个 SEG 只有四点,往往需要几个 SEG 控制一个图形,如果调换会导致内存不连续,从而增加控制难度,也需要根据定制的屏幕酌情考量。

三、驱动程序
3.1 命令格式
HT1621B 的模式包含命令模式、数据模式两种,通过帧前缀来区分。数据模式又包括 READ,WRITE 和 READ-MODIFY-WRITE 三种,下表是数据和命令模式类型码表。
| READ | 数据 | 110 |
| WRITE | 数据 | 101 |
| READ-MODIFY-RITE | 数据 | 101 |
| COMMAND | 命令 | 100 |
模式命令出现在数据和命令传送之前。如出现连续指令,命令模式 ID 100 可以被忽略。当系统工作在不连续命令或不连续地址数据模式, CS 端应设置为 1,而之前的工作模式将被复位。一旦 CS 端为 0,将出现 一个新的工作模式 ID。
常用命令表如下:
| READ | 110 | A5A4A3A2A1A0D0D1D2D3 | D | 从 RAM 中读取数据 | |
| WRITE | 101 | A5A4A3A2A1A0D0D1D2D3 | D | 把数据写入到 RAM 中 | |
| LCD OFF | 100 | 0000-0010-X | C | 关闭 LCD 偏置发生器 | YES |
| LCD ON | 100 | 0000-0011-X | C | 打开 LCD 偏置发生器 | |
| BIAS 1/2 | 100 | 0010-abX0-X | C | LCD 1/2 偏置设置ab=00:2 COMSab=01:3 COMSab=10:4 COMS | |
| BIAS 1/3 | 100 | 0010-abX1-X | C | LCD 1/3 偏置设置ab=00:2 COMSab=01:3 COMSab=10:4 COMS | |
| … | … | … |
3.2 典型时序图

3.3 地址配置
通过上面的命令格式和时序图,相信大家有一个疑问就是地址和数据是什么怎么写? 每一个数据四位,对应 COM0-COM3,而地址则对应 32×4 中的 32,故 HT1621B 共 32 个地址取值 6 位。
一般情况下,屏幕的数据手册会有以下表格: 
在上表中定义了每一个点的位置和点亮方式,如何查看呢?其中 PIN 行对应的就是地址(实际上可以有一定的偏移,这里显示从 5 开始,实际看具体情况而定,但相对关系固定),而下面四行则对应数据。 如,想要点亮左上角的 S2 需要发送 101 0x05 1xxx,分别为命令、地址、数据,向对应地址的对应位发 1 即可点亮该位,S2 对应的则为地址 5 、第 0 位(ADDR5 COM0)。 数字显示方面,我们发现驱动数字需要两个相邻地址配合,并且每一个数字的定义相同(即第 5 列和第 7 列相同、第 6 列和第 8 列相同)。如果想要显示数字 0,需要 ADDR5 -> 0101; ADDR6 ->1111 拼在一起得到 0101 1111 即 0x5F。 由此就可以得到如下的枚举定义:
/**
* @brief LCD 显示段数据
*/
typedef enum
{
Digit_0 = 0x5F,
Digit_1 = 0x06,
Digit_2 = 0x6B,
Digit_3 = 0x2F,
Digit_4 = 0x36,
Digit_5 = 0x3D,
Digit_6 = 0x7D,
Digit_7 = 0x07,
Digit_8 = 0x7F,
Digit_9 = 0x3F,
Dot = 0x80,
Minus = 0x80,
Num_1 = 0x80,
Per = 0x10,
C = 0x20,
S5 = 0x08,
S6 = 0x04,
S7 = 0x02,
S8 = 0x01,
S9 = 0x80,
S10 = 0x40,
S11 = 0x10,
S12 = 0x20,
LcdDisplay_On = 0xFF,
Off = 0x00
} HT1621_Data_t;
3.4 驱动代码
HT1621.h
/********************************************************************************
* @author: ternura-xiang
* @email: ternura_xiang@foxmail.com
* @date: 2026-10-26 10:43
* @version: 1.0.0
* @description:
* @section 更新历史
* | 版本号 | 修改说明 | 修订者 | 修订日期 |
* |———-|—————-|———-|————|
* | v1.0.0 | 创建文档 | ternura-xiang | 2026-10-26 |
********************************************************************************/
#ifndef HT1621_H
#define HT1621_H
#include <stdint.h>
/**
* @addtogroup HT1621
* @{
*/
/*******************************************************************************
* Definitions
******************************************************************************/
/** @brief HT1621B GPIO 配置 */
#define HT1621_GPIOx GPIOA
#define HT1621_CS_Pin GPIO_PIN_9
#define HT1621_SDA_Pin GPIO_PIN_10
#define HT1621_SCK_Pin GPIO_PIN_8
#define HT1621_CS_SET HAL_GPIO_WritePin(HT1621_GPIOx, HT1621_CS_Pin, GPIO_PIN_SET)
#define HT1621_CS_RESET HAL_GPIO_WritePin(HT1621_GPIOx, HT1621_CS_Pin, GPIO_PIN_RESET)
#define HT1621_SDA_SET HAL_GPIO_WritePin(HT1621_GPIOx, HT1621_SDA_Pin, GPIO_PIN_SET)
#define HT1621_SDA_RESET HAL_GPIO_WritePin(HT1621_GPIOx, HT1621_SDA_Pin, GPIO_PIN_RESET)
#define HT1621_SCK_SET HAL_GPIO_WritePin(HT1621_GPIOx, HT1621_SCK_Pin, GPIO_PIN_SET)
#define HT1621_SCK_RESET HAL_GPIO_WritePin(HT1621_GPIOx, HT1621_SCK_Pin, GPIO_PIN_RESET)
/** @brief GPIO 位bang 延时 */
#if defined(__GNUC__)
#define HT1621_DELAY_CYCLE() __asm__ volatile ("nop")
#else
#define HT1621_DELAY_CYCLE() do { __NOP(); } while (0)
#endif
/**
* @brief LCD 显示类型
*/
typedef enum
{
HT1621_DISPLAY_TEMPERATURE = 0,// 温度
HT1621_DISPLAY_HUMIDITY = 1// 湿度
} HT1621_DisplayType_t;
/**
* @brief LCD 显示段数据
*/
typedef enum
{
Digit_0 = 0x5F,
Digit_1 = 0x06,
Digit_2 = 0x6B,
Digit_3 = 0x2F,
Digit_4 = 0x36,
Digit_5 = 0x3D,
Digit_6 = 0x7D,
Digit_7 = 0x07,
Digit_8 = 0x7F,
Digit_9 = 0x3F,
Dot = 0x80,
Minus = 0x80,
Num_1 = 0x80,
Per = 0x10,
C = 0x20,
S5 = 0x08,
S6 = 0x04,
S7 = 0x02,
S8 = 0x01,
S9 = 0x80,
S10 = 0x40,
S11 = 0x10,
S12 = 0x20,
LcdDisplay_On = 0xFF,
Off = 0x00
} HT1621_Data_t;
/*******************************************************************************
* API
******************************************************************************/
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief 初始化 HT1621B 并清空屏幕
*/
void LCD_Init(void);
/**
* @brief 清空屏幕
*/
void LCD_Clear(void);
/**
* @brief 显示数字及温湿度部分
*
* @param value 数值
* @param display_type 显示类型
*/
void LCD_Display_value(float value, HT1621_DisplayType_t display_type);
void LCD_Display_power(uint8_t power);
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#endif /* HT1621_H */
HT1621.c
/********************************************************************************
* @author: ternura-xiang
* @email: ternura_xiang@foxmail.com
* @date: 2026-10-26 10:43
* @version: 1.0.0
* @description:
* @section 更新历史
* | 版本号 | 修改说明 | 修订者 | 修订日期 |
* |———-|—————-|———-|————|
* | v1.0.0 | 创建文档 | ternura-xiang | 2026-10-26 |
********************************************************************************/
#include "HT1621.h"
#include "gpio.h"
/*******************************************************************************
* Definitions
******************************************************************************/
/** @brief 数字映射 */
const uint8_t num[] = {Digit_0, Digit_1, Digit_2, Digit_3,
Digit_4, Digit_5, Digit_6, Digit_7,
Digit_8, Digit_9};
/** @brief HT1621B 命令定义 */
#define HT1621_MODE_CMD 0x80U
#define HT1621_MODE_WRITE 0xA0U
#define HT1621_CMD_SYS_EN 0x01U
#define HT1621_CMD_LCD_ON 0x03U
#define HT1621_CMD_LCD_OFF 0x02U
#define HT1621_CMD_BIAS_1_3_4COM 0x29U
/** @brief HT1621B RAM: 32 addresses, 4 bits per address */
#define HT1621_RAM_BYTES 32U
#define HT1621_LCD_FIELD_BYTES 5U
/*******************************************************************************
* Code
******************************************************************************/
/**
* @brief 传输位 (MSB first)
* @param data 要传输的数据。
* @param bit_count 要传输的位数。
*/
static void HT1621_WriteBits(uint8_t data, uint8_t bit_count)
{
uint8_t i;
for (i = 0U; i < bit_count; ++i)
{
if ((data & 0x80U) != 0U)
{
HT1621_SDA_SET;
}
else
{
HT1621_SDA_RESET;
}
HT1621_SCK_RESET;
HT1621_DELAY_CYCLE();
HT1621_SCK_SET;
HT1621_DELAY_CYCLE();
data <<= 1U;
}
}
/**
* @brief 传输一个六位 LCD RAM 地址。
* @param address RAM 地址。
*/
static void HT1621_WriteAddress(uint8_t address)
{
HT1621_WriteBits((uint8_t)(address << 2U), 6U);
}
/**
* @brief 初始化 HT1621B 并清空屏幕
*/
void LCD_Init(void)
{
HT1621_CS_RESET;
HT1621_SCK_SET;
HT1621_SDA_SET;
HT1621_WriteBits(HT1621_MODE_CMD, 3U);
HT1621_WriteBits(HT1621_CMD_SYS_EN, 9U);
HT1621_WriteBits(HT1621_CMD_BIAS_1_3_4COM, 9U);
HT1621_WriteBits(HT1621_CMD_LCD_ON, 9U);
HT1621_CS_SET;
LCD_Clear();
}
/**
* @brief 清空屏幕
*/
void LCD_Clear(void)
{
HT1621_CS_RESET;
HT1621_WriteBits(HT1621_MODE_WRITE, 3U);
HT1621_WriteAddress(9U);
for (uint8_t i = 0U; i < HT1621_LCD_FIELD_BYTES; ++i)
{
HT1621_WriteBits(Off, 8U);
}
HT1621_CS_SET;
}
/**
* @brief 显示数字及温湿度部分
*
* @param value 数值
* @param display_type 显示类型
*/
void LCD_Display_value(float value, HT1621_DisplayType_t display_type)
{
int value_int;
if (value >= 0)
{
value_int = (int)((value + 0.05f) * 10.0);
}
else
{
value_int = (int)((-value + 0.05f) * 10.0);
}
uint8_t data[HT1621_LCD_FIELD_BYTES] =
{
(uint8_t)(num[(value_int/100)%10]),
(uint8_t)(num[(value_int/10)%10]),
(uint8_t)(num[value_int%10] | Dot),
0x00U,
};
if (value < 0)data[1] |= Minus;// 负号
if (value_int >= 1000)data[0] |= Num_1;// 100以上
if (display_type == HT1621_DISPLAY_TEMPERATURE)// 温度&湿度符号
{
data[3] = C;
}
else if (display_type == HT1621_DISPLAY_HUMIDITY)
{
data[3] = Per;
}
HT1621_CS_RESET;
HT1621_WriteBits(HT1621_MODE_WRITE, 3U);
HT1621_WriteAddress(9U);
for (uint8_t i = 0U; i < HT1621_LCD_FIELD_BYTES-1; ++i)
{
HT1621_WriteBits(data[i], 8U);
}
HT1621_CS_SET;
}
void LCD_Display_power(uint8_t power)
{
uint8_t data;
switch (power)
{
case 0U:
data = 0x80U;// (S9)
break;
case 1U:
data = 0xC0U;// (S9|S10)
break;
case 2U:
data = 0xD0U;// (S9|S10|S11)
break;
case 3U:
data = 0xF0U;// (S9|S10|S11|S12)
break;
default:
data = 0x00U;
break;
}
HT1621_CS_RESET;
HT1621_WriteBits(HT1621_MODE_WRITE, 3U);
HT1621_WriteAddress(17U);
HT1621_WriteBits(data, 8U);
HT1621_CS_SET;
}
其中,LCD_Display_value 和 LCD_Display_power 是我根据我的屏幕特定的函数,但反映了两种不同的操作逻辑。 两者的发送框架均为:
HT1621_CS_RESET;
HT1621_WriteBits(HT1621_MODE_WRITE, 3U);
HT1621_WriteAddress(9U);
-> HT1621_WriteBits(data, 8U); // 区别在这里
HT1621_CS_SET;
在数据发送行,一个使用连续地址,一个是单独地址,两种应根据实际需要进行选择。连续地址下,只要不进行 CS 操作,可以一直发送下一位地址的数据。而对于跳跃地址的写入,只能完成这一次操作将 CS 拉高,而后将 CS 拉低进行第二次地址输入才能实现。
关于上述代码的完整使用请查看我的开源项目:Temperature
- 参考:STM32F103学习笔记(4) 【STM32】_02_使用HT1621B驱动段码液晶屏



