Zephyr v3.7.0 · 보드 핀과 드라이버 상태

pinctrl: 같은 UART0가 왜 보드마다 다른 핀으로 나가는가?

보드의 핀 배선은 Devicetree가 기록하고, 핀을 실제로 전환하는 시점은 드라이버가 정합니다. nRF52840 DK의 UART0을 따라가며 default·sleep 선언과 실제 레지스터 적용 사이의 빈칸을 메웁니다.

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

이 글은 Zephyr v3.7.0 문서·태그의 보드 DTS와 UART 드라이버를 읽어 만든 독립 해설이다. 여기의 그림과 점검표는 실제 핀 전압을 계측한 결과가 아니다. 기존 Devicetree 글의 전체 빌드 경로나 driver model의 인스턴스 생성 대신, 핀 상태의 소유권과 적용 시점만 추적한다.

1. pinctrl은 주변장치의 신호 경로를 맡는다

UART의 TX 신호를 P0.06으로 내보낼지, RX 핀에 내부 pull-up을 걸지 같은 설정은 pin controller의 일이다. 그 하드웨어는 STM32처럼 공통 핀 설정 블록에 모여 있을 수도, Nordic nRF처럼 주변장치의 PSEL 레지스터와 핀 설정 블록에 나뉘어 있을 수도 있다. Zephyr 드라이버는 어느 경우든 하나의 pinctrl 상태를 적용한다. 반면 LED 켜기나 버튼 수준 읽기처럼 핀을 범용 입출력으로 다루는 일은 GPIO API의 영역이다.

그림 1. 보드 선언에서 물리 핀까지, 책임이 바뀌는 네 지점

↔ 각 상자는 빌드 또는 실행 단계입니다. 좁은 화면에서는 좌우로 이동하세요.

① 보드 DTSpinctrl-0·pinctrl-names로 상태와 핀 그룹을 연결어떤 신호가 어느 핀으로 가는지 선언
② C 설정PINCTRL_DT_DEFINE가 상태별 pin 배열과 pinctrl_dev_config를 구성빌드 시 결정된 데이터
③ 주변장치 드라이버pinctrl_apply_state를 init·PM 등의 코드 경로에서 호출언제 바꿀지 결정
④ SoC 구현pinctrl_configure_pins가 벤더별 하드웨어에 적용mux·pull·저전력 설정이 효력을 얻음
표 1. 핀을 만지는 세 층의 질문
층답하는 질문UART0 사례
보드 + pinctrl신호가 어느 핀과 전기 설정을 쓰나?TX=P0.06, RX=P0.08; RX 쪽 pull-up
UART 드라이버장치가 언제 그 상태를 요청하나?초기화에서 default 적용
GPIO 드라이버범용 핀 수준을 어떻게 읽고 쓰나?UART 전용 TX 전환과 같은 계약이 아님

근거: v3.7.0 pinctrl 하드웨어 모델과 GPIO 경계, SoC 구현 계약.

2. nRF52840 DK에서 UART0의 두 상태를 읽는다

실제 nrf52840dk_nrf52840.dts는 &uart0에 pinctrl-0 = <&uart0_default>, pinctrl-1 = <&uart0_sleep>을 순서대로 붙이고 pinctrl-names = "default", "sleep"으로 이름을 대응시킨다. 두 label의 내용은 같은 보드의 -pinctrl.dtsi에 있다. default는 UART 신호를 핀에 배치하고 RX·CTS 그룹에 pull-up을 준다. sleep은 같은 네 신호에 low-power-enable을 준다.

그림 2. 같은 UART0, 서로 독립적인 핀 상태 두 묶음

각 상태는 완전한 핀 설정 묶음입니다. 한 상태에 다른 상태의 값이 순서대로 더해지는 모델이 아닙니다.

운영 · default
TX
P0.06
RX
P0.08
RTS
P0.05
CTS
P0.07
RX·CTS 그룹에 bias-pull-up. UART 드라이버가 운영 상태를 요청할 때 적용.
저전력 · sleep
TX
P0.06
RX
P0.08
RTS
P0.05
CTS
P0.07
네 신호의 한 그룹에 low-power-enable. 선언만으로 진입하는 것은 아님.
표 2. 3.7.0 보드 소스에서 직접 확인한 핀
UART0 신호보드 핀default 그룹sleep 그룹
TXP0.06group1group1 · 저전력
RXP0.08group2 · pull-upgroup1 · 저전력
RTSP0.05group1group1 · 저전력
CTSP0.07group2 · pull-upgroup1 · 저전력

