STM32G0 USB Lab: Composite Device

USB composite device trên STM32G0B1RE: HID Keyboard + CDC log + EP0 Vendor Request + Vendor Bulk IN. Một codebase phát triển từ HID đơn đến debug tool hoàn chỉnh qua commit/tag.

17 phút đọc
STM32 cover

Kết quả đạt được

  • Composite class tự viết (không dùng ST HID class mặc định)
  • 4 interface: HID Boot Keyboard + CDC ACM + Vendor Specific
  • EP0 Vendor Request: GET_FIRMWARE_INFO, SET_REPEAT_ENABLE, SET_LED_MODE, START_RAM_DUMP
  • Vendor Bulk IN: RAM dump 144 KB qua EP4, không ZLP
  • USB lifecycle: reset/suspend/resume với abort dump và clear busy flag
  • CDC log ring buffer 512 byte, ISR-safe pattern

Vai trò trong series

Project page này mô tả tầng USB trong stm32g0-usb-device-lab, từ milestone lab-08 (HID keyboard baseline) đến lab-15 (USB lifecycle).

Codebase phát triển theo commit/tag - có thể checkout từng mốc để xem hoặc build lại trạng thái firmware tại thời điểm đó:

git checkout lab-08-hid-keyboard-baseline  # chỉ HID đơngit checkout lab-09-composite-skeleton     # HID + CDC skeletongit checkout lab-10-cdc-debug-channel      # CDC log hoạt độnggit checkout lab-11-vendor-request         # EP0 vendor commandsgit checkout lab-12-vendor-bulk            # Bulk IN RAM dumpgit checkout lab-15-usb-lifecycle          # reset/suspend handling

Hardware setup: breakout board và nguồn điện

USB Device peripheral của STM32G0B1 (PA11=D-, PA12=D+) không có connector sẵn trên NUCLEO-G0B1RE - nối ra ngoài qua một breakout board USB rời. Setup hiện tại chỉ nối D+/D-, dây VBUS của breakout không nối: nguồn cho toàn bộ board (kể cả MCU) vẫn lấy qua ST-Link, jumper chọn nguồn trên board đang ở vị trí mặc định (vị trí 1).

Để thiết bị tự cấp nguồn qua chính đường USB Device, không phụ thuộc ST-Link cắm vào PC: đổi jumper chọn nguồn từ vị trí 1 sang vị trí 4. Khi đó board nhận điện trực tiếp từ VBUS của đường USB Device - có thể cắm vào sạc pin thường, không cần máy tính, USB Device vẫn enumerate và hoạt động bình thường.

Cấu trúc descriptor tổng quan

Device Descriptor
  bDeviceClass/SubClass/Protocol = 0xEF / 0x02 / 0x01  (bắt buộc khi dùng IAD)
└─ Configuration Descriptor  (wTotalLength = 100 byte, bNumInterfaces = 4)
   ├─ Interface 0 - HID Boot Keyboard
   │  ├─ HID Descriptor
   │  └─ Endpoint: EP1 IN Interrupt, 8 byte, bInterval=0x0A (10ms, thực tế 8ms)
   │
   ├─ IAD  bFirstInterface=1, bInterfaceCount=2  (group IF1+IF2 thành CDC ACM)
   ├─ Interface 1 - CDC Communication (ACM control)
   │  └─ Endpoint: EP2 IN Interrupt, 8 byte, bInterval=0xFF
   ├─ Interface 2 - CDC Data
   │  ├─ Endpoint: EP3 OUT Bulk, 64 byte
   │  └─ Endpoint: EP3 IN Bulk, 64 byte
   │
   └─ Interface 3 - Vendor Specific (RAM dump)
      └─ Endpoint: EP4 IN Bulk, 64 byte

64 byte trên các endpoint Bulk (EP3, EP4) là max packet size của Bulk endpoint ở Full-Speed theo USB 2.0 spec, không phải lựa chọn tùy ý. Nhiều packet 64 byte tự nối thành một transfer dài hơn ở tầng host; nếu độ dài data đúng bằng bội số của 64, host cần thêm một Zero-Length Packet để biết transfer đã kết thúc (không áp dụng cho RAM dump ở Interface 3 vì host đã biết trước acceptedLength).

Mảng descriptor thật: Configuration + Interface 0

