USB Descriptor Overview là gì?
Cấu trúc phân cấp USB descriptor: Device → Configuration → Interface → Endpoint. wTotalLength, bDescriptorType, cách host đọc từng bước và điểm dễ gây enumeration fail.
Descriptor là tập hợp các struct mà device dùng để tự giới thiệu với host trong quá trình enumeration. Host không biết gì về device trước khi đọc descriptor — class, speed, số endpoint, loại transfer — tất cả đều lấy từ đây.
Nếu chỉ đọc spec lần đầu, các descriptor này trông như nhiều struct rời rạc. Nhưng trong lúc debug, nên nhìn chúng như một cây: sai ở node cha thì host thường không đi tiếp được tới node con.
1. Cấu trúc phân cấp
Device Descriptor (1)
└── Configuration Descriptor (1..n)
└── Interface Descriptor (1..n)
├── Class-specific Descriptor (HID, CDC...)
└── Endpoint Descriptor (1..n)
Device Descriptor mô tả device ở mức cao nhất: USB version, VID, PID, class, số configuration.
Configuration Descriptor mô tả một chế độ hoạt động của device. Phần lớn device chỉ có 1 configuration. Configuration Descriptor chứa wTotalLength — tổng số byte của toàn bộ configuration block bao gồm tất cả Interface và Endpoint Descriptor bên trong.
Interface Descriptor mô tả một “chức năng” trong configuration. Composite device (ví dụ CDC + HID) có 2 interface. Mỗi interface có class, subclass và protocol riêng.
Endpoint Descriptor mô tả một kênh data của interface: địa chỉ endpoint, loại transfer, kích thước packet tối đa, polling interval.
2. Hai byte đầu của mọi descriptor
Mọi USB descriptor đều bắt đầu bằng hai byte:
Offset 0 : bLength - tổng số byte của descriptor này
Offset 1 : bDescriptorType - loại descriptor
bDescriptorType:
0x01 = Device
0x02 = Configuration
0x03 = String
0x04 = Interface
0x05 = Endpoint
0x21 = HID
0x22 = HID Report Descriptor
Host dùng bLength để biết bao nhiêu byte cần đọc cho descriptor này, rồi bDescriptorType để biết parse như thế nào. Nếu sai một trong hai, host không thể parse đúng phần còn lại.
3. wTotalLength — điểm dễ gây lỗi nhất
Trong các field của Configuration Descriptor, wTotalLength là field nhỏ nhưng gây lỗi nhiều hơn vẻ ngoài của nó.
wTotalLength là tổng số byte của toàn bộ configuration block, bao gồm chính Configuration Descriptor và tất cả Interface, Endpoint, class-specific Descriptor bên trong.
wTotalLength = sizeof(Configuration Descriptor) 9 byte
+ sizeof(Interface Descriptor) 9 byte
+ sizeof(HID Descriptor) 9 byte
+ sizeof(Endpoint Descriptor) 7 byte
─────────────────────────────────────────────
34 byte
4. Host đọc descriptor theo thứ tự nào?
Thứ tự chính xác có thể khác nhau tùy host, nhưng flow phổ biến là:
1. GET_DESCRIPTOR(Device, 8 byte) biết bMaxPacketSize0
2. GET_DESCRIPTOR(Device, đầy đủ) VID, PID, bNumConfigurations
3. GET_DESCRIPTOR(Configuration, 9 byte) biết wTotalLength
4. GET_DESCRIPTOR(Configuration, full) toàn bộ config block
5. GET_DESCRIPTOR(String, 0) language ID list
6. GET_DESCRIPTOR(String, n) Manufacturer, Product, Serial...
Host thường đọc 9 byte đầu của Configuration Descriptor trước để biết wTotalLength, sau đó mới request lại toàn bộ configuration block. Sau bước 4, host đã có đủ thông tin để load driver.
5. Class-specific descriptor
Một số USB class thêm descriptor riêng giữa Interface và Endpoint Descriptor. HID thêm HID Descriptor (9 byte) chứa thông tin về HID Report Descriptor:
Interface Descriptor (bInterfaceClass = 0x03 = HID)
├── HID Descriptor (bDescriptorType = 0x21)
└── Endpoint Descriptor IN Interrupt
Với HID, host còn đọc thêm HID Report Descriptor bằng GET_DESCRIPTOR với descriptor type 0x22 trên interface HID. Đây là descriptor do HID class định nghĩa, không nằm trực tiếp trong configuration block.
6. Lỗi thường gặp
| Mục | Giá trị | Ghi chú |
|---|---|---|
| wTotalLength sai | Host đọc thiếu/thừa, endpoint không được nhận diện | |
| bLength sai trong một descriptor | Host parse sai toàn bộ phần còn lại | |
| bNumEndpoints không khớp số Endpoint Descriptor | Một số endpoint bị bỏ qua | |
| Endpoint address/direction trùng nhau | Transfer fail không rõ lý do | |
| HID Descriptor thiếu hoặc đặt sai vị trí | Host không load HID driver |
Khi debug descriptor, nên nhớ rằng field này nhỏ nhưng nó quyết định host sẽ hiểu toàn bộ phần còn lại như thế nào.
Bài liên quan
- USB Enumeration Process là gì?
- USB Transfer Types là gì?
- HID Report Descriptor là gì?
- USB Descriptor từng field và HID Report Descriptor (blog)
Nguồn tham khảo public
- USB 2.0 Specification, Chapter 9.6: Standard USB Descriptor Definitions
- USB in a NutShell — beyondlogic.org, Chapter 5: USB Descriptors
Ghi chú từ project thực tế
- CubeMX sinh mouse descriptor mặc định dù chọn HID Keyboard. Luôn verify bằng USBView sau enumerate, không phải bằng đọc source.
wTotalLengthphải tính lại thủ công khi thêm interface.static_assertso sánh vớisizeof(array)giúp bắt lỗi lúc build.- Composite cần
bDeviceClass=0xEF/0x02/0x01khi dùng IAD. Thiếu triple này, USBView báo*!*ERROR: device class should be Multi-interface Function 0xEF.
Xem chi tiết: bài 3 USB Descriptor
Bài viết này hữu ích với bạn?
Chia sẻ, góp ý, hoặc ủng hộ nếu bạn thấy nội dung này có giá trị.
Nội dung liên quan
Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.
USB Descriptor là gì?
Giải thích khái niệm và cấu trúc của USB Descriptor kèm ví dụ. Lưu ý về Boot Protocol trong Configuration Descriptor
CubeMX USB pitfalls thường gặp
Những điểm CubeMX không làm đúng khi generate USB code: HID mouse descriptor thay vì keyboard, NVIC không bật, Sof_enable DISABLE, USBD_static_malloc sai kích thước.
HID Keyboard Report Format là gì?
Cấu trúc 8 byte của HID keyboard report: modifier byte, reserved byte, 6 keycode slot. Cách gửi report từ STM32 middleware.
Biến note thành bài viết hoàn chỉnh
Notes là nơi ghi nhanh khái niệm.