이 예시의 NRF_PSEL(UART_TX, 0, 6) 같은 숫자와 속성은 Nordic 바인딩의 언어다. 다른 SoC가 동일한 psels 속성을 쓴다는 뜻은 아니다. 보드 배선과 pull-up은 회로·속도에 따라 달라지므로 다른 보드에 이 설정을 그대로 복사하지 않는다.

근거: v3.7.0 보드 UART0 연결, v3.7.0 상태별 실제 핀 그룹, 독립 상태 모델.

3. Devicetree label은 드라이버가 쓸 C 설정으로 바뀐다

PINCTRL_DT_DEFINE(node_id)은 노드의 pinctrl-N 항목에서 각 상태의 pin 배열과 장치용 pinctrl_dev_config를 만든다. PINCTRL_DT_DEV_CONFIG_GET(node_id)은 그 설정의 주소를 가져온다. 같은 동작을 compatible 인스턴스 번호로 쓰는 짝이 PINCTRL_DT_INST_DEFINE(inst)과 PINCTRL_DT_INST_DEV_CONFIG_GET(inst)다. 둘은 각각 선택 방식이 다른 API이지 한 인스턴스에 둘 다 선언해야 하는 절차가 아니다.

Nordic UART 드라이버의 인스턴스 매크로는 노드 label을 사용해 PINCTRL_DT_DEFINE(UARTE(idx))를 호출하고, 정적 config의 .pcfg에 PINCTRL_DT_DEV_CONFIG_GET(UARTE(idx))을 둔다. 초기화 함수는 그 포인터로 default 상태를 적용한다. 핀 컨트롤러 구현 쪽에서는 pinctrl_soc_pin_t와 pinctrl_configure_pins()가 SoC별 인코딩과 하드웨어 쓰기를 담당한다.

표 3. 이름이 비슷한 매크로와 함수의 분업
기호입력결과·호출 시점
PINCTRL_DT_DEFINE노드 식별자상태 배열과 device config를 정의 · 컴파일
PINCTRL_DT_DEV_CONFIG_GET같은 노드 식별자정의된 config의 주소를 드라이버에 연결 · 컴파일
pinctrl_apply_stateconfig 포인터 + 상태 ID해당 상태를 찾아 적용 · 실행
pinctrl_configure_pinsSoC별 pin 배열 + 선택적 장치 주소핀 설정을 즉시 하드웨어에 반영 · 실행

상태가 있는데 배열에서 사라진다면. 3.7.0은 CONFIG_PM과 CONFIG_PM_DEVICE가 모두 꺼진 빌드에서 PINCTRL_SKIP_SLEEP을 정의한다. 그때 Devicetree의 sleep 선언이 있어도 pinctrl 매크로가 이 상태의 C 데이터를 생략할 수 있다. 반대로 PM 옵션을 켰다고 해서 모든 장치가 sleep 상태를 적용하는 것도 아니다.

CONFIG_PINCTRL_DYNAMIC은 default·sleep 사이를 바꾸는 일반 호출과 다르다. 이것은 config가 가리키는 상태 집합 자체를 pinctrl_update_states()로 교체하는 기능이다. v3.7.0 가이드는 이미 동작 중인 장치의 핀 설정을 바꾸면 예기치 않은 결과가 날 수 있으므로, 장치 초기화 이전의 이른 부팅 단계에서만 사용하라고 제한한다.

근거: v3.7.0 상태 생략 규칙, 동적 pin control의 초기화 전 제한, v3.7.0 pinctrl API, UART 매크로와 .pcfg.

4. sleep 선언은 자동 상태 전이가 아니다

