콘텐츠로 이동

Chapter 05: 출력의 마법 - 세상에 표현하다

🎯 이 장에서 배우는 것

  • [ ] PWM의 개념과 듀티 사이클을 설명할 수 있다
  • [ ] LED의 밝기를 부드럽게 조절할 수 있다
  • [ ] RGB LED로 다양한 색상을 표현할 수 있다
  • [ ] 부저로 멜로디를 연주할 수 있다
  • [ ] 서보모터로 원하는 각도를 제어할 수 있다

💡 왜 이걸 배우나요?

지금까지 우리는 LED를 켜고 끄는 것만 했어. 마치 전등 스위치처럼 ON/OFF 두 가지 상태만 있었지.

그런데 생각해봐. 선풍기는 약/중/강 세기가 있고, 방 조명은 밝기 조절이 되고, 스마트폰 화면은 수백만 가지 색을 표현해. 이런 건 어떻게 하는 걸까?

우리가 지금까지 한 것:     오늘 배울 것:

    ⬛⬜                    ⬛🟨🟧🟧🟧🟧🟧⬜
    OFF ON                 어둡게 ←→ 밝게

    (2가지)                (무한한 단계!)

오늘 배우는 PWM이라는 기술은 이 마법의 핵심이야. 이걸 이해하면: - 🌈 RGB LED로 무지개 색을 만들고 - 🎵 부저로 "학교 종" 멜로디를 연주하고
- 🤖 서보모터로 로봇 팔처럼 각도를 조절할 수 있어!


📚 핵심 개념

개념 1: PWM (Pulse Width Modulation)

비유로 시작: PWM은 마치 눈 깜빡이기와 같아요.

방 전등을 생각해봐. 전등은 켜거나 끄는 것밖에 안 되잖아. 그런데 만약 1초에 100번씩 엄청 빠르게 껐다 켰다 반복하면 어떻게 보일까?

우리 눈이 그 속도를 따라가지 못해서 중간 밝기로 보여! 이게 PWM의 원리야.

정확히 말하면: PWM은 디지털 신호를 빠르게 ON/OFF 반복해서 아날로그처럼 중간 값을 만드는 기술이야. 한 주기에서 ON 상태인 비율을 듀티 사이클(Duty Cycle)이라고 해.

예시로 확인:

flowchart TB subgraph 듀티사이클 ["⚡ 듀티 사이클 비교"] direction LR A["25% 듀티<br/>어두운 LED 💡"] B["50% 듀티<br/>중간 밝기 💡💡"] C["75% 듀티<br/>밝은 LED 💡💡💡"] end style A fill:#fff9c4,stroke:#f9a825 style B fill:#ffcc80,stroke:#ef6c00 style C fill:#ffab91,stroke:#e64a19
25% 듀티 사이클 (어두움):
┌─┐   ┌─┐   ┌─┐   ┌─┐
│█│   │█│   │█│   │█│    ← 25%만 켜짐
└─┴───┴─┴───┴─┴───┴─┘

50% 듀티 사이클 (중간):
┌──┐  ┌──┐  ┌──┐  ┌──┐
│██│  │██│  │██│  │██│   ← 50% 켜짐
└──┴──┴──┴──┴──┴──┴──┘

75% 듀티 사이클 (밝음):
┌───┐ ┌───┐ ┌───┐ ┌───┐
│███│ │███│ │███│ │███│  ← 75% 켜짐
└───┴─┴───┴─┴───┴─┴───┘

쉽게 말하면: PWM은 "엄청 빠르게 깜빡여서 눈속임하기"야. 깜빡이는 비율이 듀티 사이클!


개념 2: RGB LED의 색 혼합

비유로 시작: RGB LED는 마치 물감 섞기와 같아요. (정확히는 빛의 삼원색!)

정확히 말하면: RGB LED 안에는 빨강(R), 초록(G), 파랑(B) 3개의 LED가 들어있어. 각각의 밝기를 조절하면 1600만 가지 이상의 색을 만들 수 있어.

