☰
STM32F103C8T6 驱动 LCD1602 显示数字:从 GPIO 配置到显示程序的完整实现
2026/9/29 22:32:42 网站建设 项目流程

1. 为什么 STM32F103C8T6 驱动 LCD1602 显示数字总翻车

STM32F103C8T6 驱动 LCD1602 显示数字,是很多嵌入式新手从点灯迈向“能显示数据”的第一道坎。LCD1602 本身不复杂,它内置 HD44780 兼容控制器,能显示 2 行、每行 16 个字符,用来显示传感器数值、计数器、状态码都很合适。但问题在于:STM32 的 GPIO 是 3.3V 电平,LCD1602 通常按 5V 逻辑设计,再加上裸机环境下没有现成的延时库、没有 Arduino 那种LiquidCrystal封装,很多人一上来就卡在“屏幕亮了但不显示字”“显示乱码”“只亮背光没有黑块”这些现象上。

我见过最多的场景是:代码从网上抄了一份,引脚对不上自己的板子,或者延时函数用的是空循环,编译优化一开时序就崩了。还有人把 RW 脚直接接地,结果忙标志没法读,只能靠死延时硬等,稍微改个主频就全乱。这篇就按 STM32F103C8T6 裸机环境,从 GPIO 初始化讲到数字显示逻辑,给你一份能直接复制、能编译、能烧录验证的完整实现。适合刚学完 GPIO 输出、想做一个“能显示数字的小系统”的朋友,也适合拿它当模板去接 DHT11、超声波、ADC 采样的显示层。

核心检索词先摆出来:STM32F103C8T6 怎么配置 GPIO 驱动 LCD1602、LCD1602 显示数字的程序怎么写、4 位数据线模式怎么接线、延时和时序怎么控制。下面按“接线 → GPIO 配置 → 驱动函数 → 显示数字 → 验证 → 排障”的顺序走,每一步都给可复制的代码。

2. 动手前的准备:接线、电平与 TaoToken 辅助

2.1 LCD1602 引脚与 STM32 接线对照

LCD1602 有 16 个引脚,实际驱动只需要关注这几组:数据线 D4~D7(4 位模式)、RS(寄存器选择)、RW(读写选择)、E(使能)、V0(对比度)、背光 A/K。为了省 IO,我推荐用 4 位数据线模式,这样 6 根控制线加 4 根数据线,一共 10 根就能跑起来。

LCD1602 引脚作用接到 STM32F103C8T6说明
VSS地GND共地必须接
VDD电源5V逻辑供电建议 5V
V0对比度电位器中点调不出黑块多半是这里
RS数据/指令PA00 指令 1 数据
RW读/写PA1写模式接低
E使能PA2下降沿锁存
D4~D7数据PA4~PA74 位模式只用高 4 位
A/K背光5V/GND串限流电阻

注意:STM32 的 PA 口是 3.3V 输出,直接驱动 5V 的 LCD1602 数据线,多数模块能识别为高电平,但如果你遇到显示不稳,可以在数据线上加电平转换,或者把 LCD 的 VDD 也降到 3.3V 试试(部分模块 3.3V 也能亮,只是对比度偏淡)。

2.2 用 TaoToken 快速核对驱动写法与报错

裸机开发最烦的是编译报错和时序问题排查。我习惯把不确定的寄存器配置、延时写法丢给模型对话去核对,比如“STM32F103 推挽输出 50MHz 的 CRH 配置怎么写”“HD44780 4 位初始化为什么要发三次 0x03”。TaoToken 的模型对话入口可以直接问这类细节,省去翻手册的时间:https://taotoken.net/api 配合模型对话页 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model_chat 就能用。如果你后面要长期写 STM32 工程、接 Agent 自动补全,可以看 Coding Plan:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding_plan 。接入前先在控制台建 Key:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=console ,Key 管理在 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api_keys ,文档在 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc 。这些只是辅助你查资料和写代码,真正跑起来还得靠板子。

3. 可复制配置:GPIO 初始化与 LCD1602 驱动骨架

3.1 GPIO 初始化代码

下面这段是 STM32F103C8T6 标准外设库写法,把 PA0~PA7 配成推挽输出。注意 CRL 寄存器控制 PA0~PA7,每个引脚 4 位配置,50MHz 输出 + 推挽就是 0x3。