v3.7.0 uart_nrfx_uarte.c의 uarte_instance_init()는 PINCTRL_STATE_DEFAULT 적용 실패를 반환한 뒤 UART를 설정·활성화한다. 저전력 경로에서는 pins_state_change(dev, on)가 UARTE_CFG_FLAG_GPIO_MGMT가 있을 때에만 default 또는 sleep을 요청한다. 이 helper는 선택된 비동기 저전력 동작과 device PM의 suspend/resume 경로에서 호출된다. 특히 PM action은 TX/RX를 정리한 다음 핀을 바꾸므로, 핀 상태 변경과 UART 주변장치 정지를 하나의 마법 같은 자동 이벤트로 묶어 생각하면 안 된다.

그림 3. UART0 코드 경로에서 관찰하는 적용 시점

아래는 조건을 표시한 코드 경로도입니다. 모든 앱·모든 드라이버에서 네 칸을 반드시 지난다는 뜻은 아닙니다.

① inituarte_instance_init

default 적용, 실패면 초기화 반환.

② 운영UART RX/TX가 보드에 지정된 핀을 사용.

단순 DTS 선언이 상태 전이 스케줄러는 아님.

③ 조건부 suspend드라이버가 활동을 정리한 뒤 pins_state_change(false).

GPIO 관리 플래그가 있을 때 sleep 적용.

④ 조건부 resumepins_state_change(true) 뒤 UART 재활성화.

같은 플래그에서 default 복귀.

표 4. 선언·빌드·실행 증거를 구분하기
확인한 사실입증하는 것아직 입증하지 못하는 것
DTS의 pinctrl-1sleep용 핀 그룹 연결실행 중 suspend 호출 여부
CONFIG_PM_DEVICE=ydevice PM 코드 경로를 빌드할 조건모든 드라이버의 sleep 적용
UART init의 default 적용 성공초기 핀 설정 함수의 성공 반환배선·전압·상대 장치 통신 성공
pinctrl_apply_state(..., SLEEP) 반환 0드라이버가 이 상태를 찾아 적용보드 전체 전력 절감량

근거: 초기화 default 적용, 조건부 state helper, suspend/resume 적용 위치.

5. 핀 문제는 세 산출물을 나란히 보며 좁힌다

보드가 부팅하지만 UART가 조용하면 먼저 build/zephyr/zephyr.dts의 최종 &uart0와 pinctrl label을 본다. 다음에는 build/zephyr/.config에서 UART·pinctrl·PM 선택을 확인한다. 마지막으로 드라이버 초기화 반환값과 실제 TX/RX 핀을 계측한다. source DTS 한 파일만 보고 overlay가 최종 상태를 바꾸지 않았다고 가정하지 않는다.

표 5. 증상에서 가장 가까운 경계로 가기
증상먼저 확인잘못된 단정 피하기
컴파일 중 pinctrl 상태/label 오류merged DTS의 pinctrl-N, 이름 순서와 phandle핀 컨트롤러 레지스터 문제로 시작하지 않기
초기화가 음수 반환pinctrl_apply_state 반환과 SoC pinctrl 구현UART baud 설정 실패와 섞지 않기
TX는 되나 RX가 불안정실제 핀 배선, RX 그룹의 pull-up, 상대 장치 전기 조건보드 예시를 다른 회로에 그대로 복사하지 않기
sleep 선언했는데 전류가 그대로PM action 발생, driver 플래그·state 적용, UART·외부 회로DTS 선언만으로 저전력 진입을 증명하지 않기
PM 없는 빌드에서 sleep lookup 실패CONFIG_PM, CONFIG_PM_DEVICE, skip 규칙phandle 누락으로 바로 단정하지 않기

이 글은 공식 코드의 경로를 재구성한 읽기 지도다. 특정 보드를 빌드·플래시하거나 오실로스코프로 핀 파형과 전류를 측정한 검증 기록은 포함하지 않는다.

6. Zephyr v3.7.0 고정 근거와 이어 읽기

표 6. 문서·보드·드라이버의 서로 다른 역할
고정 근거확인한 부분
공식 Pin Control 가이드하드웨어 모델, 독립 상태, DTS 표현, sleep 생략, 드라이버 계약
공식 v3.7.0 APIDEFINE/GET, apply, state ID와 오류 반환
nRF52840 DK 보드 DTS · 핀 정의UART0의 state 연결과 P0.05–P0.08 실제 매핑
v3.7.0 Nordic UARTE 드라이버config 생성, init의 default, 조건부 PM state 전환