flowchart LR subgraph RGB ["🎨 RGB 색 혼합"] R["🔴 Red<br/>빨강"] G["🟢 Green<br/>초록"] B["🔵 Blue<br/>파랑"] end R --> Y["🟡 Yellow<br/>R+G"] G --> Y R --> M["🟣 Magenta<br/>R+B"] B --> M G --> C["🩵 Cyan<br/>G+B"] B --> C Y --> W["⚪ White<br/>R+G+B"] M --> W C --> W style R fill:#ffcdd2,stroke:#c62828 style G fill:#c8e6c9,stroke:#2e7d32 style B fill:#bbdefb,stroke:#1565c0 style Y fill:#fff9c4,stroke:#f9a825 style M fill:#f3e5f5,stroke:#7b1fa2 style C fill:#e0f7fa,stroke:#00838f style W fill:#fafafa,stroke:#616161

예시로 확인: | 색상 | R | G | B | 결과 | |------|---|---|---|------| | 빨강 | 255 | 0 | 0 | 🔴 | | 초록 | 0 | 255 | 0 | 🟢 | | 파랑 | 0 | 0 | 255 | 🔵 | | 노랑 | 255 | 255 | 0 | 🟡 | | 흰색 | 255 | 255 | 255 | ⚪ | | 주황 | 255 | 128 | 0 | 🟠 |

쉽게 말하면: RGB LED는 "3가지 색 LED를 섞어서 모든 색을 만드는 장치"야!


개념 3: 주파수와 소리

비유로 시작: 소리의 높낮이는 마치 줄넘기 속도와 같아요.

줄넘기를 천천히 돌리면 느릿느릿, 빨리 돌리면 휘잉~ 소리가 나잖아. 소리도 마찬가지야. 공기를 빠르게 진동시키면 높은 소리, 천천히 진동시키면 낮은 소리가 나.

정확히 말하면: 주파수(Hz)는 1초에 몇 번 진동하는지를 나타내. "도"는 262Hz(초당 262번), "솔"은 392Hz(초당 392번) 진동해.

예시로 확인:

낮은 도 (262Hz):
~~~~~~~~~~~~~~~~~~~~~  (1초에 262번)

높은 솔 (392Hz):  
~~~~~~~~~~~~~~~~~~~~~~~~~~~~  (1초에 392번, 더 빽빽)

음계 주파수(Hz)
262
294
330
349
392
440
494
높은 도 523

쉽게 말하면: 주파수가 높으면 높은 소리, 낮으면 낮은 소리!


개념 4: 서보모터의 각도 제어

비유로 시작: 서보모터는 마치 시계 바늘과 같아요.

시계 바늘이 정확히 3시를 가리키는 것처럼, 서보모터는 "90도로 가!" 하면 정확히 90도 위치로 이동해.

정확히 말하면: 서보모터는 PWM 신호의 펄스 폭에 따라 0~180도 사이의 각도로 회전해. 1ms 펄스면 0도, 2ms 펄스면 180도야.

flowchart LR subgraph SERVO ["🔧 서보모터 각도"] direction TB S0["1.0ms 펄스<br/>➡️ 0°"] S90["1.5ms 펄스<br/>➡️ 90°"] S180["2.0ms 펄스<br/>➡️ 180°"] end style S0 fill:#e3f2fd,stroke:#1976d2 style S90 fill:#fff3e0,stroke:#f57c00 style S180 fill:#fce4ec,stroke:#c2185b
펄스 폭에 따른 각도:

1.0ms → 0°    (완전히 왼쪽)
 ┌─┐
─┘ └────────────────

1.5ms → 90°   (중앙)
 ┌──┐
─┘  └───────────────

2.0ms → 180°  (완전히 오른쪽)
 ┌───┐
─┘   └──────────────

쉽게 말하면: 서보모터는 "몇 도로 가!" 하면 정확히 그 각도로 가는 모터야!


🔨 따라하기

Step 1: PWM으로 LED 밝기 조절하기

목표: 내장 LED의 밝기를 점점 밝게, 점점 어둡게 만들기

회로: 이번 단계는 피코 내장 LED를 사용하므로 추가 연결 없음!

코드:

# === WHAT: LED 밝기를 부드럽게 조절하는 코드 ===
# PWM을 사용해서 LED를 숨쉬듯이 밝아졌다 어두워졌다 하게 만들어요

# --- WHY: 왜 필요한지 ---
# 디지털 핀은 ON/OFF만 되는데, PWM을 쓰면 중간 밝기를 만들 수 있어요
# 이게 바로 스마트폰 화면 밝기 조절의 원리!

