STM32 使用LCD1602简单显示字符

一、概述

  1. 功能说明

本驱动适配 LCD1602 8 位并行模式,完全对标原 51 单片机逻辑迁移至 STM32,实现 LCD 初始化、清屏、指定坐标字符串打印;硬件引脚固定 PB 组,底层采用标准库 GPIO 操作,不依赖 HAL 库。

  1. 硬件引脚映射

表格

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 位并行数据总线

  1. 编译环境

芯片:STM32F10x(大容量 / 中容量通用)

编译器:Keil ARM V5

驱动库:STM32F10x_StdPeriph_Driver(标准库)

通信模式:默认 8 位并行(可宏切换 4 位模式)

二、文件结构

lcd1602.h:宏定义、引脚配置、函数声明

lcd1602.c:GPIO 初始化、底层读写、业务功能实现

三、头文件 lcd1602.h 说明

  1. 核心宏定义

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

  1. 电平操作宏

简化引脚高低电平控制,替代重复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)

  1. 对外 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 功能详解

  1. 延时函数 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--);

}

  1. GPIO 初始化 lcd1602_gpio_init

开启 GPIOB 外设时钟;

PB0/PB1/PB2(RS/RW/E)配置 50MHz 推挽输出;

PB8~PB15 数据总线配置 50MHz 推挽输出;

上电默认拉低 RS、RW、E,空闲状态。

  1. 底层数据输出静态函数 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); // 数据移位输出

}

  1. 底层写指令 lcd1602_write_cmd

RS=0:选择指令寄存器,RW=0 写模式;

E 引脚产生上升沿锁存指令;

8 位模式直接输出完整字节;4 位模式分两次输出高 4 位、低 4 位。

  1. 底层写数据 lcd1602_write_data

RS=1:选择数据寄存器,RW=0 写模式;

时序逻辑与写命令一致,用于输出 ASCII 显示字符。

  1. LCD 初始化 lcd1602_init

上电初始化标准流程(等待 20ms 稳定):

8 位模式指令:0x38(2 行 5*7 点阵);4 位模式0x28

0x0C:开启显示,关闭光标

0x06:写入字符后光标右移,屏幕不滚动

0x01:清屏

  1. 清屏 lcd1602_clear

发送指令0x01,清空屏幕所有字符,光标复位至第一行首位。

  1. 字符串显示 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)

{

// 主循环

}

}

六、编译报错解决方案

  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. #1-D last line of file ends without a newline

故障:文件末尾无空行,Keil 警告

解决:所有.h/.c文件末尾回车新增一行空白再保存。

  1. 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);
}
相关推荐
hongmai66688812 小时前
聊聊ESP32-C3-WROOM-02-N8这颗8MB Flash的RISC-V模组
笔记·嵌入式硬件·物联网·智能路由器·risc-v
VALENIAN瓦伦尼安教学设备1 天前
激光对中仪采购要点
数据库·嵌入式硬件·算法
芯岭技术1 天前
OM6625A芯片使用PY32 link烧录程序流程操作介绍
嵌入式硬件·物联网
zlinear数据采集卡1 天前
ZLinear DABM-D223 通信协议与软件开发全解析:从 USB CDC 到 DDS 波形输出
arm开发·嵌入式硬件·fpga开发·开源
小比特-combat1 天前
多模式调光台灯
单片机·嵌入式硬件
2CM_Embed1 天前
Altium Designer 中为什么一颗芯片会分成多个 Part?——以 C2000 MCU 为例
单片机·嵌入式硬件·altium designer·c2000
姓刘的哦1 天前
STM32内置Flash
stm32
大爱编程♡1 天前
STM32-USART串口
stm32·单片机·嵌入式硬件
hongmai6668881 天前
耐高温小钢炮:ESP32-C3-MINI-1U-H4模组选型分享
笔记·单片机·嵌入式硬件·物联网·risc-v
qq_573967821 天前
STM32问题
stm32·单片机·嵌入式硬件