You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

树莓派Pico连接LCM1602 I2C适配器LCD无显示问题求助

问题描述
  • 尝试将树莓派Pico与带LCM1602 I2C适配器的1602LCD连接,使用树莓派官方C/C++ SDK示例代码,编译加载成功但LCD无任何显示。
  • 已通过I2C总线扫描确认适配器I2C地址为0x27,引脚与地址配置无误。
  • 无法找到覆盖该适配器所有命令的优质数据手册,同时对代码中lcd_send_byte(uint8_t val, int mode)函数将1字节拆分为6字节发送的操作存在疑惑(不熟悉串行通信,不确定是否正常)。
  • 倾向于使用C语言解决问题,暂未尝试MicroPython。

所用代码

/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "pico/binary_info.h"

/* Example code to drive a 16x2 LCD panel via a I2C bridge chip (e.g. PCF8574)
   NOTE: The panel must be capable of being driven at 3.3v NOT 5v. The Pico
   GPIO (and therefor I2C) cannot be used at 5v.
   You will need to use a level shifter on the I2C lines if you want to run the
   board at 5v.
   Connections on Raspberry Pi Pico board, other boards may vary.
   GPIO 4 (pin 6)-> SDA on LCD bridge board
   GPIO 5 (pin 7)-> SCL on LCD bridge board
   3.3v (pin 36) -> VCC on LCD bridge board
   GND (pin 38)  -> GND on LCD bridge board
*/
// commands
const int LCD_CLEARDISPLAY = 0x01;
const int LCD_RETURNHOME = 0x02;
const int LCD_ENTRYMODESET = 0x04;
const int LCD_DISPLAYCONTROL = 0x08;
const int LCD_CURSORSHIFT = 0x10;
const int LCD_FUNCTIONSET = 0x20;
const int LCD_SETCGRAMADDR = 0x40;
const int LCD_SETDDRAMADDR = 0x80;

// flags for display entry mode
const int LCD_ENTRYSHIFTINCREMENT = 0x01;
const int LCD_ENTRYLEFT = 0x02;

// flags for display and cursor control
const int LCD_BLINKON = 0x01;
const int LCD_CURSORON = 0x02;
const int LCD_DISPLAYON = 0x04;

// flags for display and cursor shift
const int LCD_MOVERIGHT = 0x04;
const int LCD_DISPLAYMOVE = 0x08;

// flags for function set
const int LCD_5x10DOTS = 0x04;
const int LCD_2LINE = 0x08;
const int LCD_8BITMODE = 0x10;

// flag for backlight control
const int LCD_BACKLIGHT = 0x08;

const int LCD_ENABLE_BIT = 0x04;

// By default these LCD display drivers are on bus address 0x27
static int addr = 0x27;

// Modes for lcd_send_byte
#define LCD_CHARACTER  1
#define LCD_COMMAND    0

#define MAX_LINES      2
#define MAX_CHARS      16

/* Quick helper function for single byte transfers */
void i2c_write_byte(uint8_t val) {
#ifdef i2c_default
    i2c_write_blocking(i2c_default, addr, &val, 1, false);
#endif
}

void lcd_toggle_enable(uint8_t val) {
    // Toggle enable pin on LCD display
    // We cannot do this too quickly or things don't work
#define DELAY_US 600
    sleep_us(DELAY_US);
    i2c_write_byte(val | LCD_ENABLE_BIT);
    sleep_us(DELAY_US);
    i2c_write_byte(val & ~LCD_ENABLE_BIT);
    sleep_us(DELAY_US);
}

// The display is sent a byte as two separate nibble transfers
void lcd_send_byte(uint8_t val, int mode) {
    uint8_t high = mode | (val & 0xF0) | LCD_BACKLIGHT;
    uint8_t low = mode | ((val << 4) & 0xF0) | LCD_BACKLIGHT;

    i2c_write_byte(high);
    lcd_toggle_enable(high);
    i2c_write_byte(low);
    lcd_toggle_enable(low);
}

void lcd_clear(void) {
    lcd_send_byte(LCD_CLEARDISPLAY, LCD_COMMAND);
}

// go to location on LCD
void lcd_set_cursor(int line, int position) {
    int val = (line == 0) ? 0x80 + position : 0xC0 + position;
    lcd_send_byte(val, LCD_COMMAND);
}

static void inline lcd_char(char val) {
    lcd_send_byte(val, LCD_CHARACTER);
}

void lcd_string(const char *s) {
    while (*s) {
        lcd_char(*s++);
    }
}

