라즈베리파이 GPIO 제어 – Python과 C 비교

라즈베리파이 4 이하에서 잘 동작하던 RPi.GPIO 기반 코드를 라즈베리파이 5에 그대로 올리면 GPIO 핀 제어가 먹통이 되는 경우가 많다. Pi 5부터는 GPIO·USB·이더넷·카메라 등 외부 인터페이스를 처리하는 RP1이라는 별도 I/O 칩이 도입됐는데, RPi.GPIO는 /dev/mem으로 메인 프로세서(BCM2712)의 레지스터를 직접 매핑하는 방식이라 PCIe로 연결된 RP1의 레지스터에는 애초에 접근할 수 없기 때문이다. 이 변화 때문에 파이썬/C 진영 모두 GPIO 제어 방식을 다시 정리할 필요가 생겼다. 이 글에서는 라즈베리파이 5 기준으로 파이썬(gpiozero/lgpio/rpi-lgpio)과 C(libgpiod)로 GPIO를 제어하는 방법을 비교한다.

왜 기존 코드가 깨지는가

RPi.GPIO, WiringPi 같은 오래된 라이브러리는 커널 GPIO 서브시스템을 거치지 않고 /dev/mem을 열어 물리 주소를 직접 매핑한 뒤 레지스터를 읽고 쓰는 방식으로 동작했다. Pi 4까지는 GPIO 컨트롤러가 SoC 안에 있어 이 방식이 통했지만, Pi 5는 GPIO 컨트롤러가 PCIe 너머의 RP1 칩으로 옮겨가면서 같은 방식으로는 레지스터에 도달할 수 없다. 커널은 RP1의 GPIO를 /dev/gpiochip4 캐릭터 디바이스로 노출하는데, 이 인터페이스를 지원하는 라이브러리만 Pi 5에서 정상 동작한다.

sysfs에서 캐릭터 디바이스로

리눅스 커널 4.8부터 /sys/class/gpio 기반 sysfs 인터페이스는 deprecated 상태이고, 표준 사용자 공간 인터페이스는 /dev/gpiochipN 캐릭터 디바이스와 그 위의 ioctl 기반 API다. libgpiod는 이 ioctl 호출을 감싸 사용하기 쉬운 형태로 제공하는 라이브러리이며, 리소스 해제가 파일 디스크립터를 닫는 시점에 보장되고 이벤트 폴링·복수 라인 동시 제어 같은 sysfs에는 없던 기능도 지원한다. Pi 5의 gpiozero/lgpio 계열 라이브러리도 결국 이 캐릭터 디바이스 인터페이스 위에서 동작한다.

Python으로 GPIO 제어

Pi 5에서 파이썬으로 GPIO를 다루는 방법은 세 갈래로 나뉜다.

gpiozero는 고수준 API로, LED·버튼 같은 부품 단위로 추상화돼 있어 코드가 가장 간결하다. 내부적으로 pin factory를 lgpio로 지정하면 Pi 5에서도 그대로 동작한다.

# pip install gpiozero lgpio
from gpiozero import LED
from time import sleep

led = LED(17)  # BCM 핀 번호 기준

while True:
    led.on()
    sleep(1)
    led.off()
    sleep(1)

lgpio는 캐릭터 디바이스를 직접 다루는 저수준 라이브러리다. gpiozero보다 코드는 늘어나지만 칩/라인 번호를 명시적으로 다루는 만큼 세밀한 제어가 가능하다.

# pip install lgpio
import lgpio
import time

CHIP = 4       # Pi 5의 RP1은 /dev/gpiochip4
LINE = 17

h = lgpio.gpiochip_open(CHIP)
lgpio.gpio_claim_output(h, LINE)

lgpio.gpio_write(h, LINE, 1)
time.sleep(1)
lgpio.gpio_write(h, LINE, 0)

lgpio.gpiochip_close(h)

기존에 RPi.GPIO로 짠 코드가 이미 많다면 rpi-lgpio를 쓰면 된다. RPi.GPIO와 동일한 API를 제공하는 드롭인 대체 패키지라, RPi.GPIO를 제거하고 이 패키지를 설치하면 import RPi.GPIO as GPIO로 시작하는 기존 코드를 한 줄도 고치지 않고 그대로 쓸 수 있다.

