usb_dc 기반 장치 스택과 실험적인 UDC/USBD 장치 스택이 공존하므로, 아래 API 이름은 스택별로 읽어야 한다. 그림은 소스 경계를 설명하려고 새로 그린 것이며 보드 측정 결과가 아니다.1. 호스트에서 펌웨어까지, 네 층의 책임
호스트는 버스를 통해 컨트롤러와 통신하고 descriptor를 읽어 주소와 configuration을 정한다. 컨트롤러 드라이버는 endpoint 전송을 하드웨어에 연결하고, Zephyr 장치 스택은 EP0 표준 요청과 descriptor 응답을 처리한다. 클래스 또는 function은 CDC ACM·HID 같은 기능을 제공한다. 이 책임을 구분하면 “보드가 연결됨”과 “애플리케이션의 한 바이트가 호스트 앱에 도착함” 사이의 빈칸이 보인다.
↔ 좁은 화면에서는 그림을 좌우로 이동할 수 있습니다. 키보드에서는 그림 안에 초점을 놓고 방향키를 쓰세요.
버스 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()CDC ACM, HID, MSC 등 각 기능의 인터페이스·데이터 규칙을 제공한다.
예시: CDC ACM은 애플리케이션에 UART API를 제공function에는 하나 이상의 interface가 있다.왼쪽부터 호스트 ↔ 장치 컨트롤러 ↔ 장치 스택 ↔ 클래스·function의 계층 경계다. 화살표는 계층 간 상호작용이며 모든 EP0 표준 요청이 클래스까지 전달된다는 뜻은 아니다.
| 질문 | 먼저 볼 층 | 확인할 것 |
|---|---|---|
| 호스트 목록에 장치가 없다 | 컨트롤러·장치 스택 | 포트·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)를 단순 대체 이름처럼 섞으면 초기화·등록 순서를 잃는다.
| 항목 | 기존 장치 스택 | 새 UDC/USBD 장치 스택 |
|---|---|---|
| v3.7.0 상태 | 기존 사용 경로, 장래 폐기 계획이 문서에 명시됨 | 실험적 API, 변경 가능 |
| Kconfig 진입 | CONFIG_USB_DEVICE_STACK | CONFIG_USB_DEVICE_STACK_NEXT |
| 응용·클래스 쪽 API | usb_enable(status_cb), usb_read(), usb_write() 등 | USBD_DEVICE_DEFINE, usbd_add_configuration(), usbd_register_class(), usbd_init(), usbd_enable() 등 |
| 하위 드라이버 경계 | usb_dc API | UDC 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에 맞는 전송이 그 뒤에 가능해진다. 다음 순서는 개념도이므로 실제 호스트의 개별 요청 순서나 재시도 횟수를 고정한 로그로 해석하면 안 된다.
↔ 좁은 화면에서는 단계 끝까지 가로로 읽으세요.
usbd_init(); 아직 호스트 열거 완료가 아님usbd_enable() 가능. 기존 스택은 usb_enable().v3.7 기존 스택의 USB_DC_CONFIGURED는 configuration 완료 알림이다. 새 스택에서는 동일한 이름의 callback을 가정하지 말고 usbd_msg_register_cb() 메시지와 해당 class API를 확인한다.
| descriptor 개념 | 표현하는 것 | 실수하기 쉬운 점 |
|---|---|---|
| Device | 장치의 VID/PID 등 식별값 | Zephyr 샘플 VID 0x2FE3을 제품에 재사용하면 안 됨 |
| Configuration | 사용 가능한 function들의 한 묶음 | 호스트가 선택해야 사용 상태에 진입 |
| Interface | function이 노출하는 논리적 접점 | 한 function에 여러 interface가 있을 수 있음 |
| Endpoint | 방향·종류·주소가 있는 데이터 통로 | 선언한 기본 번호를 실제 런타임 주소로 하드코딩하지 않기 |
4. IN과 OUT은 항상 호스트 기준이다
EP0는 열거에 쓰이는 제어 경로다. 일반 데이터 endpoint의 IN은 장치 → 호스트, OUT은 호스트 → 장치다. 주소의 방향 비트와 endpoint 번호를 함께 봐야 한다. v3.7.0 문서에 따르면 class/function의 interface 번호와 endpoint 주소는 초기화 과정에서 재배정될 수 있다. 게다가 장치 컨트롤러마다 가능한 endpoint 수·종류·조합이 다르다.
↔ 가로 이동으로 각 행의 호스트·전송·장치 칸을 함께 보세요.
IN/OUT은 CPU 메모리 관점이 아니라 호스트 관점이다. 화살표는 데이터 방향만 나타내며 호스트가 버스 전송을 스케줄한다는 USB 역할은 그대로다.
| 경로 | v3.7.0 예시 | 확인 조건 |
|---|---|---|
| Control / EP0 | descriptor·표준 요청과 class/vendor 요청 | 호스트가 요청하고 stack이 응답하는지 |
| Bulk | CDC ACM 데이터 endpoint | IN/OUT 주소, class 준비, 호스트의 실제 읽기 |
| Interrupt | HID 입력 보고 endpoint | report descriptor·callback과 controller 지원 |
| Isochronous | Audio 관련 구현의 후보 | 스택·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이 되는 절차는 아니다.
| 하려는 일 | 기존 스택에서 확인 | 새 스택에서 확인 |
|---|---|---|
| 장치 활성화 | class 등록 후 usb_enable(status_cb) | context·descriptor·configuration·class 등록 후 usbd_init(ctx), usbd_enable(ctx) |
| 상태 알림 | USB_DC_RESET, USB_DC_CONFIGURED 등 status callback | usbd_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 기능을 거꾸로 대입하지 않았다.
- 기존 USB device stack: EP0, class, 주소 재배정, CDC ACM 제약
- 기존 stack API: usb_enable, read/write, transfer 및 callback
- 기존 usb_dc 컨트롤러 API와 상태 코드
- 실험적 새 USB device stack: context·configuration·class·초기화 순서
- 새 USBD API: class 등록·init·enable·message
- 새 UDC driver API: controller 요청 경계
- v3.7.0 legacy Kconfig · v3.7.0 new Kconfig · v3.7.0 USBD header
- v3.7.0 USB host API 분류: 별도 범위