
장치 트리 바인딩 파일의 목적 (.yaml)과 호환 속성을 이해합니다.
GPIO 핀 구성을 위한 실습을 통해 연습하고 GPIO 핀에서 읽기/쓰기 방법과 입력 GPIO 핀에 대한 인터럽트를 설정하는 방법을 배웁니다.

nrf52833_qiaa.dtsi: nRF52833 SoC의 패키지 및 기능 변형 정의
nrf52833dk_nrf52833-pinctrl.dtsi: nRF52833 DK의 핀 매핑 정보
노드 이름(node name): led_0
노드 라벨(node label): led0
gpios와 label
&gpio0이라는 참조가 사용되는데, 이는 SoC Devicetree에서 정의된 gpio0 노드를 참조합니다.
label 속성은 LED1에 대한 식별자 역할
LED1은 nRF52833 SoC의 GPIO 0번 포트의 13번 핀(P0.13)에 연결되어 있습니다.
주의사항

"Green LED 0" 이라는 문자열 대신 별칭(alias)을 따로 설정해서 사용해야 함.
이는 LED 노드가 있는 모든 보드가 LED에 대해 일정한 별칭을 갖도록 하기 위한 것입니다.

compatible = "nordic, nrf-uarte"
이 노드는 Nordic의 UART 하드웨어를 사용함을 나타냄.
status = "okay"
UART0가 활성화(사용 가능) 상태임을 나타냄.
current-speed = <115200>
UART 통신 속도가 115200 bps로 설정됨.
pinctrl-0 = <&uart0_default>
UART0의 기본(default) 상태에서 사용되는 핀 구성을 지정.
pinctrl-1 = <&uart0_sleep>
UART0의 슬립(sleep) 상태에서 사용되는 핀 구성을 지정.
pinctrl-names = "default, sleep"
pinctrl-0은 "default"로, pinctrl-1은 "sleep"으로 이름이 매핑됨.

pinctrl에 대한 정의
uart0_default 에 대한 정의


zephyr > dts > arm > nordic > nrf52833.dtsi 에서 확인 가능.
아마 해당 soc의 물리적 주소 등이 기술 된 것으로 예상.

드라이버(device driver)는 소프트웨어와 하드웨어 간의 중간 다리 역할을 합니다.
하드웨어를 우리가 원하는 방식으로 구성하고 작동시키기 위해 저수준(low-level) 세부 사항을 처리합니다.
예: GPIO 핀 제어, UART 통신 설정, 타이머 초기화 등.
API와 드라이버 구현의 분리(Decoupling):
nRF Connect SDK에서는 드라이버 구현과 API가 서로 독립적입니다.
이 구조 덕분에, 저수준 드라이버 구현을 변경하더라도 애플리케이션 코드를 수정할 필요가 없습니다.
장점:
이식성(portability): 동일한 코드를 다른 보드에서도 사용할 수 있음.
유지보수성: 하드웨어 변경 시 API는 그대로 유지되므로 관리가 쉬움.
하드웨어와 상호작용하는 방법
애플리케이션은 일반적인 API를 통해 하드웨어와 상호작용합니다.
하드웨어에 접근하려면 디바이스 포인터(device pointer)를 얻어야 합니다.
DEVICE_DT_GET() 매크로를 사용해 디바이스 포인터를 얻습니다.
디바이스 포인터를 얻는 방법
Devicetree에서 특정 하드웨어 노드의 식별자(node identifier)를 받아 디바이스 객체로 변환합니다.
애플리케이션이 특정 하드웨어 장치를 제어할 수 있도록 디바이스 포인터를 제공합니다.
노드 식별자 얻는 방법
매크로: DT_NODELABEL()
예: DT_NODELABEL(uart0) → UART0 노드의 식별자 반환.
매크로: DT_ALIAS()
예: DT_ALIAS(led0) → LED0 노드의 식별자 반환.
디바이스 사용 전 확인 (device_is_ready())


