Zephyr v3.7.0 · USB Device · fixed-version source note

USB 장치가 보인다고 데이터를 보낼 수 있는 것은 아니다

USB 컨트롤러를 켜도 호스트의 열거, configuration 선택, 클래스 드라이버의 준비가 끝난 것은 아닙니다. Zephyr 3.7.0에 공존하는 두 장치 스택을 분리하고, 호스트의 EP0 제어 흐름에서 애플리케이션 데이터 엔드포인트까지 따라갑니다.

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

기준은 Zephyr v3.7.0 태그다. 여기서 USB device는 PC 등의 USB 호스트에 연결되는 쪽을 뜻한다. Driver Model의 일반 장치 객체나 Networking의 IP 소켓과는 초점이 다르다. v3.7.0에는 기존 usb_dc 기반 장치 스택과 실험적인 UDC/USBD 장치 스택이 공존하므로, 아래 API 이름은 스택별로 읽어야 한다. 그림은 소스 경계를 설명하려고 새로 그린 것이며 보드 측정 결과가 아니다.

1. 호스트에서 펌웨어까지, 네 층의 책임

호스트는 버스를 통해 컨트롤러와 통신하고 descriptor를 읽어 주소와 configuration을 정한다. 컨트롤러 드라이버는 endpoint 전송을 하드웨어에 연결하고, Zephyr 장치 스택은 EP0 표준 요청과 descriptor 응답을 처리한다. 클래스 또는 function은 CDC ACM·HID 같은 기능을 제공한다. 이 책임을 구분하면 “보드가 연결됨”과 “애플리케이션의 한 바이트가 호스트 앱에 도착함” 사이의 빈칸이 보인다.

그림 1. USB device 관점의 책임 지도

↔ 좁은 화면에서는 그림을 좌우로 이동할 수 있습니다. 키보드에서는 그림 안에 초점을 놓고 방향키를 쓰세요.

호스트

버스 reset, descriptor 요청, 주소·configuration 선택을 시작한다.

PC의 USB core와 클래스 드라이버Zephyr 보드 쪽에서 호스트 선택을 대신할 수 없다.
컨트롤러

endpoint/FIFO와 버스 전송을 하드웨어에서 수행한다.

legacy: usb_dcnew: UDC driver API지원 endpoint 수·종류는 보드마다 다르다.
장치 스택

EP0 표준 요청, descriptor, 장치 상태와 구성 선택을 처리한다.

legacy: usb_enable()new: usbd_init() → 조건에 맞춰 usbd_enable()
클래스·function

CDC ACM, HID, MSC 등 각 기능의 인터페이스·데이터 규칙을 제공한다.

예시: CDC ACM은 애플리케이션에 UART API를 제공function에는 하나 이상의 interface가 있다.

왼쪽부터 호스트 ↔ 장치 컨트롤러 ↔ 장치 스택 ↔ 클래스·function의 계층 경계다. 화살표는 계층 간 상호작용이며 모든 EP0 표준 요청이 클래스까지 전달된다는 뜻은 아니다.

표 1. 호스트부터 펌웨어까지 책임 위치
질문먼저 볼 층확인할 것
호스트 목록에 장치가 없다컨트롤러·장치 스택포트·VBUS·enable·EP0 응답
장치는 보이지만 기능이 없다descriptor·클래스configuration, interface, class 등록
기능은 있지만 데이터가 없다endpoint·호스트 앱방향, 상태, 요청, 버퍼와 읽기 경로

2. v3.7.0에서 공존하는 두 장치 스택

기존 스택은 CONFIG_USB_DEVICE_STACK과 <zephyr/usb/usb_device.h>의 API를 사용한다. 그 밑의 usb_dc API는 Zephyr의 일반 Device Driver Model을 따르지 않는다고 문서화되어 있다. 새 스택은 CONFIG_USB_DEVICE_STACK_NEXT, <zephyr/usb/usbd.h>, UDC driver API를 사용하며 v3.7.0에서는 실험적이다. 동일한 설명에서 usb_enable()과 usbd_enable(ctx)를 단순 대체 이름처럼 섞으면 초기화·등록 순서를 잃는다.

표 2. 기존·신규 장치 스택의 차이
항목기존 장치 스택새 UDC/USBD 장치 스택
v3.7.0 상태기존 사용 경로, 장래 폐기 계획이 문서에 명시됨실험적 API, 변경 가능
Kconfig 진입CONFIG_USB_DEVICE_STACKCONFIG_USB_DEVICE_STACK_NEXT
응용·클래스 쪽 APIusb_enable(status_cb), usb_read(), usb_write() 등USBD_DEVICE_DEFINE, usbd_add_configuration(), usbd_register_class(), usbd_init(), usbd_enable() 등
하위 드라이버 경계usb_dc APIUDC driver API와 context
예시 확인기본 USB CDC-ACM 예시 설정같은 예시의 usbd_next_prj.conf 빌드 경로

