LCD 显示屏

原理简介

LCD(液晶显示器)是一种使用液晶材料的显示技术。

  • 工作原理:通过电场控制液晶分子的排列,从而控制光线的透过率
  • 类型:字符 LCD、图形 LCD
  • 接口:I2C 或并行接口

典型应用

  • 信息显示
  • 数据监测
  • 控制面板
  • 工业设备
  • 智能家居

接线图

[接线图占位,后续添加]

文字说明(具体接法见下方代码注释):

  • LCD VCC → Pico VBUS(5V,背光更亮;接 3V3 也能用但对比度偏弱)
  • LCD GND → Pico GND
  • LCD SCL → Pico GP9(I2C0 SCL)
  • LCD SDA → Pico GP8(I2C0 SDA)
  • 模块背面电位器可调对比度;I2C 地址常见 0x27 或 0x3F,跑不出来就换另一个

示例代码

基础示例:字符 LCD 显示

# ============ 接线 ============
# LCD VCC → Pico VBUS(5V;接 3V3 也可用但对比度弱)
# LCD GND → Pico GND
# LCD SCL → Pico GP9(I2C0 SCL)
# LCD SDA → Pico GP8(I2C0 SDA)
# I2C 地址常见 0x27 或 0x3F,下面 I2cLcd() 里地址不对就换
# =============================
from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
import time

# 初始化 I2C
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
# 初始化 LCD(16x2)
lcd = I2cLcd(i2c, 0x27, 2, 16)  # 地址 0x27,2行16列

print('LCD 显示测试')

# 清屏
lcd.clear()

# 显示文本
lcd.putstr('Hello, World!')
time.sleep(2)

# 第二行显示
lcd.clear()
lcd.putstr('Line 1')
lcd.move_to(0, 1)
lcd.putstr('Line 2')
time.sleep(2)

# 闪烁光标
lcd.clear()
lcd.putstr('Blinking')
lcd.blink_cursor_on()
time.sleep(2)
lcd.blink_cursor_off()

进阶示例:显示传感器数据

# ============ 接线 ============
# 接线同基础示例:VCC→VBUS,GND→GND,SCL→GP9,SDA→GP8
# 电位器:两端脚 → 3V3 和 GND,中间抽头 → GP26(ADC0)
# =============================
from machine import I2C, Pin, ADC
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
import time

# 初始化 I2C 和 LCD
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
lcd = I2cLcd(i2c, 0x27, 2, 16)

# 初始化 ADC(电位器)
adc = ADC(Pin(26))

print('LCD 显示传感器数据')

while True:
    # 读取电位器值
    value = adc.read_u16()
    voltage = value * 3.3 / 65535

    # 清屏
    lcd.clear()

    # 显示数据
    lcd.putstr(f'Value: {value}')
    lcd.move_to(0, 1)
    lcd.putstr(f'Voltage: {voltage:.2f}V')

    time.sleep(0.5)

应用示例:简单时钟

# ============ 接线 ============
# 接线同基础示例:VCC→VBUS,GND→GND,SCL→GP9,SDA→GP8
# =============================
from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
import time

# 初始化 I2C 和 LCD
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
lcd = I2cLcd(i2c, 0x27, 2, 16)

print('LCD 时钟')

while True:
    # 获取当前时间
    t = time.localtime()
    time_str = f'{t[3]:02d}:{t[4]:02d}:{t[5]:02d}'
    date_str = f'{t[0]}-{t[1]:02d}-{t[2]:02d}'

    # 清屏
    lcd.clear()

    # 显示时间和日期
    lcd.putstr(time_str)
    lcd.move_to(0, 1)
    lcd.putstr(date_str)

    time.sleep(1)

注意事项

  • 电源:确保 LCD 工作在规定的电压范围内
  • 初始化:需要正确初始化 I2C 或并行接口
  • 库:需要安装相应的 LCD 库
  • 对比度:有些 LCD 支持对比度调节
  • 背光:有些 LCD 支持背光调节
  • 温度:温度变化可能影响 LCD 性能

常见问题

Q: LCD 不显示怎么办? A: 检查接线是否正确,确保 I2C 地址正确,检查电源是否正常。

Q: 显示内容乱码怎么办? A: 检查字符编码,确保使用正确的字体。

Q: 如何提高显示速度? A: 减少刷新频率,只更新变化的部分。

库文件

本页示例用到 lcd_api 和 pico_i2c_lcd 两个库,MicroPython 固件不自带。 注意:两个都要存,少存一个都会报错(pico_i2c_lcd 内部会 from lcd_api import LcdApi)。

操作步骤(Thonny)

  1. 用 Thonny 连上 Pico
  2. 点菜单「文件 → 新建」,把下面 lcd_api.py 的代码整段粘贴进去
  3. 点「文件 → 另存为」,选 Raspberry Pi Pico,文件名填 lcd_api.py,保存
  4. 再点「文件 → 新建」,把下面 pico_i2c_lcd.py 的代码粘贴进去
  5. 同样另存为 pico_i2c_lcd.py,存到 Pico 上
  6. 两个文件都存好后,本页示例就能正常运行了

