Chapter 12. OLED와 RTC를 이용한 스마트 시계
고해상도 0.96인치 OLED와 정밀 시간 모듈(RTC)을 이용해 나만의 탁상시계를 만듭니다. I2C 버스 하나에 두 개의 장치를 연결하는 방법(Daisy Chain)을 학습합니다.
I2C 병렬 연결: OLED와 RTC의 SDA/SCL을 Pico의 GP8/GP9에 공통으로 연결합니다. OLED는 보통 0x3C, DS3231은 0x68 주소를 사용합니다.
💡 I2C와 RTC 기본 개념 정리
I2C(Inter-Integrated Circuit)는 SDA(데이터선)와 SCL(클럭선), 단 2개의 선으로 여러 개의 장치를 동시에 연결할 수 있는 통신 방식입니다. 마치 여러 사람이 같은 무전기 채널을 쓰지만 각자의 "호출 부호(주소)"로 자신을 향한 메시지만 알아듣는 것과 비슷합니다. 이 챕터에서는 OLED(0x3C)와 DS3231 RTC(0x68)가 서로 다른 주소를 가지고 있어 같은 두 선(GP8/GP9)을 공유해도 충돌하지 않습니다.
RTC(Real Time Clock)는 Pico의 전원이 꺼져도 자체 배터리(보통 CR2032)로 계속 시간을 세는 전용 시계 칩입니다. Pico 자체에도 소프트웨어 타이머는 있지만, 전원이 끊기면 시간이 초기화됩니다. DS3231은 온도 보정 수정 발진기를 내장해 하루에 오차가 몇 초 이내로 매우 정밀한 것이 특징입니다.
12.1 준비물과 연결 핀
I2C는 여러 장치를 같은 선에 연결할 수 있습니다. 중요한 것은 모든 장치의 SDA는 SDA끼리, SCL은 SCL끼리 묶고 주소가 겹치지 않는지 확인하는 것입니다.
| 장치 | Pico 2 W 핀 | 역할 |
|---|---|---|
| OLED SDA / SCL | GP8 / GP9 | 시간 표시 |
| DS3231 SDA / SCL | GP8 / GP9 | 정밀 시간 유지 |
| VCC / GND | 3V3, GND | 공통 전원 |
12.2 DS3231 기반 스마트 시계 소스 코드
사전 준비: Thonny 패키지 관리자에서 ssd1306 라이브러리를 설치해야 합니다.
import machine
import ssd1306
import time
DS3231_ADDR = 0x68
OLED_ADDR = 0x3C
i2c = machine.I2C(0, sda=machine.Pin(8), scl=machine.Pin(9), freq=400000)
oled = ssd1306.SSD1306_I2C(128, 64, i2c, addr=OLED_ADDR)
def bcd_to_dec(value):
return (value >> 4) * 10 + (value & 0x0F)
def dec_to_bcd(value):
return ((value // 10) << 4) | (value % 10)
def set_ds3231(year, month, day, weekday, hour, minute, second):
data = bytearray(7)
data[0] = dec_to_bcd(second)
data[1] = dec_to_bcd(minute)
data[2] = dec_to_bcd(hour)
data[3] = dec_to_bcd(weekday)
data[4] = dec_to_bcd(day)
data[5] = dec_to_bcd(month)
data[6] = dec_to_bcd(year - 2000)
i2c.writeto_mem(DS3231_ADDR, 0x00, data)
def read_ds3231():
data = i2c.readfrom_mem(DS3231_ADDR, 0x00, 7)
second = bcd_to_dec(data[0] & 0x7F)
minute = bcd_to_dec(data[1])
hour = bcd_to_dec(data[2] & 0x3F)
weekday = bcd_to_dec(data[3])
day = bcd_to_dec(data[4])
month = bcd_to_dec(data[5] & 0x1F)
year = 2000 + bcd_to_dec(data[6])
return year, month, day, weekday, hour, minute, second
def draw_clock(now):
year, month, day, weekday, hour, minute, second = now
oled.fill(0)
oled.text("SMART CLOCK", 18, 0)
oled.hline(0, 12, 128, 1)
oled.text(f"{year}-{month:02d}-{day:02d}", 18, 22)
oled.text(f"{hour:02d}:{minute:02d}:{second:02d}", 30, 42)
oled.show()
devices = i2c.scan()
print("I2C devices:", [hex(addr) for addr in devices])
if DS3231_ADDR not in devices:
oled.fill(0)
oled.text("RTC not found", 12, 24)
oled.text("Check wiring", 14, 42)
oled.show()
raise RuntimeError("DS3231 not found")
# 최초 1회만 시간을 맞출 때 사용하고, 맞춘 뒤에는 주석 처리하세요.
# set_ds3231(2026, 6, 2, 2, 12, 0, 0)
print("Smart Clock 가동...")
while True:
now = read_ds3231()
draw_clock(now)
time.sleep(1)
💡 I2C 주소 확인
OLED는 보통 0x3C, RTC는 0x68 주소를 가집니다. Chapter 9의 스캔 코드를 활용해 두 장치가 모두 인식되는지 먼저 확인하세요.
12.3 동작 흐름
프로그램은 먼저 I2C 버스에서 OLED와 DS3231이 보이는지 확인합니다. RTC가 연결되어 있으면 DS3231의 BCD 형식 시간 데이터를 읽어 사람이 읽는 숫자로 바꾸고, OLED에 날짜와 시간을 1초마다 다시 그립니다. 한 번 시간을 맞춘 뒤에는 DS3231의 배터리가 시간을 계속 유지합니다.
💡 BCD(Binary-Coded Decimal)란?
DS3231은 시간 값을 BCD 형식으로 저장합니다. 예를 들어 숫자 45는 이진수로 101101이 아니라, "4"와 "5"를 각각 4비트씩 나눠 0100 0101로 표현합니다. bcd_to_dec()는 이 형식을 다시 우리가 읽는 10진수로 바꿔주고, dec_to_bcd()는 반대로 변환해 RTC에 기록할 때 사용합니다.
🏆 과제 해결해보기!
과제 1. 요일을 한글로 표시하기
DS3231의 weekday 값은 1(월요일)~7(일요일)의 숫자로만 반환됩니다. 이 숫자를 "월", "화", "수" 등 한글 요일 문자열로 바꿔 OLED 화면에 함께 표시하도록 draw_clock() 함수를 수정해 보세요.
과제 2. 알람 기능 추가하기
지정한 시각(예: 매일 오전 7시 30분)이 되면 OLED에 "ALARM!" 문구를 표시하는 기능을 추가해 보세요. (힌트: read_ds3231()로 읽은 hour, minute 값을 원하는 알람 시각과 비교합니다.)
12.4 Wokwi 시뮬레이션으로 실습하기
Chapter 1(1.8)에서 소개한 Wokwi 온라인 시뮬레이터에서 OLED와 RTC 조합을 미리 연습해볼 수 있습니다.
⚠️ DS3231 대신 DS1307을 사용합니다
Wokwi의 부품 라이브러리에는 DS3231이 아직 정식으로 등록되어 있지 않습니다. 대신 레지스터 구조가 거의 동일한 wokwi-ds1307 부품을 대안으로 사용해 시뮬레이션할 수 있습니다. DS1307도 같은 BCD 형식으로 초/분/시/요일/일/월/년 레지스터(0x00~0x06)를 제공하므로, 위 코드의 i2c.writeto_mem() / readfrom_mem() 호출 방식은 거의 그대로 재사용할 수 있습니다. 다만 이것은 시뮬레이션 전용 대체이며, 실제 배선/코드 검증은 반드시 실제 DS3231 모듈로 진행하세요.
아래 diagram.json을 Wokwi 프로젝트의 diagram 편집기에 붙여넣고, main.py에는 위 12.2절의 코드를 그대로 붙여넣은 뒤 초록색 ▶️ 재생 버튼을 누르면 OLED에 시간이 표시되는지 확인할 수 있습니다.
{
"version": 1,
"author": "Pico 2 Guide",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-pi-pico", "id": "pico", "top": 0, "left": 0, "attrs": { "env": "micropython-20231005-v1.21.0" } },
{ "type": "wokwi-ssd1306", "id": "oled1", "top": -60, "left": 120, "attrs": { "i2cAddress": "0x3c" } },
{ "type": "wokwi-ds1307", "id": "rtc1", "top": 40, "left": 120, "attrs": {} }
],
"connections": [
[ "pico:GP8", "oled1:SDA", "green", [] ],
[ "pico:GP9", "oled1:SCL", "yellow", [] ],
[ "pico:GP8", "rtc1:SDA", "green", [] ],
[ "pico:GP9", "rtc1:SCL", "yellow", [] ],
[ "oled1:VCC", "pico:3V3", "red", [] ],
[ "oled1:GND", "pico:GND", "black", [] ],
[ "rtc1:VCC", "pico:3V3", "red", [] ],
[ "rtc1:GND", "pico:GND", "black", [] ]
]
}
💡 Tip: DS3231 주소(0x68)와 DS1307 주소가 동일하게 0x68이기 때문에 DS3231_ADDR = 0x68 코드는 수정 없이 그대로 사용할 수 있습니다. 정확한 부품 이름과 속성은 Wokwi 편집기의 부품 검색창에서 다시 확인하는 것을 권장합니다.