Chapter 8. Neopixel(WS2812B) 화려한 조명 제어

단 한 가닥의 데이터 선으로 수많은 RGB LED를 개별 제어하는 기술을 배웁니다. Pico 2의 고속 PIO 기능을 활용해 부드러운 무지개 효과를 구현합니다.

Pico 2 W와 Neopixel LED 스트립 연결도 Pico의 GP22 데이터 핀을 Neopixel DIN에 연결하고, VSYS와 GND로 전원을 공급합니다. 제어 보드 주소 지정 RGB LED Pico 2 W Neopixel Controller 색상 데이터 1선 제어 GP22 DATA VSYS 5V GND WS2812B / Neopixel Strip DIN 5V GND 데이터 방향은 Pico GP22 -> LED DIN입니다. LED가 많아질수록 외부 5V 전원과 공통 GND가 중요합니다.

Neopixel: VCC→5V(VSYS), DIN→GP22, GND→GND. LED 개수가 많으면 별도 5V 전원을 사용하고 GND는 Pico와 공통으로 묶습니다.

8.1 준비물과 연결 핀

Neopixel은 데이터 선 하나로 여러 LED를 순서대로 제어합니다. 첫 번째 LED의 DIN 방향을 꼭 확인하고, 전류가 커지는 프로젝트에서는 밝기를 제한해서 시작하세요.

Neopixel 핀 Pico 2 W 핀 설명
DINGP22색상 데이터 입력
5V / VCCVSYS 또는 외부 5VLED 전원
GNDGND공통 기준 전압

8.2 WS2812B(Neopixel) 통신 원리 쉽게 이해하기

WS2812B는 하나의 데이터 선(DIN)만으로 수십~수백 개의 LED를 각각 다른 색으로 제어할 수 있는 "주소 지정형(addressable)" LED입니다. 초보자가 헷갈리기 쉬운 핵심 3가지를 짚고 넘어갑시다.

💡 ① 데이터가 체인처럼 전달됩니다

Pico가 GP22로 색상 데이터를 보내면, 첫 번째 LED가 자신의 몫(24비트)만 읽어서 저장하고, 나머지 데이터는 그대로 다음 LED로 흘려보냅니다. 이 과정이 줄줄이 반복되어 LED 여러 개를 한 가닥의 선으로 순서대로 제어할 수 있습니다.

💡 ② 색상 순서는 GRB (RGB 아님!)

WS2812B 칩은 내부적으로 초록(G) → 빨강(R) → 파랑(B) 순서로 24비트(각 8비트씩)의 데이터를 받습니다. 다행히 MicroPython의 neopixel 라이브러리가 이 변환을 자동으로 처리해 주기 때문에, 우리는 코드에서 항상 (R, G, B) 순서의 튜플만 신경 쓰면 됩니다. 예: np[0] = (255, 0, 0) → 빨간색.

💡 ③ 타이밍이 매우 중요합니다

WS2812B는 "0"과 "1"을 펄스의 길이(마이크로초 단위)로 구분합니다. 대략 0.4µs(짧은 펄스)면 0, 0.8µs(긴 펄스)면 1로 인식하는 방식입니다. 이 미세한 타이밍은 사람이 time.sleep()으로 직접 맞추기 불가능할 정도로 정밀해서, Pico 2(RP2350)는 PIO(Programmable I/O)라는 전용 하드웨어 회로를 이용해 CPU 부담 없이 정확한 타이밍을 자동 생성합니다. neopixel.NeoPixel() 객체를 만드는 순간 이 PIO 설정이 내부적으로 완료됩니다.

비트 "0"과 "1"의 펄스 길이 차이 비트 0 짧게 HIGH(~0.4µs) + 길게 LOW 비트 1 길게 HIGH(~0.8µs) + 짧게 LOW

이런 정밀 타이밍 생성을 Pico 2의 PIO 하드웨어가 자동으로 처리해 줍니다.

8.3 밝기 제한 무지개 효과 코드

import machine
import neopixel
import time

NUM_LEDS = 8
DATA_PIN = 22
BRIGHTNESS = 0.25

np = neopixel.NeoPixel(machine.Pin(DATA_PIN), NUM_LEDS)

