스위치를 이용한 3 Color LED 제어 > IoT 유무선 제어

본문 바로가기

[실습] 스위치를 이용한 3 Color LED 제어

필기자
2026-03-26 15:37 97 2
  • - 첨부파일 : KakaoTalk_20240223_175217786.mp4 (4.7M) - 다운로드

본문

스위치를 이용한 3 Color LED 제어

목 적
  • 라즈베리파이에 GPIO 이벤트 방식에 대해 이해한다.
  • 라즈베리파이에 복합적인 액츄레이터 모듈 구현에 대해 이해한다.
목 차
1. LED 버튼 앳츄레이터 묘듈 설치
2. LED 버튼 앳츄레이터 묘듈 구동

1. LED 버튼 앳츄레이터 뮤듈 설치
  • YwRobot LED 버튼 모듈
    • 내장된 LED를 가진 푸시버튼 스위치로 버튼을 누르면 LED가 켜지거나 꺼진다.
    • 학습, 실험, 프로토타이핑에 주로 사용되며, 마이크로컨트롤러 보드에 쉽게 연결할 수 있다.
    • 전원(VCC), 접지(GND), 신호(OUT) 등의 핀을 포함하며, 디지털 입력으로 버튼 상태를 마이크로컨트롤러가 읽는다.
    • 다양한 전자 프로젝트에 사용자 인터페이스 요소로 활용된다.
    • GPIO 연결
      • GND  : Ground 접지(핀 14)
      • VCC : 5V Power(핀 2) -> 수정 3.3V Power
      • OUT : GPIO 18(핀 12)
3529946169_7Fl2PytO_a1b2e946cf98576b7b8494fb433893df34d984af.png


3529946169_h4MWJUkq_c1a282beaa40cf30f1f2f507c4bfd07bbe77e75d.png

2. LED 버튼 앳츄레이터 묘듈 구동
  • 라즈베리파이 가상환경에서 파이썬 코딩(polling 기법)
    • VSCode에서 3_color_led_switch.py 파일 생성
    • GPIO 17, 27, 22 핀을 출력 모드로 설정, GPIO 18 핀을 입력 모드로 설정
    • GPIO 18 핀의 이벤트에 따라 LED 색상 변경
3529946169_AnCkK6JO_7358ace00fb67cf2433a472fe0b4c07b3f545b2f.png

import RPi.GPIO as GPIO
import time
# 핀 번호 설정
BUTTON_PIN = 18
BLUE_PIN = 17
GREEN_PIN = 22
RED_PIN = 27
# 글로벌 변수
led_state = 0
# GPIO 설정 함수
def setup_gpio():
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(BLUE_PIN, GPIO.OUT)
    GPIO.setup(GREEN_PIN, GPIO.OUT)
    GPIO.setup(RED_PIN, GPIO.OUT)
# 모든 LED 끄는 함수
def turn_off_leds():
    global led_state
    led_state = 0
    GPIO.output(BLUE_PIN, GPIO.LOW)
    GPIO.output(GREEN_PIN, GPIO.LOW)
    GPIO.output(RED_PIN, GPIO.LOW)
# LED 상태 변경 함수
def change_led_state():
    global led_state
    led_state += 1
    if led_state > 3:
        led_state = 1
    if led_state == 1:
        GPIO.output(BLUE_PIN, GPIO.HIGH)
        GPIO.output(GREEN_PIN, GPIO.LOW)
        GPIO.output(RED_PIN, GPIO.LOW)
    elif led_state == 2:
        GPIO.output(BLUE_PIN, GPIO.LOW)
        GPIO.output(GREEN_PIN, GPIO.HIGH)
        GPIO.output(RED_PIN, GPIO.LOW)
    elif led_state == 3:
        GPIO.output(BLUE_PIN, GPIO.LOW)
        GPIO.output(GREEN_PIN, GPIO.LOW)
        GPIO.output(RED_PIN, GPIO.HIGH)