#include "stm32f10x.h" #define LCD_RS_PIN GPIO_Pin_0 #define LCD_RW_PIN GPIO_Pin_1 #define LCD_E_PIN GPIO_Pin_2 #define LCD_D4_PIN GPIO_Pin_4 #define LCD_D5_PIN GPIO_Pin_5 #define LCD_D6_PIN GPIO_Pin_6 #define LCD_D7_PIN GPIO_Pin_7 #define LCD_PORT GPIOA void LCD_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = LCD_RS_PIN | LCD_RW_PIN | LCD_E_PIN | LCD_D4_PIN | LCD_D5_PIN | LCD_D6_PIN | LCD_D7_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LCD_PORT, &GPIO_InitStructure); GPIO_ResetBits(LCD_PORT, LCD_RS_PIN | LCD_RW_PIN | LCD_E_PIN); }

3.2 微秒延时与写字节函数

HD44780 的时序要求 E 高电平脉宽至少 450ns,写指令后要等 40us 以上。裸机没有delay_us,用 SysTick 或者简单的空循环都行。下面用空循环实现,主频 72MHz 下大致可用,实际可以按示波器微调。

void LCD_DelayUs(uint32_t us) { uint32_t i; for (i = 0; i < us * 8; i++) { __NOP(); } } void LCD_WriteByte(uint8_t data, uint8_t rs) { if (rs) GPIO_SetBits(LCD_PORT, LCD_RS_PIN); else GPIO_ResetBits(LCD_PORT, LCD_RS_PIN); GPIO_ResetBits(LCD_PORT, LCD_RW_PIN); // 写模式 // 先发高 4 位 GPIO_ResetBits(LCD_PORT, LCD_D4_PIN | LCD_D5_PIN | LCD_D6_PIN | LCD_D7_PIN); if (data & 0x10) GPIO_SetBits(LCD_PORT, LCD_D4_PIN); if (data & 0x20) GPIO_SetBits(LCD_PORT, LCD_D5_PIN); if (data & 0x40) GPIO_SetBits(LCD_PORT, LCD_D6_PIN); if (data & 0x80) GPIO_SetBits(LCD_PORT, LCD_D7_PIN); GPIO_SetBits(LCD_PORT, LCD_E_PIN); LCD_DelayUs(1); GPIO_ResetBits(LCD_PORT, LCD_E_PIN); LCD_DelayUs(50); // 再发低 4 位 GPIO_ResetBits(LCD_PORT, LCD_D4_PIN | LCD_D5_PIN | LCD_D6_PIN | LCD_D7_PIN); if (data & 0x01) GPIO_SetBits(LCD_PORT, LCD_D4_PIN); if (data & 0x02) GPIO_SetBits(LCD_PORT, LCD_D5_PIN); if (data & 0x04) GPIO_SetBits(LCD_PORT, LCD_D6_PIN); if (data & 0x08) GPIO_SetBits(LCD_PORT, LCD_D7_PIN); GPIO_SetBits(LCD_PORT, LCD_E_PIN); LCD_DelayUs(1); GPIO_ResetBits(LCD_PORT, LCD_E_PIN); LCD_DelayUs(50); }

3.3 初始化序列与显示数字函数

4 位模式的初始化有个坑:上电后必须先按 8 位模式发三次 0x03,再切到 4 位模式发 0x02,否则控制器不认。下面这段是标准流程。

void LCD_WriteCmd(uint8_t cmd) { LCD_WriteByte(cmd, 0); } void LCD_WriteData(uint8_t dat) { LCD_WriteByte(dat, 1); } void LCD1602_Init(void) { LCD_GPIO_Init(); LCD_DelayUs(15000); // 强制 8 位模式三次 LCD_WriteByte(0x03, 0); LCD_DelayUs(5000); LCD_WriteByte(0x03, 0); LCD_DelayUs(200); LCD_WriteByte(0x03, 0); LCD_DelayUs(200); // 切 4 位 LCD_WriteByte(0x02, 0); LCD_DelayUs(200); LCD_WriteCmd(0x28); // 4 位,2 行,5x7 点阵 LCD_WriteCmd(0x0C); // 开显示,关光标 LCD_WriteCmd(0x06); // 写入后光标右移 LCD_WriteCmd(0x01); // 清屏 LCD_DelayUs(2000); } void LCD1602_SetCursor(uint8_t row, uint8_t col) { uint8_t addr = (row == 0) ? 0x80 : 0xC0; LCD_WriteCmd(addr + col); } void LCD1602_ShowChar(uint8_t ch) { LCD_WriteData(ch); } void LCD1602_ShowString(char *str) { while (*str) { LCD_WriteData(*str++); } } void LCD1602_ShowNum(uint32_t num) { char buf[12]; int i = 0; if (num == 0) { LCD_WriteData('0'); return; } while (num > 0) { buf[i++] = '0' + (num % 10); num /= 10; } while (i > 0) { LCD_WriteData(buf[--i]); } }

4. 验证请求:主函数与显示结果

4.1 主函数:显示一个递增数字

把上面的函数拼起来,主函数里做一个 0 到 999 的计数器,每 500ms 加一,显示在第二行。

#include "stm32f10x.h" int main(void) { uint32_t count = 0; LCD1602_Init(); LCD1602_SetCursor(0, 0); LCD1602_ShowString("Number:"); while (1) { LCD1602_SetCursor(1, 0); LCD1602_ShowString(" "); // 清掉旧数字 LCD1602_SetCursor(1, 0); LCD1602_ShowNum(count); count++; if (count > 999) count = 0; for (volatile uint32_t i = 0; i < 7200000; i++); // 约 500ms } }

4.2 编译烧录后的预期现象

烧录后你应该看到:第一行固定显示Number:,第二行从 0 开始每半秒加一,到 999 后回到 0。如果第二行数字位数变化时残留旧字符,说明清行没做干净,可以在写新数字前先写 4 个空格。实测下来,4 位模式下这套代码在 72MHz 主频、标准库环境下能稳定跑,数字刷新不闪。

提示:如果你用的是 HAL 库,把GPIO_SetBits/ResetBits换成HAL_GPIO_WritePin,延时换成HAL_Delay或 DWT 计数即可,逻辑完全一样。

5. 本篇常见错排查

5.1 屏幕只有背光,没有黑块

这是最典型的对比度问题。V0 脚必须接电位器中点,调到能看到一排黑方块为止。如果 V0 直接接地,黑块会过深;接 5V 则完全看不到。先把对比度调出黑块,再谈显示内容。

5.2 显示乱码或只显示第一行

先检查 4 位初始化序列有没有发够三次 0x03。少发一次,控制器可能还停在 8 位模式,后续所有数据都会错位。其次检查 D4~D7 的接线顺序,很多人把 D4 接到 PA7、D7 接到 PA4,顺序反了就是乱码。

5.3 数字显示不全或跳变

多半是延时不够。HD44780 写指令后需要 40us 以上,清屏指令需要 1.64ms。如果你把LCD_DelayUs里的系数调太小,或者编译器开了高优化把空循环优化掉,时序就会崩。可以把延时函数改成基于 SysTick 的精确延时,或者在空循环里加volatile防止被优化。

5.4 编译报错找不到 GPIO_InitTypeDef

这是没加标准库头文件或没配 include 路径。确认工程里包含了stm32f10x_gpio.c、stm32f10x_rcc.c,并在 Keil 的 C/C++ 选项里把库目录加进 Include Paths。如果用的是寄存器版,直接操作 CRL、BSRR 也行,但可读性差一些。

6. 继续往下走:把显示层接到真实数据

数字显示跑通后,下一步通常是把LCD1602_ShowNum接到真实数据源,比如 ADC 采样的电压值、DHT11 的温湿度、超声波测距的厘米数。这时候你会发现显示层和采集层要解耦,建议把LCD1602_ShowNum扩展成支持小数和负数的版本,或者直接格式化成字符串再LCD1602_ShowString。

如果你在接传感器时遇到寄存器配置、时序对不上的问题,可以用模型对话快速核对写法:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model_chat 。长期写 STM32 工程、想让 Agent 帮你补全驱动代码的,可以看 Coding Plan:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding_plan 。接入前先在控制台建 Key:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=console ,Key 管理在 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api_keys ,文档在 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc 。官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

最后留一个我踩过的坑:LCD1602 的 E 使能脚一定要在数据稳定后再拉高,拉高时间别太短,1us 是保险值。如果你用示波器看,E 的下降沿才是锁存时刻,数据要在下降沿之前就稳定。把这一点吃透,后面接什么传感器显示都不会慌。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询