<zephyr/drivers/gpio.h>
GPIO API의 경우, gpio_dt_spec라는 구조체가 사용됩니다.
이 구조체는 GPIO 장치와 관련된 정보를 포함:
디바이스 포인터(const struct device * port):
GPIO 핀을 제어하는 장치를 가리킴.
예: GPIO0, GPIO1 등.
핀 번호(gpio_pin_t pin):
특정 GPIO 핀 번호.
예: P0.13 (핀 13번).
구성 플래그(gpio_dt_flags_t dt_flags):
핀의 동작 방식을 정의.
예: 풀업(pull-up), 풀다운(pull-down), 액티브 로우(active-low) 등.
GPIO_DT_SPEC_GET() 함수
구조체를 가져오기 위한 함수
사용 예시 :
#include <zephyr/drivers/gpio.h>
#include <zephyr/devicetree.h>
// led_0 노드의 GPIO 정보를 가져옴
struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_NODELABEL(led_0), gpios);
void main(void) {
// 디바이스가 준비되었는지 확인
if (!device_is_ready(led.port)) {
printk("LED GPIO device is not ready\n");
return;
}
// GPIO 핀을 출력 모드로 설정
gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
while (1) {
gpio_pin_toggle_dt(&led);
k_msleep(1000); // 1초 대기
}
}
초인종 설치 및 설정
gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
“관리자님, 초인종을 설치했습니다. 누군가 초인종을 누르면 바로 알림이 가도록 설정했어요. 이 알림은 문이 열렸을 때(논리 1 상태)만 울리도록 세팅했습니다!”
비서 배정
gpio_init_callback(&pin_cb_data, pin_isr, BIT(button.pin));
“좋아요, 이제 초인종이 눌리면 비서가 저한테 바로 알림을 주는 거죠? 저는 제 일에 집중하겠습니다.”
인터럽트 발생
void pin_isr(const struct device *dev, struct gpio_callback *cb, uint32_t pins) {
gpio_pin_toggle_dt(&led); // LED 상태 변경
}
방문자:
“띵동!”
비서(ISR):
“관리자님, 초인종이 눌렸습니다! 제가 지시하신 대로 LED를 켜겠습니다!”
관리자:
“좋아요. 저는 제 업무를 계속하겠습니다.”
관리자는 초인종을 직접 확인하지 않고, 비서를 통해 알림을 받고 필요한 작업만 수행합니다.
덕분에 공장에서의 중요한 업무(전력 관리, 데이터 처리 등)를 효율적으로 수행할 수 있습니다.
실습 코드 :
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
/* STEP 9 - Increase the sleep time from 100ms to 10 minutes */
#define SLEEP_TIME_MS 10 * 60 * 1000
Step1. Devicetree에서 sw0(버튼)과 led0(LED)에 해당하는 노드를 참조합니다.
#define SW0_NODE DT_ALIAS(sw0)
Step2. GPIO_DT_SPEC_GET() 매크로를 사용해 버튼과 LED 핀의 디바이스 포인터, 핀 번호, 플래그를 포함한 구조체를 생성합니다.
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(SW0_NODE, gpios);
#define LED0_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
/* STEP 4 - 버튼 인터럽트가 발생했을 때 호출될 콜백 함수를 정의합니다. */
void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
gpio_pin_toggle_dt(&led);
}
/* STEP 5 - 인터럽트를 처리하기 위한 콜백 변수(gpio_callback)를 정의합니다. */
static struct gpio_callback button_cb_data;
int main(void)
{
int ret;
if (!device_is_ready(led.port)) {
return -1;
}
if (!device_is_ready(button.port)) {
return -1;
}
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return -1;
}
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret < 0) {
return -1;
}
/*Step3. 버튼의 핀에 인터럽트를 설정합니다.*/
ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
/* STEP 6 - 콜백 초기화 */
콜백 변수(button_cb_data)를 초기화하고, 버튼 핀에서 발생하는 인터럽트를 처리하도록 콜백 함수를 연결합니다.
gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
/* STEP 7 - 초기화된 콜백 변수를 버튼 GPIO 포트에 추가하여 버튼 이벤트가 발생했을 때 콜백이 호출되도록 설정합니다. */
gpio_add_callback(button.port, &button_cb_data);
while (1) {
k_msleep(SLEEP_TIME_MS);
}
}
gpio를 사용하기 위해서는 하드웨어(핀번호, 물리적 주소 등) 정보를 등록해야한다.
인터럽트 방식을 사용하면 CPU에 부담을 덜 줄 수 있다.