# HOW: 어떻게 동작하는지
from machine import Pin, PWM  # Pin과 PWM 기능 가져오기
import time                    # 시간 관련 기능

# 내장 LED를 PWM 모드로 설정
led_pwm = PWM(Pin("LED"))  # "LED"는 피코의 내장 LED
led_pwm.freq(1000)         # 주파수 1000Hz (1초에 1000번 깜빡임)

print("LED 밝기 조절 시작!")

# 점점 밝아지기
print("밝아지는 중...")
for brightness in range(0, 65536, 1000):  # 0부터 65535까지 1000씩 증가
    led_pwm.duty_u16(brightness)  # 밝기 설정 (0~65535)
    time.sleep(0.02)              # 0.02초 대기

# 점점 어두워지기  
print("어두워지는 중...")
for brightness in range(65535, -1, -1000):  # 65535부터 0까지 1000씩 감소
    led_pwm.duty_u16(brightness)
    time.sleep(0.02)

print("완료!")
led_pwm.deinit()  # PWM 해제

실행 결과:

LED 밝기 조절 시작!
밝아지는 중...
어두워지는 중...
완료!
👁️ LED가 부드럽게 밝아졌다가 어두워지는 것을 볼 수 있어!

여기서 잠깐! 🤔

duty_u16()의 값 범위가 왜 0~65535일까? - 16비트 = 2¹⁶ = 65536가지 단계 - 0 = 0% (완전 꺼짐) - 32767 = 50% (중간 밝기) - 65535 = 100% (최대 밝기)


Step 2: Grove RGB LED로 색 만들기

목표: RGB LED로 빨강, 초록, 파랑, 그리고 혼합색 만들기

회로 연결:

Grove RGB LED 연결 (D18 포트 사용)

Pico 2W                    Grove RGB LED
┌─────────────┐            ┌──────────────┐
│             │            │              │
│   3V3  ●────┼────────────┼─ VCC (빨강)  │
│   GND  ●────┼────────────┼─ GND (검정)  │
│  GP18  ●────┼────────────┼─ SIG (노랑)  │
│             │            │              │
└─────────────┘            └──────────────┘

💡 Grove 케이블로 D18 포트에 꽂으면 끝!

코드:

# === WHAT: RGB LED로 다양한 색을 표현하는 코드 ===
# WS2812 타입 RGB LED를 제어해서 무지개 색을 만들어요

# --- WHY: 왜 필요한지 ---
# 단색 LED는 한 가지 색만 나오지만
# RGB LED는 빨강+초록+파랑을 섞어서 모든 색을 만들 수 있어요

# HOW: 어떻게 동작하는지
from machine import Pin
from neopixel import NeoPixel  # RGB LED 제어 라이브러리
import time

# RGB LED 설정 (D18 포트, LED 1개)
rgb = NeoPixel(Pin(18), 1)  # GPIO 18번에 연결, 1개의 LED

# 색상 정의 (R, G, B) - 각각 0~255 범위
RED = (255, 0, 0)      # 빨강
GREEN = (0, 255, 0)    # 초록
BLUE = (0, 0, 255)     # 파랑
YELLOW = (255, 255, 0) # 노랑 (빨강+초록)
CYAN = (0, 255, 255)   # 청록 (초록+파랑)
MAGENTA = (255, 0, 255)# 자주 (빨강+파랑)
WHITE = (255, 255, 255)# 흰색 (전부 최대)
OFF = (0, 0, 0)        # 끄기

print("🌈 RGB LED 색상 테스트!")

# 색상 리스트
colors = [
    ("빨강", RED),
    ("초록", GREEN),
    ("파랑", BLUE),
    ("노랑", YELLOW),
    ("청록", CYAN),
    ("자주", MAGENTA),
    ("흰색", WHITE)
]

# 각 색상 표시
for name, color in colors:
    print(f"현재 색: {name}")
    rgb[0] = color  # 첫 번째 LED에 색상 설정
    rgb.write()     # LED에 적용!
    time.sleep(1)   # 1초 대기

# 끄기
rgb[0] = OFF
rgb.write()
print("완료!")

실행 결과:

🌈 RGB LED 색상 테스트!
현재 색: 빨강
현재 색: 초록
현재 색: 파랑
현재 색: 노랑
현재 색: 청록
현재 색: 자주
현재 색: 흰색
완료!

여기서 잠깐! 🤔