Trích từ sCompositeCfgDesc[] trong usbd_composite.c - phần Configuration Descriptor header và Interface 0 (HID), đúng nguyên văn kèm comment gốc trong code:

__ALIGN_BEGIN static uint8_t sCompositeCfgDesc[COMP_CFG_DESC_SIZE] __ALIGN_END ={  /* ---------- Configuration Descriptor ---------- */  0x09,                          /* bLength */  USB_DESC_TYPE_CONFIGURATION,   /* bDescriptorType */  COMP_CFG_DESC_SIZE, 0x00,      /* wTotalLength */  0x04,                          /* bNumInterfaces: HID(0) + CDC Comm(1) + CDC Data(2) + Vendor(3) */  0x01,                          /* bConfigurationValue */  0x00,                          /* iConfiguration */#if (USBD_SELF_POWERED == 1U)  0xC0,                          /* bmAttributes: Self-powered */#else  0x80,                          /* bmAttributes: Bus-powered */#endif  0x32,                          /* MaxPower: 100 mA */  /* ---------- Interface 0: HID Keyboard ---------- */  0x09,                          /* bLength */  USB_DESC_TYPE_INTERFACE,       /* bDescriptorType */  COMP_IF_HID,                   /* bInterfaceNumber */  0x00,                          /* bAlternateSetting */  0x01,                          /* bNumEndpoints */  0x03,                          /* bInterfaceClass: HID */  0x01,                          /* bInterfaceSubClass: Boot */  0x01,                          /* bInterfaceProtocol: Keyboard */  0x00,                          /* iInterface */  /* HID Class Descriptor */  0x09,                          /* bLength */  HID_DESCRIPTOR_TYPE,           /* bDescriptorType: HID */  0x11, 0x01,                    /* bcdHID: 1.11 */  0x0F,                          /* bCountryCode: Japan */  0x01,                          /* bNumDescriptors */  HID_REPORT_DESC_TYPE,          /* bDescriptorType: Report */  HID_KEYBOARD_REPORT_DESC_SIZE, /* wItemLength low */  0x00,                          /* wItemLength high */  /* EP1 IN: HID Keyboard Interrupt IN */  0x07,                          /* bLength */  USB_DESC_TYPE_ENDPOINT,        /* bDescriptorType */  COMP_HID_EPIN_ADDR,            /* bEndpointAddress: IN EP1 */  0x03,                          /* bmAttributes: Interrupt */  COMP_HID_EPIN_SIZE, 0x00,      /* wMaxPacketSize: 8 bytes */  COMP_HID_FS_BINTERVAL,         /* bInterval: 10 ms */  /* ---------- IAD: CDC ACM (Interfaces 1 and 2) ---------- */  0x08,                          /* bLength */  0x0B,                          /* bDescriptorType: IAD */  COMP_IF_CDC_COMM,              /* bFirstInterface */  0x02,                          /* bInterfaceCount */  0x02,                          /* bFunctionClass: CDC */  /* ... IAD tiếp tục, IF1 CDC Comm, IF2 CDC Data, IF3 Vendor - xem đầy đủ trong repo */

bCountryCode = 0x0F (Japan) là giá trị thật đang dùng trong firmware, không phải 0x00 (not localized) như ví dụ minh hoạ chung ở bài 3 - lựa chọn cụ thể của project này, không ảnh hưởng layout ký tự vì OS/input method mới là bên quyết định layout thật.

bmAttributes có 2 nhánh biên dịch tuỳ USBD_SELF_POWERED - repo mặc định build ở nhánh bus-powered (0x80), khớp với setup phần cứng thật hiện tại (lấy điện qua ST-Link, xem phần “Hardware setup” phía trên).

Composite class: nền tảng chung cho cả 4 interface

Sau khi lab-08 chạy được bàn phím thật bằng usbd_hid.c chuẩn của ST, bước tiếp theo không phải sửa tiếp file đó mà là viết hẳn một file mới, usbd_composite.c, để thay thế hoàn toàn. Lý do: usbd_hid.c chỉ xử lý được đúng một class (HID), trong khi bước kế tiếp cần thêm CDC log và sau đó là Vendor Bulk. Đặt nền bằng composite class ngay từ đầu giúp CDC và Vendor chỉ là “thêm nhánh” vào file đã có, không phải viết lại lần nữa mỗi khi thêm chức năng.

Việc này chia làm 2 bước nhỏ thay vì viết một lần: bước 1 chỉ có HID tự viết (để xác nhận cơ chế class hoạt động đúng - Init/DeInit/Setup/DataIn), bước 2 mới thêm CDC ACM. Tách nhỏ như vậy giúp nếu có bug, phạm vi nghi ngờ chỉ nằm trong đúng phần vừa thêm - cùng nguyên tắc “làm từng bước” áp dụng cho descriptor ở bài 3.

Bước 1 (chỉ HID) lộ ra 5 bug thuộc về hạ tầng composite class, ảnh hưởng chung tới mọi interface sau này chứ không riêng HID - hầu hết build vẫn OK nên không có gì báo trước:

BugTriệu chứngFix
Include usbd_hid.h đã xóaBuild errorXóa include
USBD_static_malloc sai kích thướcCrash ngầmSửa sizeof
Sof_enable = DISABLESOF callback không bắnENABLE
PMA 0x018 đè BTABLETransfer lỗi ngẫu nhiênOffset từ 0x040
Thiếu reset hidTxBusy trong DataInChỉ gửi được 1 phím (HID)Thêm hcomp->hidTxBusy = false

Riêng bug bDeviceClass sai khi thêm IAD cũng thuộc về bước này, ở tầng Device Descriptor chứ không riêng interface nào:

USBView báo *!*ERROR: device class should be Multi-interface Function 0xEF. Fix: đổi bDeviceClass/SubClass/Protocol sang 0xEF/0x02/0x01 trong usbd_desc.c.

Interface 0: HID Boot Keyboard

bInterfaceClass/SubClass/Protocol = 0x03/0x01/0x01, endpoint duy nhất EP1 IN Interrupt 8 byte. Report descriptor cố định theo Boot Protocol, không tự viết Report Descriptor tùy ý (xem lý do ở bài 3).

Bug thật (lab-08): CubeMX tạo HID Mouse descriptor. Chọn “HID Keyboard” trong CubeMX nhưng template được generate là mouse (bInterfaceProtocol = 0x02, report descriptor 74 byte, wMaxPacketSize = 4). Không có lỗi build, không cảnh báo - phát hiện qua USBView. Fix: thay toàn bộ bằng Boot Keyboard descriptor 45 byte.

Mảng gốc CubeMX sinh (HID_MOUSE_ReportDesc, HID_MOUSE_REPORT_DESC_SIZE = 74U), lấy nguyên văn từ lịch sử git trước khi sửa:

static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] ={  0x05, 0x01,        /* Usage Page (Generic Desktop Ctrls)     */  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 (0x01)               */  0x29, 0x03,        /*     Usage Maximum (0x03)               */  0x15, 0x00,        /*     Logical Minimum (0)                */  0x25, 0x01,        /*     Logical Maximum (1)                */  0x95, 0x03,        /*     Report Count (3)                   */  0x75, 0x01,        /*     Report Size (1)                    */  0x81, 0x02,        /*     Input (Data,Var,Abs)               */  0x95, 0x01, 0x75, 0x05,  0x81, 0x01,        /*     Input (Const,Array,Abs) - padding  */  0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,  0x15, 0x81, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x03,  0x81, 0x06,        /*     Input (Data,Var,Rel) - X, Y, Wheel */  0xC0,              /*   End Collection                       */  0x09, 0x3C, 0x05, 0xFF, 0x09, 0x01,  0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x02,  0xB1, 0x22,        /*   Feature (Data,Var,Abs,NoWrp) - Motion Wakeup */  0x75, 0x06, 0x95, 0x01,  0xB1, 0x01,        /*   Feature (Const,Array,Abs,NoWrp)      */  0xC0               /* End Collection */};