void lcd_init() {
    lcd_send_byte(0x03, LCD_COMMAND);
    lcd_send_byte(0x03, LCD_COMMAND);
    lcd_send_byte(0x03, LCD_COMMAND);
    lcd_send_byte(0x02, LCD_COMMAND);

    lcd_send_byte(LCD_ENTRYMODESET | LCD_ENTRYLEFT, LCD_COMMAND);
    lcd_send_byte(LCD_FUNCTIONSET | LCD_2LINE, LCD_COMMAND);
    lcd_send_byte(LCD_DISPLAYCONTROL | LCD_DISPLAYON, LCD_COMMAND);
    lcd_clear();
}

int main() {
#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
    #warning i2c/lcd_1602_i2c example requires a board with I2C pins
#else
    // This example will use I2C0 on the default SDA and SCL pins (4, 5 on a Pico)
    i2c_init(i2c_default, 100 * 1000);
    gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
    gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
    gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
    // Make the I2C pins available to picotool
    bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));

    lcd_init();

    static char *message[] =
            {
                    "RP2040 by", "Raspberry Pi",
                    "A brand new", "microcontroller",
                    "Twin core M0", "Full C SDK",
                    "More power in", "your product",
                    "More beans", "than Heinz!"
            };

    while (1) {
        for (int m = 0; m < sizeof(message) / sizeof(message[0]); m += MAX_LINES) {
            for (int line = 0; line < MAX_LINES; line++) {
                lcd_set_cursor(line, (MAX_CHARS / 2) - strlen(message[m + line]) / 2);
                lcd_string(message[m + line]);
            }
            sleep_ms(2000);
            lcd_clear();
        }
    }

    return 0;
#endif
}

解答

关于lcd_send_byte的字节拆分说明

这种操作是完全正常的,原因如下:

  • 你的I2C适配器核心是PCF8574(或PCF8574A)8位IO扩展芯片,它将LCD的并行接口转为I2C。
  • 代码中LCD工作在4位模式(HD44780控制器的常见模式),需要将1字节数据拆分为高4位和低4位分别传输。
  • 每次传输还需配合LCD_ENABLE_BIT的脉冲来让LCD锁存数据,再加上背光控制位,最终就会出现1字节拆分为多次I2C字节发送的情况。

排查建议

  • 检查电源与电平兼容性

    • 确认LCD适配器的供电电压:如果适配器是5V供电,Pico的3.3V I2C引脚可能无法稳定驱动,必须添加电平转换器(虽然I2C扫描能找到地址,但数据传输可能失败)。
    • 检查背光是否点亮:部分适配器的背光需要单独接线,或者尝试将代码中的LCD_BACKLIGHT值改为0x00(部分适配器的背光控制位是低电平有效)。
  • 调整初始化时序
    LCD对初始化时序要求严格,可尝试在lcd_init函数的每个lcd_send_byte调用后添加更长延迟:

    void lcd_init() {
        lcd_send_byte(0x03, LCD_COMMAND);
        sleep_ms(5);
        lcd_send_byte(0x03, LCD_COMMAND);
        sleep_ms(5);
        lcd_send_byte(0x03, LCD_COMMAND);
        sleep_ms(5);
        lcd_send_byte(0x02, LCD_COMMAND);
        sleep_ms(5);
    
        lcd_send_byte(LCD_ENTRYMODESET | LCD_ENTRYLEFT, LCD_COMMAND);
        sleep_ms(1);
        lcd_send_byte(LCD_FUNCTIONSET | LCD_2LINE, LCD_COMMAND);
        sleep_ms(1);
        lcd_send_byte(LCD_DISPLAYCONTROL | LCD_DISPLAYON, LCD_COMMAND);
        sleep_ms(1);
        lcd_clear();
        sleep_ms(2);
    }
    

    也可尝试增大lcd_toggle_enable中的DELAY_US值(比如改为1000)。

  • 验证I2C写入有效性
    修改i2c_write_byte函数,检查写入是否成功:

    void i2c_write_byte(uint8_t val) {
    #ifdef i2c_default
        int ret = i2c_write_blocking(i2c_default, addr, &val, 1, false);
        if (ret != 1) {
            // 可通过UART打印错误信息,确认写入失败
            printf("I2C write failed: %d\n", ret);
        }
    #endif
    }
    

    需提前初始化UART:在main函数开头添加stdio_init_all();。

  • 简化测试代码
    跳过复杂的循环显示,直接在main中写入简单测试内容,排除其他代码干扰:

    int main() {
        stdio_init_all();
        // I2C初始化代码不变...
        lcd_init();
        sleep_ms(100);
        lcd_set_cursor(0,0);
        lcd_string("TEST");
        while(1);
    }
    

参考资料

  • LCM1602的核心是HD44780液晶显示控制器,所有命令遵循HD44780的规范。
  • I2C适配器的核心是PCF8574 IO扩展芯片,它的引脚映射为:P0=RS, P1=RW, P2=E, P3=背光控制, P4-P7=LCD的D4-D7引脚。

内容的提问来源于stack exchange,提问作者Plertz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 11:25:20