4. LCD1602字符显示驱动

任务目标

在嵌入式Linux环境下,通过GPIO模拟时序驱动LCD1602字符液晶模块,实现两行字符的初始化与显示。本案例聚焦于用户空间GPIO操作,不涉及内核驱动模块,适用于快速验证与原型开发。

环境与依赖

  • 硬件:LCD1602模块(HD44780兼容)、开发板(如树莓派、全志H3等)、杜邦线
  • Linux内核需启用 /sys/class/gpio 用户空间GPIO接口(CONFIG_GPIO_SYSFS)
  • 交叉编译工具链或开发板原生gcc

接线定义

LCD1602引脚GPIO编号(示例)说明
RS17寄存器选择(0=指令,1=数据)
RW18读写选择(接地=写)
E27使能脉冲
D4-D722,23,24,254位数据总线

RW引脚直接接地,仅执行写操作。实际GPIO编号需根据开发板调整。

关键完整代码


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#define GPIO_BASE "/sys/class/gpio"
#define RS  17
#define E   27
#define D4  22
#define D5  23
#define D6  24
#define D7  25

static void gpio_export(int pin) {
    int fd = open(GPIO_BASE "/export", O_WRONLY);
    char buf[4];
    sprintf(buf, "%d", pin);
    write(fd, buf, strlen(buf));
    close(fd);
    usleep(100000);
}

static void gpio_direction(int pin, const char *dir) {
    char path[64];
    sprintf(path, GPIO_BASE "/gpio%d/direction", pin);
    int fd = open(path, O_WRONLY);
    write(fd, dir, strlen(dir));
    close(fd);
}

static void gpio_write(int pin, int value) {
    char path[64];
    sprintf(path, GPIO_BASE "/gpio%d/value", pin);
    int fd = open(path, O_WRONLY);
    if (value) write(fd, "1", 1);
    else write(fd, "0", 1);
    close(fd);
}

static void lcd_pulse(void) {
    gpio_write(E, 1);
    usleep(500);
    gpio_write(E, 0);
    usleep(500);
}

static void lcd_nibble(unsigned char nibble) {
    gpio_write(D4, (nibble >> 0) & 1);
    gpio_write(D5, (nibble >> 1) & 1);
    gpio_write(D6, (nibble >> 2) & 1);
    gpio_write(D7, (nibble >> 3) & 1);
    lcd_pulse();
}

static void lcd_byte(unsigned char data, int rs) {
    gpio_write(RS, rs);
    lcd_nibble(data >> 4);
    lcd_nibble(data & 0x0F);
}

static void lcd_command(unsigned char cmd) {
    lcd_byte(cmd, 0);
    usleep(2000);
}

static void lcd_data(char ch) {
    lcd_byte((unsigned char)ch, 1);
    usleep(2000);
}

static void lcd_init(void) {
    usleep(50000);
    // 切换到4位模式
    gpio_write(RS, 0);
    lcd_nibble(0x03);
    usleep(5000);
    lcd_nibble(0x03);
    usleep(5000);
    lcd_nibble(0x03);
    usleep(5000);
    lcd_nibble(0x02);
    usleep(5000);
    // 功能设置:4位总线,2行,5x8点阵
    lcd_command(0x28);
    // 显示开,光标关,闪烁关
    lcd_command(0x0C);
    // 清屏
    lcd_command(0x01);
    usleep(2000);
    // 光标右移,不移动显示
    lcd_command(0x06);
}

static void lcd_print(const char *str) {
    while (*str) {
        lcd_data(*str++);
    }
}

int main(void) {
    int pins[] = {RS, E, D4, D5, D6, D7};
    for (int i = 0; i < 6; i++) {
        gpio_export(pins[i]);
        gpio_direction(pins[i], "out");
    }
    lcd_init();
    lcd_command(0x80);  // 第一行地址
    lcd_print("Hello Linux!");
    lcd_command(0xC0);  // 第二行地址
    lcd_print("LCD1602 OK");
    return 0;
}

编译与运行


# 交叉编译(以arm-linux-gnueabihf为例)
arm-linux-gnueabihf-gcc -o lcd1602 lcd1602.c

# 或开发板原生编译
gcc -o lcd1602 lcd1602.c

# 运行(需root权限访问GPIO)
sudo ./lcd1602

验证方法

程序运行后,观察LCD1602屏幕:

  • 第一行显示 Hello Linux!
  • 第二行显示 LCD1602 OK
  • 若显示乱码或空白,检查接线是否正确、GPIO编号是否与硬件匹配、LCD对比度电位器是否调节合适

通过条件:屏幕两行字符清晰、无乱码,与预期输出完全一致。

常见问题

  • 屏幕无反应:检查GPIO是否已导出成功(ls /sys/class/gpio),确认E引脚脉冲时序是否正常
  • 显示乱码:多为4位模式初始化时序错误,或D4-D7接线顺序与代码不一致
  • 字符闪烁:电源纹波过大,建议在VDD与GND间加100nF去耦电容