一、概述
- 功能说明
本驱动适配 LCD1602 8 位并行模式,完全对标原 51 单片机逻辑迁移至 STM32,实现 LCD 初始化、清屏、指定坐标字符串打印;硬件引脚固定 PB 组,底层采用标准库 GPIO 操作,不依赖 HAL 库。
- 硬件引脚映射
表格
LCD1602 信号 STM32 引脚 原 51 引脚 功能
RS PB1 P2.6 寄存器选择:0 = 命令,1 = 数据
RW PB2 P2.5 读写选择:0 = 写,1 = 读
E PB0 P2.7 使能时钟,上升沿锁存数据
D0~D7 PB8~PB15 P0.0~P0.7 8 位并行数据总线
- 编译环境
芯片:STM32F10x(大容量 / 中容量通用)
编译器:Keil ARM V5
驱动库:STM32F10x_StdPeriph_Driver(标准库)
通信模式:默认 8 位并行(可宏切换 4 位模式)
二、文件结构
lcd1602.h:宏定义、引脚配置、函数声明
lcd1602.c:GPIO 初始化、底层读写、业务功能实现
三、头文件 lcd1602.h 说明
- 核心宏定义
c
运行
// 接口模式 0=8位并口,1=4位并口
#define LCD1602_4OR8_DATA_INTERFACE 0
// 控制端口GPIOB
#define LCD_RS_PIN GPIO_Pin_1
#define LCD_RW_PIN GPIO_Pin_2
#define LCD_E_PIN GPIO_Pin_0
#define LCD_CTRL_PORT GPIOB
#define LCD_CTRL_RCC RCC_APB2Periph_GPIOB
// 数据端口 PB8~PB15
#define LCD_DATA_MASK GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15
#define LCD_DATA_PORT GPIOB
#define LCD_DATA_RCC RCC_APB2Periph_GPIOB
- 电平操作宏
简化引脚高低电平控制,替代重复GPIO_SetBits/ResetBits
c
运行
#define LCD_RS_H() GPIO_SetBits(LCD_CTRL_PORT, LCD_RS_PIN)
#define LCD_RS_L() GPIO_ResetBits(LCD_CTRL_PORT, LCD_RS_PIN)
#define LCD_RW_H() GPIO_SetBits(LCD_CTRL_PORT, LCD_RW_PIN)
#define LCD_RW_L() GPIO_ResetBits(LCD_CTRL_PORT, LCD_RW_PIN)
#define LCD_E_H() GPIO_SetBits(LCD_CTRL_PORT, LCD_E_PIN)
#define LCD_E_L() GPIO_ResetBits(LCD_CTRL_PORT, LCD_E_PIN)
- 对外 API 声明
c
运行
void lcd1602_gpio_init(void); // LCD引脚GPIO初始化
void lcd1602_write_cmd(uint8_t cmd); // 写指令
void lcd1602_write_data(uint8_t dat);// 写显示数据
void lcd1602_init(void); // LCD整机初始化
void lcd1602_clear(void); // LCD清屏
void lcd1602_show_string(uint8_t x, uint8_t y, uint8_t *str); // 指定坐标显示字符串
void delay_ms(uint32_t ms); // 毫秒延时(文件内置简易循环延时)
四、源文件 lcd1602.c 功能详解
- 延时函数 delay_ms
72MHz 系统时钟简易阻塞式毫秒延时,仅用于 LCD 时序等待;若工程存在全局SysTick/delay.c延时,需删除本函数避免multiply defined链接报错。
c
运行
void delay_ms(uint32_t ms)
{
uint32_t i,j;
for(i = ms; i > 0; i--)
for(j = 7200; j > 0; j--);
}
- GPIO 初始化 lcd1602_gpio_init
开启 GPIOB 外设时钟;
PB0/PB1/PB2(RS/RW/E)配置 50MHz 推挽输出;
PB8~PB15 数据总线配置 50MHz 推挽输出;
上电默认拉低 RS、RW、E,空闲状态。
- 底层数据输出静态函数 lcd_set_dataport
适配硬件 D0 接 PB8、D7 接 PB15 的引脚布局,将单字节 8 位数据左移 8 位输出到 PB8~PB15:
c
运行
static void lcd_set_dataport(uint8_t val)
{
LCD_DATA_PORT->ODR &= ~(0xFF << 8); // 清空PB8~PB15
LCD_DATA_PORT->ODR |= ((uint16_t)val << 8); // 数据移位输出
}
- 底层写指令 lcd1602_write_cmd
RS=0:选择指令寄存器,RW=0 写模式;
E 引脚产生上升沿锁存指令;
8 位模式直接输出完整字节;4 位模式分两次输出高 4 位、低 4 位。
- 底层写数据 lcd1602_write_data
RS=1:选择数据寄存器,RW=0 写模式;
时序逻辑与写命令一致,用于输出 ASCII 显示字符。
- LCD 初始化 lcd1602_init
上电初始化标准流程(等待 20ms 稳定):
8 位模式指令:0x38(2 行 5*7 点阵);4 位模式0x28
0x0C:开启显示,关闭光标
0x06:写入字符后光标右移,屏幕不滚动
0x01:清屏
- 清屏 lcd1602_clear
发送指令0x01,清空屏幕所有字符,光标复位至第一行首位。
- 字符串显示 lcd1602_show_string
参数规则:
x:横向坐标 0~15;y:纵向坐标 0/1(两行屏幕)
自动换行:字符串超出本行 16 字符,自动切换至另一行输出
边界校验:y>1 或 x>15 直接退出,防止地址越界
五、调用示例 main.c
c
运行
#include "lcd1602.h"
int main(void)
{
lcd1602_init(); // 初始化LCD与GPIO
// 第一行0坐标打印
lcd1602_show_string(0,0,(uint8_t*)"PB LCD1602 8bit");
// 第二行1坐标打印
lcd1602_show_string(1,1,(uint8_t*)"StdLib No HAL");
while(1)
{
// 主循环
}
}
六、编译报错解决方案
- L6200E delay_ms multiply defined
故障:lcd1602.c内置延时与工程 SysTick/delay.c 延时重定义
解决:删除lcd1602.c内delay_ms函数,在lcd1602.h引入全局延时头文件#include "Public/SysTick.h",移除头文件内void delay_ms(uint32_t ms);声明。
- #1-D last line of file ends without a newline
故障:文件末尾无空行,Keil 警告
解决:所有.h/.c文件末尾回车新增一行空白再保存。
- L6218E Undefined symbol delay_10us
故障:按键驱动调用微秒延时,本驱动无该函数
解决:在 SysTick 驱动中补充delay_10us函数实现与声明,按键头文件包含 SysTick.h。
七、移植修改说明
如需更换硬件引脚,仅修改lcd1602.h内引脚宏:
控制端口:LCD_CTRL_PORT、LCD_RS_PIN、LCD_RW_PIN、LCD_E_PIN
数据端口:LCD_DATA_PORT、LCD_DATA_MASK、LCD_DATA_RCC
切换 4 位并行:修改宏#define LCD1602_4OR8_DATA_INTERFACE 1
八、详细代码
cpp
#include "lcd1602.h"
// 72MHz简易ms延时
void delay_ms(uint32_t ms)
{
uint32_t i,j;
for(i = ms; i > 0; i--)
for(j = 7200; j > 0; j--);
}
// IO初始化
void lcd1602_gpio_init(void)
{
GPIO_InitTypeDef gpio_cfg;
RCC_APB2PeriphClockCmd(LCD_CTRL_RCC, ENABLE);
// 控制脚 PB0 PB1 PB2 推挽输出
gpio_cfg.GPIO_Pin = LCD_E_PIN | LCD_RS_PIN | LCD_RW_PIN;
gpio_cfg.GPIO_Mode = GPIO_Mode_Out_PP;
gpio_cfg.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(LCD_CTRL_PORT, &gpio_cfg);
// 数据脚 PB8~PB15 推挽输出
gpio_cfg.GPIO_Pin = LCD_DATA_MASK;
gpio_cfg.GPIO_Mode = GPIO_Mode_Out_PP;
gpio_cfg.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(LCD_DATA_PORT, &gpio_cfg);
LCD_RS_L();
LCD_RW_L();
LCD_E_L();
}
// 将8位数据输出到PB8~PB15(D0→PB8,D7→PB15)
static void lcd_set_dataport(uint8_t val)
{
// 先清除PB8~PB15
LCD_DATA_PORT->ODR &= ~(0xFF << 8);
// val低8位移到PB8~PB15输出
LCD_DATA_PORT->ODR |= ((uint16_t)val << 8);
}
// 写命令
#if (LCD1602_4OR8_DATA_INTERFACE == 0)
void lcd1602_write_cmd(uint8_t cmd)
{
LCD_RS_L();
LCD_RW_L();
LCD_E_L();
lcd_set_dataport(cmd);
delay_ms(1);
LCD_E_H();
delay_ms(1);
LCD_E_L();
}
#else
void lcd1602_write_cmd(uint8_t cmd)
{
LCD_RS_L();
LCD_RW_L();
LCD_E_L();
lcd_set_dataport(cmd >> 4);
delay_ms(1);
LCD_E_H();
delay_ms(1);
LCD_E_L();
lcd_set_dataport(cmd & 0x0F);
delay_ms(1);
LCD_E_H();
delay_ms(1);
LCD_E_L();
}
#endif
// 写数据
#if (LCD1602_4OR8_DATA_INTERFACE == 0)
void lcd1602_write_data(uint8_t dat)
{
LCD_RS_H();
LCD_RW_L();
LCD_E_L();
lcd_set_dataport(dat);
delay_ms(1);
LCD_E_H();
delay_ms(1);
LCD_E_L();
}
#else
void lcd1602_write_data(uint8_t dat)
{
LCD_RS_H();
LCD_RW_L();
LCD_E_L();
lcd_set_dataport(dat >> 4);
delay_ms(1);
LCD_E_H();
delay_ms(1);
LCD_E_L();
lcd_set_dataport(dat & 0x0F);
delay_ms(1);
LCD_E_H();
delay_ms(1);
LCD_E_L();
}
#endif
// LCD初始化
#if (LCD1602_4OR8_DATA_INTERFACE == 0)
void lcd1602_init(void)
{
lcd1602_gpio_init();
delay_ms(20);
lcd1602_write_cmd(0x38);
lcd1602_write_cmd(0x0C);
lcd1602_write_cmd(0x06);
lcd1602_write_cmd(0x01);
}
#else
void lcd1602_init(void)
{
lcd1602_gpio_init();
delay_ms(20);
lcd1602_write_cmd(0x28);
lcd1602_write_cmd(0x0C);
lcd1602_write_cmd(0x06);
lcd1602_write_cmd(0x01);
}
#endif
// 清屏
void lcd1602_clear(void)
{
lcd1602_write_cmd(0x01);
}
// 显示字符串 x:0~15 y:0/1
void lcd1602_show_string(uint8_t x, uint8_t y, uint8_t *str)
{
uint8_t i = 0;
if(y > 1 || x > 15) return;
if(y == 0)
{
while(*str != '\0')
{
if(i < 16 - x)
lcd1602_write_cmd(0x80 + i + x);
else
lcd1602_write_cmd(0x80 + 0x40 + i + x - 16);
lcd1602_write_data(*str);
str++;
i++;
}
}
else
{
while(*str != '\0')
{
if(i < 16 - x)
lcd1602_write_cmd(0x80 + 0x40 + i + x);
else
lcd1602_write_cmd(0x80 + i + x - 16);
lcd1602_write_data(*str);
str++;
i++;
}
}
}
#ifndef _LCD1602_H
#define _LCD1602_H
#include "stm32f10x.h"
// 0=8位并口 1=4位并口
#define LCD1602_4OR8_DATA_INTERFACE 0
// 控制引脚映射
#define LCD_RS_PIN GPIO_Pin_1
#define LCD_RW_PIN GPIO_Pin_2
#define LCD_E_PIN GPIO_Pin_0
#define LCD_CTRL_PORT GPIOB
#define LCD_CTRL_RCC RCC_APB2Periph_GPIOB
// 数据引脚 PB8~PB15 对应D0~D7
#define LCD_DATA_MASK GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15
#define LCD_DATA_PORT GPIOB
#define LCD_DATA_RCC RCC_APB2Periph_GPIOB
// 控制电平宏
#define LCD_RS_H() GPIO_SetBits(LCD_CTRL_PORT, LCD_RS_PIN)
#define LCD_RS_L() GPIO_ResetBits(LCD_CTRL_PORT, LCD_RS_PIN)
#define LCD_RW_H() GPIO_SetBits(LCD_CTRL_PORT, LCD_RW_PIN)
#define LCD_RW_L() GPIO_ResetBits(LCD_CTRL_PORT, LCD_RW_PIN)
#define LCD_E_H() GPIO_SetBits(LCD_CTRL_PORT, LCD_E_PIN)
#define LCD_E_L() GPIO_ResetBits(LCD_CTRL_PORT, LCD_E_PIN)
// 函数声明
void lcd1602_gpio_init(void);
void lcd1602_write_cmd(uint8_t cmd);
void lcd1602_write_data(uint8_t dat);
void lcd1602_init(void);
void lcd1602_clear(void);
void lcd1602_show_string(uint8_t x, uint8_t y, uint8_t *str);
void delay_ms(uint32_t ms);
#endif
#include "lcd1602.h"
int main(void)
{
lcd1602_init();
lcd1602_show_string(0,0,(uint8_t*)"PB LCD1602 8bit");
lcd1602_show_string(1,1,(uint8_t*)"StdLib No HAL");
while(1);
}