콘텐츠로 이동

OLED 디스플레이 - 화면에 표시하기

🎯 이 장에서 배우는 것

  • [ ] I2C 통신의 기본 원리를 설명할 수 있다
  • [ ] OLED에 텍스트를 표시할 수 있다
  • [ ] 센서 데이터를 OLED에 시각화할 수 있다
  • [ ] 간단한 도형(선, 사각형)을 그릴 수 있다

💡 왜 이걸 배우나요?

지금까지 우리는 print()로 컴퓨터 화면에만 결과를 출력했어. 그런데 생각해봐 - 피코를 컴퓨터에서 분리해서 쓰고 싶다면? 예를 들어 침대 옆에 온도계를 놓고 싶다면, 컴퓨터 없이도 값을 볼 수 있어야 하잖아!

OLED 디스플레이는 바로 이 문제를 해결해줘. 작은 화면이지만 글자, 숫자, 심지어 그림까지 표시할 수 있어서 피코만으로 완전한 장치를 만들 수 있게 돼.

실제로 이런 곳에서 쓰여: - 🌡️ 스마트 온습도계 - ⌚ DIY 스마트워치 - 🎮 레트로 게임기 - 📊 서버 모니터링 장치

📚 핵심 개념

개념 1: I2C 통신이란?

  1. 비유로 시작: "I2C는 마치 버스 정류장 시스템과 같아요"
  2. 하나의 버스 노선(2개의 선)에 여러 승객(장치)이 탑승해
  3. 각 승객은 고유한 주소(좌석 번호)를 가지고 있어
  4. 버스 기사(피코)가 "42번 승객!"하고 부르면 해당 승객만 대답해

  5. 정확한 정의: "I2C(Inter-Integrated Circuit)는 단 2개의 선으로 여러 장치와 통신하는 프로토콜이야"

  6. SDA (Serial Data): 실제 데이터가 오가는 선
  7. SCL (Serial Clock): 박자를 맞춰주는 신호 선

  8. 예시로 확인: "OLED의 기본 주소는 보통 0x3C(60번)야. 피코가 '0x3C야, 이 글자 보여줘!'하면 OLED가 화면에 표시해"

쉽게 말하면: I2C는 2개의 선만으로 여러 장치와 대화할 수 있는 효율적인 통신 방법이야!

flowchart LR subgraph PICO["🎛️ Pico 2W"] M[Master] end subgraph BUS["🚌 I2C 버스 (2선)"] SDA["SDA (데이터)"] SCL["SCL (클럭)"] end subgraph DEVICES["📟 장치들"] OLED["OLED\n주소: 0x3C"] SENSOR["센서\n주소: 0x68"] end M --> SDA M --> SCL SDA --> OLED SDA --> SENSOR SCL --> OLED SCL --> SENSOR style PICO fill:#e3f2fd,stroke:#1976d2 style BUS fill:#fff3e0,stroke:#f57c00 style DEVICES fill:#e8f5e9,stroke:#388e3c

개념 2: OLED 디스플레이

  1. 비유로 시작: "OLED는 마치 128×64칸의 모눈종이와 같아요"
  2. 각 칸에 점을 찍거나 지울 수 있어
  3. 점들을 모아서 글자나 그림을 만들어

  4. 정확한 정의: "0.96인치 OLED는 128×64 픽셀의 자체발광 디스플레이야"

  5. 백라이트 없이 픽셀 자체가 빛을 내서 선명해
  6. 검은 배경에 흰색(또는 노란색+파란색) 표시

  7. 예시로 확인: "좌표 (0,0)은 왼쪽 위 모서리, (127,63)은 오른쪽 아래 모서리야"

쉽게 말하면: OLED는 128×64개의 작은 전구가 모여있는 화면이야!

flowchart TB subgraph SCREEN["📺 OLED 화면 좌표계"] direction TB A["(0,0) ← 시작점"] B["X축 →\n0 ~ 127"] C["Y축 ↓\n0 ~ 63"] D["(127,63) ← 끝점"] end A --> B A --> C C --> D B --> D style SCREEN fill:#f3e5f5,stroke:#7b1fa2