rgb.write()를 왜 매번 호출해야 할까? - rgb[0] = color는 메모리에 값만 저장하는 거야 - rgb.write()를 해야 실제로 LED에 신호가 전송돼! - 마치 문서 작성 후 "저장" 버튼을 눌러야 저장되는 것처럼


Step 3: 무지개 색 그라데이션 만들기

목표: 색이 부드럽게 변하는 무지개 효과 만들기

코드:

# === WHAT: 무지개 그라데이션을 만드는 코드 ===
# 색상 값을 조금씩 바꿔서 부드러운 색 변화를 만들어요

# --- WHY: 왜 필요한지 ---
# 색이 뚝뚝 끊기지 않고 부드럽게 변하면 더 예뻐요
# 게이밍 키보드, 무드등에서 많이 쓰이는 효과!

# HOW: 어떻게 동작하는지
from machine import Pin
from neopixel import NeoPixel
import time

rgb = NeoPixel(Pin(18), 1)

def wheel(pos):
    """색상환에서 0~255 위치의 색을 반환하는 함수"""
    # 0~255 값을 받아서 무지개 색을 만들어요
    if pos < 85:
        # 빨강 → 초록
        return (255 - pos * 3, pos * 3, 0)
    elif pos < 170:
        # 초록 → 파랑
        pos -= 85
        return (0, 255 - pos * 3, pos * 3)
    else:
        # 파랑 → 빨강
        pos -= 170
        return (pos * 3, 0, 255 - pos * 3)

print("🌈 무지개 그라데이션 시작!")
print("Ctrl+C로 종료하세요")

try:
    while True:
        # 0부터 255까지 색상 변화
        for i in range(256):
            rgb[0] = wheel(i)
            rgb.write()
            time.sleep(0.02)  # 부드러운 변화를 위해 짧은 딜레이

except KeyboardInterrupt:
    # Ctrl+C를 누르면 종료
    rgb[0] = (0, 0, 0)
    rgb.write()
    print("\n종료!")

실행 결과:

🌈 무지개 그라데이션 시작!
Ctrl+C로 종료하세요
👁️ LED가 빨강→주황→노랑→초록→파랑→보라→빨강... 무한 반복!


Step 4: Grove 부저로 소리 내기

목표: 부저로 "도레미파솔라시도" 연주하기

회로 연결:

Grove Buzzer 연결 (D20 포트 사용)

Pico 2W                    Grove Buzzer
┌─────────────┐            ┌──────────────┐
│             │            │   🔊         │
│   3V3  ●────┼────────────┼─ VCC (빨강)  │
│   GND  ●────┼────────────┼─ GND (검정)  │
│  GP20  ●────┼────────────┼─ SIG (노랑)  │
│             │            │              │
└─────────────┘            └──────────────┘

💡 Grove 케이블로 D20 포트에 연결!

코드:

# === WHAT: 부저로 음계를 연주하는 코드 ===
# PWM 주파수를 바꿔서 도레미파솔라시도를 연주해요

# --- WHY: 왜 필요한지 ---
# 부저는 전기 신호를 소리로 바꿔주는 장치예요
# 알람, 효과음, 간단한 멜로디를 만들 수 있어요

# HOW: 어떻게 동작하는지
from machine import Pin, PWM
import time

# 부저를 PWM 모드로 설정 (D20 포트)
buzzer = PWM(Pin(20))

# 음계별 주파수 정의 (Hz)
NOTES = {
    'C4': 262,   # 도
    'D4': 294,   # 레
    'E4': 330,   # 미
    'F4': 349,   # 파
    'G4': 392,   # 솔
    'A4': 440,   # 라
    'B4': 494,   # 시
    'C5': 523,   # 높은 도
    'REST': 0    # 쉼표 (무음)
}

def play_tone(frequency, duration):
    """특정 주파수의 소리를 duration 초 동안 재생"""
    if frequency == 0:
        # 쉼표면 소리 끄기
        buzzer.duty_u16(0)
    else:
        buzzer.freq(frequency)    # 주파수 설정
        buzzer.duty_u16(32768)    # 50% 듀티 (소리 크기)
    time.sleep(duration)
    buzzer.duty_u16(0)            # 소리 끄기
    time.sleep(0.05)              # 음 사이 짧은 간격

print("🎵 도레미파솔라시도 연주!")

# 음계 연주
scale = ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5']