lcd_api.py

import time

class LcdApi:

    # Implements the API for talking with HD44780 compatible character LCDs.
    # This class only knows what commands to send to the LCD, and not how to get
    # them to the LCD.
    #
    # It is expected that a derived class will implement the hal_xxx functions.
    #
    # The following constant names were lifted from the avrlib lcd.h header file,
    # with bit numbers changed to bit masks.

    # HD44780 LCD controller command set
    LCD_CLR             = 0x01  # DB0: clear display
    LCD_HOME            = 0x02  # DB1: return to home position

    LCD_ENTRY_MODE      = 0x04  # DB2: set entry mode
    LCD_ENTRY_INC       = 0x02  # DB1: increment
    LCD_ENTRY_SHIFT     = 0x01  # DB0: shift

    LCD_ON_CTRL         = 0x08  # DB3: turn lcd/cursor on
    LCD_ON_DISPLAY      = 0x04  # DB2: turn display on
    LCD_ON_CURSOR       = 0x02  # DB1: turn cursor on
    LCD_ON_BLINK        = 0x01  # DB0: blinking cursor

    LCD_MOVE            = 0x10  # DB4: move cursor/display
    LCD_MOVE_DISP       = 0x08  # DB3: move display (0-> move cursor)
    LCD_MOVE_RIGHT      = 0x04  # DB2: move right (0-> left)

    LCD_FUNCTION        = 0x20  # DB5: function set
    LCD_FUNCTION_8BIT   = 0x10  # DB4: set 8BIT mode (0->4BIT mode)
    LCD_FUNCTION_2LINES = 0x08  # DB3: two lines (0->one line)
    LCD_FUNCTION_10DOTS = 0x04  # DB2: 5x10 font (0->5x7 font)
    LCD_FUNCTION_RESET  = 0x30  # See "Initializing by Instruction" section

    LCD_CGRAM           = 0x40  # DB6: set CG RAM address
    LCD_DDRAM           = 0x80  # DB7: set DD RAM address

    LCD_RS_CMD          = 0
    LCD_RS_DATA         = 1

    LCD_RW_WRITE        = 0
    LCD_RW_READ         = 1

    def __init__(self, num_lines, num_columns):
        self.num_lines = num_lines
        if self.num_lines > 4:
            self.num_lines = 4
        self.num_columns = num_columns
        if self.num_columns > 40:
            self.num_columns = 40
        self.cursor_x = 0
        self.cursor_y = 0
        self.implied_newline = False
        self.backlight = True
        self.display_off()
        self.backlight_on()
        self.clear()
        self.hal_write_command(self.LCD_ENTRY_MODE | self.LCD_ENTRY_INC)
        self.hide_cursor()
        self.display_on()

    def clear(self):
        # Clears the LCD display and moves the cursor to the top left corner
        self.hal_write_command(self.LCD_CLR)
        self.hal_write_command(self.LCD_HOME)
        self.cursor_x = 0
        self.cursor_y = 0

    def show_cursor(self):
        # Causes the cursor to be made visible
        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |
                               self.LCD_ON_CURSOR)

    def hide_cursor(self):
        # Causes the cursor to be hidden
        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)

    def blink_cursor_on(self):
        # Turns on the cursor, and makes it blink
        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |
                               self.LCD_ON_CURSOR | self.LCD_ON_BLINK)

    def blink_cursor_off(self):
        # Turns on the cursor, and makes it no blink (i.e. be solid)
        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY |
                               self.LCD_ON_CURSOR)

    def display_on(self):
        # Turns on (i.e. unblanks) the LCD
        self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)

    def display_off(self):
        # Turns off (i.e. blanks) the LCD
        self.hal_write_command(self.LCD_ON_CTRL)

    def backlight_on(self):
        # Turns the backlight on.

        # This isn't really an LCD command, but some modules have backlight
        # controls, so this allows the hal to pass through the command.
        self.backlight = True
        self.hal_backlight_on()

    def backlight_off(self):
        # Turns the backlight off.

        # This isn't really an LCD command, but some modules have backlight
        # controls, so this allows the hal to pass through the command.
        self.backlight = False
        self.hal_backlight_off()

    def move_to(self, cursor_x, cursor_y):
        # Moves the cursor position to the indicated position. The cursor
        # position is zero based (i.e. cursor_x == 0 indicates first column).
        self.cursor_x = cursor_x
        self.cursor_y = cursor_y
        addr = cursor_x & 0x3f
        if cursor_y & 1:
            addr += 0x40    # Lines 1 & 3 add 0x40
        if cursor_y & 2:    # Lines 2 & 3 add number of columns
            addr += self.num_columns
        self.hal_write_command(self.LCD_DDRAM | addr)

    def putchar(self, char):
        # Writes the indicated character to the LCD at the current cursor
        # position, and advances the cursor by one position.
        if char == '\n':
            if self.implied_newline:
                # self.implied_newline means we advanced due to a wraparound,
                # so if we get a newline right after that we ignore it.
                pass
            else:
                self.cursor_x = self.num_columns
        else:
            self.hal_write_data(ord(char))
            self.cursor_x += 1
        if self.cursor_x >= self.num_columns:
            self.cursor_x = 0
            self.cursor_y += 1
            self.implied_newline = (char != '\n')
        if self.cursor_y >= self.num_lines:
            self.cursor_y = 0
        self.move_to(self.cursor_x, self.cursor_y)

    def putstr(self, string):
        # Write the indicated string to the LCD at the current cursor
        # position and advances the cursor position appropriately.
        for char in string:
            self.putchar(char)

    def custom_char(self, location, charmap):
        # Write a character to one of the 8 CGRAM locations, available
        # as chr(0) through chr(7).
        location &= 0x7
        self.hal_write_command(self.LCD_CGRAM | (location << 3))
        self.hal_sleep_us(40)
        for i in range(8):
            self.hal_write_data(charmap[i])
            self.hal_sleep_us(40)
        self.move_to(self.cursor_x, self.cursor_y)

    def hal_backlight_on(self):
        # Allows the hal layer to turn the backlight on.
        # If desired, a derived HAL class will implement this function.
        pass

    def hal_backlight_off(self):
        # Allows the hal layer to turn the backlight off.
        # If desired, a derived HAL class will implement this function.
        pass

    def hal_write_command(self, cmd):
        # Write a command to the LCD.
        # It is expected that a derived HAL class will implement this function.
        raise NotImplementedError

    def hal_write_data(self, data):
        # Write data to the LCD.
        # It is expected that a derived HAL class will implement this function.
        raise NotImplementedError

    def hal_sleep_us(self, usecs):
        # Sleep for some time (given in microseconds)
        time.sleep_us(usecs)