개념 3: 프레임버퍼

  1. 비유로 시작: "프레임버퍼는 마치 스케치북과 같아요"
  2. 스케치북에 먼저 그림을 그리고 (버퍼에 저장)
  3. 완성되면 액자에 걸어 (show()로 화면에 표시)

  4. 정확한 정의: "프레임버퍼는 화면에 표시할 내용을 메모리에 미리 그려두는 공간이야"

  5. 예시로 확인:

    oled.text("Hello", 0, 0)  # 버퍼에 그리기
    oled.show()               # 화면에 표시!
    

쉽게 말하면: 그림을 다 그린 다음에 한 번에 보여주는 거야!

🔨 따라하기

Step 0: 라이브러리 설치하기

목표: OLED를 제어하기 위한 ssd1306 라이브러리 설치

OLED를 사용하려면 먼저 라이브러리를 설치해야 해. Thonny에서 쉽게 할 수 있어!

설치 방법: 1. Thonny 상단 메뉴에서 도구(Tools)패키지 관리(Manage packages) 클릭 2. 검색창에 ssd1306 입력 3. micropython-ssd1306 선택 후 Install 클릭 4. 설치 완료될 때까지 대기

flowchart LR A["🔧 도구 메뉴"] --> B["📦 패키지 관리"] B --> C["🔍 ssd1306 검색"] C --> D["⬇️ Install 클릭"] D --> E["✅ 설치 완료!"] style A fill:#e3f2fd,stroke:#1976d2 style E fill:#e8f5e9,stroke:#388e3c

Step 1: OLED 연결하기

목표: Grove OLED를 피코에 연결하고 초기화

회로 연결: Grove Shield의 I2C 포트에 OLED 연결

flowchart LR subgraph PICO["🎛️ Pico 2W + Grove Shield"] I2C["I2C 포트"] end subgraph CABLE["🔌 Grove 케이블"] C["4핀 케이블"] end subgraph OLED["📺 Grove OLED"] D["0.96인치 디스플레이"] end I2C <--> C <--> D style PICO fill:#e3f2fd,stroke:#1976d2 style CABLE fill:#fff3e0,stroke:#f57c00 style OLED fill:#e8f5e9,stroke:#388e3c

코드:

# === WHAT: OLED 초기화하고 Hello 표시하기 ===
# 첫 번째 OLED 프로그램! 화면에 글자를 띄워보자

# --- WHY: 왜 필요한지 ---
# OLED를 사용하려면 먼저 I2C 통신을 설정하고
# 라이브러리로 OLED 객체를 만들어야 해

# HOW: 어떻게 동작하는지
from machine import Pin, I2C  # I2C 통신을 위한 모듈
import ssd1306                 # OLED 제어 라이브러리

# I2C 설정 - GP4(SDA), GP5(SCL)은 Grove Shield의 I2C 포트
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)

# OLED 객체 생성 - 128x64 크기, I2C 통신 사용
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

# 화면 지우기 (모두 검은색으로)
oled.fill(0)

# 텍스트 쓰기 - ("내용", x좌표, y좌표)
oled.text("Hello!", 0, 0)
oled.text("Pico 2W", 0, 16)

# 버퍼 내용을 실제 화면에 표시
oled.show()

print("OLED 초기화 완료!")

실행 결과:

OLED 초기화 완료!
그리고 OLED 화면에 "Hello!"와 "Pico 2W"가 표시돼!

여기서 잠깐! 🤔 - freq=400000은 I2C 통신 속도야 (400kHz = 빠른 모드) - oled.fill(0)은 검은색(0), oled.fill(1)은 흰색(1) - 반드시 show()를 호출해야 화면에 나타나!

Step 2: 다양한 텍스트 표시하기

목표: 여러 줄의 텍스트를 다양한 위치에 표시

코드:

# === WHAT: 여러 텍스트를 다양한 위치에 표시 ===
# 좌표를 이해하고 원하는 위치에 글자를 배치해보자

# --- WHY: 왜 필요한지 ---
# 센서 값, 시간 등 여러 정보를 화면에 정리해서 보여주려면
# 텍스트 위치 조절을 할 줄 알아야 해

from machine import Pin, I2C
import ssd1306

# I2C, OLED 초기화
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

# 화면 지우기
oled.fill(0)

# 다양한 위치에 텍스트 배치
# 기본 글꼴은 8x8 픽셀이라 한 글자가 8픽셀 차지해
oled.text("Line 1", 0, 0)    # 첫 번째 줄 (y=0)
oled.text("Line 2", 0, 10)   # 두 번째 줄 (y=10)
oled.text("Line 3", 0, 20)   # 세 번째 줄 (y=20)