# 버튼 처리 함수 (기존 callback 그대로 유지)
def button_callback(channel):
    start_time = time.time()
    # 버튼 눌린 상태 유지 확인
    while GPIO.input(channel) == GPIO.LOW:
        time.sleep(0.01)
    button_press_duration = time.time() - start_time
    if button_press_duration >= 1:
        turn_off_leds()
    else:
        change_led_state()
# GPIO 종료 함수
def cleanup_gpio():
    GPIO.cleanup()
# 메인 함수 (polling 적용)
def main():
    setup_gpio()
    turn_off_leds()
    prev_state = GPIO.input(BUTTON_PIN)
    try:
        while True:
            current_state = GPIO.input(BUTTON_PIN)
            # 버튼 눌림 감지 (HIGH → LOW)
            if prev_state == GPIO.HIGH and current_state == GPIO.LOW:
                button_callback(BUTTON_PIN)
            prev_state = current_state
            time.sleep(0.01)  # debounce + CPU 보호
    except KeyboardInterrupt:
        pass
    finally:
        cleanup_gpio()
# 실행
if __name__ == "__main__":
    main()
  • 메인 스레드 점유형 폴링 단점 (핵심 요약)
    • CPU 낭비: 입력 없어도 계속 루프 실행
    • 메인 스레드 블로킹: 다른 작업(웹/API) 병행 불가
    • 응답 지연: polling 간격만큼 처리 늦음
    • 이벤트 누락: 짧은 입력 신호 놓칠 수 있음
  • 라즈베리파이 가상환경에서 파이썬 코딩(event 기법)
    • gpio_patch.py 파일 생성
      • GPIO.add_event_detect() 버그로 오버라이딩 패치
      • 사용법
        • import RPi.GPIO as GPIO
        • import gpio_patch
        • GPIO.setwarnings(False)
    • 3_color_led_event_switch.py 파일 생성

#gpio_patch.py
import RPi.GPIO as GPIO
import time
import threading

_original_add_event_detect = GPIO.add_event_detect
_original_remove_event_detect = GPIO.remove_event_detect

# 공유 자원
_event_registry = {}
_prev_states = {}

_lock = threading.Lock()
_polling_thread = None
_running = False

# 설정값
POLL_INTERVAL = 0.005  # configurable

def _start_polling():
    global _polling_thread, _running

    if _polling_thread is not None:
        return

    _running = True

    def worker():
        while _running:
            with _lock:
                pins = list(_event_registry.keys())

            for pin in pins:
                try:
                    curr = GPIO.input(pin)
                except:
                    continue

                with _lock:
                    cfg = _event_registry.get(pin)
                    if cfg is None:
                        continue

                    prev = _prev_states.get(pin, curr)

                edge = cfg["edge"]
                callback = cfg["callback"]
                bouncetime = cfg["bouncetime"]

                trigger = False

                if edge == GPIO.FALLING and prev == 1 and curr == 0:
                    trigger = True
                elif edge == GPIO.RISING and prev == 0 and curr == 1:
                    trigger = True
                elif edge == GPIO.BOTH and prev != curr:
                    trigger = True

                now = time.time() * 1000

                if trigger:
                    with _lock:
                        last_time = cfg["last_time"]

                        if now - last_time >= bouncetime:
                            cfg["last_time"] = now

                            # callback은 별도 thread로 실행 (non-blocking)
                            if callback:
                                threading.Thread(
                                    target=_safe_callback,
                                    args=(callback, pin),
                                    daemon=True
                                ).start()

                with _lock:
                    _prev_states[pin] = curr

            time.sleep(POLL_INTERVAL)

    _polling_thread = threading.Thread(target=worker, daemon=True)
    _polling_thread.start()

def _safe_callback(callback, pin):
    try:
        callback(pin)
    except Exception as e:
        print(f"[ERROR] callback error (pin {pin}): {e}")