버전 경계: v3.7.0 문서에 적힌 폐기·전환 목표는 그 시점의 계획이다. 이후 릴리스의 현재 상태를 이 글의 고정 버전 구현 사실로 바꾸어 읽지 않는다. 제품에서는 사용하는 Zephyr 태그와 보드 컨트롤러 지원을 따로 확인해야 한다.

3. 열거는 호스트가 진행한다

장치 준비와 물리 연결 조건이 맞으면 호스트가 버스 연결을 보고 EP0 제어 전송으로 descriptor를 읽고 주소와 configuration을 정한다. 새 스택에서는 일부 컨트롤러가 usbd_enable() 전에 VBUS를 요구하므로 usbd_init() 직후 항상 enable한다고 가정하면 안 된다. VBUS 감지 기능이 있는 컨트롤러라면 usbd_can_detect_vbus()를 확인하고 선택적으로 USBD_MSG_VBUS_READY 뒤에 enable할 수 있다. EP0는 기본 제어 경로다. class/function의 interface와 endpoint 정보는 descriptor에 나타나며, 선택된 configuration에 맞는 전송이 그 뒤에 가능해진다. 다음 순서는 개념도이므로 실제 호스트의 개별 요청 순서나 재시도 횟수를 고정한 로그로 해석하면 안 된다.

그림 2. 장치 활성화와 호스트 열거의 서로 다른 주체

↔ 좁은 화면에서는 단계 끝까지 가로로 읽으세요.

01. 장치 구성펌웨어가 descriptor·configuration·class를 준비새 스택은 등록 후 usbd_init(); 아직 호스트 열거 완료가 아님
02. VBUS와 활성화컨트롤러·연결 조건에 맞춰 장치를 enable하고 호스트가 연결을 감지일부 새 UDC는 VBUS가 필요하다. READY 뒤 usbd_enable() 가능. 기존 스택은 usb_enable().
03. EP0 요청호스트가 reset과 표준 요청으로 장치 descriptor·주소를 다룸장치 스택이 표준 제어 요청에 응답
04. 구성 선택호스트가 descriptor에서 interface·endpoint 정보를 읽고 configuration을 선택이전의 enable 성공만으로 여기까지 완료된 것은 아님
05. 기능 사용호스트 클래스 드라이버와 응용 프로그램이 필요한 endpoint를 사용CDC ACM의 경우 호스트 프로그램이 데이터를 요청해야 한다.

v3.7 기존 스택의 USB_DC_CONFIGURED는 configuration 완료 알림이다. 새 스택에서는 동일한 이름의 callback을 가정하지 말고 usbd_msg_register_cb() 메시지와 해당 class API를 확인한다.

표 3. USB descriptor의 식별·구성
descriptor 개념표현하는 것실수하기 쉬운 점
Device장치의 VID/PID 등 식별값Zephyr 샘플 VID 0x2FE3을 제품에 재사용하면 안 됨
Configuration사용 가능한 function들의 한 묶음호스트가 선택해야 사용 상태에 진입
Interfacefunction이 노출하는 논리적 접점한 function에 여러 interface가 있을 수 있음
Endpoint방향·종류·주소가 있는 데이터 통로선언한 기본 번호를 실제 런타임 주소로 하드코딩하지 않기

4. IN과 OUT은 항상 호스트 기준이다

EP0는 열거에 쓰이는 제어 경로다. 일반 데이터 endpoint의 IN은 장치 → 호스트, OUT은 호스트 → 장치다. 주소의 방향 비트와 endpoint 번호를 함께 봐야 한다. v3.7.0 문서에 따르면 class/function의 interface 번호와 endpoint 주소는 초기화 과정에서 재배정될 수 있다. 게다가 장치 컨트롤러마다 가능한 endpoint 수·종류·조합이 다르다.

그림 3. EP0와 데이터 endpoint의 방향

↔ 가로 이동으로 각 행의 호스트·전송·장치 칸을 함께 보세요.

EP0 제어
호스트SETUP 및 표준 요청 시작
⇄ descriptor·주소·configuration 처리
장치 스택표준 요청 응답; 필요하면 class·vendor handler
데이터 IN
호스트장치가 제공한 데이터를 읽음
← 장치에서 호스트로
장치 function예시: CDC ACM 송신 또는 HID 입력 보고
데이터 OUT
호스트장치로 데이터 전송
→ 호스트에서 장치로
장치 functionendpoint callback·수신 처리