# 오른쪽 정렬 효과 - x 좌표 조절
oled.text("Right!", 80, 40)  # 오른쪽으로 이동

# 중앙 배치 (대략적으로)
oled.text("Center", 40, 56)  # 아래쪽 중앙

oled.show()
print("텍스트 표시 완료!")

실행 결과: OLED에 5개의 텍스트가 각기 다른 위치에 표시돼!

여기서 잠깐! 🤔 - 기본 글꼴은 8x8 픽셀 크기야 - 한 화면에 최대 16글자 × 8줄 정도 들어가 - 한글은 지원 안 돼 😢 (영문, 숫자, 특수문자만)

Step 3: 도형 그리기

목표: 선, 사각형 등 기본 도형 그리기

코드:

# === WHAT: OLED에 도형 그리기 ===
# 점, 선, 사각형으로 간단한 그래픽 만들기

# --- WHY: 왜 필요한지 ---
# 그래프, 게이지, UI 요소 등을 만들려면
# 기본 도형을 그릴 줄 알아야 해

from machine import Pin, I2C
import ssd1306

i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

oled.fill(0)  # 화면 지우기

# === 점 찍기 ===
# pixel(x, y, color) - color: 1=흰색, 0=검은색
oled.pixel(10, 10, 1)
oled.pixel(12, 10, 1)
oled.pixel(14, 10, 1)

# === 수평선 그리기 ===
# hline(x, y, width, color)
oled.hline(0, 20, 128, 1)  # 화면 전체 가로선

# === 수직선 그리기 ===
# vline(x, y, height, color)
oled.vline(64, 25, 30, 1)  # 세로선

# === 사각형 (테두리만) ===
# rect(x, y, width, height, color)
oled.rect(10, 30, 40, 25, 1)

# === 채워진 사각형 ===
# fill_rect(x, y, width, height, color)
oled.fill_rect(70, 30, 40, 25, 1)

# 도형 위에 텍스트
oled.text("Shapes!", 35, 0)

oled.show()
print("도형 그리기 완료!")

실행 결과:

도형 그리기 완료!
화면에 점 3개, 가로선, 세로선, 빈 사각형, 채운 사각형이 표시돼!

flowchart TB subgraph METHODS["📐 OLED 그리기 메서드"] A["pixel(x, y, c)\n점 찍기"] B["hline(x, y, w, c)\n가로선"] C["vline(x, y, h, c)\n세로선"] D["rect(x, y, w, h, c)\n사각형 테두리"] E["fill_rect(x, y, w, h, c)\n채운 사각형"] end style METHODS fill:#e8f5e9,stroke:#388e3c

Step 4: 센서 데이터 표시하기

목표: 온습도 센서 값을 OLED에 실시간 표시

코드:

# === WHAT: 센서 값을 OLED에 표시하는 대시보드 ===
# 온습도 센서(DHT11)와 OLED를 연결해서 미니 모니터 만들기

# --- WHY: 왜 필요한지 ---
# 컴퓨터 없이도 센서 값을 확인할 수 있는
# 독립적인 모니터링 장치를 만들 수 있어

from machine import Pin, I2C
import ssd1306
import dht
import time

# === 장치 초기화 ===
# I2C, OLED 설정
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

# DHT11 센서 (D16 포트)
sensor = dht.DHT11(Pin(16))

# === 대시보드 그리기 함수 ===
def draw_dashboard(temp, humi):
    """온습도 데이터를 화면에 표시"""
    oled.fill(0)  # 화면 지우기

    # 제목
    oled.text("== Weather ==", 15, 0)

    # 구분선
    oled.hline(0, 12, 128, 1)

    # 온도 표시
    oled.text("Temp:", 0, 20)
    oled.text(str(temp) + " C", 60, 20)

    # 습도 표시
    oled.text("Humi:", 0, 35)
    oled.text(str(humi) + " %", 60, 35)

    # 상태 바 (습도를 막대 그래프로)
    bar_width = int(humi * 1.2)  # 최대 120픽셀
    oled.rect(4, 50, 120, 10, 1)       # 테두리
    oled.fill_rect(4, 50, bar_width, 10, 1)  # 채우기

    oled.show()

# === 메인 루프 ===
print("센서 대시보드 시작!")
print("Ctrl+C로 종료")