Thay bằng (HID_Keyboard_ReportDesc, HID_KEYBOARD_REPORT_DESC_SIZE = 45U):

static uint8_t HID_Keyboard_ReportDesc[HID_KEYBOARD_REPORT_DESC_SIZE] ={  0x05, 0x01,        /* Usage Page (Generic Desktop) */  0x09, 0x06,        /* Usage (Keyboard) */  0xA1, 0x01,        /* Collection (Application) */  0x05, 0x07,        /* Usage Page (Key Codes) */  0x19, 0xE0,        /* Usage Minimum (224) */  0x29, 0xE7,        /* Usage Maximum (231) */  0x75, 0x01, 0x95, 0x08,  0x81, 0x02,        /* Input - modifier byte (8 bit) */  0x95, 0x01, 0x75, 0x08,  0x81, 0x03,        /* Input (Constant) - reserved byte */  0x95, 0x06, 0x75, 0x08,  0x15, 0x00, 0x25, 0x65,  0x05, 0x07,  0x19, 0x00, 0x29, 0x65,  0x81, 0x00,        /* Input - 6 byte keycode array */  0xC0               /* End Collection */};

Cấu trúc mouse có 3 bit button, padding, rồi 3 byte X/Y/Wheel dạng delta tương đối (Input Data,Var,Rel) - không có khái niệm “6 keycode slot” nào cả. Report bàn phím gửi vào driver chuột không tạo ra ký tự gì vì driver chuột parse đúng theo cấu trúc trên, không tìm thấy trường nào khớp với dữ liệu bàn phím gửi lên.