def _safe_add_event_detect(pin, edge, callback=None, bouncetime=200):
    # 기존 제거
    try:
        _original_remove_event_detect(pin)
    except:
        pass

    # 정상 시도
    try:
        _original_add_event_detect(pin, edge,
                                  callback=callback,
                                  bouncetime=bouncetime)
        return
    except RuntimeError:
        pass

    # fallback 등록
    print(f"[FALLBACK] pin {pin} polling 등록")

    with _lock:
        _event_registry[pin] = {
            "edge": edge,
            "callback": callback,
            "bouncetime": bouncetime,
            "last_time": 0
        }

        try:
            _prev_states[pin] = GPIO.input(pin)
        except:
            _prev_states[pin] = 1

    _start_polling()

def _safe_remove_event_detect(pin):
    # 원래 제거 시도
    try:
        _original_remove_event_detect(pin)
    except:
        pass

    # fallback 제거
    with _lock:
        if pin in _event_registry:
            del _event_registry[pin]
        if pin in _prev_states:
            del _prev_states[pin]

# override
GPIO.add_event_detect = _safe_add_event_detect
GPIO.remove_event_detect = _safe_remove_event_detect



# 3_color_led_event_switch.py
import RPi.GPIO as GPIO
import gpio_patch
GPIO.setwarnings(False)
import time

# 핀 번호 설정
BUTTON_PIN = 18  # 버튼이 연결될 GPIO 핀 번호
BLUE_PIN = 17
GREEN_PIN = 22
RED_PIN = 27

# 글로벌 변수
led_state = 0  # LED 상태 (0: 모두 꺼짐, 1: 파란색, 2: 초록색, 3: 빨간색)

# GPIO 설정 함수
def setup_gpio():
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(BLUE_PIN, GPIO.OUT)
    GPIO.setup(GREEN_PIN, GPIO.OUT)
    GPIO.setup(RED_PIN, GPIO.OUT)

# 모든 LED 끄는 함수
def turn_off_leds():
    global led_state
    led_state = 0
    GPIO.output(BLUE_PIN, GPIO.LOW)
    GPIO.output(GREEN_PIN, GPIO.LOW)
    GPIO.output(RED_PIN, GPIO.LOW)

# LED 상태 변경 함수
def change_led_state():
    global led_state
    led_state += 1
    if led_state > 3:
        led_state = 1
    if led_state == 1:
        GPIO.output(BLUE_PIN, GPIO.HIGH)
        GPIO.output(GREEN_PIN, GPIO.LOW)
        GPIO.output(RED_PIN, GPIO.LOW)
    elif led_state == 2:
        GPIO.output(BLUE_PIN, GPIO.LOW)
        GPIO.output(GREEN_PIN, GPIO.HIGH)
        GPIO.output(RED_PIN, GPIO.LOW)
    elif led_state == 3:
        GPIO.output(BLUE_PIN, GPIO.LOW)
        GPIO.output(GREEN_PIN, GPIO.LOW)
        GPIO.output(RED_PIN, GPIO.HIGH)

# 버튼 이벤트 콜백 함수
def button_callback(channel):
    start_time = time.time()

    # 버튼이 눌린 상태 유지 확인
    while GPIO.input(channel) == GPIO.LOW:
        # 누린 상태는 LOW, 뗀 상태는 HIGH
        # 버튼이 눌린 상태이면 무한반복 -> 버튼이 뗀 상태이면 반복문 빠짐
        # GPIO.FALLING 상태로 눌린 순간 이벤트가 발생하지만 GPIO.RISING 처럼 움직임(버튼을 뗀 순간 다음 로직 실행).
        time.sleep(0.01)  # 디바운싱 대기

    button_press_duration = time.time() - start_time
    if button_press_duration >= 1:  # 1초 이상 누르면 LED 끄기
        turn_off_leds()
    else:  # 짧게 누르면 LED 색상 변경
        change_led_state()

# GPIO 종료 함수
def cleanup_gpio():
    GPIO.cleanup()

