Chapter 9. I2C LCD 디스플레이 문자열 출력
컴퓨터 모니터 없이도 보드의 현재 상태나 센서 데이터 값을 시각적으로 뿌려줄 수 있는 I2C 규격의 LCD1602 모듈을 통제해 봅니다.
뒷면에 부착된 검은색 I2C 백팩 모듈 덕분에 단 4가닥의 선만으로 화면 제어가 가능합니다.
LCD I2C 백팩: VCC→3V3, SDA→GP8(8번 핀), SCL→GP9(9번 핀), GND→GND. I2C 버스 덕분에 4가닥만으로 16×2 LCD 전체를 제어합니다.
9.1 사전 준비: 구동 라이브러리 파일 업로드 (매우 중요)
LCD를 파이썬 코드로 쉽게 제어하려면, 전 세계 마이크로파이썬 개발자들이 표준으로 사용하는 오픈소스 라이브러리 파일 2개가 Pico 보드 내부에 미리 저장되어 있어야 합니다. 아래 두 코드를 각각 복사하여 Thonny에서 새 파일로 만든 뒤, 지정된 이름으로 저장해 주세요.
첫 번째 파일: 공통 뼈대 역할을 하는 파일입니다. 아래 코드를 lcd_api.py 이름으로 저장하세요.
import time
class LcdApi:
# HD44780 LCD controller command set
LCD_CLR = 0x01
LCD_HOME = 0x02
LCD_ENTRY_MODE = 0x04
LCD_ENTRY_INC = 0x02
LCD_ENTRY_SHIFT = 0x01
LCD_ON_CTRL = 0x08
LCD_ON_DISPLAY = 0x04
LCD_ON_CURSOR = 0x02
LCD_ON_BLINK = 0x01
LCD_MOVE = 0x10
LCD_MOVE_DISP = 0x08
LCD_MOVE_RIGHT = 0x04
LCD_FUNCTION = 0x20
LCD_FUNCTION_8BIT = 0x10
LCD_FUNCTION_2LINES = 0x08
LCD_FUNCTION_10DOTS = 0x04
LCD_CGRAM = 0x40
LCD_DDRAM = 0x80
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):
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):
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | self.LCD_ON_CURSOR)
def hide_cursor(self):
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)
def blink_cursor_on(self):
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):
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | self.LCD_ON_CURSOR)
def display_on(self):
self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY)
def display_off(self):
self.hal_write_command(self.LCD_ON_CTRL)
def backlight_on(self):
self.backlight = True
self.hal_backlight_on()
def backlight_off(self):
self.backlight = False
self.hal_backlight_off()
def move_to(self, cursor_x, cursor_y):
self.cursor_x = cursor_x
self.cursor_y = cursor_y
addr = cursor_x & 0x3f
if cursor_y & 1:
addr += 0x40
if cursor_y & 2:
addr += self.num_columns
self.hal_write_command(self.LCD_DDRAM | addr)
def putchar(self, char):
if char == '\n':
if self.implied_newline:
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):
for char in string:
self.putchar(char)
def custom_char(self, location, charmap):
location &= 0x07
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):
pass
def hal_backlight_off(self):
pass
def hal_write_command(self, cmd):
raise NotImplementedError
def hal_write_data(self, data):
raise NotImplementedError
def hal_sleep_us(self, usecs):
time.sleep_us(usecs)
두 번째 파일: I2C 통신 규격을 다루는 파일입니다. 아래 코드를 pico_i2c_lcd.py 이름으로 저장하세요.
from lcd_api import LcdApi
from machine import I2C
from time import sleep_ms
# The PCF8574 has a jumper selectable address: 0x20 - 0x27
DEFAULT_I2C_ADDR = 0x27
MASK_RS = 0x01
MASK_RW = 0x02
MASK_E = 0x04
SHIFT_BACKLIGHT = 3
SHIFT_DATA = 4
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, bytearray([0]))
sleep_ms(20) # Allow LCD time to powerup
# Send reset 3 times
self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
sleep_ms(5) # need to delay at least 4.1 msec
self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
sleep_ms(1)
self.hal_write_init_nibble(self.LCD_FUNCTION_RESET)
sleep_ms(1)
# Put LCD into 4 bit mode
self.hal_write_init_nibble(self.LCD_FUNCTION)
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)
def hal_write_init_nibble(self, nibble):
byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
def hal_backlight_on(self):
self.i2c.writeto(self.i2c_addr, bytearray([1 << SHIFT_BACKLIGHT]))
def hal_backlight_off(self):
self.i2c.writeto(self.i2c_addr, bytearray([0]))
def hal_write_command(self, cmd):
byte = ((self.backlight << SHIFT_BACKLIGHT) | (((cmd >> 4) & 0x0f) << SHIFT_DATA))
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
byte = ((self.backlight << SHIFT_BACKLIGHT) | ((cmd & 0x0f) << SHIFT_DATA))
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
if cmd <= 3:
sleep_ms(5)
def hal_write_data(self, data):
byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | (((data >> 4) & 0x0f) << SHIFT_DATA))
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | ((data & 0x0f) << SHIFT_DATA))
self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E]))
self.i2c.writeto(self.i2c_addr, bytearray([byte]))
9.2 I2C 디바이스 주소 스캔하기
라이브러리 준비가 끝났다면 하드웨어가 올바른 물리 채널에 접속되었는지 확인해야 합니다. 아래 코드를 실행하여 내 디스플레이의 고유 16진수 주소(Address)를 알아냅니다.
from machine import I2C, Pin
# Pico 2의 I2C0번 채널 활성화 (SDA: GPIO 8, SCL: GPIO 9)
i2c = I2C(0, sda=Pin(8), scl=Pin(9), freq=400000)
print("I2C 무선 장치 스캔 시작...")
devices = i2c.scan()
if len(devices) == 0:
print("연결된 I2C 부품을 찾지 못했습니다. 배선을 확인하세요.")
else:
for device in devices:
print(f"발견된 디바이스 16진수 고유 주소: {hex(device)}")
9.3 LCD 화면에 실시간 문자열 시각화
위에서 저장한 라이브러리를 임포트(import)하여 LCD 화면을 통제합니다. 화면 상단에는 고정 문자를 찍고, 하단에는 실시간으로 데이터가 변동되는 대시보드 출력 마스터 코드입니다.
from machine import I2C, Pin
import time
# 미리 저장해둔 라이브러리 파일에서 클래스를 불러옵니다.
from pico_i2c_lcd import I2cLcd
i2c = I2C(0, sda=Pin(8), scl=Pin(9), freq=400000)
# 위 스캔에서 발견된 16진수 주소값(보통 0x27) 입력
lcd_address = 0x27
lcd = I2cLcd(i2c, lcd_address, 2, 16)
# 화면 초기화 및 백라이트 점등
lcd.clear()
lcd.backlight_on()
# 1행에 텍스트 고정 출력 (0번방 열, 0번방 행)
lcd.move_to(0, 0)
lcd.putstr("Pico 2 IoT Monitor")
count = 0
while True:
# 2행 0열로 커서 이동 후 실시간 데이터 교체
lcd.move_to(0, 1)
lcd.putstr(f"System Running:{count}")
count += 1
time.sleep(1.0)