Cả hai mảng đều lấy nguyên văn từ commit 7b56832 (Add USB HID transport and fix Boot Keyboard descriptor) trong lịch sử git của repo.

USBView xác nhận Interface 0 là Boot Keyboard sau khi fix
USBView: bInterfaceProtocol=0x01 (Boot Keyboard), wMaxPacketSize=8, không còn *!*ERROR.

Wireshark xác nhận report thật khi nhấn phím “1”: 00 00 1E 00 00 00 00 00 (0x1E = HID usage cho phím 1/!). Chi tiết cách tạo report 8 byte, tap-style null report, và macro sequence nằm ở bài 5 HID Keyboard Baseline.

Vì sao HidKeyboardConvert_Run() nằm ngoài scan loop

Main loop trong app_main.c gọi hai thứ tách biệt:

while (ScanScheduler_TakeRequest() != 0U){  KeyDetect_Run();}HidKeyboardConvert_Run();   /* ngoài while loop, không phụ thuộc scan tick */

HidKeyboardConvert_Run() cố tình đặt ngoài vòng while, không gọi bên trong cùng với KeyDetect_Run(). Nếu đặt bên trong, HID report chỉ được gửi khi có đúng một scan request đang chờ xử lý - nghĩa là report phải đợi đến tick TIM6 kế tiếp (5ms) mới có cơ hội gửi, ngay cả khi endpoint đã rảnh từ trước. Đặt ngoài loop giúp report được gửi ngay khi endpoint idle, không phụ thuộc vào nhịp scan - tránh cộng thêm độ trễ không cần thiết vào đường truyền phím.

Evidence đầy đủ: toàn bộ 16 phím và macro qua Wireshark

Capture USBPcap cho từng phím, mỗi phím là 2 frame (key-down rồi null ngay sau):

Phím         HID bytes (8 byte)
───────────  ────────────────────────
'1'          00 00 1E 00 00 00 00 00
'2'          00 00 1F 00 00 00 00 00
'3'          00 00 20 00 00 00 00 00
'4'          00 00 21 00 00 00 00 00
'a'          00 00 04 00 00 00 00 00
'b'          00 00 05 00 00 00 00 00
'c'          00 00 06 00 00 00 00 00
'd'          00 00 07 00 00 00 00 00
Enter        00 00 28 00 00 00 00 00
Space        00 00 2C 00 00 00 00 00
Backspace    00 00 2A 00 00 00 00 00
Tab          00 00 2B 00 00 00 00 00

Macro (Ctrl+C/V/S là 2 bước, Alt+Tab là 3 bước vì phải giữ Alt trước khi thêm Tab):

Macro               HID bytes (8 byte)
──────────────────  ────────────────────────
Ctrl+C  (bước 1)    01 00 06 00 00 00 00 00   LCtrl + c
Ctrl+V  (bước 1)    01 00 19 00 00 00 00 00   LCtrl + v
Ctrl+S  (bước 1)    01 00 16 00 00 00 00 00   LCtrl + s
Alt+Tab (bước 1)    04 00 00 00 00 00 00 00   LAlt giữ trước
Alt+Tab (bước 2)    04 00 2B 00 00 00 00 00   LAlt + Tab

Mỗi macro kết thúc bằng null report 00 00 00 00 00 00 00 00 sau bước cuối, giống key thường.