while True:
    try:
        # 센서 측정
        sensor.measure()
        temp = sensor.temperature()
        humi = sensor.humidity()

        # 화면 업데이트
        draw_dashboard(temp, humi)

        # 콘솔에도 출력
        print(f"온도: {temp}°C, 습도: {humi}%")

    except Exception as e:
        # 센서 오류 시
        oled.fill(0)
        oled.text("Sensor Error!", 10, 28)
        oled.show()
        print(f"오류: {e}")

    time.sleep(2)  # 2초마다 갱신

실행 결과:

센서 대시보드 시작!
Ctrl+C로 종료
온도: 24°C, 습도: 65%
온도: 24°C, 습도: 64%
...
OLED에 온도, 습도, 습도 막대 그래프가 표시되고 2초마다 갱신돼!

여기서 잠깐! 🤔 - fill(0)으로 매번 화면을 지우고 다시 그려 - 안 그러면 이전 글자 위에 새 글자가 겹쳐!

📝 전체 코드

# === 센서 데이터 OLED 대시보드 완성판 ===
# 파일명: step4_oled_dashboard.py
# 온습도 센서 값을 OLED에 실시간으로 표시하는 프로그램

from machine import Pin, I2C
import ssd1306
import dht
import time

# ===== 장치 초기화 =====
# I2C 통신 설정 (Grove Shield의 I2C 포트)
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)

# OLED 디스플레이 (128x64 픽셀)
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

# DHT11 온습도 센서 (D16 포트에 연결)
sensor = dht.DHT11(Pin(16))

# ===== 함수 정의 =====
def draw_dashboard(temp, humi):
    """온습도 데이터를 대시보드 형태로 표시

    Args:
        temp: 온도 값 (정수)
        humi: 습도 값 (정수)
    """
    oled.fill(0)  # 화면 초기화 (검은색)

    # 제목 영역
    oled.text("== Weather ==", 15, 0)
    oled.hline(0, 12, 128, 1)  # 구분선

    # 온도 표시
    oled.text("Temp:", 0, 20)
    oled.text(str(temp) + " C", 60, 20)

    # 습도 표시
    oled.text("Humi:", 0, 35)
    oled.text(str(humi) + " %", 60, 35)

    # 습도 막대 그래프
    oled.rect(4, 50, 120, 10, 1)  # 테두리
    bar_width = int(humi * 1.2)   # 습도에 비례한 막대 길이
    if bar_width > 0:
        oled.fill_rect(4, 50, bar_width, 10, 1)

    oled.show()  # 화면에 표시

def show_error(message):
    """에러 메시지를 화면에 표시

    Args:
        message: 표시할 에러 메시지
    """
    oled.fill(0)
    oled.text("ERROR!", 40, 20)
    oled.text(message, 0, 35)
    oled.show()

# ===== 메인 프로그램 =====
print("=" * 30)
print("센서 대시보드 시작!")
print("2초마다 센서 값을 갱신합니다")
print("Ctrl+C로 종료하세요")
print("=" * 30)

# 시작 화면
oled.fill(0)
oled.text("Starting...", 25, 28)
oled.show()
time.sleep(1)

# 무한 루프
while True:
    try:
        # 센서에서 데이터 읽기
        sensor.measure()
        temp = sensor.temperature()
        humi = sensor.humidity()

        # OLED에 표시
        draw_dashboard(temp, humi)

        # 콘솔에도 출력
        print(f"온도: {temp}°C | 습도: {humi}%")

    except OSError as e:
        # 센서 통신 오류
        show_error("Sensor N/A")
        print(f"센서 오류: {e}")

    except Exception as e:
        # 기타 오류
        show_error("Unknown Err")
        print(f"오류 발생: {e}")

    # 2초 대기
    time.sleep(2)

⚠️ 자주 하는 실수

실수 1: show()를 안 부름

증상: 코드는 에러 없이 실행되는데 화면에 아무것도 안 나와

원인: OLED는 프레임버퍼 방식이라 show()를 호출해야 실제 화면이 갱신돼

해결:

# 잘못된 코드
oled.text("Hello", 0, 0)
# 여기서 끝나면 화면에 안 나타남!

# 올바른 코드
oled.text("Hello", 0, 0)
oled.show()  # 이걸 꼭 호출해야 해!

실수 2: 화면을 안 지우고 덮어씀