for note in scale:
    print(f"♪ {note}")
    play_tone(NOTES[note], 0.3)  # 0.3초씩 연주

print("🎵 역순으로!")
for note in reversed(scale):
    print(f"♪ {note}")
    play_tone(NOTES[note], 0.3)

buzzer.deinit()  # PWM 해제
print("연주 완료!")

실행 결과:

🎵 도레미파솔라시도 연주!
♪ C4
♪ D4
♪ E4
♪ F4
♪ G4
♪ A4
♪ B4
♪ C5
🎵 역순으로!
♪ C5
♪ B4
...
연주 완료!

여기서 잠깐! 🤔

duty_u16(32768)은 뭘까? - 32768은 65535의 절반, 즉 50% 듀티 사이클이야 - 부저는 ON/OFF 비율이 50%일 때 가장 깨끗한 소리가 나! - 값을 바꾸면 소리 크기가 변해 (0이면 무음)


Step 5: 학교 종 멜로디 연주하기

목표: "학교 종이 땡땡땡" 멜로디 완성하기

코드:

# === WHAT: "학교 종" 멜로디를 연주하는 코드 ===
# 음계와 박자를 조합해서 실제 노래를 연주해요

# --- WHY: 왜 필요한지 ---
# 단순 음계보다 실제 노래를 연주하면 더 재미있어요!
# 알람 시계, 도어벨, 게임 효과음 등에 활용 가능

# HOW: 어떻게 동작하는지
from machine import Pin, PWM
import time

buzzer = PWM(Pin(20))

# 음계 주파수
NOTES = {
    'C4': 262, 'D4': 294, 'E4': 330, 'F4': 349,
    'G4': 392, 'A4': 440, 'B4': 494, 'C5': 523,
    'REST': 0
}

def play_tone(frequency, duration):
    if frequency == 0:
        buzzer.duty_u16(0)
    else:
        buzzer.freq(frequency)
        buzzer.duty_u16(32768)
    time.sleep(duration)
    buzzer.duty_u16(0)
    time.sleep(0.03)

# 🎵 학교 종이 땡땡땡 악보
# (음, 박자) 형식 - 박자 1 = 0.4초
school_bell = [
    ('G4', 1), ('G4', 1), ('A4', 1), ('A4', 1),  # 학교 종이
    ('G4', 1), ('G4', 1), ('E4', 2),              # 땡땡땡
    ('G4', 1), ('G4', 1), ('E4', 1), ('E4', 1),  # 어서 모이
    ('D4', 2), ('REST', 1),                       # 자~
    ('G4', 1), ('G4', 1), ('A4', 1), ('A4', 1),  # 선생님이
    ('G4', 1), ('G4', 1), ('E4', 2),              # 우리를
    ('G4', 1), ('E4', 1), ('D4', 1), ('E4', 1),  # 기다리신
    ('C4', 2),                                    # 다
]

print("🔔 학교 종이 땡땡땡 연주 시작!")
print()

beat = 0.4  # 한 박자 = 0.4초

for note, duration in school_bell:
    play_tone(NOTES[note], beat * duration)

buzzer.deinit()
print("🎵 연주 완료!")

실행 결과:

🔔 학교 종이 땡땡땡 연주 시작!

🎵 연주 완료!
🔊 익숙한 멜로디가 들려!


Step 6: Grove 서보모터 각도 제어하기

목표: 서보모터를 0도, 90도, 180도로 움직이기

회로 연결:

Grove Servo 연결 (D16 포트 사용)

Pico 2W                    Grove Servo
┌─────────────┐            ┌──────────────┐
│             │            │    🔧        │
│   3V3  ●────┼────────────┼─ VCC (빨강)  │
│   GND  ●────┼────────────┼─ GND (검정)  │
│  GP16  ●────┼────────────┼─ SIG (노랑)  │
│             │            │              │
└─────────────┘            └──────────────┘

💡 Grove 케이블로 D16 포트에 연결!
⚠️ 서보모터는 전류를 많이 먹어서 USB 전원으로 1-2개까지만!

코드:

# === WHAT: 서보모터 각도를 제어하는 코드 ===
# PWM 신호의 펄스 폭을 조절해서 0~180도 회전시켜요

# --- WHY: 왜 필요한지 ---
# 서보모터는 정확한 각도 제어가 가능해서
# 로봇 팔, 자동문, RC카 조향 등에 사용돼요