def wheel(pos):
    if pos < 85:
        return (pos * 3, 255 - pos * 3, 0)
    elif pos < 170:
        pos -= 85
        return (255 - pos * 3, 0, pos * 3)
    else:
        pos -= 170
        return (0, pos * 3, 255 - pos * 3)

def dim(color, brightness=BRIGHTNESS):
    r, g, b = color
    return (int(r * brightness), int(g * brightness), int(b * brightness))

def clear():
    for i in range(NUM_LEDS):
        np[i] = (0, 0, 0)
    np.write()

def rainbow_cycle(delay=0.02):
    for offset in range(256):
        for i in range(NUM_LEDS):
            color_index = (i * 256 // NUM_LEDS + offset) & 255
            np[i] = dim(wheel(color_index))
        np.write()
        time.sleep(delay)

print("Neopixel 효과 시작...")
clear()

while True:
    rainbow_cycle()

8.6 동작 흐름

wheel() 함수는 0~255 값을 빨강, 초록, 파랑이 이어지는 색상으로 바꿉니다. 각 LED에 서로 다른 색상 인덱스를 주고, 반복할 때마다 인덱스를 조금씩 밀어주면 무지개가 흐르는 것처럼 보입니다. BRIGHTNESS 값은 전류를 줄이기 위한 안전 장치입니다.

⚠️ 전원 주의사항

Neopixel은 밝기가 밝을수록 많은 전류를 소비합니다. Pico의 3V3 핀 대신 VSYS(5V) 핀을 사용하세요. LED가 30개 이상이라면 5V 외부 전원 어댑터를 사용해야 보드가 손상되지 않습니다.

8.4 특정 LED만 색칠하기

전체 LED를 한 번에 다루기 전에, 인덱스 번호로 원하는 LED 딱 하나(혹은 몇 개)만 켜는 연습을 해봅시다. np[i]에서 i는 0번부터 시작하는 LED의 순서 번호입니다.

import machine
import neopixel
import time

NUM_LEDS = 8
DATA_PIN = 22

np = neopixel.NeoPixel(machine.Pin(DATA_PIN), NUM_LEDS)

# 먼저 전체 소등
for i in range(NUM_LEDS):
    np[i] = (0, 0, 0)
np.write()

# 0번 LED만 빨간색, 3번 LED만 초록색, 7번(마지막) LED만 파란색
np[0] = (50, 0, 0)     # 빨강 (밝기를 낮게 설정: 0~255 중 50)
np[3] = (0, 50, 0)     # 초록
np[7] = (0, 0, 50)     # 파랑
np.write()   # write()를 호출해야 실제로 LED에 반영됩니다!

print("0번, 3번, 7번 LED만 켜졌습니다.")

💡 np.write()를 꼭 호출해야 하는 이유

np[i] = (r, g, b)는 아직 메모리 상의 색상 값만 바꿔놓은 상태입니다. 실제로 LED에 데이터가 전송되어 화면에 반영되려면 반드시 np.write()를 호출해야 합니다. 초보자가 가장 자주 하는 실수가 이 write()를 빠뜨리는 것입니다!

8.5 밝기 조절하기

같은 색상이라도 RGB 숫자 값 자체를 낮추면 더 어둡게, 높이면 더 밝게 표현됩니다(0~255 범위). 아래 예제는 하나의 색상을 점점 밝게 켰다가 다시 어둡게 줄이는 "숨쉬기(breathing)" 효과입니다.

import machine
import neopixel
import time

NUM_LEDS = 8
DATA_PIN = 22

np = neopixel.NeoPixel(machine.Pin(DATA_PIN), NUM_LEDS)

def set_all(r, g, b):
    for i in range(NUM_LEDS):
        np[i] = (r, g, b)
    np.write()

print("밝기 조절(숨쉬기) 효과 시작...")
while True:
    # 0 -> 100까지 점점 밝게 (흰색 기준)
    for level in range(0, 101, 5):
        set_all(level, level, level)
        time.sleep(0.03)
    # 100 -> 0까지 점점 어둡게
    for level in range(100, -1, -5):
        set_all(level, level, level)
        time.sleep(0.03)

💡 밝기 조절 팁

모든 LED를 최대 밝기(255,255,255)로 오래 켜두면 전류 소모가 매우 커집니다. 실습 중에는 밝기 값을 100 이하로 제한하는 습관을 들이면 USB 전원만으로도 안전하게 실습할 수 있습니다.

8.7 과제 해결해보기!

과제 1. 짝수/홀수 LED 다른 색으로 켜기

8개의 LED 중에서 짝수 번째(0, 2, 4, 6)는 파란색, 홀수 번째(1, 3, 5, 7)는 노란색으로 켜는 코드를 작성해 보세요. (힌트: i % 2 연산을 활용하면 짝수/홀수를 구분할 수 있습니다.)

✅ 정답 보기
import machine
import neopixel

NUM_LEDS = 8
np = neopixel.NeoPixel(machine.Pin(22), NUM_LEDS)

for i in range(NUM_LEDS):
    if i % 2 == 0:
        np[i] = (0, 0, 50)     # 짝수 -> 파란색
    else:
        np[i] = (50, 50, 0)    # 홀수 -> 노란색(R+G)

np.write()

과제 2. 한 칸씩 이동하는 "달리는 불빛" 만들기

8개의 LED 중 단 하나의 LED만 흰색으로 켜지고, 그 위치가 0번 → 7번까지 한 칸씩 이동하다가 끝에 도달하면 다시 0번으로 돌아오는(무한 반복) 효과를 만들어 보세요.

  • 현재 켜진 LED를 제외한 나머지는 항상 꺼져 있어야 합니다.
  • 이동 속도는 time.sleep(0.15) 정도로 설정합니다.
💻 정답 코드 보기
import machine
import neopixel
import time

NUM_LEDS = 8
np = neopixel.NeoPixel(machine.Pin(22), NUM_LEDS)

def clear():
    for i in range(NUM_LEDS):
        np[i] = (0, 0, 0)

while True:
    for pos in range(NUM_LEDS):
        clear()                    # 모든 LED 끄기
        np[pos] = (60, 60, 60)     # 현재 위치만 흰색으로 켜기
        np.write()
        time.sleep(0.15)

8.8 Wokwi 시뮬레이션으로 실습하기

Chapter 1.8에서 소개한 Wokwi 온라인 시뮬레이터를 이용하면 실제 Neopixel LED 스트립이 없어도 브라우저에서 바로 색상 제어 코드를 테스트해 볼 수 있습니다.

💻 실습 순서

  1. wokwi.com에서 [New Project] → "Raspberry Pi Pico" (MicroPython) 템플릿을 선택합니다.
  2. 왼쪽 diagram.json 탭을 열고, 아래 제공된 코드를 그대로 붙여넣어 Pico와 Neopixel(8구 스트립)을 자동으로 연결합니다.
  3. 오른쪽 main.py 탭에 이번 챕터의 무지개 효과 코드(8.3) 또는 밝기 조절 코드(8.5)를 붙여넣습니다.
  4. 화면 상단의 ▶️ (녹색 재생 버튼)을 클릭하면 시뮬레이션이 시작되고, 8개의 LED가 실시간으로 색이 바뀌는 모습을 확인할 수 있습니다.
{
  "version": 1,
  "author": "Pico 2 IoT Guide",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-pi-pico", "id": "pico", "top": 0, "left": 0, "attrs": {} },
    { "type": "wokwi-neopixel", "id": "strip1", "top": -60, "left": 150, "attrs": { "pixels": "8" } }
  ],
  "connections": [
    [ "pico:GP22", "strip1:DIN", "green", [] ],
    [ "pico:VSYS", "strip1:VCC", "red", [] ],
    [ "pico:GND", "strip1:GND", "black", [] ]
  ]
}

⚠️ 참고 사항

Wokwi 에디터의 부품 이름이나 속성 키(pixels 등)는 버전 업데이트에 따라 달라질 수 있습니다. 위 diagram.json을 붙여넣었을 때 부품이 보이지 않는다면, 에디터 좌측의 부품 검색(parts search)창에서 "neopixel"을 직접 검색하여 이름을 확인하고 type 값을 알맞게 수정하세요.