증상: 글자가 겹쳐서 뭉개져 보임

원인: 이전에 그린 내용 위에 새 내용이 덧그려짐

해결:

# 잘못된 코드
while True:
    oled.text(str(count), 0, 0)  # 계속 겹침
    oled.show()
    count += 1

# 올바른 코드
while True:
    oled.fill(0)  # 먼저 화면 지우기!
    oled.text(str(count), 0, 0)
    oled.show()
    count += 1

실수 3: I2C 핀 번호 오류

증상: OSError: [Errno 5] EIO 에러 발생

원인: I2C 핀 번호가 잘못됐거나 연결이 안 됨

해결:

# 잘못된 코드 (핀 번호 틀림)
i2c = I2C(0, sda=Pin(0), scl=Pin(1))

# 올바른 코드 (Grove Shield I2C 포트)
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)

실수 4: 좌표가 화면을 벗어남

증상: 글자나 도형이 잘려서 보이거나 안 보임

원인: x는 0~127, y는 0~63 범위를 벗어남

해결:

# 잘못된 코드
oled.text("Hello", 100, 0)  # 글자가 오른쪽으로 잘림
oled.text("Test", 0, 60)     # 글자가 아래로 잘림

# 올바른 코드 (글자 크기 8x8 고려)
oled.text("Hello", 88, 0)   # 5글자 * 8 = 40, 128-40 = 88
oled.text("Test", 0, 56)     # 64-8 = 56

실수 5: 라이브러리 설치 안 함

증상: ImportError: no module named 'ssd1306'

원인: ssd1306 라이브러리가 피코에 설치되지 않음

해결: 1. Thonny → 도구 → 패키지 관리 2. ssd1306 검색 3. micropython-ssd1306 설치

✅ 스스로 점검하기

  1. I2C 통신에서 SDA와 SCL의 역할은 각각 무엇인가요?

  2. OLED에 "Score: 100"을 화면 중앙에 표시하려면 x 좌표를 대략 얼마로 해야 할까요?

  3. 왜 화면을 갱신하기 전에 oled.fill(0)을 호출하는 게 좋을까요?

정답 확인 1. **SDA**(Serial Data)는 실제 데이터가 오가는 선이고, **SCL**(Serial Clock)은 데이터 전송의 타이밍(박자)을 맞춰주는 신호 선이야. 2. "Score: 100"은 10글자야. 글자당 8픽셀이니까 총 80픽셀. 중앙 정렬: (128 - 80) / 2 = **24** 정도! 3. `fill(0)`을 안 하면 이전에 그렸던 내용이 그대로 남아있어서 새 내용과 겹쳐 보여. 화면을 깨끗이 지우고 다시 그려야 깔끔하게 표시돼!

🚀 더 해보기

도전 1: 카운터 만들기 (쉬움)

버튼을 누를 때마다 숫자가 1씩 증가하고 OLED에 표시되는 카운터를 만들어봐!

힌트:

count = 0
# 버튼 누르면 count += 1
# OLED에 count 표시

도전 2: 간단한 애니메이션 (중간)

사각형이 왼쪽에서 오른쪽으로 이동하는 애니메이션을 만들어봐!

힌트:

for x in range(0, 100, 5):
    oled.fill(0)
    oled.fill_rect(x, 28, 20, 20, 1)
    oled.show()
    time.sleep(0.1)

도전 3: 미니 게임 만들기 ⭐ (어려움)

조도 센서 값에 따라 막대가 움직이는 게임을 만들어봐! - 막대가 목표 영역에 들어오면 점수 획득 - 점수와 레벨을 OLED에 표시

힌트:

# 조도 값을 y 좌표로 변환
# 목표 영역과 충돌 체크
# 점수 시스템 추가

🔗 다음 장으로

이번 장에서 우리는: - ✅ I2C 통신으로 OLED와 대화하는 법을 배웠어 - ✅ 텍스트, 선, 사각형을 화면에 그릴 수 있게 됐어 - ✅ 센서 데이터를 실시간으로 시각화하는 대시보드를 만들었어

다음 장에서는 피코를 인터넷에 연결해볼 거야! WiFi로 세상과 연결되면 날씨 정보도 받아오고, 시간도 동기화할 수 있어. 지금 만든 OLED 대시보드에 인터넷 정보까지 더하면 진짜 IoT 장치가 완성돼! 🌐