-
Embedded Swift: 핀(GPIO Pin) 제어하기 (2) - 입력 받기 (Input Mode)공부/Swift(프로그래밍 언어) 2026. 4. 29. 23:00반응형
ESP32-C6 + idf.py + Embedded Swift 6.2 를 이용해 마이크로컨트롤러에서 핀을 통해 입력을 받고 그에 대한 작업을 처리하는 방법에 대해 설명하겠습니다.
Embedded Swift 시리즈 목록 (분량상 모든 내용을 다 설명할 수 없어 생략한 부분이 있으니 일부 내용은 이전 글들을 참고해주세요.)
GPIOPin 구조체에 핀 읽기 부분(gpio_get_level) 추가
기본 제공되는 함수로 gpio_get_level(gpio_num_t)가 있습니다.
gpio_get_level(gpio_num_t)이 함수를 그대로 사용해도 되지만, Embedded Swift 의 방향성에 맞게 핀오브젝트.read() 로 호출하면 바로 결과를 리턴하도록 래핑하겠습니다.
struct GPIOPin { let pinNumber: Int private let gpioNumber: gpio_num_t init(pinNumber: Int, mode: Mode = .outputOnly) { self.pinNumber = pinNumber self.gpioNumber = gpio_num_t(Int32(pinNumber)) // ... // } // 💡 추가 func read() -> Int32 { gpio_get_level(gpioNumber) } }GPIOPin 구조체 생성자를 입력 설정이 가능하게 업데이트
핀 번호를 입력 모드로 하는 기본 함수는 다음과 같습니다.
gpio_set_direction(핀번호, GPIO_MODE_INPUT)
ESP32-C6에는 풀업/풀다운 저항이 내장되어 있어 이것을 설정할 수 있습니다. 풀업 풀다운 설정에 사용되는 기본 함수는 다음과 같습니다.gpio_set_pull_mode(gpio_num_t, gpio_pull_mode_t)- gpio_pull_mode_t 종류 (일부)
- GPIO_PULLUP_ONLY: 입력 핀을 내부 풀업 저항으로 VCC(전원)에 연결합니다. 외부 입력이 없을 때 기본값은 HIGH(1)이며, 스위치를 눌러 GND로 연결하면 LOW(0)가 됩니다.
- GPIO_PULLDOWN_ONLY: 입력 핀을 내부 풀다운 저항으로 GND(접지)에 연결합니다. 외부 입력이 없을 때 기본값은 LOW(0)이며, 스위치를 눌러 VCC에 연결하면 HIGH(1)가 됩니다.
외부 저항 없이 입력 신호를 사용해도 되는지 여부
ESP32-C6의 내부 풀업/다운은 약 ~45kΩ 수준으로 매우 약합니다. 버튼 입력이나 간단한 디지털 신호는 사용 가능하지만, 노이즈가 많은 환경이나 복잡하고 긴 배선, 타이밍이 중요한 경우에는 외부 저항 사용을 추천합니다.
GPIOPin 생성자
기본 제공 함수들을 사용해도 되지만 굳이 Swift 스럽게 래핑하겠습니다.
struct GPIOPin { let pinNumber: Int private let gpioNumber: gpio_num_t init(pinNumber: Int, mode: Mode = .outputOnly) { self.pinNumber = pinNumber self.gpioNumber = gpio_num_t(Int32(pinNumber)) gpio_reset_pin(gpioNumber) gpio_set_direction(gpioNumber, mode.value) if case .inputOnly(let pull) = mode { gpio_set_pull_mode(gpioNumber, pull.value) } } // ... // }- mode.value: .outputOnly 또는 .inputOnly(Pull)을 받아 해당 핀 번호에 대한 입출력 여부를 결정합니다.
- if case .inputOnly(let pull) = mode { ... }
- mode == .inputOnly인 경우 let pull 을 할당해 pull 변수를 사용합니다.
- Mode 및 Pull 의 구현은 밑에 있습니다.
GPIOPin.Mode 및 GPIOPin.Pull
이것을 enum을 사용해 관리하도록 하겠습니다.
extension GPIOPin { enum Mode: Equatable { case inputOnly(Pull) case outputOnly var value: gpio_mode_t { switch self { case .inputOnly: GPIO_MODE_INPUT case .outputOnly: GPIO_MODE_OUTPUT } } } enum Pull: Equatable { /// GPIO_PULLUP_ONLY - 기본값: HIGH / 연결: LOW case pullUpOnly /// GPIO_PULLDOWN_ONLY - 기본값: LOW / 연결: HIGH case pullDownOnly var value: gpio_pull_mode_t { switch self { case .pullUpOnly: GPIO_PULLUP_ONLY case .pullDownOnly: GPIO_PULLDOWN_ONLY } } } }- case .inputOnly(Pull) : GPIO 핀을 입력 전용으로 설정하면서, 내부 풀업 또는 풀다운 저항 설정을 함께 지정하는 케이스입니다. `Pull` 값을 통해 기본 입력 상태가 결정되며, 외부 신호가 없을 때의 기준 전압을 정의합니다.
- .pullUpOnly: GPIO_PULLUP_ONLY; 기본값 HIGH, 입력 없을 때 1로 읽힘
- .pullDownOnly: GPIO_PULLDOWN_ONLY;기본값 LOW, 입력 없을 때 0으로 읽힘
- 연관 값이 있는 열거형(enum with associated values) 형태를 사용했습니다.
GPIOPin의 사용 예
위 구조체 및 열거체들은 다음과 같이 Swift 코드 내에서 사용합니다.
// 설정 let input = GPIOPin(pinNumber: 11, mode: .inputOnly(.pullDownOnly)) // 사용 let current = input.read() // 1 또는 0 반환 if current == 1 { // ... // }하드웨어 연결
푸시 버튼을 이용해 한 쪽은 GPIO 입력 핀과 연결하고, 다른 쪽은 (+) 레일에 연결합니다. (풀업으로 설정한 경우. 풀다운으로 설정한 경우에는 (-)레일) 예를 들어 11번 핀을 입력 핀으로 설정한 경우 다음과 같이 연결합니다.

핀을 점퍼선으로 연결 [예제] 입력을 받고, 모스 부호 SOS를 입력하면 LED 점멸하기
버튼 입력을 모스 부호(점/선)로 해석해서 SOS(…---…) 패턴을 감지하는 로직입니다. 입력 길이를 비트로 변환하고, 최근 N비트만 잘라서 특정 패턴(SOS)을 검출하는 구조입니다.
@_silgen_name("esp_timer_get_time") /// 부팅 시간으로부터 us 단위 (1s = 100_0000 = 1000ms , 1ms = 1000us) func getTime() -> Int64static func morse1() { let input = GPIOPin(pinNumber: 11, mode: .inputOnly(.pullDownOnly)) let strip = LedStrip(gpioPin: 8, maxLeds: 1) var startTime = getTime() var lastInput: Int32 = 0 var isCodeAppended = false var lineCodeBitFlags: UInt32 = 0 strip.clear() while(true) { let current = input.read() if current == 1 && lastInput == 0 { startTime = getTime() strip.setPixel(index: 0, color: .init(r: 2, g: 2, b: 2)) strip.refresh() } else if current == 0 && lastInput == 1 { strip.clear() let duration = getTime() - startTime lineCodeBitFlags <<= 1 if duration >= 250000 { lineCodeBitFlags += 1 } else { lineCodeBitFlags += 0 } isCodeAppended = true } lastInput = current if (lineCodeBitFlags & 0b111_111_111) == 0b000111000 { flickLED() } if current == 0 && isCodeAppended && getTime() - startTime > 1000000 { lineCodeBitFlags = 0 isCodeAppended = false } delay(ms: 10) } // 점멸 func flickLED() { print("SOS(...---...) Detected!!") while(true) { strip.setPixel(index: 0, color: .init(r: 32, g: 0, b: 0)) strip.refresh() delay(ms: 40) strip.clear() delay(ms: 60) if input.read() == 1 { return } } } }LED Strip
내장 LED와 관련된 부분입니다.
- setPixel 및 refesh: LED를 켜는 부분
- clear: LED를 끄는 부분
delay(ms)
ms 단위로 딜레이 설정하는 부분으로, 다음 코드의 래핑입니다.
vTaskDelay(ms / (1000 / UInt32(configTICK_RATE_HZ)))전체 흐름
- 루프를 계속 돌면서 입력 핀의 상태 변화를 감지합니다.
- 상태 변화(edge) 기준으로
- 1 → 0: 버튼을 뗀 순간 → “입력 한 개 완성”
- 0 → 1: 버튼을 누른 순간 → “시간 측정 시작”
시간 계산
- if current == 1 && lastInput == 0를 이용해 버튼이 눌리는 순간의 시간을 기록합니다.
- getTime()은 부팅 이후 경과 시간 (마이크로초 µs)을 Int64 형태로 가져옵니다.
- let duration = getTime() - startTime 을 이용해 버튼을 누르고 있던 시간을 마이크로초 단위로 가져옵니다.
- 버튼을 누른 시간이 0.25초보다 길면 - 신호, 그보다 짧으면 . 신호로 인식합니다.
모스부호 저장 및 판독 - 비트플래그 사용
[Bool] 배열을 사용해도 무관하나 임베디드 장비의 특성에 맞춰 비트플래그를 사용했습니다.
기본 입력 및 저장
- lineCodeBitFlags: 입력된 모스 부호를 비트로 누적 저장합니다.
- lineCodeBitFlags <<= 1: 새로운 입력 추가시 비트를 왼쪽으로 밉니다. 처음에는 0이므로 옮겨도 0이며, 이후에는 저장된 값에 따라 값이 변하게 됩니다.
- lineCodeBitFlags += 1 // 또는 0: 마지막 자리수에 비트값을 추가합니다. 먼저 입력한 값이 낮은 자리수, 나중에 입력한 값이 높은 자리수에 저장되게 됩니다. 입력 신호에 따른 비트 변화는 다음과 같습니다.

입력 신호에 따른 비트 변화 비트플래그 패턴 비교 방식
(lineCodeBitFlags & 0b111_111_111) == 0b000111000- 0b111_111_111: 마스크 비트입니다. SOS의 총 비트는 9개이므로 최근 9비트만 비교하기 위해 사용됩니다.
- 앞부분은 0이 3개이므로 000_111_000, 앞자리의 0은 생략되어 111_000 으로 저장됩니다. 이렇게 되면 0을 몇번 눌렀는지 판단이 어렵기 때문에 자릿수를 정해 맞는 비교가 되도록 합니다.
- == 0b000111000: 저장된 값과 마스크를 연산한 뒤 SOS 패턴 (...---...)과 일치하는 경우
- 슬라이딩 윈도우 방식
입력 종료 판단 (리셋 조건)
if current == 0 && isCodeAppended && getTime() - startTime > 1000000- 마지막 입력 이후 1초 이상 아무 입력 없음 → 하나의 입력 시퀀스 끝
- lineCodeBitFlags = 0: 다음 입력을 위해 초기화
동작 확인
빌드 및 플래시하여 신호가 맞게 입력되는지 확인합니다.

모니터 화면, 핀 설정 내역 및 신호 감지 여부 
버튼을 ...---... 으로 누르면 SOS 신호 발생 GIF 반응형'공부 > Swift(프로그래밍 언어)' 카테고리의 다른 글
[Swift iOS] Lite 버전 배포 - 이미 유료로 개발 및 판매중인 앱을 일반 버전, Lite 버전으로 나눠서 앱스토어에 배포하는 방법 (0) 2026.05.08 Embedded Swift: 핀(GPIO Pin) 제어하기 (3) - 도트 전광판 제어 (MAX7219 32x8 도트 매트릭스 LED 5핀제어) (3) 2026.05.03 [SwiftUI] SF Symbol에서 계층, 팔레트, 여러 가지 색상 사용하기 (0) 2026.04.28 Embedded Swift: 핀(GPIO Pin) 제어하기 (1) - LED 깜빡이기 (0) 2026.04.23 Embedded Swift: ESP32-C6을 Wifi에 연결시켜 웹 서버로 사용하기, 브라우저에서 LED 제어 (0) 2026.03.31 - gpio_pull_mode_t 종류 (일부)