USB Descriptor: Phân tích từng field và HID Report Descriptor từ góc firmware
Phân tích USB Descriptor, HID Report Descriptor của thiết bị composite CDC+HID.
1. Descriptor là gì và tại sao cần hiểu từng field
Descriptor là những mảng byte mà USB device gửi cho host trong quá trình enumeration. Host đọc, parse, rồi quyết định load driver nào và cấu hình giao tiếp như thế nào.
Với ST middleware, CubeMX generate sẵn descriptor trong usbd_desc.c. Nhưng khi cần:
- Thay đổi VID/PID để host load driver đúng
- Tăng/giảm max power để board chạy đúng power budget
- Thêm interface mới vào composite device
- Sửa HID Report Descriptor để gửi data theo format khác
…bạn phải hiểu từng field có ý nghĩa gì. Sửa sai một byte là enumeration fail hoặc host interpret data sai.
2. Device Descriptor - giới thiệu tổng quát về device
Device Descriptor là cấu trúc đầu tiên host đọc, luôn đúng 18 bytes:
/* usbd_desc.c - generated bởi CubeMX, đã annotate thêm */
uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] = {
0x12, /* [0] bLength = 18 bytes - luôn cố định */
0x01, /* [1] bDescriptorType = 0x01 (Device) */
0x00, /* [2] bcdUSB LSB - USB version: 0x0200 = USB 2.0 */
0x02, /* [3] bcdUSB MSB */
0x00, /* [4] bDeviceClass = 0 → class defined per interface */
/* 0xFF = Vendor specific, 0x00 = interface-defined */
0x00, /* [5] bDeviceSubClass */
0x00, /* [6] bDeviceProtocol */
0x40, /* [7] bMaxPacketSize0 = 64 bytes (EP0 max packet size) */
/* Valid values: 8, 16, 32, 64 */
0x83, /* [8] idVendor LSB = 0x0483 (ST Microelectronics) */
0x04, /* [9] idVendor MSB */
0x50, /* [10] idProduct LSB = 0x5750 */
0x57, /* [11] idProduct MSB */
0x00, /* [12] bcdDevice LSB = firmware version (0x0200 = v2.0) */
0x02, /* [13] bcdDevice MSB */
USBD_IDX_MFC_STR, /* [14] iManufacturer - index vào String Descriptor */
USBD_IDX_PRODUCT_STR, /* [15] iProduct */
USBD_IDX_SERIAL_STR, /* [16] iSerialNumber */
0x01, /* [17] bNumConfigurations = 1 */
};
Những field hay cần sửa:
bDeviceClass (byte 4): Nếu device chỉ có một class (ví dụ HID thuần), có thể đặt class code ở đây. Nếu là composite (nhiều class), đặt 0x00 - host sẽ đọc class code từ từng Interface Descriptor.
idVendor/idProduct (byte 8-11): Đây là cặp định danh quan trọng nhất. Host dùng VID/PID để load driver. 0x0483 là VID của ST - phù hợp để phát triển. Nếu ship sản phẩm, bạn cần VID riêng (mua từ USB-IF hoặc dùng sub-license). Với POS internal tool không ship ra ngoài, dùng ST VID với PID riêng của project là đủ.
bcdDevice (byte 12-13): Firmware version theo format BCD. 0x0200 = version 2.00. Hữu ích để debug - nhìn vào Device Manager biết ngay đang chạy firmware version nào.
iManufacturer/iProduct/iSerialNumber (byte 14-16): Index vào String Descriptor array. 0x00 = không có string. Những chuỗi này hiển thị trong Device Manager.
3. Configuration Descriptor - power và số interface
Configuration Descriptor không đứng một mình - nó là header của một khối lớn chứa tất cả Interface và Endpoint Descriptor bên dưới. Khi host request GET_DESCRIPTOR(Configuration), device trả toàn bộ khối này trong một lần.
/* Configuration Descriptor header - 9 bytes */
uint8_t USBD_CfgDesc[] = {
/* --- Configuration Descriptor --- */
0x09, /* [0] bLength = 9 */
0x02, /* [1] bDescriptorType = 0x02 (Configuration) */
LOBYTE(USB_CDC_CONFIG_DESC_SIZ), /* [2] wTotalLength LSB */
HIBYTE(USB_CDC_CONFIG_DESC_SIZ), /* [3] wTotalLength MSB */
/* wTotalLength = tổng size của toàn bộ khối
* (Config + tất cả Interface + Endpoint bên dưới) */
0x02, /* [4] bNumInterfaces = 2 (ví dụ: CDC có 2 interface) */
0x01, /* [5] bConfigurationValue = 1 - ID của config này */
0x00, /* [6] iConfiguration - string index, 0 = không có */
0xC0, /* [7] bmAttributes:
* bit 7: Reserved, must be 1
* bit 6: Self-powered (1 = device có nguồn riêng)
* bit 5: Remote Wakeup (1 = device có thể wake host)
* bit 4..0: Reserved, must be 0
* 0xC0 = 1100 0000 = Self-powered, no remote wakeup */
0x32, /* [8] bMaxPower = 0x32 = 50 × 2mA = 100mA
* Đơn vị là 2mA. 0x32=50 → 100mA
* 0xFA=250 → 500mA (max cho USB 2.0 bus-powered) */
/* --- Interface Descriptors và Endpoint Descriptors tiếp theo --- */
/* ... */
};
bmAttributes - hay bị đặt sai:
0x80= Bus-powered, no remote wakeup (device lấy nguồn từ USB, không tự wake được)0xC0= Self-powered (device có nguồn riêng - board POS thường có nguồn adapter)0xA0= Bus-powered + Remote Wakeup
Nếu device là Self-powered nhưng khai báo 0x80 (Bus-powered), host có thể limit current request - không ảnh hưởng chức năng nhưng không chính xác.
bMaxPower - quan trọng cho bus-powered device:
Đây là lượng current device request từ host. Nếu board POS cắm thẳng USB vào PC (không có adapter riêng), phải khai báo đúng. Khai báo quá thấp → board có thể thiếu điện. Khai báo quá cao → một số host từ chối enumerate.
4. Interface Descriptor - mô tả một “chức năng”
Mỗi Interface Descriptor mô tả một chức năng của device. CDC có 2 interface (Control và Data). Composite CDC+HID có 3 interface.
/* Interface Descriptor - 9 bytes */
0x09, /* [0] bLength = 9 */
0x04, /* [1] bDescriptorType = 0x04 (Interface) */
0x00, /* [2] bInterfaceNumber = 0 - thứ tự interface, bắt đầu từ 0 */
0x00, /* [3] bAlternateSetting = 0 - thường là 0 */
0x01, /* [4] bNumEndpoints = 1 - số endpoint (không tính EP0) */
0x02, /* [5] bInterfaceClass = 0x02 (CDC) */
/* 0x02 = CDC, 0x03 = HID, 0x08 = MSC, 0xFF = Vendor */
0x02, /* [6] bInterfaceSubClass = 0x02 (Abstract Control Model) */
0x01, /* [7] bInterfaceProtocol = 0x01 (AT Commands) */
0x00, /* [8] iInterface - string index */
bInterfaceClass/SubClass/Protocol là bộ ba quan trọng nhất. Host dùng ba giá trị này để bind class driver:
| Mục | Giá trị | Ghi chú |
|---|---|---|
| 0x02 / 0x02 / 0x01 | CDC ACM (Virtual COM Port) | Abstract Control Model + AT Commands. Windows/Linux bind cdc_acm driver. |
| 0x03 / 0x00 / 0x00 | HID - no subclass, no protocol | Custom HID, không phải keyboard/mouse standard. |
| 0x03 / 0x01 / 0x01 | HID Boot Keyboard | Keyboard chuẩn, BIOS có thể dùng trước khi load driver. |
| 0x03 / 0x01 / 0x02 | HID Boot Mouse | Mouse chuẩn. |
| 0x08 / 0x06 / 0x50 | Mass Storage (MSC) | SCSI + Bulk Only Transport - USB drive. |
| 0xFF / 0x00 / 0x00 | Vendor Specific | Không có class driver - cần WinUSB/Zadig trên Windows. |
5. Endpoint Descriptor - kênh dữ liệu
Mỗi endpoint (ngoài EP0) cần một Endpoint Descriptor:
/* Endpoint Descriptor - 7 bytes */
0x07, /* [0] bLength = 7 */
0x05, /* [1] bDescriptorType = 0x05 (Endpoint) */
0x81, /* [2] bEndpointAddress:
* bit 7: Direction (1=IN device→host, 0=OUT host→device)
* bit 6..4: Reserved, must be 0
* bit 3..0: Endpoint number
* 0x81 = IN, EP1
* 0x01 = OUT, EP1
* 0x82 = IN, EP2 */
0x03, /* [3] bmAttributes:
* bit 1..0: Transfer Type
* 0x00 = Control
* 0x01 = Isochronous
* 0x02 = Bulk
* 0x03 = Interrupt
* bit 3..2: Sync type (isochronous only)
* bit 5..4: Usage type (isochronous only)
* 0x03 = Interrupt */
0x40, 0x00, /* [4..5] wMaxPacketSize = 0x0040 = 64 bytes
* Full Speed: Interrupt max 64, Bulk max 64
* Low Speed: Interrupt max 8 */
0x0A, /* [6] bInterval:
* Interrupt/Isochronous: polling interval
* Full Speed interrupt: 1-255ms trực tiếp
* High Speed interrupt: 2^(bInterval-1) × 125µs
* Bulk/Control: ignored (thường đặt 0)
* 0x0A = 10ms polling */
bEndpointAddress - hay bị nhầm:
Bit 7 quyết định direction từ góc nhìn của device:
0x81= EP1 IN = device gửi lên host (host đọc)0x01= EP1 OUT = host gửi xuống device (device nhận)
Hai endpoint cùng số nhưng khác direction (EP1 IN và EP1 OUT) là hai endpoint riêng biệt.
Địa chỉ endpoint không trùng nhau trong cùng configuration. STM32 Full Speed có tối đa 8 endpoint (EP0 + EP1-EP7), mỗi endpoint có thể IN hoặc OUT hoặc cả hai tùy hardware.
6. HID Descriptor - đặc biệt của HID class
HID interface cần thêm một descriptor đặc biệt nằm giữa Interface Descriptor và Endpoint Descriptor:
/* HID Descriptor - 9 bytes (minimum) */
0x09, /* [0] bLength = 9 */
0x21, /* [1] bDescriptorType = 0x21 (HID) */
0x11, 0x01, /* [2..3] bcdHID = 0x0111 - HID spec version 1.11 */
0x00, /* [4] bCountryCode = 0 (not localized) */
/* 0x21 = US keyboard layout */
0x01, /* [5] bNumDescriptors = 1 - số Report Descriptor */
0x22, /* [6] bDescriptorType = 0x22 (Report Descriptor) */
LOBYTE(HID_REPORT_DESC_SIZE), /* [7] wDescriptorLength LSB */
HIBYTE(HID_REPORT_DESC_SIZE), /* [8] wDescriptorLength MSB */
Host đọc HID Descriptor này để biết Report Descriptor dài bao nhiêu, rồi gửi thêm GET_DESCRIPTOR(Report) request để lấy Report Descriptor thật sự.
Thứ tự trong Configuration Descriptor phải đúng:
Interface Descriptor (HID)
HID Descriptor ← nằm trước Endpoint
Endpoint Descriptor (Interrupt IN)
Nếu đặt sai thứ tự, host parse sai hoặc không nhận được HID Report Descriptor.
7. HID Report Descriptor - ngôn ngữ mô tả data
Đây là phần phức tạp và thú vị nhất của HID. Report Descriptor không mô tả hardware - nó mô tả cấu trúc của data packet mà device gửi/nhận.
Host đọc Report Descriptor một lần khi enumerate, sau đó biết cách interpret mọi byte trong mọi report về sau.
Cú pháp: Item format
Mỗi item trong Report Descriptor là 1-5 bytes:
Byte 0: Tag (bit 7..4) | Type (bit 3..2) | Size (bit 1..0)
Size: 00=0 bytes, 01=1 byte, 10=2 bytes, 11=4 bytes
Byte 1..n: Data (nếu Size > 0)
Ví dụ: 0x05, 0x01
0x05= tag=0000 (Usage Page), type=01 (Global), size=01 (1 byte data)0x01= value = 0x01 (Generic Desktop Controls)
Các item quan trọng
Global items - áp dụng cho tất cả field sau đó cho đến khi thay đổi:
0x05, xx → Usage Page : namespace cho Usage values
0x15, xx → Logical Minimum : giá trị nhỏ nhất data field có thể có
0x25, xx → Logical Maximum : giá trị lớn nhất
0x75, xx → Report Size : số bit của một field
0x95, xx → Report Count : số field
0x85, xx → Report ID : ID đặt ở byte đầu report (nếu dùng)
Local items - chỉ áp dụng cho item kế tiếp:
0x09, xx → Usage : ý nghĩa cụ thể của field
0x19, xx → Usage Minimum : dùng khi có range
0x29, xx → Usage Maximum
Main items - tạo field thực sự:
0x81, xx → Input : field trong report gửi từ device lên host (IN)
0x91, xx → Output : field trong report gửi từ host xuống device (OUT)
0xA1, xx → Collection : nhóm các field lại
0xC0 → End Collection
bmAttributes của Input/Output:
0x02 = Data, Variable, Absolute
Data : có thể thay đổi (≠ Constant)
Variable : mỗi field là một giá trị riêng (≠ Array)
Absolute : giá trị tuyệt đối (≠ Relative)
0x03 = Constant (padding - không dùng, chỉ để align byte)
0x06 = Data, Variable, Relative (dùng cho delta movement như chuột)
Usage Page - namespace cho Usage
Usage Page giống namespace trong lập trình. Cùng một Usage value (ví dụ 0x30) có ý nghĩa khác nhau tùy Usage Page:
Usage Page 0x01 (Generic Desktop): 0x30 = X axis
Usage Page 0x09 (Button): 0x30 = Button 48
Các Usage Page hay dùng:
0x01 = Generic Desktop Controls (X/Y/Z, mouse, keyboard, joystick)
0x07 = Keyboard/Keypad
0x08 = LEDs
0x09 = Buttons
0x0D = Digitizer (touchscreen, pen)
0xFF = Vendor Defined (custom, no standard meaning)
Collection - nhóm field có liên quan
0xA1, 0x01 → Collection (Application) : nhóm top-level, mỗi HID device cần ít nhất 1
0xA1, 0x02 → Collection (Logical) : nhóm logic trong Application
0xA1, 0x00 → Collection (Physical) : nhóm dữ liệu từ cùng vị trí vật lý
0xC0 → End Collection
8. Ví dụ thực tế: đọc HID Mouse Report Descriptor từng byte
Mouse là ví dụ cổ điển nhất. Đây là descriptor của ST middleware:
/* HID_MOUSE_ReportDesc trong usbd_hid.c */
static uint8_t HID_MOUSE_ReportDesc[] = {
0x05, 0x01, /* Usage Page (Generic Desktop Controls) */
0x09, 0x02, /* Usage (Mouse) */
0xA1, 0x01, /* Collection (Application) ─────────────── */
0x09, 0x01, /* Usage (Pointer) */
0xA1, 0x00, /* Collection (Physical) ──────────────── */
0x05, 0x09, /* Usage Page (Button) */
0x19, 0x01, /* Usage Minimum (Button 1) */
0x29, 0x03, /* Usage Maximum (Button 3) */
0x15, 0x00, /* Logical Minimum (0) - button released */
0x25, 0x01, /* Logical Maximum (1) - button pressed */
0x95, 0x03, /* Report Count (3) - 3 buttons */
0x75, 0x01, /* Report Size (1) - 1 bit per button */
0x81, 0x02, /* Input (Data, Variable, Absolute) */
/* → 3 bits: [btn3][btn2][btn1] */
0x95, 0x01, /* Report Count (1) */
0x75, 0x05, /* Report Size (5) - 5 bits padding */
0x81, 0x03, /* Input (Constant) - padding to byte */
/* → 5 bits: [0][0][0][0][0] */
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x30, /* Usage (X) */
0x09, 0x31, /* Usage (Y) */
0x09, 0x38, /* Usage (Wheel) */
0x15, 0x81, /* Logical Minimum (-127) */
0x25, 0x7F, /* Logical Maximum (127) */
0x75, 0x08, /* Report Size (8) - 8 bits per axis */
0x95, 0x03, /* Report Count (3) - X, Y, Wheel */
0x81, 0x06, /* Input (Data, Variable, Relative) */
/* → 3 bytes: X delta, Y delta, Wheel */
0xC0, /* End Collection (Physical) ─────────────*/
0xC0, /* End Collection (Application) ────────────*/
};
Report packet sẽ có cấu trúc:
Byte 0: [pad5][btn3][btn2][btn1] ← 3 button bits + 5 padding bits
Byte 1: X movement (-127..+127) ← signed delta
Byte 2: Y movement (-127..+127)
Byte 3: Wheel (-127..+127)
Tổng: 4 bytes mỗi report. Host parse đúng cấu trúc này mà không cần code parser riêng - Report Descriptor đã mô tả đầy đủ.
9. Ví dụ thực tế: custom HID cho POS card reader
Trên POS system, đầu đọc thẻ cần gửi dữ liệu track lên host. Thiết kế Report Descriptor cho use case này:
Yêu cầu: gửi 64 bytes data (track data từ thẻ từ) với 1 byte status ở đầu.
/* Custom HID Report Descriptor cho card reader */
static uint8_t HID_CardReader_ReportDesc[] = {
/* Input Report - device gửi lên host */
0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */
0x09, 0x01, /* Usage (Vendor Usage 1) */
0xA1, 0x01, /* Collection (Application) */
/* Report ID 1: Card Data IN */
0x85, 0x01, /* Report ID (1) */
0x09, 0x01, /* Usage (Input Report Data) */
0x15, 0x00, /* Logical Minimum (0) */
0x26, 0xFF, 0x00, /* Logical Maximum (255) */
0x75, 0x08, /* Report Size (8 bits) */
0x95, 0x41, /* Report Count (65) = 1 status + 64 data */
0x81, 0x02, /* Input (Data, Variable, Absolute) */
/* Report ID 2: Command OUT - host gửi lệnh xuống */
0x85, 0x02, /* Report ID (2) */
0x09, 0x02, /* Usage (Output Report Data) */
0x15, 0x00, /* Logical Minimum (0) */
0x26, 0xFF, 0x00, /* Logical Maximum (255) */
0x75, 0x08, /* Report Size (8 bits) */
0x95, 0x08, /* Report Count (8) = 8 bytes command */
0x91, 0x02, /* Output (Data, Variable, Absolute) */
0xC0, /* End Collection */
};
Report IN sẽ có cấu trúc:
Byte 0: Report ID = 0x01
Byte 1: Status (0x00=OK, 0x01=No card, 0x02=Read error, ...)
Byte 2..65: Track data (64 bytes)
Firmware gửi report:
typedef struct __attribute__((packed)) {
uint8_t report_id; /* Luôn = 0x01 */
uint8_t status;
uint8_t track_data[64];
} CardReaderReport_t; /* Tổng 66 bytes */
void CardReader_SendData(uint8_t status,
const uint8_t *track, uint8_t len)
{
CardReaderReport_t report = {0};
report.report_id = 0x01;
report.status = status;
memcpy(report.track_data, track, MIN(len, 64));
USBD_HID_SendReport(&hUsbDeviceFS,
(uint8_t*)&report, sizeof(report));
}
10. Ví dụ thực tế: Composite CDC + HID cho POS
Một POS terminal cần cả hai: CDC để debug/update và HID để card reader. Đây là Configuration Descriptor đầy đủ:
#define USB_COMPOSITE_DESC_SIZE (9 + 8 + 9 + 5 + 5 + 4 + 5 + 7 + 7 + 9 + 9 + 7)
/* Cfg IAD CDC_Ctrl CDC_Func×3 CDC_EP CDC_Data CDC_EP×2 HID HID_Desc HID_EP */
uint8_t USBD_CompositeDesc[] = {
/* ===== Configuration Descriptor ===== */
0x09, 0x02,
LOBYTE(USB_COMPOSITE_DESC_SIZE),
HIBYTE(USB_COMPOSITE_DESC_SIZE),
0x03, /* bNumInterfaces = 3 (CDC Control + CDC Data + HID) */
0x01, /* bConfigurationValue */
0x00, /* iConfiguration */
0xC0, /* bmAttributes: Self-powered */
0x32, /* bMaxPower: 100mA */
/* ===== IAD (Interface Association Descriptor) cho CDC ===== */
/* IAD nói cho host biết Interface 0 và 1 thuộc cùng một function (CDC) */
0x08, /* bLength */
0x0B, /* bDescriptorType = 0x0B (IAD) */
0x00, /* bFirstInterface = 0 */
0x02, /* bInterfaceCount = 2 (CDC dùng 2 interface) */
0x02, /* bFunctionClass = CDC */
0x02, /* bFunctionSubClass = ACM */
0x01, /* bFunctionProtocol = AT Commands */
0x00, /* iFunction */
/* ===== Interface 0: CDC Control ===== */
0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x01, 0x00,
/* CDC Functional Descriptors (Header, ACM, Union, Call Mgmt) */
0x05, 0x24, 0x00, 0x10, 0x01, /* Header */
0x04, 0x24, 0x02, 0x02, /* ACM */
0x05, 0x24, 0x06, 0x00, 0x01, /* Union: ctrl=0, data=1 */
0x05, 0x24, 0x01, 0x00, 0x01, /* Call Mgmt */
/* Notification Endpoint (Interrupt IN) */
0x07, 0x05, 0x82, 0x03, 0x08, 0x00, 0xFF,
/* EP2 IN, Interrupt, 8 bytes, 255ms interval */
/* ===== Interface 1: CDC Data ===== */
0x09, 0x04, 0x01, 0x00, 0x02, 0x0A, 0x00, 0x00, 0x00,
/* Bulk OUT - host gửi data xuống */
0x07, 0x05, 0x01, 0x02, 0x40, 0x00, 0x00,
/* EP1 OUT, Bulk, 64 bytes */
/* Bulk IN - device gửi data lên */
0x07, 0x05, 0x81, 0x02, 0x40, 0x00, 0x00,
/* EP1 IN, Bulk, 64 bytes */
/* ===== Interface 2: HID (Card Reader) ===== */
0x09, 0x04, 0x02, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00,
/* Interface 2, 1 endpoint, Class=HID, no subclass, no protocol */
/* HID Descriptor */
0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22,
LOBYTE(HID_CARD_REPORT_DESC_SIZE),
HIBYTE(HID_CARD_REPORT_DESC_SIZE),
/* Interrupt IN Endpoint cho HID */
0x07, 0x05, 0x83, 0x03, 0x40, 0x00, 0x01,
/* EP3 IN, Interrupt, 64 bytes, 1ms interval */
};
Endpoint allocation trong composite này:
EP0 IN/OUT - Control (enumeration, mandatory)
EP1 OUT - CDC Data OUT (host → device)
EP1 IN - CDC Data IN (device → host)
EP2 IN - CDC Notification (Interrupt IN, 255ms)
EP3 IN - HID Card Reader (Interrupt IN, 1ms)
IAD là bắt buộc cho composite có CDC. Nếu thiếu IAD, Windows không bind CDC driver đúng cách. CDC cần 2 interface kết hợp với nhau - IAD báo cho host biết điều đó.
11. String Descriptor - text hiển thị trong Device Manager
String Descriptor là optional nhưng có thì debug dễ hơn:
/* String Descriptor 0 - Language ID, bắt buộc nếu dùng string */
uint8_t USBD_LangIDDesc[] = {
0x04, /* bLength */
0x03, /* bDescriptorType = String */
0x09, 0x04, /* wLANGID = 0x0409 (English US) */
};
/* String Descriptor 1 - Manufacturer */
/* Unicode UTF-16LE, không có null terminator */
uint8_t USBD_ManufacturerDesc[] = {
0x14, /* bLength = 2 + 9 chars × 2 bytes = 20 */
0x03, /* bDescriptorType */
'M',0, 'y',0, ' ',0, 'C',0, 'o',0, 'm',0, 'p',0, 'a',0, 'n',0,
};
/* String Descriptor 2 - Product */
uint8_t USBD_ProductDesc[] = {
0x18,
0x03,
'P',0, 'O',0, 'S',0, ' ',0, 'T',0, 'e',0, 'r',0,
'm',0, 'i',0, 'n',0, 'a',0, 'l',0,
};
Trên Windows, chuỗi này hiển thị trong Device Manager → Properties → Details. Giúp phân biệt khi có nhiều board cắm cùng lúc.
12. Debug descriptor - tool và kỹ thuật
USB Device Tree Viewer (Windows, free): Hiển thị descriptor tree dạng parsed, từng field có label. Download tại uwe-sieber.de.
Wireshark + USBPcap: Capture traffic khi enumerate, filter usb.request_in để xem các GET_DESCRIPTOR request và response.
Kiểm tra size descriptor:
Lỗi hay gặp nhất là wTotalLength trong Configuration Descriptor sai - không khớp với tổng size thực tế của mảng:
/* Kiểm tra khi build */
static_assert(sizeof(USBD_CompositeDesc) == USB_COMPOSITE_DESC_SIZE,
"Descriptor size mismatch!");
static_assert fail at compile time nếu tính sai - tốt hơn crash runtime.
Kiểm tra HID Report Descriptor với HID Descriptor Tool:
ST cung cấp HID Descriptor Tool (dt2_4.exe) để validate Report Descriptor - tải từ usb.org. Paste array vào, tool parse và hiện từng field. Nếu descriptor sai cú pháp, tool báo lỗi.
Checklist descriptor trước khi test
13. Tóm tắt
Descriptor là “hợp đồng” giữa firmware và host. Sai một byte là host hiểu sai hoặc từ chối communicate.
Device Descriptor: VID/PID quyết định driver, bDeviceClass=0 cho composite.
Configuration Descriptor: wTotalLength phải khớp size thực tế, bNumInterfaces phải đúng, power config phải phản ánh hardware.
Interface Descriptor: bInterfaceClass/SubClass/Protocol quyết định class driver. Composite CDC cần IAD.
Endpoint Descriptor: bit 7 của bEndpointAddress = direction, bInterval phải power-of-2 cho high-speed interrupt.
HID Report Descriptor: ngôn ngữ mô tả data format. Usage Page là namespace, Usage là ý nghĩa field, Report Size × Report Count = số bit. Padding Input(Constant) để align theo byte.
Đọc tiếp
- USB Transfer Types: Interrupt, Bulk, Control và Isochronous - cơ chế của từng loại transfer dùng descriptor trên
- USB Device STM32: CDC, HID và ST Middleware - ứng dụng thực tế
- USB HID Usage Tables - usb.org - reference đầy đủ cho Usage Page và Usage values
- HID Descriptor Tool - usb.org - validate Report Descriptor
Tài liệu tham khảo
- USB 2.0 Specification - Chapter 9: USB Device Framework
- HID Class Specification 1.11 - HID Descriptor, Report Descriptor format
- HID Usage Tables 1.5 - Usage Page, Usage values
- USB in a NutShell Chapter 5 - Descriptor format
- USB Device Tree Viewer - parse và hiển thị descriptor
Thấy nội dung này hữu ích?
Lưu lại hoặc chia sẻ cho người cũng đang học firmware, BIOS/UEFI và embedded systems.
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 Device STM32 nâng cao: Vendor Request, Bulk, DMA và Debug với JTAG
USB Device STM32: xử lý vendor request và truyền qua DMA cho bulk và interrupt.
USB Device trên STM32: CDC, HID và cách dùng ST Middleware thực tế
USB Device trên STM32 với CDC và HID trong dự án thực tế.
USB Transfer Types: Interrupt, Bulk, Control và Isochronous từ góc firmware
Giải thích về 4 loại USB transfer.
Đọc thêm về BIOS/UEFI
Khám phá các bài viết về BIOS/UEFI, embedded firmware, debugging và system-level thinking.