pico_i2c_lcd.py

import utime
import gc

from lcd_api import LcdApi
from machine import I2C

# PCF8574 pin definitions
MASK_RS = 0x01       # P0
MASK_RW = 0x02       # P1
MASK_E  = 0x04       # P2

SHIFT_BACKLIGHT = 3  # P3
SHIFT_DATA      = 4  # P4-P7

class I2cLcd(LcdApi):

    #Implements a HD44780 character LCD connected via PCF8574 on I2C

    def __init__(self, i2c, i2c_addr, num_lines, num_columns):
        self.i2c = i2c
        self.i2c_addr = i2c_addr
        self.i2c.writeto(self.i2c_addr, bytes([0]))
        utime.sleep_ms(20)   # Allow LCD time to powerup
        # Send reset 3 times
        self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
        utime.sleep_ms(5)    # Need to delay at least 4.1 msec
        self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
        utime.sleep_ms(1)
        self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
        utime.sleep_ms(1)
        # Put LCD into 4-bit mode
        self.hal_write_init_nibble(self.LCD_FUNCTION)
        utime.sleep_ms(1)
        LcdApi.__init__(self, num_lines, num_columns)
        cmd = self.LCD_FUNCTION
        if num_lines > 1:
            cmd |= self.LCD_FUNCTION_2LINES
        self.hal_write_command(cmd)
        gc.collect()

    def hal_write_init_nibble(self, nibble):
        # Writes an initialization nibble to the LCD.
        # This particular function is only used during initialization.
        byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        gc.collect()

    def hal_backlight_on(self):
        # Allows the hal layer to turn the backlight on
        self.i2c.writeto(self.i2c_addr, bytes([1 << SHIFT_BACKLIGHT]))
        gc.collect()

    def hal_backlight_off(self):
        #Allows the hal layer to turn the backlight off
        self.i2c.writeto(self.i2c_addr, bytes([0]))
        gc.collect()

    def hal_write_command(self, cmd):
        # Write a command to the LCD. Data is latched on the falling edge of E.
        byte = ((self.backlight << SHIFT_BACKLIGHT) |
                (((cmd >> 4) & 0x0f) << SHIFT_DATA))
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        byte = ((self.backlight << SHIFT_BACKLIGHT) |
                ((cmd & 0x0f) << SHIFT_DATA))
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        if cmd <= 3:
            # The home and clear commands require a worst case delay of 4.1 msec
            utime.sleep_ms(5)
        gc.collect()

    def hal_write_data(self, data):
        # Write data to the LCD. Data is latched on the falling edge of E.
        byte = (MASK_RS |
                (self.backlight << SHIFT_BACKLIGHT) |
                (((data >> 4) & 0x0f) << SHIFT_DATA))
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        byte = (MASK_RS |
                (self.backlight << SHIFT_BACKLIGHT) |
                ((data & 0x0f) << SHIFT_DATA))      
        self.i2c.writeto(self.i2c_addr, bytes([byte | MASK_E]))
        self.i2c.writeto(self.i2c_addr, bytes([byte]))
        gc.collect()