ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Embedded Swift: 핀(GPIO Pin) 제어하기 (1) - LED 깜빡이기
    공부/Swift(프로그래밍 언어) 2026. 4. 23. 20:26
    반응형

    GPIO(General Purpose Input/Output) 핀은 마이크로컨트롤러나 라즈베리 파이 같은 임베디드 시스템에서 외부 센서, LED, 모터 등과 디지털 신호(0 또는 1, High/Low)를 주고받는 범용 입출력 핀입니다. 프로그램 런타임에 입력(Input) 또는 출력(Output) 모드를 설정하여 제어할 수 있는 것이 특징입니다. 범용 입출력 핀, I/O 핀, 디지털 핀, GPIO 포트 라는 이름으로도 불립니다.

     

    주요 특징 및 기능

    • 범용성: 고정된 기능 대신 사용자가 상황에 따라 입력 또는 출력 핀으로 설정 가능.
    • 디지털 제어: 3.3V 또는 5.5V 의 논리 레벨(High/Low)로 동작.
    • 구성: 라즈베리 파이 40핀 등 보드 내외에 위치하며, 입력 시 신호 감지, 출력 시 기기 구동.

     

    활용 사례

    • 출력(Output): LED 온/오프 제어, 모터 드라이버 제어, 릴레이 스위칭, LCD 디스플레이 구동.
    • 입력(Input): 푸시 버튼 눌림 감지, 동작 센서(PIR) 신호 감지, 조도 센서 값 읽기.
    • 통신: I2C, SPI, UART 같은 통신 프로토콜 핀으로도 사용 가능. 

     

    이번 포스트에서는 단순 출력을 활용하여 LED를 깜빡이는 예제를 진행합니다.

     

    동작 원리

    • 핀 모드 설정: Embedded Swift 프로그래밍으로 핀을 INPUT 또는 OUTPUT으로 설정.
    • 출력: HIGH(1) 신호를 보내 전류를 공급하거나 LOW(0)로 접지(GND).
    • 입력: 외부의 전압 신호를 받아 0(Low) 혹은 1(High) 상태를 감지.

     


    방법

    마이크로컨트롤러(ESP32-C6)를 핀과 연결

    ESP32의 GPIO 핀 및 핀헤더


    기판의 가장자리에 있는 구멍이 GPIO 핀이며, 그 옆에 있는 연결 파트는 '핀 헤더'라고 합니다. 일반적으로 핀 헤더를 연결한 후, 납땜을 해서 고정시키는 것이 일반적입니다. 납땜을 하지 않으면 연결상태가 불안정해 동작이 제대로 되지 않을 수 있습니다. 테스트 목적이라면 납땜하지 않을 수도 있습니다.

    핀 헤더를 납땜한 모습

     

    전원 연결

    컨트롤러를 브레드보드에 꼽고, 핀 중 GND를 전원 레일의 (-)에 연결합니다. 핀 중 5V(또는 Vcc)를 전원 레일의 (+)에 연결합니다. 다음 브레드보드 또는 ESP32의 USB로 전원을 공급하면 컨트롤러의 전원이 켜져야 합니다.

    브레드보드에 꽂은 모습

    위의 사진을 보면 브레드보드를 꼽으면 남는 자리가 없습니다. 이처럼 기판 크기에 따라 브레드보드에 꼽을 자리가 하나도 없을 수 있습니다. 제 경우에는 공간 9칸을 차지하고 자리 하나가 남았기 때문에 그대로 진행합니다.

     

    내장 LED가 표시되도록 미리 빌드 및 플래시하고, 전원이 정상적으로 연결되었는지 확인합니다. (방법은 아래 포스트 링크 참조)

     

    Embedded Swift 설치, 코드 작성, ESP32-C6에서 빌드 및 작동시키는 방법

    맥북에 ESP32-C6 드라이버 및 필수 프로그램 설치 방법 [임베디드] ESP32-C6 맥북 등 macOS에서 빌드 및 동작 테스트 하기ESP32-C6 라는 임베디드 보드의 빌드 및 동작이 잘 되는지 'macOS' 에서 확인하는 방

    infoarmory.tistory.com

     

     

    컨트롤러 프로그래밍 및 빌드 & 플래시

    사전 설정을 통해 GPIO를 다룰 수 있는 라이브러리를 추가해야 합니다. BridgingHeader.h 파일을 열고 #include "driver/gpio.h"를 추가합니다.

    #ifndef BRIDGING_HEADER_H
    #define BRIDGING_HEADER_H
    
    #include <stdio.h>
    
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "led_strip.h"
    #include "sdkconfig.h"
    
    #include "driver/gpio.h" // 👈 추가
    
    // ... //
    
    #endif /* BRIDGING_HEADER_H */

     

    GPIO를 리셋하고 Input/Output 여부를 설정하는 것이 중요합니다. GPIOPin 이라는 이름의 구조체를 만들고 초기화 부분을 추가합니다.

    struct GPIOPin {
      let pinNumber: Int
    
      init(pinNumber: Int) {
        self.pinNumber = pinNumber
        gpio_reset_pin(gpio_num_t(Int32(pinNumber)))
        gpio_set_direction(gpio_num_t(Int32(pinNumber)), GPIO_MODE_OUTPUT)
      }
      // ...
    }
    • pinNumber: 사용할 핀의 번호로, 기판에 1, 2, 3... 등의 숫자로 적혀 있습니다.
    • gpio_reset_pin: 기존에 부여되어 있던 핀의 역할을 리셋합니다. 이 부분을 실행하지 않는 경우 이용할 수 있는 핀의 개수가 매우 적을 수 있습니다. 제 경우에는 리셋하지 않은 경우 2번 핀, 15번 핀 두개만이 원래부터 비어있던 핀이어서 사용할 수 있었습니다.
    • gpio_set_direction: Input/Output을 설정합니다.  이번 예제는 출력만 사용하므로 GPIO_MODE_OUTPUT을 사용합니다.

     

    다음 GPIO의 출력을 High/Low로 토글시키는 함수를 추가합니다. 

      func set(_ level: Bool) {
        gpio_set_level(gpio_num_t(Int32(pinNumber)), level ? 1 : 0)
      }
    
      func high() {
        set(true)
      }
    
      func low() {
        set(false)
      }
    • gpio_set_level: GPIO 의 레벨을 High 또는 Low로 설정합니다. 1을 입력하면 High, 0을 입력하면 Low가 됩니다.
      • high의 경우 (+) 레일과 연결한 것과 동일한 효과가 되고, low인 경우 접지에 연결한 것과 동일한 효과가 됩니다.

     

    이용할 핀 목록 작성

    struct GPIO {
      let pins: [GPIOPin]
    
      init(pinNumbers: [Int] = [2, 3, 4, 5, 6, 7, 10, 11, 15]) {
        self.pins = pinNumbers.map  { GPIOPin(pinNumber: $0) }
      }
    }
    • 편의상 구조체로 만들었으나 구조체 말고도 단순 배열 또는 단일 GPIOPin 오브젝트로도 추가할 수 있습니다.

     

    메인 프로그램 작성

    @_cdecl("app_main")
    func main() {
      ExamplePin.run()
    }
    func delay(ms: UInt32) {
      vTaskDelay(ms / (1000 / UInt32(configTICK_RATE_HZ)))
    }
    struct ExamplePin {
      static func run() {
        let strip = LedStrip(gpioPin: 8, maxLeds: 1)
        strip.setPixel(index: 0, color: .init(r: 2, g: 2, b: 0))
        strip.refresh()
    
        let gpio = GPIO() // 👈 이용 가능한 GPIO 목록 추가
    
        while true {
          gpio.pins.forEach { $0.high() } // 👈 GPIO를 전부 켜기
    
          strip.setPixel(index: 0, color: .init(r: UInt8.random(in: 0..<4), g: UInt8.random(in: 0..<4), b: UInt8.random(in: 0..<4)))
          strip.refresh()
          delay(ms: 1000) // 1초 딜레이
    
          gpio.pins.forEach { $0.low() } // 👈 GPIO를 전부 끄기
          strip.clear()
          delay(ms: 1000) // 1초 딜레이
        }
      }
    }
    • LED strip 부분은 내장 LED 테스트용 코드이기 때문에 이 예제와는 무관합니다.
      • 다만 LED 스트립이 핀 번호 8번을 점유하고 있기 때문에, 핀 8번은 외부에서 사용할 수 없다는 점은 참고해주세요
    • while 문
      • 이용 가능한 GPIO 목록을 forEach로 순회하며 1초간 high 상태로 만듭니다.
      • 다음 GPIO 목록을 다시 순회하며 1초간 low 상태로 만듭니다.
      • 이를 통해 핀 연결이 제대로 되었다면 외부 LED가 1초간 켰다 꺼짐을 반복하게 되고, 내장 LED도 동일하게 동작하도록 하였으므로 같은 타이밍에 불빛이 켜지고 꺼져야 합니다.

     

    빌드 & 플래시를 하여 컨트롤러를 업데이트합니다.

     

    LED 출력 확인

    이렇게 해서 프로그래밍 방식으로 핀에 high/low를 번갈아가면서 출력하도록 했다면, 이것을 외부 LED에 연결해서 실제로 핀 출력이 의도대로 되고 있는지 확인해야 합니다.

    원래 브레드보드에 단독으로 LED를 연결하는 방법에 알아보겠습니다.

     

    브레드보드 사용법 및 실습 예제: LED 1개, 버튼을 통해 LED 켜기

    브레드보드를 처음 쓰는 초보자 기준으로 가장 간단하면서 핵심 개념을 배우는 회로를 만들어 보는 방법을 설명하도록 하겠습니다. 브레드보드 구조브레드보드는 내부가 이렇게 연결되어 있습

    infoarmory.tistory.com

     

    (+) 전원 레일과 LED의 긴 다리 부분 (애노드)이 연결되어 있는데, 여기서 전원 레일에 연결된 부분을 빼고 이것을 ESP32의 핀에 연결합니다. 핀이 High 가 된 경우 (+) 레일에 연결한 것과 동일한 효과를 나타내므로 LED가 켜져야 합니다.

    동작할 것이라 예상되는 핀 중 하나인 15번 핀을 사용하여 점퍼선 등으로 LED 긴 다리 부분(애노드)와 연결합니다.

    전원을 넣어 불빛이 동기화가 되는지 확인합니다. 나머지 핀도 동일한 방법으로 테스트합니다.

    동작 GIF

     

    [참고] ESP32-C6-WROOM-1 기준 안전 GPIO (권장)

    아래 핀들은 일반 출력/입력으로 안정적으로 사용 가능한 핀입니다.

    2, 3, 4, 5, 6, 7, 10, 11, 15

     

    왜 이 핀들이 안전한가

    • 플래시/PSRAM 미사용 핀
    • USB / JTAG / UART 충돌 없음
    • 출력 가능
    • 부트 영향 없음

     

    주의해야 할 핀 (사용 피하기)

    • 0  → 부트 스트랩 (부팅 과정에 사용)
    • 1  → TX (로그 출력)
    • 8  → 지금 strip 사용 중
    • 9  → 내부 기능 충돌 가능
    • 12~14 → 상황 따라 제약 있음
    • 16~17 → 일부 보드에서 제한
    • 18~20 → 보드별 편차 큼
    반응형
Designed by Tistory.