Interface 1-2: CDC ACM (log qua cổng COM ảo)

Hai interface CDC (Comm + Data) được IAD nhóm thành một chức năng duy nhất - không có IAD, Windows không tạo cổng COM dù cả hai interface vẫn enumerate riêng lẻ bình thường.

CdcLog_Printf không phải ISR-safe (dùng vsnprintf, không có lock). Tất cả call site phải là main loop context. Với EP0 vendor handler (chạy trong USB stack context), dùng pattern pending log flag:

/* Trong VendorCmd_HandleSetup - USB stack context */sPendingLogVal = (uint32_t)someValue;   /* giá trị cần in, nếu có */sPendingLog    = VLOG_GET_INFO_OK;      /* chỉ set cờ *//* Trong VendorCmd_FlushPendingLog - main loop */VendorLogEvent_t evt = sPendingLog;if (evt == VLOG_NONE) return;sPendingLog = VLOG_NONE;   /* clear trước khi log */CdcLog_Printf("[VREQ] GET_FIRMWARE_INFO\r\n");

Pattern tương tự dùng cho USB lifecycle events (reset/suspend/resume), qua module usb_lifecycle.c riêng - sPendingLog ở đó không dùng chung biến với vendor command, mỗi module tự có cờ riêng.

Không cần lock cho sWriteIdx/sReadIdx: producer (CdcLog_Printf qua CdcLog_Write) và consumer (CdcLog_Run) đều chạy main loop, tuần tự, không ISR nào chạm vào ring buffer. Ràng buộc “chỉ gọi từ main loop” ở trên chính là lý do bỏ qua critical section, không phải thiếu sót.

Kết nối CDC còn có race condition lúc boot (driver bind vs app mở port thật), fix bằng delay 100ms sau cạnh lên kết nối - đã giải thích chi tiết trong bài 6: CDC Debug Channel.

Tera Term nhận log CDC thật khi nhấn phím và gặp lỗi simultaneous
[CDC] log channel connected, theo sau là log 16 phím bấm lần lượt và [ERR] khi nhấn đồng thời.

Interface 3: Vendor Specific (EP0 command + Bulk dump)

Interface 3 phục vụ hai đường giao tiếp riêng: lệnh điều khiển qua EP0 Control, và truyền dữ liệu lớn qua EP4 Bulk IN. Thiết kế chi tiết, lý do tách control path/data path, và code thật của state machine RAM dump nằm ở bài 7 Vendor Request và Bulk - phần dưới đây chỉ tóm tắt để tra cứu nhanh.

EP0 Vendor Command Table:

bRequestTênwValueResponse
0x01GET_FIRMWARE_INFO-FirmwareInfo_t 17 byte
0x02SET_REPEAT_ENABLE0/1VendorResponse_t
0x03SET_LED_MODE0-3VendorResponse_t
0x04START_RAM_DUMP-VendorDumpResponse_t

Tất cả dùng bmRequestType = 0xC0 (Device→Host, Vendor, Device). FirmwareInfo_t có magic "SG0L" được chọn khác với magic của firmware phiên bản trước, để tránh host tool nhầm lẫn khi phát hiện thiết bị nếu vô tình cắm nhầm firmware cũ.

Vendor Bulk IN: RAM dump. START_RAM_DUMP arm một state machine dump 144 KB SRAM (0x20000000, khớp linker script của G0B1):

Host gửi START_RAM_DUMP qua EP0Firmware trả VendorDumpResponse_t: status=OK, acceptedLength=147456VendorDump_Run() gửi chunk đầu 64 byte từ main loopVendorDump_OnTxCplt() (ISR, gọi từ Composite_DataIn) gửi chunk tiếp theoLặp cho đến khi gửi đủ acceptedLength byte, set sDumpDone (volatile)VendorDump_Run() lần gọi kế tiếp thấy sDumpDone, tự gọi CdcLog_Printflog "[BULK] RAM dump complete" - không qua UsbLifecycle, vì đây đã chạysẵn trong main loop context, không cần thêm một lớp pending flag nữa

Không dùng ZLP để báo kết thúc vì host đã biết chính xác acceptedLength từ response của START_RAM_DUMP - tự dừng đọc khi đủ byte.

Output thật từ vendor_test.py (CLI test tuần tự 5 lệnh, lab-13):

