Chapter 03: 버튼과 대화 - 입력 받기¶
🎯 이 장에서 배우는 것¶
- [ ] Pin.IN과 PULL_DOWN의 개념을 설명할 수 있다
- [ ] Grove 버튼의 상태를 읽을 수 있다
- [ ] if문으로 버튼에 따른 LED 제어를 구현할 수 있다
- [ ] 버튼 토글 기능을 만들 수 있다
- [ ] 디바운싱이 왜 필요한지 이해할 수 있다
💡 왜 이걸 배우나요?¶
지금까지 우리는 피코에게 일방적으로 명령만 내렸어. "LED 켜!", "1초 기다려!", "LED 꺼!" 이런 식으로.
그런데 생각해봐. 너희가 쓰는 모든 전자기기는 사용자의 입력을 받아서 동작하잖아?
- 🎮 게임 컨트롤러: 버튼을 누르면 캐릭터가 점프
- 💡 방 스위치: 누르면 불이 켜지고, 다시 누르면 꺼짐
- 📱 스마트폰: 터치하면 앱이 실행
입력 없이는 진정한 인터랙션이 없어!
이번 장에서는 피코가 "사용자가 버튼을 눌렀네? 그럼 이렇게 해야지!"라고 스스로 판단하게 만들 거야. 이게 바로 디지털 입력의 시작이야.
📚 핵심 개념¶
개념 1: 디지털 입력 (Digital Input)¶
비유로 시작: 디지털 입력은 마치 예/아니오 질문과 같아. "버튼 눌렸어?" → "응(1)" 또는 "아니(0)". 중간은 없어!
정확한 정의: 디지털 입력이란 0(LOW) 또는 1(HIGH) 두 가지 상태만 읽어오는 것이야. 피코는 핀에 전압이 걸려있는지(1) 아닌지(0)를 판단해.
예시로 확인:
버튼 안 눌림 → 전압 없음 → 0 (LOW)
버튼 눌림 → 전압 있음 → 1 (HIGH)
쉽게 말하면: 버튼이 눌렸는지 아닌지를 0과 1로 읽어오는 것!
개념 2: Pin.IN과 Pin.OUT의 차이¶
비유로 시작:
- Pin.OUT은 스피커야 - 피코가 바깥 세상에 말하는 것 (LED 켜기)
- Pin.IN은 마이크야 - 바깥 세상의 소리를 듣는 것 (버튼 읽기)
정확한 정의:
- Pin.OUT: 피코가 핀으로 전기 신호를 내보내는 모드
- Pin.IN: 피코가 핀에서 전기 신호를 읽어오는 모드
쉽게 말하면: OUT은 말하기, IN은 듣기!
개념 3: PULL_DOWN과 PULL_UP¶
비유로 시작: 버튼이 안 눌렸을 때, 핀은 붕 떠있는 상태가 돼. 마치 라디오 주파수를 안 맞췄을 때 지지직거리는 것처럼, 0인지 1인지 불안정해져.
정확한 정의:
- PULL_DOWN: 버튼이 안 눌렸을 때 핀을 0(LOW)으로 고정
- PULL_UP: 버튼이 안 눌렸을 때 핀을 1(HIGH)으로 고정
우리가 쓸 것: Grove 버튼은 PULL_DOWN을 써. - 안 누름 → 0 - 누름 → 1
이게 직관적이잖아! 누르면 1, 안 누르면 0.
쉽게 말하면: PULL_DOWN은 "기본값을 0으로 잡아줘"라는 설정!
개념 4: value() 메서드¶
비유로 시작: value()는 온도계를 읽는 것과 같아. 온도계를 보면 "지금 25도구나"라고 알 수 있듯이, value()를 호출하면 "지금 버튼이 눌렸구나(1)" 또는 "안 눌렸구나(0)"를 알 수 있어.
정확한 정의: value() 메서드는 핀의 현재 상태를 읽어서 0 또는 1을 반환해.
예시로 확인:
button = Pin(20, Pin.IN, Pin.PULL_DOWN)
state = button.value() # 0 또는 1 반환
print(state) # 버튼 안 눌림: 0, 눌림: 1
쉽게 말하면: "지금 버튼 눌렸어?" 하고 물어보는 함수!
🔨 따라하기¶
Step 1: Grove 버튼 연결하기¶
목표: Grove 버튼을 피코에 연결하기
준비물¶
- 라즈베리파이 피코 2W + Grove Shield
- Grove 버튼 모듈
- Grove 케이블
연결 방법¶
- Grove 케이블의 한쪽을 버튼 모듈에 연결
- 다른 쪽을 Grove Shield의 D20 포트에 연결
- 끝! Grove 시스템이라 배선 실수가 없어서 편하지? 😊
💡 왜 D20 포트? D는 Digital의 약자야. 20번 GPIO 핀에 연결된 디지털 포트라는 뜻이야.
Step 2: 버튼 상태 읽기¶
목표: 버튼이 눌렸는지 안 눌렸는지 확인하기
코드:
# === WHAT: 버튼의 상태를 읽어서 출력하는 코드 ===
# 버튼을 누르면 1, 떼면 0이 출력돼요
# --- WHY: 왜 필요한지 ---
# 버튼이 제대로 연결됐는지 확인하고,
# 디지털 입력이 어떻게 동작하는지 이해하기 위해서예요
# HOW: 어떻게 동작하는지
from machine import Pin # Pin 클래스 가져오기
import time # 시간 관련 기능
# 버튼을 입력 모드로 설정 (GP20, 입력, 풀다운)
button = Pin(20, Pin.IN, Pin.PULL_DOWN)
# 무한 반복
while True:
state = button.value() # 버튼 상태 읽기 (0 또는 1)
print("버튼 상태:", state) # 상태 출력
time.sleep(0.3) # 0.3초 대기 (너무 빠르면 읽기 힘들어서)
실행 결과:
버튼 상태: 0
버튼 상태: 0
버튼 상태: 1 ← 버튼 누른 순간!
버튼 상태: 1
버튼 상태: 0 ← 버튼 뗀 순간
버튼 상태: 0
여기서 잠깐! 🤔
코드를 자세히 보자:
button = Pin(20, Pin.IN, Pin.PULL_DOWN)
| 부분 | 의미 |
|---|---|
20 |
GPIO 20번 핀 사용 (D20 포트) |
Pin.IN |
입력 모드 (버튼 읽기) |
Pin.PULL_DOWN |
기본값을 0으로 설정 |
실험해보기: 버튼을 꾹 누르고 있으면 계속 1이 출력되는지 확인해봐!
Step 3: if문으로 버튼 반응하기¶
목표: 버튼을 누르면 "눌림!", 떼면 "안 눌림!" 출력하기
코드:
# === WHAT: 버튼 상태에 따라 다른 메시지를 출력하는 코드 ===
# if문을 사용해서 조건에 따라 다르게 반응해요
# --- WHY: 왜 필요한지 ---
# 0과 1만으로는 불친절하잖아?
# 사람이 이해하기 쉬운 메시지로 바꿔주면 좋으니까!
# HOW: 어떻게 동작하는지
from machine import Pin
import time
button = Pin(20, Pin.IN, Pin.PULL_DOWN)
while True:
if button.value() == 1: # 버튼이 눌렸으면
print("🔘 버튼 눌림!")
else: # 버튼이 안 눌렸으면
print("⚪ 버튼 안 눌림")
time.sleep(0.3)
실행 결과:
⚪ 버튼 안 눌림
⚪ 버튼 안 눌림
🔘 버튼 눌림!
🔘 버튼 눌림!
⚪ 버튼 안 눌림
여기서 잠깐! 🤔
if문의 구조를 다시 보자:
if 조건:
# 조건이 True일 때 실행
else:
# 조건이 False일 때 실행
button.value() == 1은 "버튼 값이 1이야?"라고 묻는 거야.
- 맞으면(True): "눌림!" 출력
- 틀리면(False): "안 눌림" 출력
Step 4: 버튼으로 LED 제어하기¶
목표: 버튼을 누르는 동안 LED가 켜지고, 떼면 꺼지기
추가 연결¶
- Grove 버튼 → D20 포트
- Grove LED → D16 포트
코드:
# === WHAT: 버튼을 누르면 LED가 켜지는 코드 ===
# 버튼을 누르고 있는 동안만 LED가 켜져요
# --- WHY: 왜 필요한지 ---
# 입력(버튼)과 출력(LED)을 연결하는 가장 기본적인 예제예요
# 이게 모든 인터랙티브 시스템의 기초야!
# HOW: 어떻게 동작하는지
from machine import Pin
import time
# 버튼 설정 (입력)
button = Pin(20, Pin.IN, Pin.PULL_DOWN)
# LED 설정 (출력)
led = Pin(16, Pin.OUT)
while True:
if button.value() == 1: # 버튼 눌림
led.on() # LED 켜기
print("💡 LED ON")
else: # 버튼 안 눌림
led.off() # LED 끄기
print("⬛ LED OFF")
time.sleep(0.1)
실행 결과:
⬛ LED OFF
⬛ LED OFF
💡 LED ON ← 버튼 누르는 순간, LED도 켜짐!
💡 LED ON
⬛ LED OFF ← 버튼 떼는 순간, LED도 꺼짐!
여기서 잠깐! 🤔
지금 만든 건 손전등 같은 거야! 버튼 누르고 있어야 불이 켜지지.
그런데 실제 방 스위치는 어때? 한 번 누르면 켜지고, 다시 누르면 꺼지잖아? 이걸 토글(Toggle) 기능이라고 해. 다음 단계에서 만들어보자!
Step 5: 토글 기능 만들기¶
목표: 버튼을 한 번 누르면 LED가 켜지고, 다시 누르면 꺼지기
핵심 아이디어:
코드:
# === WHAT: 버튼 토글로 LED를 제어하는 코드 ===
# 버튼을 누를 때마다 LED가 켜졌다 꺼졌다 해요
# --- WHY: 왜 필요한지 ---
# 실제 스위치처럼 동작하게 만들려면
# '상태'를 기억하고 있어야 해요
# HOW: 어떻게 동작하는지
from machine import Pin
import time
button = Pin(20, Pin.IN, Pin.PULL_DOWN)
led = Pin(16, Pin.OUT)
# LED 상태를 저장할 변수 (False = 꺼짐, True = 켜짐)
led_on = False
# 이전 버튼 상태 (버튼을 "새로 눌렀는지" 확인용)
last_button = 0
while True:
current_button = button.value() # 현재 버튼 상태
# 버튼이 "방금" 눌렸을 때만 반응 (0→1로 바뀔 때)
if current_button == 1 and last_button == 0:
led_on = not led_on # 상태 뒤집기 (True↔False)
if led_on:
led.on()
print("💡 LED 켜짐!")
else:
led.off()
print("⬛ LED 꺼짐!")
last_button = current_button # 현재 상태를 '이전 상태'로 저장
time.sleep(0.05) # 짧은 대기
실행 결과:
💡 LED 켜짐! ← 첫 번째 버튼 누름
⬛ LED 꺼짐! ← 두 번째 버튼 누름
💡 LED 켜짐! ← 세 번째 버튼 누름
⬛ LED 꺼짐! ← 네 번째 버튼 누름
여기서 잠깐! 🤔
이 코드의 핵심은 "버튼이 방금 눌렸는지" 감지하는 거야!
if current_button == 1 and last_button == 0:
| current_button | last_button | 의미 |
|---|---|---|
| 0 | 0 | 계속 안 누르고 있음 |
| 1 | 0 | 방금 눌렸다! ✅ |
| 1 | 1 | 계속 누르고 있음 |
| 0 | 1 | 방금 뗐다 |
우리가 원하는 건 "방금 눌렸을 때"만 반응하는 거니까, 1 and 0일 때만 토글해!
not 연산자:
led_on = not led_on
not True → False
- not False → True
즉, 상태를 뒤집어주는 거야!
Step 6: 디바운싱 이해하기¶
목표: 버튼 채터링 문제를 이해하고 해결하기
문제 상황¶
버튼을 한 번만 눌렀는데, LED가 여러 번 깜빡이는 경우가 있어. 왜 그럴까?
원인: 버튼 내부의 금속 접점이 닿을 때 바운싱(튕김) 현상이 발생해. 아주 짧은 시간(수 밀리초) 동안 접촉과 분리가 여러 번 반복돼.
해결 방법 (디바운싱): 버튼 상태가 바뀐 후 잠깐 대기했다가 다시 확인하면 돼!
코드:
# === WHAT: 디바운싱이 적용된 버튼 토글 코드 ===
# 채터링 문제를 해결해서 안정적으로 동작해요
# --- WHY: 왜 필요한지 ---
# 버튼을 눌렀을 때 한 번만 정확히 인식하려면
# 디바운싱이 필수예요!
# HOW: 어떻게 동작하는지
from machine import Pin
import time
button = Pin(20, Pin.IN, Pin.PULL_DOWN)
led = Pin(16, Pin.OUT)
led_on = False
last_button = 0
while True:
current_button = button.value()
if current_button == 1 and last_button == 0:
time.sleep(0.05) # ⭐ 50ms 대기 (디바운싱)
# 대기 후에도 여전히 눌려있는지 확인
if button.value() == 1:
led_on = not led_on
if led_on:
led.on()
print("💡 LED 켜짐!")
else:
led.off()
print("⬛ LED 꺼짐!")
# 버튼을 뗄 때까지 대기
while button.value() == 1:
time.sleep(0.01)
last_button = current_button
time.sleep(0.01)
여기서 잠깐! 🤔
디바운싱의 핵심은: 1. 버튼이 눌렸다고 감지되면 50ms 대기 2. 대기 후에도 여전히 눌려있으면 진짜 눌린 거로 판단 3. 버튼을 뗄 때까지 대기해서 중복 인식 방지
💡 50ms는 어떻게 정한 거야? 대부분의 버튼 바운싱은 10~50ms 내에 끝나. 50ms면 거의 모든 버튼에서 안정적이야!
📝 전체 코드¶
기본 버튼-LED 제어 (step2_button_led.py)¶
# === 버튼으로 LED 제어하기 ===
# 버튼을 누르고 있으면 LED가 켜지고, 떼면 꺼집니다.
#
# 연결:
# - Grove 버튼 → D20 포트
# - Grove LED → D16 포트
from machine import Pin
import time
# --- 핀 설정 ---
button = Pin(20, Pin.IN, Pin.PULL_DOWN) # 버튼: 입력, 풀다운
led = Pin(16, Pin.OUT) # LED: 출력
# --- 메인 루프 ---
while True:
if button.value() == 1: # 버튼 눌림
led.on()
else: # 버튼 안 눌림
led.off()
time.sleep(0.05)
토글 기능 + 디바운싱 (step4_debounce.py)¶
# === 디바운싱이 적용된 버튼 토글 ===
# 버튼을 누를 때마다 LED가 켜졌다 꺼졌다 합니다.
# 채터링 문제를 해결해서 안정적으로 동작합니다.
#
# 연결:
# - Grove 버튼 → D20 포트
# - Grove LED → D16 포트
from machine import Pin
import time
# --- 핀 설정 ---
button = Pin(20, Pin.IN, Pin.PULL_DOWN)
led = Pin(16, Pin.OUT)
# --- 상태 변수 ---
led_on = False # LED 상태 (False=꺼짐, True=켜짐)
last_button = 0 # 이전 버튼 상태
# --- 메인 루프 ---
while True:
current_button = button.value()
# 버튼이 방금 눌렸을 때 (0→1)
if current_button == 1 and last_button == 0:
time.sleep(0.05) # 디바운싱 대기
# 여전히 눌려있으면 진짜 눌린 것
if button.value() == 1:
led_on = not led_on # 상태 토글
if led_on:
led.on()
print("💡 LED 켜짐!")
else:
led.off()
print("⬛ LED 꺼짐!")
# 버튼 뗄 때까지 대기
while button.value() == 1:
time.sleep(0.01)
last_button = current_button
time.sleep(0.01)
⚠️ 자주 하는 실수¶
실수 1: Pin.OUT으로 버튼 설정하기¶
증상: 버튼을 눌러도 아무 반응이 없음
원인: 버튼은 입력 장치인데 출력 모드로 설정함
해결:
# ❌ 잘못된 코드
button = Pin(20, Pin.OUT) # 출력 모드 - 틀림!
# ✅ 올바른 코드
button = Pin(20, Pin.IN, Pin.PULL_DOWN) # 입력 모드 + 풀다운
실수 2: PULL_DOWN 빼먹기¶
증상: 버튼을 안 눌러도 값이 불안정하게 바뀜 (0과 1이 랜덤하게 출력)
원인: 풀업/풀다운 없이 핀이 플로팅(떠 있는) 상태
해결:
# ❌ 잘못된 코드
button = Pin(20, Pin.IN) # PULL_DOWN 없음!
# ✅ 올바른 코드
button = Pin(20, Pin.IN, Pin.PULL_DOWN) # 풀다운으로 안정화
실수 3: value() 괄호 빼먹기¶
증상: <bound_method> 같은 이상한 메시지가 출력됨
원인: value는 메서드(함수)인데 괄호 없이 호출함
해결:
# ❌ 잘못된 코드
state = button.value # 괄호 없음!
print(state) # <bound_method> 출력됨
# ✅ 올바른 코드
state = button.value() # 괄호 필수!
print(state) # 0 또는 1 출력됨
실수 4: 토글에서 이전 상태 업데이트 안 하기¶
증상: 버튼을 누르고 있으면 계속 토글됨 (깜빡깜빡)
원인: last_button을 업데이트하지 않아서 매 루프마다 "새로 눌림"으로 인식
해결:
# ❌ 잘못된 코드
while True:
if button.value() == 1 and last_button == 0:
led_on = not led_on
# last_button 업데이트 빼먹음!
time.sleep(0.05)
# ✅ 올바른 코드
while True:
current_button = button.value()
if current_button == 1 and last_button == 0:
led_on = not led_on
last_button = current_button # 반드시 업데이트!
time.sleep(0.05)
실수 5: GPIO 번호 vs 물리적 핀 번호 혼동¶
증상: 연결은 제대로 했는데 동작 안 함
원인: Grove Shield의 D20은 GPIO 20번을 의미해. 피코 보드의 물리적 핀 번호와 다름!
해결:
# Grove Shield D20 포트 사용 시
button = Pin(20, Pin.IN, Pin.PULL_DOWN) # GPIO 20
# Grove Shield D16 포트 사용 시
led = Pin(16, Pin.OUT) # GPIO 16
💡 기억하기: Grove Shield를 쓰면 포트 번호 = GPIO 번호야!
✅ 스스로 점검하기¶
질문 1: Pin.IN과 Pin.OUT의 차이는?¶
정답 확인
- **Pin.IN**: 외부 장치(버튼, 센서)에서 **신호를 읽어오는** 입력 모드 - **Pin.OUT**: 외부 장치(LED, 모터)에 **신호를 보내는** 출력 모드 버튼처럼 "눌렀는지 아닌지" 확인할 때는 `Pin.IN`, LED처럼 "켜라/꺼라" 명령할 때는 `Pin.OUT`을 써!질문 2: PULL_DOWN을 쓰면 버튼을 안 눌렀을 때 value()는?¶
정답 확인
**0**을 반환해! PULL_DOWN은 "기본값을 0(LOW)으로 고정"하는 설정이야. - 버튼 안 누름 → 0 - 버튼 누름 → 1 직관적이라서 기억하기 쉽지?질문 3: 토글 기능에서 last_button 변수는 왜 필요해?¶
정답 확인
버튼이 **"방금 눌렸는지"** 알기 위해서야! 버튼을 꾹 누르고 있으면 `value()`는 계속 1을 반환해. 만약 `last_button` 없이 "값이 1이면 토글"로 만들면, 누르고 있는 동안 계속 토글돼서 LED가 미친 듯이 깜빡여. `last_button`으로 **이전 상태**를 기억해서: - 이전: 0, 현재: 1 → 방금 눌렸다! ✅ 토글 - 이전: 1, 현재: 1 → 계속 누르고 있음, 무시질문 4: 디바운싱이 필요한 이유는?¶
정답 확인
버튼의 물리적인 **바운싱(채터링)** 현상 때문이야! 버튼 내부의 금속 접점이 닿을 때 **미세하게 튕기면서** 아주 짧은 시간 동안 여러 번 접촉/분리가 반복돼. 이걸 피코는 "여러 번 눌렸다"고 인식할 수 있어. **해결**: 버튼이 눌렸다고 감지되면 50ms 정도 기다렸다가 다시 확인!🚀 더 해보기¶
도전 1: 두 개의 버튼으로 LED 밝기 조절 (쉬움)¶
요구사항: 버튼 A를 누르면 밝아지고, 버튼 B를 누르면 어두워지는 LED - 힌트: PWM 사용 (2장 복습!) - 추가 연결: Grove 버튼 하나 더 (D18 포트)
# 힌트 코드
from machine import Pin, PWM
button_up = Pin(20, Pin.IN, Pin.PULL_DOWN)
button_down = Pin(18, Pin.IN, Pin.PULL_DOWN)
led_pwm = PWM(Pin(16))
led_pwm.freq(1000)
brightness = 32768 # 중간값으로 시작
도전 2: 버튼 길게 누르기 감지 (중간)¶
요구사항: - 짧게 누르면(0.5초 미만): LED 토글 - 길게 누르면(0.5초 이상): LED 깜빡이기 모드
# 힌트 코드
import time
press_start = 0 # 버튼 누른 시각
# 버튼 눌렸을 때 시작 시간 기록
if current_button == 1 and last_button == 0:
press_start = time.ticks_ms()
# 버튼 뗐을 때 얼마나 눌렸는지 계산
if current_button == 0 and last_button == 1:
duration = time.ticks_diff(time.ticks_ms(), press_start)
if duration < 500:
# 짧게 누름
else:
# 길게 누름
도전 3: 모스 부호 입력기 ⭐ (어려움)¶
요구사항: 버튼으로 모스 부호를 입력하면 알파벳으로 변환! - 짧게 누름: 점(.) - 길게 누름: 선(-) - 오래 안 누름: 문자 구분
모스 부호 예시:
A = .-
B = -...
S = ...
O = ---
SOS = ... --- ...
힌트:
morse_dict = {
'.-': 'A',
'-...': 'B',
'...': 'S',
'---': 'O',
# ... 더 추가
}
current_morse = "" # 현재 입력 중인 모스 부호
🔗 다음 장으로¶
이번 장에서 배운 것 ✅¶
- Pin.IN: 외부 신호를 읽어오는 입력 모드
- PULL_DOWN: 기본값을 0으로 고정하는 설정
- value(): 핀의 현재 상태(0 또는 1) 읽기
- if문: 조건에 따라 다른 동작 실행
- 토글: 상태를 뒤집는 기능 구현
- 디바운싱: 버튼 채터링 문제 해결
다음 장 미리보기 👀¶
Chapter 04: 센서 입문 - 세상을 느끼다
버튼은 0 또는 1, 두 가지 상태만 있었어. 하지만 세상의 대부분은 그렇게 딱 떨어지지 않아.
"지금 얼마나 밝아?" → 50? 1000? 30000? "온도가 몇 도야?" → 25.5도
이런 연속적인 값을 읽으려면 아날로그 입력이 필요해. 다음 장에서 조도 센서와 온습도 센서로 세상을 느껴보자! 🌡️💡