# HOW: 어떻게 동작하는지
from machine import Pin, PWM
import time

# 서보모터 PWM 설정 (D16 포트)
servo = PWM(Pin(16))
servo.freq(50)  # 서보모터는 50Hz (1초에 50번)

def set_angle(angle):
    """0~180도 각도를 설정하는 함수"""
    # 서보모터는 펄스 폭으로 각도 결정
    # 0도 = 0.5ms, 90도 = 1.5ms, 180도 = 2.5ms
    # 50Hz에서 한 주기 = 20ms = 20000us

    # 각도를 듀티 사이클로 변환
    # 0도 → 500us/20000us = 2.5% → 1638 (65535의 2.5%)
    # 180도 → 2500us/20000us = 12.5% → 8192 (65535의 12.5%)
    min_duty = 1638   # 0도 (약 0.5ms)
    max_duty = 8192   # 180도 (약 2.5ms)

    duty = int(min_duty + (max_duty - min_duty) * angle / 180)
    servo.duty_u16(duty)

print("🔧 서보모터 테스트!")

# 기본 위치로
print("90도 (중앙)")
set_angle(90)
time.sleep(1)

# 0도로
print("0도 (왼쪽)")
set_angle(0)
time.sleep(1)

# 180도로
print("180도 (오른쪽)")
set_angle(180)
time.sleep(1)

# 다시 중앙으로
print("90도 (중앙)")
set_angle(90)
time.sleep(1)

servo.deinit()
print("완료!")

실행 결과:

🔧 서보모터 테스트!
90도 (중앙)
0도 (왼쪽)
180도 (오른쪽)
90도 (중앙)
완료!
👁️ 서보모터가 왼쪽, 오른쪽, 중앙으로 움직이는 것을 볼 수 있어!


Step 7: 부드러운 서보모터 움직임

목표: 서보모터를 부드럽게 스윕하기

코드:

# === WHAT: 서보모터를 부드럽게 움직이는 코드 ===
# 각도를 조금씩 바꿔서 부드러운 움직임을 만들어요

# --- WHY: 왜 필요한지 ---
# 급격한 움직임보다 부드러운 움직임이 더 자연스럽고
# 서보모터에도 무리가 덜 가요

# HOW: 어떻게 동작하는지
from machine import Pin, PWM
import time

servo = PWM(Pin(16))
servo.freq(50)

def set_angle(angle):
    min_duty = 1638
    max_duty = 8192
    duty = int(min_duty + (max_duty - min_duty) * angle / 180)
    servo.duty_u16(duty)

print("🔧 부드러운 서보모터 스윕!")
print("Ctrl+C로 종료")

try:
    while True:
        # 0도에서 180도까지
        print("→ 오른쪽으로...")
        for angle in range(0, 181, 2):  # 2도씩 증가
            set_angle(angle)
            time.sleep(0.02)

        # 180도에서 0도까지
        print("← 왼쪽으로...")
        for angle in range(180, -1, -2):  # 2도씩 감소
            set_angle(angle)
            time.sleep(0.02)

except KeyboardInterrupt:
    set_angle(90)  # 중앙으로 복귀
    servo.deinit()
    print("\n종료!")


📝 전체 코드: 감정 표현 장치

센서 없이 버튼으로 감정을 선택하고, RGB LED + 부저 + 서보모터로 표현하는 장치!

# === 감정 표현 장치 ===
# 버튼을 누를 때마다 다른 감정을 RGB LED, 부저, 서보모터로 표현해요
# 복사해서 바로 실행 가능!

from machine import Pin, PWM
from neopixel import NeoPixel
import time

# === 하드웨어 설정 ===
button = Pin(20, Pin.IN, Pin.PULL_DOWN)  # D20에 버튼 (Grove 버튼이 없으면 주석처리)
rgb = NeoPixel(Pin(18), 1)  # D18에 RGB LED
buzzer = PWM(Pin(16))       # D16에 부저
# servo = PWM(Pin(16))      # 서보모터 사용 시 (부저와 같은 핀이면 번갈아 사용)

# === 음계 정의 ===
NOTES = {
    'C4': 262, 'D4': 294, 'E4': 330, 'F4': 349,
    'G4': 392, 'A4': 440, 'B4': 494, 'C5': 523
}