# 메인 함수
def main():
    setup_gpio()
    # 프로그램 시작 시 모든 LED 끄기
    turn_off_leds()
    # 버튼 이벤트 감지 설정
    # GPIO.FALLING 는 누르는 순간(LOW) 이벤트 발생
    GPIO.add_event_detect(BUTTON_PIN, GPIO.FALLING, callback=button_callback, bouncetime=300)

    try:
        while True:
            time.sleep(0.1)
    except KeyboardInterrupt:
        pass
    finally:
        cleanup_gpio()

# 실행 진입점
if __name__ == "__main__":
    main()
  • GPIO.add_event_detect()
    • GPIO 핀에 이벤트 감지(callback) 기능을 추가하는 데 사용된다.
    • 특정 핀의 상태 변경(예: 라이징 엣지, 폴링 엣지)을 비동기적으로 감지한다.
    • 변경이 감지될 때마다 지정된 콜백 함수를 자동으로 호출할 수 있다. 
인자 이름 데이터 타입 기능
channel int 이벤트 감지를 추가할 GPIO 핀의 번호.
GPIO.RISING, GPIO.FALLING, GPIO.BOTH   감지할 이벤트의 종류를 지정. RISING은 0에서 1로의 변화, FALLING은 1에서 0으로의 변화, BOTH는 둘 다 감지.
callback function 이벤트가 감지될 때 호출될 콜백 함수.
bouncetime int 선택적. 이벤트 콜백 호출 사이에 적용될 디바운스(신호안정) 시간(밀리초 단위).
 
이벤트 감지 플레그 설명
GPIO.RISING GPIO 핀의 신호가 LOW에서 HIGH로 변할 때
GPIO.FALLING GPIO 핀의 신호가 HIGH에서 LOW로 변할 때
GPIO.BOTH GPIO 핀의 신호가 변화할 때(LOW에서 HIGH 또는 HIGH에서 LOW)
 
  • 3_color_led_switch.py 실행
    • VSCode > 하단 터미널 > python 3_color_led_switch.py 실행
3529946169_rZHVt4OA_54d6aefc63e041e73b09aa05e9d1b4f78453255b.png

3529946169_s8UR0Cn7_12f3cf21de2472a2ccc59aad8c4726f2e9367556.gif

[문제: RGB LED 색상 조합 제어 실습]
  • 하나의 공통형 RGB LED를 사용하여, 버튼을 누를 때마다 LED 색상이 아래 순서대로 변경되도록 하시오.
    • 3_color_led_switch_ranibow.py 파일 생성
    • 버튼 누를 때마다 색상 변경 순서
      • 1. 빨강
      • 2. 초록
      • 3. 파랑
      • 4. 노랑
      • 5. 하늘
      • 6. 보라
      • 7. 흰색
      • 8. OFF
※ 버튼은 짧게 누를 때만 반응, 길게 눌렀을 때는 무시.
※ 버튼은 GPIO.FALLING 이벤트로 감지.
※ LED 색의 정보는 배열을[0~7] 활용 함. 


import RPi.GPIO as GPIO
import time
# 핀 설정
BUTTON_PIN = 18
RED_PIN = 27
GREEN_PIN = 22
BLUE_PIN = 17

# 상태 변수
led_state = 0  # 0: OFF, 1~7: 색 조합
# GPIO 설정
def setup_gpio():
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(RED_PIN, GPIO.OUT, initial=GPIO.LOW)
    GPIO.setup(GREEN_PIN, GPIO.OUT, initial=GPIO.LOW)
    GPIO.setup(BLUE_PIN, GPIO.OUT, initial=GPIO.LOW)
