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.
1. Tiếp theo từ bài trước
Bài trước đã cover CDC và HID - hai class phổ biến nhất, không cần driver riêng. Bài này đi vào những thứ phức tạp hơn mà tutorial thường bỏ qua:
- Vendor request - control transfer tùy chỉnh để gửi lệnh điều khiển từ host
- Bulk endpoint và tại sao cần driver riêng trên Windows
- DMA cho USB endpoint - tránh CPU busy-wait
- Multi-interface - tổ chức code khi có nhiều endpoint
- Log qua USB - dùng CDC như serial monitor
- Debug với JTAG - gotcha breakpoint làm hỏng enumeration
2. Vendor Request - lệnh điều khiển tùy chỉnh
Trong USB, control transfer qua Endpoint 0 là cách host gửi lệnh cấu hình và điều khiển. Ngoài các standard request (GET_DESCRIPTOR, SET_ADDRESS…) và class-specific request (CDC SET_LINE_CODING, HID SET_REPORT…), còn có vendor request - tùy chỉnh hoàn toàn, format do bạn định nghĩa.
Dùng vendor request khi cần gửi lệnh ngắn từ host (ví dụ: “reset device”, “set mode X”, “get status”) mà không muốn tốn bulk endpoint cho việc đó.
Cấu trúc Setup Packet
Mỗi control transfer bắt đầu bằng một Setup Packet 8 byte:
Offset Field Size Ý nghĩa
0 bmRequestType 1 Direction + Type + Recipient
1 bRequest 1 Request code - do bạn định nghĩa
2 wValue 2 Parameter 1
4 wIndex 2 Parameter 2
6 wLength 2 Số byte data theo sau (nếu có)
Với vendor request, bmRequestType bit 5:6 = 0b10 (Vendor type). Ví dụ:
0x40= Host → Device, Vendor, Device recipient0xC0= Device → Host, Vendor, Device recipient
Xử lý trong custom USB class
ST middleware gọi USBD_ClassName_Setup() khi có control transfer đến:
/* Định nghĩa request code của project */
#define VENDOR_REQ_SET_MODE 0x01
#define VENDOR_REQ_GET_STATUS 0x02
#define VENDOR_REQ_RESET 0x03
#define VENDOR_REQ_SET_CONFIG 0x04 /* Kèm data */
static uint8_t s_vendor_rx_buf[64]; /* Buffer nhận data của vendor request */
static uint8_t s_vendor_status[8]; /* Buffer gửi status lên host */
static uint8_t USBD_Custom_Setup(USBD_HandleTypeDef *pdev,
USBD_SetupReqTypedef *req)
{
/* Chỉ xử lý vendor request */
if ((req->bmRequest & USB_REQ_TYPE_MASK) != USB_REQ_TYPE_VENDOR) {
return USBD_OK;
}
switch (req->bRequest) {
/* Host → Device, không có data stage */
case VENDOR_REQ_RESET:
RequestQueue_Enqueue(REQ_RESET, req->wValue, req->wIndex,
NULL, 0);
USBD_CtlSendStatus(pdev); /* Gửi ACK (zero-length status) */
break;
case VENDOR_REQ_SET_MODE:
RequestQueue_Enqueue(REQ_SET_MODE, req->wValue, req->wIndex,
NULL, 0);
USBD_CtlSendStatus(pdev);
break;
/* Host → Device, có data stage - cần đọc thêm data */
case VENDOR_REQ_SET_CONFIG:
if (req->wLength > 0 && req->wLength <= sizeof(s_vendor_rx_buf)) {
/* Chuẩn bị buffer nhận, middleware sẽ gọi EP0_RxReady khi xong */
USBD_CtlPrepareRx(pdev, s_vendor_rx_buf, req->wLength);
} else {
USBD_CtlError(pdev, req);
}
break;
/* Device → Host, gửi data lên */
case VENDOR_REQ_GET_STATUS:
s_vendor_status[0] = Device_GetMode();
s_vendor_status[1] = Device_GetErrorCode();
/* ... fill thêm nếu cần ... */
USBD_CtlSendData(pdev, s_vendor_status,
MIN(req->wLength, sizeof(s_vendor_status)));
break;
default:
USBD_CtlError(pdev, req);
break;
}
return USBD_OK;
}
/* Gọi khi data stage của VENDOR_REQ_SET_CONFIG hoàn tất */
static uint8_t USBD_Custom_EP0_RxReady(USBD_HandleTypeDef *pdev)
{
/* s_vendor_rx_buf giờ có data từ host */
RequestQueue_Enqueue(REQ_SET_CONFIG, 0, 0,
s_vendor_rx_buf, /* len đã lưu từ setup */ 0);
return USBD_OK;
}
3. Request Queue - tách interrupt context khỏi processing
Vendor request handler chạy trong USB interrupt context. Không nên xử lý logic phức tạp tại đây - chỉ validate và enqueue:
/* ipc/usb_request_queue.h */
typedef enum {
REQ_RESET = 0x01,
REQ_SET_MODE = 0x02,
REQ_SET_CONFIG = 0x03,
} UsbRequestType_t;
typedef struct {
UsbRequestType_t type;
uint16_t wValue;
uint16_t wIndex;
uint8_t data[64];
uint16_t data_len;
} UsbRequest_t;
/* Ring buffer đơn giản cho request queue */
#define USB_REQ_QUEUE_SIZE 8
typedef struct {
UsbRequest_t items[USB_REQ_QUEUE_SIZE];
volatile uint8_t head;
volatile uint8_t tail;
volatile uint8_t count;
} UsbRequestQueue_t;
static UsbRequestQueue_t s_req_queue = {0};
/* Gọi từ interrupt - chỉ enqueue, không process */
bool RequestQueue_Enqueue(UsbRequestType_t type, uint16_t wValue,
uint16_t wIndex,
const uint8_t *data, uint16_t data_len)
{
if (s_req_queue.count >= USB_REQ_QUEUE_SIZE) {
return false; /* Queue đầy - drop request */
}
UsbRequest_t *slot = &s_req_queue.items[s_req_queue.tail];
slot->type = type;
slot->wValue = wValue;
slot->wIndex = wIndex;
slot->data_len = (data && data_len > 0) ? data_len : 0;
if (slot->data_len > 0) {
memcpy(slot->data, data, slot->data_len);
}
/* Atomic update - head/tail đọc bởi task, ghi bởi ISR */
s_req_queue.tail = (s_req_queue.tail + 1) % USB_REQ_QUEUE_SIZE;
/* Trong FreeRTOS: dùng xTaskNotifyGiveFromISR thay vì count++ */
BaseType_t woken = pdFALSE;
xTaskNotifyGiveFromISR(xUsbHandlerTask, &woken);
portYIELD_FROM_ISR(woken);
return true;
}
/* Gọi từ task - dequeue và process */
bool RequestQueue_Dequeue(UsbRequest_t *out)
{
taskENTER_CRITICAL();
if (s_req_queue.head == s_req_queue.tail) {
taskEXIT_CRITICAL();
return false;
}
*out = s_req_queue.items[s_req_queue.head];
s_req_queue.head = (s_req_queue.head + 1) % USB_REQ_QUEUE_SIZE;
taskEXIT_CRITICAL();
return true;
}
Task xử lý request:
void TaskUsbHandler(void *arg)
{
UsbRequest_t req;
for (;;) {
/* Chờ notification từ ISR */
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
while (RequestQueue_Dequeue(&req)) {
switch (req.type) {
case REQ_RESET:
Device_Reset();
break;
case REQ_SET_MODE:
Device_SetMode((uint8_t)req.wValue);
break;
case REQ_SET_CONFIG:
Device_ApplyConfig(req.data, req.data_len);
break;
}
}
}
}
4. Bulk Endpoint - throughput cao, nhưng cần driver
Bulk transfer dùng khi cần truyền lượng data lớn (firmware update, log dump, sensor data stream). Không có guaranteed timing như interrupt, nhưng throughput cao hơn nhiều - lý thuyết tới ~1 MB/s ở Full Speed.
Bulk trên Windows cần driver
Đây là điểm khác biệt lớn nhất so với HID/CDC.
HID và CDC có class driver built-in trong Windows - không cần cài gì. Bulk endpoint của Vendor class thì không - Windows không biết dùng driver nào để mở pipe.
Có hai hướng:
Hướng 1: WinUSB (khuyến nghị)
WinUSB là generic driver của Microsoft - có sẵn trong Windows Vista+, không cần cài thêm. Muốn Windows tự động dùng WinUSB cho device của bạn, thêm Microsoft OS Descriptor (WCID) vào firmware:
/* String Descriptor index 0xEE - Microsoft OS String Descriptor */
/* Khi host đọc string index 0xEE, biết device hỗ trợ Microsoft OS Descriptor */
const uint8_t USBD_MS_OS_StringDesc[] = {
0x12, /* bLength = 18 */
0x03, /* bDescriptorType = String */
/* "MSFT100" in UTF-16LE */
'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0,
0xEE, /* qwSignature: vendor code để host dùng khi hỏi tiếp */
0x00 /* padding */
};
/* Extended Compatible ID OS Feature Descriptor */
/* Báo cho Windows biết dùng WinUSB driver */
const uint8_t USBD_MS_CompatID[] = {
0x28, 0x00, 0x00, 0x00, /* dwLength = 40 bytes */
0x00, 0x01, /* bcdVersion = 1.0 */
0x04, 0x00, /* wIndex = 0x0004 (Extended Compat ID) */
0x01, /* bCount = 1 function */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* reserved */
/* Function 0 */
0x00, /* bFirstInterfaceNumber */
0x01, /* reserved */
/* compatibleID = "WINUSB\0\0" */
'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00,
/* subCompatibleID = 8 zeros */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 /* reserved */
};
Khi Windows kết nối với device có WCID, nó tự động cài WinUSB driver - không cần user làm gì. Host app sau đó dùng WinUSB API hoặc libusb để open và transfer.
Hướng 2: Zadig - cài thủ công một lần
Nếu firmware không có WCID, dùng Zadig (zadig.akeo.ie) để thủ công cài WinUSB hoặc libusbK cho device. Phù hợp cho development, không phù hợp cho end-user product.
Zadig workflow:
1. Cắm USB device vào Windows
2. Device Manager hiện "Unknown device" hoặc tên device
3. Mở Zadig → Options → List All Devices
4. Chọn device → chọn driver WinUSB hoặc libusbK
5. Click "Install Driver"
6. Sau đó dùng libusb hoặc WinUSB API để access
Host app dùng libusb (cross-platform)
# Python, libusb backend
import usb.core
import usb.util
# Tìm device theo VID/PID
dev = usb.core.find(idVendor=0x0483, idProduct=0x5750)
if dev is None:
raise ValueError("Device not found")
dev.set_configuration()
cfg = dev.get_active_configuration()
intf = cfg[(0, 0)]
# Tìm bulk IN và OUT endpoint
ep_out = usb.util.find_descriptor(intf,
custom_match=lambda e:
usb.util.endpoint_direction(e.bEndpointAddress) ==
usb.util.ENDPOINT_OUT)
ep_in = usb.util.find_descriptor(intf,
custom_match=lambda e:
usb.util.endpoint_direction(e.bEndpointAddress) ==
usb.util.ENDPOINT_IN)
# Gửi data
ep_out.write(b'\x01\x02\x03\x04')
# Nhận data
data = ep_in.read(64, timeout=1000)
5. DMA cho USB endpoint - tránh CPU busy-wait
Mặc định, ST middleware dùng CPU để copy data giữa RAM và USB FIFO. Với high-throughput bulk transfer, điều này tốn CPU đáng kể.
Bật DMA trong CubeMX: USB → Configuration → Enable DMA. Sau đó trong usbd_conf.c, các hàm USBD_LL_Transmit và USBD_LL_PrepareReceive sẽ dùng DMA thay vì CPU copy.
Constraint quan trọng khi dùng DMA:
Buffer phải nằm trong vùng RAM DMA có thể access. Trên STM32G0, toàn bộ SRAM đều DMA-accessible nên ít vấn đề. Trên STM32H7 có TCM (Tightly Coupled Memory) không DMA-accessible - buffer để ở 0x20000000 (AXI SRAM), không phải 0x00000000 (ITCM).
Buffer phải align theo DMA burst size. Thường là 4-byte align.
/* Khai báo buffer với align đúng */
static uint8_t __attribute__((aligned(4))) s_usb_rx_buf[USB_MAX_EP_SIZE];
static uint8_t __attribute__((aligned(4))) s_usb_tx_buf[USB_MAX_EP_SIZE];
Không modify buffer khi DMA đang transfer. Nếu cần double-buffering:
/* Hai buffer xen kẽ - DMA dùng một, CPU xử lý cái còn lại */
static uint8_t s_rx_buf[2][USB_MAX_EP_SIZE] __attribute__((aligned(4)));
static volatile uint8_t s_active_buf = 0;
void USB_StartReceive(void)
{
USBD_LL_PrepareReceive(&hUsbDeviceFS,
CDC_OUT_EP,
s_rx_buf[s_active_buf],
USB_MAX_EP_SIZE);
}
/* Callback khi DMA nhận xong */
void CDC_Receive_FS_Done(uint8_t *buf, uint32_t len)
{
/* Xử lý s_rx_buf[s_active_buf] */
ProcessData(s_rx_buf[s_active_buf], len);
/* Flip buffer, re-arm */
s_active_buf ^= 1;
USB_StartReceive();
}
Interrupt endpoint và DMA
Interrupt endpoint thường dùng cho HID - data nhỏ, polling định kỳ. DMA ít có lợi ở đây vì packet size nhỏ (8-64 bytes). Nhưng nếu cần minimize CPU overhead:
/* HID send với DMA */
static uint8_t s_hid_report[HID_REPORT_SIZE] __attribute__((aligned(4)));
void HID_SendReport_DMA(void)
{
/* Fill report */
s_hid_report[0] = GetButtonState();
s_hid_report[1] = GetAxisX();
/* ... */
/* Transmit - với DMA enable, middleware dùng DMA tự động */
USBD_HID_SendReport(&hUsbDeviceFS, s_hid_report, HID_REPORT_SIZE);
}
6. Multi-interface - tổ chức code khi có nhiều endpoint
Composite device (ví dụ CDC + HID + Vendor Bulk) có nhiều interface, mỗi interface có endpoint riêng. Tổ chức code sạch là quan trọng khi project lớn lên.
Cấu trúc thư mục
USB_DEVICE/
├── App/
│ ├── usb_device.c - Init, register classes
│ ├── usbd_cdc_if.c - CDC application layer
│ ├── usbd_hid_if.c - HID application layer
│ └── usbd_vendor_if.c - Vendor class application layer
├── Class/
│ ├── CDC/usbd_cdc.c/.h - CDC class driver
│ ├── HID/usbd_hid.c/.h - HID class driver
│ └── Custom/usbd_custom.c/.h - Vendor class driver
└── Target/
├── usbd_conf.c - HAL PCD callbacks, DMA config
└── usbd_desc.c - Composite descriptor
Descriptor cho Composite
Phần khó nhất của Composite là Configuration Descriptor phải list tất cả interface và endpoint của tất cả class:
/* Tóm tắt cấu trúc - details phụ thuộc class */
const uint8_t USBD_CfgDesc[] = {
/* Configuration Descriptor header */
0x09, 0x02, TOTAL_LEN_LSB, TOTAL_LEN_MSB,
NUM_INTERFACES, /* bNumInterfaces - tổng số interface */
0x01, 0x00, 0xC0, MAX_POWER,
/* Interface 0: CDC Control */
/* ... CDC Control Interface Descriptor ... */
/* ... CDC Functional Descriptors ... */
/* ... Notification Endpoint Descriptor ... */
/* Interface 1: CDC Data */
/* ... CDC Data Interface Descriptor ... */
/* ... Bulk IN Endpoint ... */
/* ... Bulk OUT Endpoint ... */
/* Interface 2: HID */
/* ... HID Interface Descriptor ... */
/* ... HID Descriptor ... */
/* ... Interrupt IN Endpoint ... */
/* Interface 3: Vendor Bulk */
/* ... Vendor Interface Descriptor ... */
/* ... Bulk IN Endpoint ... */
/* ... Bulk OUT Endpoint ... */
};
Mỗi interface phải có Interface Number riêng, không trùng. Mỗi endpoint phải có địa chỉ riêng - STM32 Full Speed có tối đa 8 endpoint (EP0 + 7 data endpoints).
Routing request đến đúng class
Trong composite, khi có control transfer đến, middleware cần biết request thuộc interface nào:
/* Middleware gọi Setup cho class được register */
/* Mỗi class kiểm tra wIndex (interface number) */
static uint8_t USBD_Composite_Setup(USBD_HandleTypeDef *pdev,
USBD_SetupReqTypedef *req)
{
uint8_t interface = (uint8_t)(req->wIndex);
switch (interface) {
case CDC_INTERFACE_NUM:
return USBD_CDC_Setup(pdev, req);
case HID_INTERFACE_NUM:
return USBD_HID_Setup(pdev, req);
case VENDOR_INTERFACE_NUM:
return USBD_Vendor_Setup(pdev, req);
default:
USBD_CtlError(pdev, req);
return USBD_FAIL;
}
}
7. Log qua USB CDC - debug không cần UART
Một trong những ứng dụng thực tế nhất của USB CDC trên POS system: dùng như debug serial port. Thay vì cần USB-UART converter riêng, chỉ cần cắm USB vào máy tính và mở TeraTerm/PuTTY.
/* usb_log.h - wrapper đơn giản */
#define USB_LOG_BUF_SIZE 256
static char s_usb_log_buf[USB_LOG_BUF_SIZE];
void USB_Log(const char *fmt, ...)
{
if (!IsUsbConnected()) return;
va_list args;
va_start(args, fmt);
int len = vsnprintf(s_usb_log_buf, sizeof(s_usb_log_buf), fmt, args);
va_end(args);
if (len > 0) {
/* Retry loop ngắn nếu BUSY */
for (int retry = 0; retry < 3; retry++) {
if (CDC_Transmit_FS((uint8_t*)s_usb_log_buf, len) == USBD_OK) {
break;
}
HAL_Delay(1);
}
}
}
/* Kiểm tra USB có đang connected không */
bool IsUsbConnected(void)
{
/* Check device state */
return (hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED);
}
Dùng như printf:
USB_Log("[BOOT] firmware v%d.%d.%d\r\n", VER_MAJOR, VER_MINOR, VER_PATCH);
USB_Log("[CARD] card detected, uid=%02X%02X%02X%02X\r\n",
uid[0], uid[1], uid[2], uid[3]);
USB_Log("[ERR] printer timeout after %dms\r\n", elapsed);
Trên máy tính: mở TeraTerm, chọn COM port là “STM32 Virtual COM Port”, baud rate không quan trọng (USB CDC không có UART thật).
Lưu ý: Không log trong ISR - CDC_Transmit_FS không thread-safe trong interrupt context. Dùng queue giống pattern bài FreeRTOS.
8. Debug với JTAG - gotcha 3ms đầu tiên
Đây là lỗi rất hay gặp và rất khó giải thích nếu không biết nguyên nhân.
Vấn đề
Khi cắm USB và bắt đầu debug với ST-Link/JTAG, nếu đặt breakpoint trong hàm init USB (MX_USB_DEVICE_Init, USBD_Start, hoặc các callback đầu tiên trong enumeration), chương trình dừng lại giữa quá trình enumeration - host không nhận đủ response từ device → timeout → báo “Unknown USB Device” hoặc “Device Descriptor Request Failed”.
Nguyên nhân: USB enumeration có strict timing. Host gửi request và expect response trong vài ms. Breakpoint làm CPU đứng → response không đến kịp → host abort enumeration.
Host: GET_DESCRIPTOR(Device) → gửi request
STM32: dừng ở breakpoint
Host: chờ 5ms... 10ms... timeout!
Host: "Unknown USB Device"
Giải pháp
1. Không đặt breakpoint trong 3-5ms đầu sau khi USB được cắm hoặc sau reset.
Thực tế: USBD_Start() đến khi enumeration hoàn tất (dev_state == USBD_STATE_CONFIGURED) mất khoảng 2-3ms. Đặt breakpoint sau khi dev_state đã là CONFIGURED.
void MX_USB_DEVICE_Init(void)
{
USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);
USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC);
USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);
USBD_Start(&hUsbDeviceFS);
/* Không đặt breakpoint ở đây hoặc bất kỳ hàm USB callback nào
* cho đến khi hUsbDeviceFS.dev_state == USBD_STATE_CONFIGURED */
}
2. Đặt breakpoint sau khi USB đã connected.
Dùng watch expression trong debugger: thêm hUsbDeviceFS.dev_state vào Watch window. Chạy không breakpoint đến khi state = 3 (CONFIGURED), sau đó mới set breakpoint.
3. Dùng semi-hosting hoặc SWO thay vì USB CDC để debug giai đoạn init.
SWO (Serial Wire Output) của ST-Link không bị ảnh hưởng bởi USB enumeration - dùng SWO để log quá trình USB init, không dùng CDC_Transmit_FS trong giai đoạn đó.
Recover khi bị “Unknown USB Device”
Khi đã lỡ gặp trạng thái lỗi này trên Windows:
Cách nhanh nhất: Device Manager disable/enable
1. Mở Device Manager (devmgmt.msc)
2. Tìm "Unknown USB Device" hoặc tên device
3. Click phải → Disable device
4. Click phải → Enable device
5. Windows thử enumerate lại
Cách này reset USB state của Windows side mà không cần unplug/replug cable. Tiết kiệm thời gian khi debug liên tục.
Cách khác: unplug/replug cable - đơn giản nhưng tốn thêm vài giây.
Cách khác nữa: software disconnect/reconnect trong firmware
/* Disconnect USB soft */
USBD_Stop(&hUsbDeviceFS);
HAL_Delay(100);
/* Reconnect */
USBD_Start(&hUsbDeviceFS);
9. Sniffer và công cụ debug USB
Khi debug USB phức tạp, cần tool nhìn thấy traffic thật sự trên bus.
Wireshark với USBPcap (Windows):
- Capture USB traffic trực tiếp - thấy descriptor, request, data
- Free, nhưng chỉ thấy traffic sau khi device đã enumerate thành công
USBlyzer / Device Monitoring Studio (Windows, commercial):
- Thấy được traffic ở mức thấp hơn, kể cả giai đoạn enumeration fail
- Hữu ích khi debug descriptor bị sai
Bus Pirate hoặc protocol analyzer phần cứng:
- Capture được tất cả mọi thứ kể cả khi enumeration fail hoàn toàn
- Đắt hơn nhưng là công cụ tốt nhất khi cần debug sâu
Dùng JTAG để check state trong firmware:
/* Thêm vào watch window trong debugger */
hUsbDeviceFS.dev_state /* 0=Default 1=Addressed 2=Configured 3=Suspended */
hUsbDeviceFS.dev_config /* Configuration number */
hUsbDeviceFS.ep0_state /* EP0 state */
10. Checklist USB nâng cao
Checklist USB nâng cao
11. Tóm tắt
USB Device nâng cao có nhiều lớp cần hiểu:
- Vendor request qua EP0 là cách gửi lệnh tùy chỉnh - handler phải enqueue về task, không process trong ISR
- Bulk endpoint cần driver trên Windows - WinUSB (tự động với WCID) hoặc Zadig (thủ công)
- DMA giảm CPU load cho high-throughput - buffer phải aligned và ở RAM DMA-accessible
- Multi-interface cần descriptor và request routing đúng - endpoint address không được trùng
- USB CDC làm được debug log - thay thế USB-UART converter
- JTAG breakpoint trong 3ms đầu enumeration = lỗi “Unknown USB Device” - đây là lỗi thường gặp nhất khi bắt đầu debug USB
Đọc tiếp
- USB Device trên STM32: CDC, HID và ST Middleware - bài trước, foundation
- FreeRTOS trên STM32: Task, Queue, Signal - queue pattern cho USB request handling
- USB in a NutShell - beyondlogic.org - giải thích transfer type và descriptor sâu hơn
Tài liệu tham khảo
- STM32G0B1 Reference Manual RM0444 - USB DRD_FS, DMA
- Microsoft WinUSB - learn.microsoft.com - WinUSB API cho host app
- WCID Devices - github.com/pbatard/libwdi - cách thêm Microsoft OS Descriptor để auto-install WinUSB
- Zadig - cài WinUSB/libusbK thủ công cho development
- libusb - cross-platform USB access từ host app
- Wireshark USBPcap - capture USB traffic trên Windows
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 Transfer Types: Interrupt, Bulk, Control và Isochronous từ góc firmware
Giải thích về 4 loại USB transfer.
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.
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ế.
Đọ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.