IN/OUT은 CPU 메모리 관점이 아니라 호스트 관점이다. 화살표는 데이터 방향만 나타내며 호스트가 버스 전송을 스케줄한다는 USB 역할은 그대로다.

표 4. endpoint 경로별 확인 조건
경로v3.7.0 예시확인 조건
Control / EP0descriptor·표준 요청과 class/vendor 요청호스트가 요청하고 stack이 응답하는지
BulkCDC ACM 데이터 endpointIN/OUT 주소, class 준비, 호스트의 실제 읽기
InterruptHID 입력 보고 endpointreport descriptor·callback과 controller 지원
IsochronousAudio 관련 구현의 후보스택·class·controller의 해당 모드 지원을 보드별 확인

5. 클래스 API에서 데이터 수명을 읽기

기존 스택의 저수준 class 경로는 usb_write() 후 완료 endpoint callback을 기다려 다음 패킷을 보내고, OUT callback에서 usb_read()로 수신 데이터를 꺼낸다. 대안인 usb_transfer()는 최대 패킷 크기에 따라 전송을 나누며 별도 완료 callback을 쓴다. 이는 기존 스택의 API 설명이다. 새 스택의 class 등록과 controller의 udc_ep_enqueue()를 같은 예시의 연속 호출처럼 사용하지 않는다.

새 스택에서는 context를 정의하고 descriptor·configuration을 추가한 다음 class 인스턴스를 configuration에 등록하고 usbd_init(), usbd_enable()을 진행한다. 문서가 제시하는 class 이름에는 cdc_acm_n, hid_n, msc_n 등이 있다. 이 단계는 device 역할의 준비이며, USB host stack이 되는 절차는 아니다.

표 5. 두 장치 스택의 API 선택
하려는 일기존 스택에서 확인새 스택에서 확인
장치 활성화class 등록 후 usb_enable(status_cb)context·descriptor·configuration·class 등록 후 usbd_init(ctx), usbd_enable(ctx)
상태 알림USB_DC_RESET, USB_DC_CONFIGURED 등 status callbackusbd_msg_register_cb(ctx, cb)와 메시지 유형
endpoint 데이터class의 usb_write()/usb_read() 또는 usb_transfer()선택한 새 class API, 그 아래 UDC 요청 경계

CDC ACM 주의: Zephyr 문서에서 이 기능은 애플리케이션에 UART API를 제공하지만 실제 UART 컨트롤러와 같지 않다. USB 스택 초기화·시작 전 데이터는 버려질 수 있고, 연결 후에도 호스트 쪽 응용 프로그램이 읽어야 한다. 콘솔 예시는 DTR 대기를 권한다.

6. 안 보일 때와 안 흘러갈 때를 분리해서 확인

① 스택을 확정

빌드의 CONFIG_USB_DEVICE_STACK 또는 CONFIG_USB_DEVICE_STACK_NEXT, 샘플 설정 파일과 header/API를 한 묶음으로 확인한다.

② 물리·컨트롤러

보드의 장치 모드, VBUS 감지 조건, endpoint 수·종류·FIFO 제약을 확인한다. 성공적으로 빌드됐다고 모든 컨트롤러 조합이 가능한 것은 아니다.

③ 열거

호스트의 descriptor 읽기와 configuration 선택 여부를 본다. 기존 스택의 USB_DC_CONFIGURED 또는 새 스택의 메시지를 해당 버전 방식으로 기록한다.

④ 기능 데이터

호스트가 맞는 class 드라이버와 앱을 열었는지, endpoint 방향·실제 주소·완료 callback·읽기 경로를 본다. CDC ACM이라면 DTR도 확인한다.

특히 composite 장치에서는 “CDC ACM은 되는데 HID는 없다”를 전체 USB 연결 실패로 묶지 말자. 각 function의 interface와 endpoint가 descriptor에 들어갔는지, 초기화 중 재배정된 실제 주소를 class가 읽는지, 컨트롤러가 그 조합을 감당하는지를 순서대로 확인한다.

7. Zephyr v3.7.0 고정 버전 근거

이 글의 API·상태·한계는 다음 v3.7.0 문서와 같은 태그의 Kconfig/header를 기준으로 정리했다. 그 뒤 릴리스의 폐기 일정이나 확장된 USB host 기능을 거꾸로 대입하지 않았다.