[5] START_RAM_DUMP -> ram_dump.bin  147456/147456 bytes (100%)  0.34 s  ->  418.7 KB/s  saved 147456 bytes -> 'ram_dump.bin'
GUI host tool gộp CDC log và vendor command panel
GUI (lab-14): panel CDC log bên trái, panel vendor command và progress bar RAM dump bên phải.

Chi tiết Zadig/libusbK, protocol document nằm ở trang project Host Debug Tool. Câu chuyện viết GUI mới dựa trên vendor_usb.py đã ổn định từ lab-13, và pattern queue-poll thống nhất trong tkinter nằm ở bài 8 Composite Debug Tool.

USB lifecycle: ảnh hưởng chung tới cả 3 interface

usb_lifecycle.c xử lý 3 sự kiện bus, tác động tới busy flag của cả HID (Interface 0) lẫn Vendor Bulk (Interface 3) - không phải chuyện riêng của một interface:

Reset (UsbLifecycle_OnReset, gọi từ Composite_Init):

  • Abort dump đang chạy nếu có (VendorDump_Abort)
  • Tăng reset counter, log [USB] RESET #N dumpAborted=0|1

Suspend (UsbLifecycle_OnSuspend, gọi từ HAL_PCD_SuspendCallback):

  • Chỉ log sự kiện, không abort dump (suspend giữ nguyên state theo chuẩn USB)

Resume (UsbLifecycle_OnResume, gọi từ HAL_PCD_ResumeCallback):

  • Force-clear tất cả *TxBusy flag trên composite handle (cả HID lẫn Vendor Bulk)
  • Abort dump nếu đang chạy (transfer dở dang không thể tiếp tục)
  • Log [USB] RESUME #N dumpAborted=0|1

Bug tìm thấy trong project cũ: HAL_PCD_ResumeCallback dùng sai kiểu USBD_HID_HandleTypeDef* thay vì USBD_Composite_HandleTypeDef*. Không crash vì hai struct có field đầu giống nhau (đủ may mắn), nhưng reset sai offset - đây chính là nguyên nhân bug “TX kẹt sau suspend” ở bài 5. Repo này sửa bằng cách dùng đúng kiểu composite handle từ usb_lifecycle.c.

Evidence: bảng tra nhanh theo milestone

MilestoneEvidence
lab-08USBView: bInterfaceProtocol=0x01 (Boot Keyboard), wMaxPacketSize=8, Wireshark HID report 00 00 1E ...
lab-09USBView: composite descriptor, wTotalLength=0x0064, 4 interface, IAD, CDC COM port xuất hiện
lab-10Screenshot Tera Term: [CDC] log channel connected + 16 phím + [ERR] simultaneous
lab-11build OK, test độc lập không cần lab-12
lab-12USBView: wTotalLength=0x0074, bNumInterfaces=4, EP4 0x84 Bulk IN
lab-13vendor_test.py: 5 checks OK, 418.7 KB/s RAM dump
lab-14Screenshot GUI + 2 log file (CDC log và vendor response khớp nhau); video demo youtu.be/AYjgSDcFNJ8
lab-15Build OK (0 errors, 0 warnings); kịch bản test reset/suspend/resume có sẵn trong DEVLOG nhưng video chưa quay

Xem thêm: bài 3 USB Descriptor, bài 5 HID Baseline, bài 6 CDC Debug Channel, bài 7 Vendor Request và Bulk, bài 8 Composite Debug Tool

Giới hạn hiện tại

  • RAM dump luôn dump toàn bộ 144 KB từ 0x20000000, host không thể chỉ định address hay size tùy ý.
  • Vendor interface trên Windows cần Zadig/libusbK binding trước khi Python tool có thể dùng.
  • Suspend/resume không tối ưu low-power - chỉ abort dump và clear flag, không vào STOP mode.
  • Bus reset hoặc suspend/resume giữa lúc dump làm abort toàn bộ transfer đang chạy - không resume từ chỗ dở dang, host phải gửi lại START_RAM_DUMP và đọc lại từ đầu.
  • Microsoft OS 2.0 Descriptor chưa implement (cần Zadig thủ công).

Video demo

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ị.

Góp ý

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.

Tiếp tục xem các project embedded

Các project thực chiến giúp biến ghi chú kỹ thuật thành kinh nghiệm.