# pip uninstall RPi.GPIO
# pip install rpi-lgpio
import RPi.GPIO as GPIO  # 코드는 그대로, 내부 구현만 lgpio로 교체됨
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.OUT)

GPIO.output(17, GPIO.HIGH)
time.sleep(1)
GPIO.output(17, GPIO.LOW)

GPIO.cleanup()

C로 GPIO 제어 (libgpiod)

C에서는 libgpiod의 라인 리퀘스트 API를 쓴다. 칩을 열고, 라인 설정(방향 등)을 만들고, 그 설정으로 라인을 요청(request)한 뒤 값을 읽고 쓰는 흐름이다.

#include 
#include 
#include 

int main(void) {
    unsigned int offset = 17;

    struct gpiod_chip *chip = gpiod_chip_open("/dev/gpiochip4"); // Pi 5 RP1

    struct gpiod_line_settings *settings = gpiod_line_settings_new();
    gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_OUTPUT);

    struct gpiod_line_config *line_cfg = gpiod_line_config_new();
    gpiod_line_config_add_line_settings(line_cfg, &offset, 1, settings);

    struct gpiod_request_config *req_cfg = gpiod_request_config_new();
    gpiod_request_config_set_consumer(req_cfg, "blink-example");

    struct gpiod_line_request *request =
        gpiod_chip_request_lines(chip, req_cfg, line_cfg);

    gpiod_line_request_set_value(request, offset, GPIOD_LINE_VALUE_ACTIVE);
    sleep(1);
    gpiod_line_request_set_value(request, offset, GPIOD_LINE_VALUE_INACTIVE);

    gpiod_line_request_release(request);
    gpiod_line_config_free(line_cfg);
    gpiod_request_config_free(req_cfg);
    gpiod_line_settings_free(settings);
    gpiod_chip_close(chip);
    return 0;
}
# 빌드
gcc blink.c -o blink $(pkg-config --libs --cflags libgpiod)
sudo ./blink

Python과 C 비교

구분Python (gpiozero/lgpio)C (libgpiod)
개발 속도빠름 (특히 gpiozero)느림 (구조체/설정 객체 준비 필요)
실행 성능인터프리터 오버헤드 존재네이티브 코드, 지연시간에 민감한 제어에 유리
세밀한 제어lgpio로 어느 정도 가능ioctl 기반 API를 직접 다뤄 가장 세밀
적합한 사용처프로토타이핑, 스크립트, 저속 제어실시간성이 필요한 제어, 다른 C 코드베이스와 통합

주의사항

GPIO 칩 번호는 보드마다 다르다. Pi 5는 RP1이 /dev/gpiochip4로 잡히지만 Pi 4 이하에서는 SoC 내장 컨트롤러가 /dev/gpiochip0으로 잡히는 경우가 많다. 코드를 여러 세대의 보드에서 돌려야 한다면 칩 번호를 하드코딩하지 말고 gpiodetect 명령으로 실제 매핑을 확인한 뒤 설정값으로 분리하는 편이 안전하다.

일반 사용자로 /dev/gpiochip*에 접근하려면 gpio 그룹에 속해 있어야 한다. 권한 오류가 나면 sudo usermod -aG gpio $USER 후 재로그인해야 한다.

풀업/풀다운 저항 설정 방식도 라이브러리마다 API가 다르므로, 기존에 RPi.GPIO의 GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) 같은 코드를 lgpio 네이티브 API로 옮길 때는 옵션 이름이 그대로 대응되지 않는다는 점을 감안해야 한다. rpi-lgpio를 쓰면 이 부분까지 호환되므로, 마이그레이션 부담을 줄이고 싶다면 rpi-lgpio를 우선 검토하는 편이 낫다.

마무리

Pi 5의 RP1 도입은 GPIO 제어 방식을 sysfs/직접 레지스터 접근에서 캐릭터 디바이스 기반으로 옮기는 흐름을 강제한 계기가 됐다. 새로 프로토타이핑한다면 gpiozero로 시작해 필요할 때 lgpio로 내려가는 것이 가장 무난하고, 기존 RPi.GPIO 코드를 유지보수해야 한다면 rpi-lgpio로 갈아타는 것이 가장 적은 노력으로 Pi 5를 지원하는 방법이다. C로 실시간성이 필요한 제어를 해야 한다면 libgpiod의 라인 리퀘스트 API가 표준이다.

참고

답글 남기기