# LED 색상 설정
def set_color(state):
    colors = (
        (0, 0, 0),  # off
        (1, 0, 0),  # 빨강
        (0, 1, 0),  # 초록
        (0, 0, 1),  # 파랑
        (1, 1, 0),  # 노랑
        (0, 1, 1),  # 하늘
        (1, 0, 1),  # 보라
        (1, 1, 1),  # 흰색
    )
    r, g, b = colors[state]
    #GPIO.output(RED_PIN, GPIO.HIGH if r else GPIO.LOW) # r == 1 ? GPIO.HIGH : GPIO.LOW
    #GPIO.output(GREEN_PIN, GPIO.HIGH if g else GPIO.LOW)
    #GPIO.output(BLUE_PIN, GPIO.HIGH if b else GPIO.LOW)
    #GPIO.output(RED_PIN, r) # r == 1 ? GPIO.HIGH : GPIO.LOW
    #GPIO.output(GREEN_PIN, g)
    #GPIO.output(BLUE_PIN, b)
    GPIO.output([RED_PIN, GREEN_PIN, BLUE_PIN], [r,g,b])

# 버튼 콜백
def button_callback(channel):
    global led_state
    start = time.time()
    while GPIO.input(BUTTON_PIN) == GPIO.LOW:
        time.sleep(0.01)
    duration = time.time() - start
    if duration < 1:
        led_state += 1
        if led_state > 7:
            led_state = 0
        set_color(led_state)
# 메인
def main():
    setup_gpio()
    GPIO.add_event_detect(BUTTON_PIN, GPIO.FALLING, callback=button_callback, bouncetime=300)
    try:
        while True:
            time.sleep(0.1)
    except KeyboardInterrupt:
        pass
    finally:
        GPIO.cleanup()
if __name__ == "__main__":
    main()

댓글목록2

필기자님의 댓글

필기자
2026-03-26 15:37
RPi.GPIO가 설치되지 않을때

cp -r /usr/lib/python3/dist-packages/RPi /home/pi/iot/iot/lib/python3.14/site-packages/
pip install lgpio
python -c "import RPi.GPIO as GPIO; print(GPIO.__file__)"

필기자님의 댓글

필기자
2026-03-26 15:37
사용법
import RPi.GPIO as GPIO
import gpio_patch
GPIO.setwarnings(False)


#gpio_patch.py
import RPi.GPIO as GPIO
import time
import threading

_original_add_event_detect = GPIO.add_event_detect

# 등록된 핀 관리
_event_registry = {}
_polling_thread = None
_running = False

def _start_polling():
    global _polling_thread, _running

    if _polling_thread is not None:
        return

    _running = True

    def worker():
        prev_states = {}

        # 초기 상태 저장
        for pin in _event_registry:
            prev_states[pin] = GPIO.input(pin)

        while _running:
            for pin, cfg in _event_registry.items():
                curr = GPIO.input(pin)
                prev = prev_states[pin]

                edge = cfg["edge"]
                callback = cfg["callback"]
                last_time = cfg["last_time"]
                bouncetime = cfg["bouncetime"]

                trigger = False

                if edge == GPIO.FALLING and prev == 1 and curr == 0:
                    trigger = True
                elif edge == GPIO.RISING and prev == 0 and curr == 1:
                    trigger = True
                elif edge == GPIO.BOTH and prev != curr:
                    trigger = True

                now = time.time() * 1000

                if trigger:
                    if now - last_time >= bouncetime:
                        cfg["last_time"] = now
                        if callback:
                            callback(pin)

                prev_states[pin] = curr

            time.sleep(0.005)  # 고속 polling

    _polling_thread = threading.Thread(target=worker, daemon=True)
    _polling_thread.start()

def _safe_add_event_detect(pin, edge, callback=None, bouncetime=200):
    # 기존 제거
    try:
        GPIO.remove_event_detect(pin)
    except:
        pass

    # 정상 시도
    try:
        _original_add_event_detect(pin, edge,
                                  callback=callback,
                                  bouncetime=bouncetime)
        return
    except RuntimeError:
        pass

    # fallback 등록
    print(f"[FALLBACK] pin {pin} polling 등록")

    _event_registry[pin] = {
        "edge": edge,
        "callback": callback,
        "bouncetime": bouncetime,
        "last_time": 0
    }

    _start_polling()

# override
GPIO.add_event_detect = _safe_add_event_detect
게시판 전체검색