# === 감정 정의 ===
emotions = [
    {
        'name': '😊 행복',
        'color': (255, 255, 0),  # 노랑
        'melody': [('C5', 0.2), ('E4', 0.2), ('G4', 0.2), ('C5', 0.4)]
    },
    {
        'name': '😢 슬픔',
        'color': (0, 0, 255),    # 파랑
        'melody': [('E4', 0.4), ('D4', 0.4), ('C4', 0.6)]
    },
    {
        'name': '😠 화남',
        'color': (255, 0, 0),    # 빨강
        'melody': [('C4', 0.1), ('C4', 0.1), ('C4', 0.1)]
    },
    {
        'name': '😌 평화',
        'color': (0, 255, 100),  # 연두
        'melody': [('G4', 0.3), ('A4', 0.3), ('G4', 0.5)]
    }
]

def play_tone(freq, duration):
    """음 재생"""
    if freq > 0:
        buzzer.freq(freq)
        buzzer.duty_u16(32768)
    time.sleep(duration)
    buzzer.duty_u16(0)
    time.sleep(0.02)

def express_emotion(emotion):
    """감정 표현"""
    print(f"\n{emotion['name']}")

    # RGB LED 색상
    rgb[0] = emotion['color']
    rgb.write()

    # 멜로디 재생
    for note, duration in emotion['melody']:
        play_tone(NOTES.get(note, 0), duration)

    time.sleep(0.5)

    # LED 끄기
    rgb[0] = (0, 0, 0)
    rgb.write()

# === 메인 프로그램 ===
print("🎭 감정 표현 장치")
print("버튼을 누르면 감정이 바뀝니다!")
print("(버튼이 없으면 자동으로 순환)")
print("-" * 30)

current_emotion = 0

# 버튼이 있는 경우
if button:
    print("버튼을 눌러보세요!")
    while True:
        if button.value() == 1:
            express_emotion(emotions[current_emotion])
            current_emotion = (current_emotion + 1) % len(emotions)
            time.sleep(0.3)  # 디바운싱
        time.sleep(0.1)
else:
    # 버튼이 없으면 자동 순환
    print("자동 순환 모드")
    while True:
        express_emotion(emotions[current_emotion])
        current_emotion = (current_emotion + 1) % len(emotions)
        time.sleep(2)

⚠️ 자주 하는 실수

실수 1: PWM 주파수를 설정 안 함

증상: 서보모터가 떨리거나 이상하게 움직임 원인: 서보모터는 정확히 50Hz가 필요한데 기본값이 다를 수 있어 해결:

# 잘못된 코드
servo = PWM(Pin(16))
# 주파수 설정 안 함!

# 올바른 코드
servo = PWM(Pin(16))
servo.freq(50)  # 서보모터는 반드시 50Hz!


실수 2: rgb.write() 호출 안 함

증상: RGB LED 색상이 안 바뀜 원인: 값만 설정하고 실제 전송을 안 함 해결:

# 잘못된 코드
rgb[0] = (255, 0, 0)
# write() 안 함 - LED는 그대로!

# 올바른 코드
rgb[0] = (255, 0, 0)
rgb.write()  # 이걸 해야 LED가 바뀜!


실수 3: 부저 duty를 0으로 안 바꿈

증상: 소리가 계속 나거나 이상한 소음 원인: 음 사이에 소리를 끄지 않음 해결:

# 잘못된 코드
def play_tone(freq, duration):
    buzzer.freq(freq)
    buzzer.duty_u16(32768)
    time.sleep(duration)
    # 소리 안 끔!

# 올바른 코드
def play_tone(freq, duration):
    buzzer.freq(freq)
    buzzer.duty_u16(32768)
    time.sleep(duration)
    buzzer.duty_u16(0)  # 반드시 소리 끄기!


실수 4: 서보모터 각도 범위 초과

증상: 서보모터가 부들부들 떨리거나 "윙~" 소리 원인: 0~180도 범위를 벗어난 값 입력 해결:

# 잘못된 코드
set_angle(200)  # 180도 초과!
set_angle(-10)  # 0도 미만!

# 올바른 코드
def set_angle(angle):
    # 범위 제한 추가!
    angle = max(0, min(180, angle))
    # 이하 동일...


실수 5: NeoPixel 개수 잘못 설정

