Bốn loại Transfer dưới góc nhìn firmware
Khi nào dùng Interrupt, Bulk, Control hay Isochronous? Vai trò và ảnh hưởng của bInterval, SOF timing đến việc thiết kế USB Device.
Bài 2/8 trong series USB Device trên STM32.
Trước khi đọc bài này
Bài này là phần lý thuyết nền tảng cho toàn bộ series USB, giải thích cơ chế của bốn loại transfer trước khi đi vào triển khai. Bạn không cần biết USB từ trước, nhưng sẽ dễ theo hơn nếu đã từng làm việc với UART hoặc SPI trên STM32 và có khái niệm về interrupt và polling.
Ý chính của bài này
- USB là hệ thống do host điều khiển.
- Bốn loại transfer có đặc điểm riêng phục vụ bốn mục đích khác nhau.
- Lưu ý về
bIntervaltrong Interrupt Transfer để tránh việc thiết kế HID report sai độ trễ. - Vai trò của SOF trong toàn bộ hệ thống USB Full Speed và ảnh hưởng của nó đến việc thiết kế firmware.
USB là hệ thống host-centric
Nếu có một điều quan trọng nhất cần nắm trước khi đi vào từng loại transfer thì đó là:
Host là bên duy nhất chủ động khởi xướng mọi transaction. Device không bao giờ tự ý gửi data lên host.
Khái niệm transaction sẽ được trình bày ở đoạn sau. Ở đây, chỉ cần hiểu là host hỏi và nhận phản hồi từ device qua một chu kỳ duy nhất gọi là transaction (host hỏi và device phản hồi trong cùng một transaction).
Điểm này nghe đơn giản nhưng rất dễ bị hiểu nhầm, đặc biệt với Interrupt Transfer bởi vì tên gọi của nó khiến nhiều người liên tưởng đến việc device tự “bắn” dữ liệu lên host giống như ngắt ngoài của GPIO.
Transaction là gì?
Transaction trong USB là một chu kỳ trao đổi dữ liệu duy nhất do host khởi xướng, nó gồm ba phần:
-
Token packet: host nói nó muốn làm gì (IN, OUT, SETUP) với ai (endpoint nào).
-
Data packet:
- OUT/SETUP: host gửi data cho device
- IN: device gửi data cho host
- Handshake packet: device phản hồi (ACK/NAK/STALL).
Một transaction luôn diễn ra trọn vẹn trong một frame/microframe và device không thể tự tạo transaction mà nó chỉ phản hồi khi host gửi token.
Các thuật ngữ vừa đề cập sẽ được giải thích chi tiết và cung cấp ví dụ cụ thể ở những phần sau và trong các bài tiếp theo của series.
Nhịp đập của bus
Cấu trúc timeline của bus trên Full Speed sẽ trông như sau:
Full Speed timeline:
|--Frame 0 (1ms)--|--Frame 1 (1ms)--|--Frame 2 (1ms)--|
SOF SOF SOF
└─ transactions └─ transactions └─ transactions
Frame, Microframe
Đầu tiên ta cần hiểu về frame - đơn vị thời gian cơ bản của USB:
- USB Full Speed (12 Mbps) : 1 frame = 1 ms
- USB High Speed (480 Mbps): 1 microframe = 125 µs
Mỗi frame có thể chứa nhiều transaction tùy theo loại transfer và lịch của host controller. Ở High Speed, mỗi frame chia thành 8 microframe.
SOF
Đầu mỗi frame, host gửi SOF (Start of Frame) packet - một packet đặc biệt mang frame number và broadcast đến tất cả device.
SOF có hai vai trò quan trọng:
-
Clock synchronization: Device dùng SOF để đồng bộ internal clock với host. Điều này đặc biệt quan trọng với Isochronous transfer vì nó cần timing chính xác.
-
Keepalive signal: Khi host gửi SOF đều đặn, device biết kết nối vẫn hoạt động.
Cách firmware giữ kết nối với host
Ngoài việc phản hồi các transaction, firmware cần chú ý đến hai trạng thái bus quan trọng là suspend và reset. Nếu không xử lý đúng, thiết bị có thể bị treo hoặc kết nối giữa host và device bị gián đoạn hoàn toàn.
Suspend - USB idle quá 3ms
Nếu device không thấy SOF trong 3ms (với Full Speed), nó xác định rằng bus đã vào trạng thái Suspend và có thể chuyển sang chế độ tiết kiệm năng lượng.
Normal: |SOF|...|SOF|...|SOF|...|SOF|...|SOF|......|
Suspend: |SOF|.......................(3ms+).........|
↑ Device xác định suspend
Khi suspend, device giảm dòng tiêu thụ xuống dưới 2.5mA (bus-powered) hoặc 500µA (low-power suspend). Khi này firmware nên tắt hoặc deinit những peripheral không cần thiết.
Một số điểm quan trọng:
-
ST-Link debug tool giữ USB luôn active: khi debug qua ST-Link, tool có thể giữ USB luôn active khiến device không bao giờ suspend. Hành vi thực tế trên sản phẩm thật có thể khác hoàn toàn.
-
Single-bank Flash erase: trong thời gian erase Flash thì CPU bị block nên firmware không thể xử lý USB interrupt dù Host vẫn gửi SOF đều đặn. Kết quả thực tế có thể là transfer timeout, host reset device, hoặc Windows báo disconnect tùy thời điểm erase.
Reset - host bắt đầu enumerate
Host gửi USB reset bằng cách kéo D+ và D- xuống 0V trong ít nhất 10ms. Sau reset, tất cả trạng thái USB của thiết bị sẽ quay về trạng thái mặc định:
- Address = 0
- Configuration = 0 (unconfigured)
- Tất cả endpoint về trạng thái mặc định
Sau quá trình reset, host sẽ bắt đầu thực thi một loạt lệnh đọc ghi để cấu hình/tái cấu hình cho thiết bị USB. Quá trình này gọi là enumeration. Thường thì enumeration sẽ diễn ra ngay sau khi cắm USB nhưng cũng có thể xảy ra khi host gửi lệnh reset lại bus (ví dụ khi driver crash hoặc device bị treo).
Sau reset dù là chủ động hay bị động thì firmware cũng cần khởi tạo lại các biến, buffer và flag nội bộ. Nếu không làm bước này, thiết bị có thể giữ lại trạng thái cũ gây treo, gửi nhầm dữ liệu hoặc không phản hồi đúng trong quá trình re-enumerate. Đây là một trong những điểm quan trọng nhất cần chú ý để tăng độ tin cậy cho firmware USB.
Soft disconnect - firmware chủ động ngắt/kết nối lại
Đôi khi cũng cần ngắt USB bằng phần mềm để host re-enumerate. Ví dụ như sau khi update firmware.
void USB_SoftReconnect(void){ /* Ngắt kết nối - tắt pull-up D+ */ USBD_Stop(&hUsbDeviceFS); HAL_Delay(200); /* Đủ lâu để host nhận ra disconnect */ /* Kết nối lại */ USBD_Start(&hUsbDeviceFS); /* Host sẽ tự enumerate lại */}Trên STM32G0, USBD_Stop() sẽ xóa bit DPPU trong thanh ghi USB_BCDR khiến pull-up
D+ bị vô hiệu, và host sẽ thấy device unplug.
Bốn loại transfer
Trong USB, có bốn loại transfer chính phục vụ bốn mục đích khác nhau:
- Control : truyền lệnh và thiết lập cấu hình.
- Interrupt : truyền dữ liệu nhỏ, cần độ trễ ổn định và có bảo đảm thời gian.
- Bulk : truyềndữ liệu lớn, không yêu cầu timing, ưu tiên thông lượng.
- Isochronous : truyền thời gian thực, không retry, chấp nhận mất mát.
Control Transfer
Control transfer là loại transfer duy nhất bắt buộc với mọi USB device. Toàn bộ quá
trình enumeration (GET_DESCRIPTOR, SET_ADDRESS, SET_CONFIGURATION) đều đi qua control
transfer trên Endpoint 0.
Control transfer có 3 stage:
1. Setup Stage (8 bytes, luôn có):
Host → Device: Setup token + Setup packet
Setup packet mô tả request: loại, command, parameter, data length
2. Data Stage (có thể có hoặc không):
Nếu wLength > 0: trao đổi data theo hướng bmRequestType chỉ định
IN : Device → Host (device gửi data lên host)
OUT: Host → Device (host gửi data xuống device)
3. Status Stage (luôn có, ngược hướng với Data Stage):
Zero-length packet để xác nhận hoàn tất
Cấu trúc Setup Packet
Setup Stage luôn gửi đúng 8 byte với cấu trúc cố định như sau:
Byte 0: bmRequestType - hướng + loại + đích của request
Byte 1: bRequest - command cụ thể (GET_DESCRIPTOR, SET_ADDRESS...)
Byte 2-3: wValue - tham số phụ (ví dụ: descriptor type)
Byte 4-5: wIndex - tham số phụ thứ hai (ví dụ: interface number)
Byte 6-7: wLength - số byte data trong Data Stage (0 = không có Data Stage)
bmRequestType (byte 0) là byte quan trọng nhất - nó xác định host hay device
gửi data, request thuộc loại nào và gửi đến đâu:
Direction [7] Hướng truyền dữ liệu
-
0 - Host → Device (OUT)
-
1 - Device → Host (IN)
Type [6:5] Loại request
-
00 - Standard
-
01 - Class
-
10 - Vendor
-
11 - Reserved
Recipient [4:0] Đối tượng nhận request
-
00000 - Device
-
00001 - Interface
-
00010 - Endpoint
-
00011 - Other
Ví dụ giải mã 0x80 (dùng cho GET_DESCRIPTOR):
0x80 · 1000 0000
1 Device → Host (IN) 00 Standard 00000 Device Vài giá trị thường gặp khác:
| bmRequestType | Ý nghĩa |
|---|---|
0x00 | Host→Device, Standard, Device - dùng cho SET_ADDRESS |
0x21 | Host→Device, Class, Interface - dùng cho HID SET_REPORT |
0xC0 | Device→Host, Vendor, Device - dùng cho vendor request tùy chỉnh |
Timing: Control transfer có 10% băng thông dành riêng trên Full Speed bus. Tuy nhiên, nó không có đảm bảo về độ trễ, tức là việc truyền có thể bị chậm lại nếu bus đang bận.
Trên STM32, phần lớn control transfer đã được ST middleware xử lý. Ta chỉ cần triển khai khi có class-specific hoặc vendor request.
Interrupt Transfer
Đây là loại transfer hay bị hiểu nhầm kiểu:
“Interrupt transfer hoạt động như MCU interrupt, device gửi tín hiệu lên host khi có sự kiện.”
Điều này sai hoàn toàn.
Trong MCU, interrupt là device-initiated: peripheral tự bật flag và CPU nhảy vào handler.
Trong USB, interrupt transfer là host-initiated: host poll theo chu kỳ cố định, device chỉ có thể gửi data khi được hỏi. Data được gửi lên này gọi là “report”.
Đây là câu trích từ tài liệu USB in a NutShell:
“Any one who has had experience of interrupt requests on microcontrollers will know that interrupts are device generated. However under USB, if a device requires the attention of the host, it must wait until the host polls it before it can report that it needs urgent attention.”
Cơ chế thực sự
Mỗi bInterval ms:
Host: IN token → [Endpoint address]
↓
Device có data? YES → DATA packet (data của report)
↓
Host nhận OK → ACK
Device có data? NO → NAK
↓
Host ghi nhận, không có gì để xử lý
Chờ đến chu kỳ poll tiếp theo
ACK và NAK:
- ACK : Device có data, đã gửi, host nhận thành công
- NAK : Device không có data - không phải lỗi, chỉ là “không có gì mới”
- STALL: Error condition - endpoint bị lỗi hoặc request không hợp lệ
DMA endpoint buffer và cách firmware xử lý
Firmware đặt data vào endpoint buffer (vùng RAM được USB controller quản lý). Khi host poll và buffer có data, USB hardware tự động gửi, CPU không cần làm gì thêm.
Điểm quan trọng là xóa buffer sau poll và đánh dấu trạng thái TX về idle để chuẩn bị lại endpoint khi có report hợp lệ tiếp theo. Với HID keyboard, điều đó có nghĩa là:
- Gửi report mới khi có sự kiện mới (trạng thái phím thay đổi).
- Gửi null report (tất cả byte = 0) để xóa report cũ tránh stale report bug.
stale report bug là việc firmware gửi lại report cũ khiến host nhầm lẫn trạng thái cũ, biểu hiện là kẹt phím hoặc phím bị lặp liên tục.
/* Trong usbd_hid.c, callback khi TX hoàn tất */static uint8_t USBD_HID_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum){ HID_HandleTypeDef *hhid = (HID_HandleTypeDef *)pdev->pClassData; hhid->state = HID_IDLE; /* Đánh dấu buffer đã được tiêu thụ */ return USBD_OK;}/* Chỉ gửi report khi có data mới VÀ buffer idle */uint8_t USB_HID_SendReport(uint8_t *report, uint8_t len){ HID_HandleTypeDef *hhid = hUsbDeviceFS.pClassData; if (hhid->state == HID_IDLE) { hhid->state = HID_BUSY; /* Ghi vào buffer - host sẽ fetch trong chu kỳ poll tiếp theo */ USBD_LL_Transmit(&hUsbDeviceFS, HID_EPIN_ADDR, report, len); return USBD_OK; } return USBD_BUSY; /* Buffer chưa idle - chờ */}bInterval - chu kỳ poll của host
Full Speed và Low Speed interrupt endpoint: bInterval là số frame giữa các
lần host poll, có giá trị từ 1 đến 255. Ví dụ bInterval=10 nghĩa là device yêu cầu
host poll sau khoảng mỗi 10ms.
Tuy nhiên, host controller và OS có thể có cách lên lịch riêng. Trong thực tế, khi dùng
USB Protocol Analyzer thì bInterval=10 cho kết quả poll sau mỗi 8ms trên Windows. Vì vậy
nên cân nhắc chọn các giá trị như 1ms, 2ms, 4ms, 8ms,.. và đo lại thay vì chỉ tin vào descriptor.
High Speed interrupt endpoint: bInterval là số mũ và khoảng thời gian thực tế được
tính theo công thức:
interval = 2^(bInterval-1) x 125µs
Ví dụ:
| bInterval (HS) | Interval thực tế |
|---|---|
| 1 | 2^0 = 1 x 125µs = 125µs |
| 4 | 2^3 = 8 x 125µs = 1ms |
| 8 | 2^7 = 128 x 125µs = 16ms |
Tại sao host controller thích lũy thừa của 2?
Host controller tổ chức interrupt endpoint polling bằng một cây nhị phân gọi là binary tree scheduler. Mỗi tầng của cây tương ứng với một khoảng thời gian là lũy thừa của 2.
- Device ở level 0 (bInterval=1ms) → poll mỗi frame.
- Device ở level 1 (bInterval=2ms) → poll mỗi 2 frame.
- Device ở level 2 (bInterval=4ms) → poll mỗi 4 frame.
Cách tổ chức này giúp host controller lên lịch poll hiệu quả mà không cần tạo timer riêng cho từng endpoint.
Nếu đặt bInterval là giá trị không phải lũy thừa của 2 (ví dụ 3ms, 5ms), host
controller sẽ làm tròn xuống mức lũy thừa của 2 gần nhất:
bInterval=3→ thực tế là 2msbInterval=5→ thực tế là 4ms
/* Trong usbd_hid.h - ST middleware mặc định */#define HID_FS_BINTERVAL 0x0A /* khai báo 10ms, host thực tế poll 8ms *//* Nếu cần nhanh hơn */#define HID_FS_BINTERVAL 0x04 /* 4ms - đủ nhanh cho hầu hết HID input *//* Không nên đặt 0x01 trừ khi firmware xử lý kịp trong 1ms */Lưu ý với bInterval=1ms: host poll mỗi frame. Chỉ nên đặt giá trị này nếu firmware
có thể xử lý xong callback, xóa buffer hay chuẩn bị report mới trước khi frame tiếp theo.
Với hầu hết HID device, 4ms hoặc 8ms là đủ và an toàn hơn nhiều.
Lưu ý về băng thông
Interrupt endpoint polling nhanh sẽ chiếm một phần lịch bus theo chu kỳ. Với một HID report nhỏ, chi phí thường chấp nhận được. Nhưng nếu có nhiều interrupt endpoint poll ở 1ms, phần băng thông còn lại cho Bulk transfer sẽ giảm.
Bulk Transfer
Bulk transfer dùng khi cần truyền lượng dữ liệu lớn và không yêu cầu timing chính xác. Ví dụ như firmware update, log dump hoặc truyền file.
Cơ chế hoạt động
Host gửi IN token, device trả data hoặc NAK. Khác interrupt, bulk không có phần băng thông được dành riêng. Nó chỉ dùng băng thông còn lại sau khi interrupt và isochronous đã được phục vụ. Khi bus bận, bulk bị delay không giới hạn.
Host: IN token
Device: DATA0 (64 bytes) → Host: ACK
Host: IN token
Device: DATA1 (64 bytes) → Host: ACK ← toggle DATA0/DATA1
Host: IN token
Device: NAK (buffer chưa sẵn sàng)
...
Host: IN token (retry không giới hạn)
Device: DATA0 (64 bytes) → Host: ACK
DATA0/DATA1 toggle - cơ chế chống trùng packet
USB dùng hai loại packet DATA0 và DATA1 để đánh dấu thứ tự của các packet trong một transfer. Nếu device vừa gửi DATA0, host sẽ kỳ vọng DATA1 ở lần kế tiếp. Nếu host nhận lại DATA0, nó biết đó là packet cũ và sẽ bỏ qua.
Framing - vấn đề không có trong tài liệu chuẩn
Ở tầng USB, Bulk chỉ truyền byte thô, không có cấu trúc message nào được định nghĩa. Firmware và host app phải tự thỏa thuận protocol để biết một message bắt đầu và kết thúc ở đâu. Đây là điểm nhiều người không nghĩ đến khi thiết kế.
Vấn đề: bulk endpoint có max packet size 64 bytes (Full Speed). Nếu một message dài hơn 64 bytes thì ta phải chia nó thành nhiều packet rồi gửi tuần tự. Host phải nhận nhiều packet thì làm sao biết đây là một message dài hay hai message khác biệt?
Chiến lược 1: Fixed length
Mỗi message dài đúng N bytes cố định. Cách này đơn giản nhưng kém linh hoạt.
/* Device: gửi luôn 64 bytes, pad nếu cần */typedef struct { uint8_t cmd; uint8_t status; uint16_t data_len; uint8_t data[60];} __attribute__((packed)) BulkMsg_t; /* Luôn 64 bytes */Chiến lược 2: Length-prefixed
Dùng 2-4 byte đầu chứa tổng length của message. Đây là cách phổ biến nhất, dễ triển khai và debug.
/* Header: 4 bytes length, sau đó là payload */typedef struct { uint32_t total_len; /* Tổng số byte payload theo sau */ uint8_t payload[]; /* Variable length */} __attribute__((packed)) BulkFrame_t;/* Host: đọc 4 bytes header, rồi đọc thêm total_len bytes */Chiến lược 3: Start/end marker
Tự định nghĩa các byte đặc biệt để đánh dấu đầu và cuối. Cách này linh hoạt nhưng cần byte stuffing khi data chứa marker.
#define FRAME_START 0x7E#define FRAME_END 0x7F#define FRAME_ESCAPE 0x7D /* Escape byte nếu data chứa START/END *//* Encoding: nếu data byte = 0x7E hoặc 0x7F → gửi 0x7D + (byte XOR 0x20) */Chiến lược 4: CRC framing
Mỗi frame gồm header + payload + CRC. Host đọc header để biết độ dài, đọc payload, rồi tính CRC và so sánh. Nếu không khớp thì bỏ qua.
typedef struct { uint8_t magic[2]; /* 0xAA, 0x55 - start marker */ uint16_t length; /* Payload length */ uint8_t payload[]; /* Variable */ /* uint16_t crc; ← nằm sau payload */} __attribute__((packed)) BulkFrame_t;Short packet và ZLP
Nếu protocol dựa vào short packet để xác định kết thúc transfer và tổng dữ liệu đúng bằng bội số của max packet size (64 bytes), device cần gửi thêm một Zero-Length Packet (ZLP) để báo hiệu end of transfer.
Nếu protocol đã có length prefix rõ ràng, host biết trước cần đọc bao nhiêu byte và ZLP có thể không cần thiết tùy theo USB stack và cách thiết kế protocol. ST CDC middleware thường tự xử lý ZLP, nhưng nếu viết custom bulk class thì cần kiểm tra lại.
Ví dụ pseudo-code (chú ý endpoint có thể vẫn busy - xem state machine thật ở bài 5):
void BulkSend(const uint8_t *data, uint32_t len){ /* Gửi data */ CDC_Transmit_FS((uint8_t*)data, len); /* Nếu len là bội số của 64, gửi thêm ZLP */ if (len % USB_FS_MAX_PACKET_SIZE == 0) { CDC_Transmit_FS(NULL, 0); /* ZLP - chỉ khi endpoint idle */ }}Lưu ý quan trọng
Với bulk transfer, device cũng không thể tự ý gửi data lên host. Nó chỉ có thể gửi khi host chủ động gửi IN token để báo rằng nó đã sẵn sàng nhận. Nếu host không poll, device sẽ đứng yên mãi mãi dù firmware có gọi transmit bao nhiêu lần.
Điều này dẫn đến một số vấn đề thực tế:
- Device không bao giờ push data lên host: bulk hoàn toàn phụ thuộc vào host. Nếu host không hành động, device không thể gửi.
- Vendor-specific Bulk thường cần driver riêng trên Windows: nếu Bulk endpoint nằm trong class chuẩn như CDC hoặc MSC, OS có class driver tương ứng. Nhưng nếu nằm trong vendor-specific interface, Windows thường cần WinUSB, libusbK hoặc Microsoft OS Descriptor để host app truy cập trực tiếp.
Isochronous Transfer
Isochronous dùng cho audio, video - dữ liệu phải đến đúng thời điểm, không yêu cầu chính xác tuyệt đối 100%. Nếu packet bị lỗi, nó bị bỏ qua ngay lập tức, không có retry, không có ACK. Nó được thiết kế cho kiểu dữ liệu streaming, nơi mà việc bỏ qua một packet lỗi còn tốt hơn là delay toàn bộ stream.
Phần này chưa có kinh nghiệm thực tế để chia sẻ nên không đi sâu, nhưng có vài điểm quan trọng cần biết:
- Isochronous có băng thông được đảm bảo - host luôn phân bổ băng thông trước mỗi frame/microframe.
- Không có handshake (ACK/NAK/STALL) - host gửi, device nhận, xong.
bIntervaltrên Full Speed luôn là 1 (mỗi frame 1ms) - không thể đặt chậm hơn.- USB Audio Class sử dụng isochronous - microphone, speaker, tai nghe USB.
- Ở Full Speed, tối đa 90% băng thông của mỗi frame có thể dùng cho isochronous.
Full Speed isochronous: 1 packet mỗi frame, tối đa 1023 bytes/packet
High Speed isochronous: 1-3 packets mỗi microframe, tối đa 1024 bytes/packet
So sánh 4 loại transfer
| Loại | Đặc điểm | Chú thích |
|---|---|---|
| Control | Endpoint 0, có chuỗi Setup / Data / Status | Bắt buộc cho enumeration. Host dành trước ~10% băng thông. Firmware hầu như không phải xử lý trực tiếp vì middleware lo phần lớn. |
| Interrupt | Host chủ động poll theo bInterval, thiết bị trả ACK hoặc NAK | Dùng cho HID, chuột, bàn phím, đầu đọc thẻ. Đảm bảo độ trễ tối đa. Cần chú ý bInterval và null report. |
| Bulk | Truyền theo kiểu ưu tiên thấp, host có thể thử lại nhiều lần | Dùng cho CDC data, MSC, firmware update. Tốc độ cao nhưng không đảm bảo thời gian. Firmware phải tự đóng gói dữ liệu. Nhiều hệ thống cần driver riêng hoặc libusb để host giao tiếp đúng với endpoint. |
| Isochronous | Băng thông được đảm bảo, không có cơ chế retry | Dùng cho audio/video. Độ trễ rất thấp. Không có ACK/NAK, packet lỗi bị bỏ qua. |
Các transfer này xuất hiện ở đâu trong series này?
Để bài viết không chỉ dừng ở lý thuyết, series này dùng một firmware thực hành xuyên suốt: stm32g0-usb-device-lab, phát triển từ HID đơn giản đến composite device hoàn chỉnh (HID + CDC log + vendor request + bulk dump) qua từng bước.
Bảng dưới đây tóm tắt nơi xuất hiện của mỗi loại transfer trong series:
| Transfer | Xuất hiện ở đâu |
|---|---|
| Control | Bài 3 khi host đọc descriptor lúc enumeration, bài 7 khi vendor request gửi qua EP0 |
| Interrupt | Bài 5 khi HID keyboard gửi report 8 byte, host poll theo bInterval |
| Bulk | Bài 6 khi CDC log truyền qua bulk endpoint, bài 7 khi RAM dump 144 KB đi qua vendor bulk IN |
| Isochronous | Chưa có kinh nghiệm nên không dùng trong series hiện tại |
Tóm tắt
Bốn loại transfer đều có một nguyên tắc chung: host luôn là bên khởi xướng transaction. Firmware / device chỉ chuẩn bị dữ liệu và chờ được hỏi.
Một vài điều cần lưu ý khi thiết kế firmware:
- Với Interrupt: không gửi lại report cũ, đánh dấu TX idle ngay sau mỗi transfer hoàn tất,
và luôn đo
bIntervalthật bằng USB protocol analyzer thay vì chỉ tin vào giá trị khai báo. - Với Bulk: phải tự thiết kế framing và nhớ vendor-specific bulk trên Windows thường cần WinUSB/libusbK.
- Với Control: hầu hết đã có middleware xử lý sẵn, chỉ cần tự viết khi có class-specific hoặc vendor request riêng.
- Với SOF: mất SOF liên tục trong 3ms là dấu hiệu suspend, thao tác chặn CPU lâu (như xoá Flash) có thể khiến phản hồi USB bị trễ dẫn đến timeout hoặc host reset device.
- Không lưu lại trạng thái USB cũ sau reset, phải reinit toàn bộ biến, buffer và flag nội bộ.
Tài liệu tham khảo
- USB 2.0 Specification - tài liệu chuẩn chính thức, phần 5 (Transfer Types), phần 8 (Protocol Layer)
- USB in a NutShell - beyondlogic.org - tài liệu giải thích USB protocol
- USB Bulk and Interrupt Transfer - learn.microsoft.com - dưới góc nhìn của Windows driver
- USB Developers FAQ - janaxelson.com - bInterval và nhiều câu hỏi thực tế
- STM32G0B1 Reference Manual RM0444 - USB DRD_FS controller
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ị.