증상: LED가 안 켜지거나 이상한 색 원인: 연결된 LED 개수와 설정이 다름 해결:

# 잘못된 코드 (LED 1개인데 10개로 설정)
rgb = NeoPixel(Pin(18), 10)

# 올바른 코드 (실제 연결된 개수로!)
rgb = NeoPixel(Pin(18), 1)  # Grove RGB LED는 보통 1개


✅ 스스로 점검하기

1. PWM의 듀티 사이클이 50%라면 LED 밝기는 어느 정도일까?

a) 최대 밝기
b) 절반 밝기
c) 꺼진 상태
d) 25% 밝기

2. RGB LED로 노란색을 만들려면 어떤 값을 설정해야 할까?

a) (255, 255, 0)
b) (255, 0, 255)
c) (0, 255, 255)
d) (255, 255, 255)

3. 서보모터의 PWM 주파수는 보통 몇 Hz로 설정할까?

a) 1000Hz
b) 100Hz
c) 50Hz
d) 10Hz

4. "라" 음의 주파수는?

a) 262Hz
b) 392Hz
c) 440Hz
d) 523Hz

5. duty_u16(65535)는 몇 %의 듀티 사이클일까?

a) 0%
b) 50%
c) 75%
d) 100%

정답 확인 1. **b) 절반 밝기** - 듀티 사이클 50%는 절반의 시간만 켜지므로 절반 밝기 2. **a) (255, 255, 0)** - 빨강 + 초록 = 노랑 3. **c) 50Hz** - 서보모터의 표준 PWM 주파수 4. **c) 440Hz** - 라(A4)는 음악의 기준음으로 440Hz 5. **d) 100%** - 65535는 최대값이므로 100% 듀티 사이클

🚀 더 해보기

도전 1: 신호등 만들기 (쉬움)

RGB LED로 신호등을 만들어봐! 빨강 3초 → 노랑 1초 → 초록 3초 반복

# 힌트
colors = [(255,0,0), (255,255,0), (0,255,0)]  # 빨, 노, 초
times = [3, 1, 3]  # 각각의 지속 시간

도전 2: 피아노 키보드 (중간)

버튼 여러 개를 연결해서 각각 다른 음이 나는 피아노를 만들어봐! - 버튼 1 → 도 - 버튼 2 → 레 - 버튼 3 → 미 - ...

도전 3: 무드등 + BGM ⭐ (어려움)

RGB LED 무지개 그라데이션을 돌리면서 동시에 잔잔한 멜로디가 나오는 무드등을 만들어봐!

힌트: 한 번에 두 가지를 하려면 타이밍 조절이 중요해!

# 힌트: 멜로디를 잘게 쪼개서 색 변화 사이에 끼워넣기
for i in range(256):
    rgb[0] = wheel(i)
    rgb.write()
    # 여기서 아주 짧은 음을 재생
    if i % 32 == 0:  # 가끔씩만
        play_short_note()
    time.sleep(0.02)

🔗 다음 장으로

이번 장에서 배운 것: - ⚡ PWM: 빠르게 껐다 켜서 중간값 만들기 - 🌈 RGB LED: 세 가지 색을 섞어 모든 색 표현 - 🎵 부저: 주파수로 음높이 조절 - 🔧 서보모터: 펄스 폭으로 정확한 각도 제어

flowchart LR subgraph 이번장 ["📚 5장: 출력의 마법"] A["💡 PWM 원리"] B["🌈 RGB LED"] C["🎵 부저"] D["🔧 서보모터"] end subgraph 다음장 ["📚 6장: 조건과 반복"] E["🔄 if/elif/else"] F["♾️ while 루프"] G["🌙 자동 야간등"] end 이번장 --> 다음장 style A fill:#fff3e0,stroke:#f57c00 style B fill:#fce4ec,stroke:#c2185b style C fill:#e3f2fd,stroke:#1976d2 style D fill:#e8f5e9,stroke:#388e3c style E fill:#f3e5f5,stroke:#7b1fa2 style F fill:#e0f7fa,stroke:#00838f style G fill:#fff9c4,stroke:#f9a825

다음 장에서는 조건문과 반복문을 배워서, 센서 값에 따라 자동으로 반응하는 "자동 야간등"을 만들어볼 거야!

"밝으면 LED 끄고, 어두우면 LED 켜기" - 진짜 스마트한 장치의 시작이야! 🌙