STM32 USB CDC的CDC_Receive_FS回调调用机制及并发保护咨询
Great question—let’s break this down clearly for your STM32 Virtual Com Port project.
1. CDC_Receive_FS Invocation Mechanism
The CDC_Receive_FS callback is called directly from the USB interrupt service routine (ISR) context. Here’s the behind-the-scenes flow:
- When your USB host sends data to the STM32, the USB hardware triggers an interrupt.
- The USB ISR (usually
USBD_IRQHandlerin CubeMX-generated code) processes the incoming packet and passes the data to the CDC class driver. - The CDC driver then invokes
CDC_Receive_FSto hand off the received data to your application code.
Keep in mind: since this runs at interrupt priority, you must keep the callback’s execution time as short as possible. Avoid blocking operations, long loops, or heavy computations here—they’ll delay handling other critical interrupts.
2. Concurrency Protection When Copying to a Main Queue
Yes, you absolutely need concurrency protection when moving data from the callback’s buffer to a queue accessed by your main code. Here’s why:
CDC_Receive_FSruns in interrupt context, while your main code runs in thread context (or the main loop, in bare-metal setups). These two contexts can access the queue at the same time, leading to data races—like the main loop reading the queue while the interrupt writes to it, causing corrupted data or lost bytes.
Implementation Examples:
Bare-metal setup (no RTOS):
Use critical sections to temporarily disable interrupts during the data copy:
uint8_t main_queue[QUEUE_SIZE]; volatile uint32_t queue_write_ptr = 0; int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len) { // Disable global interrupts to protect queue access __disable_irq(); // Copy data to main queue (adjust logic to match your queue design) for(uint32_t i=0; i<*Len; i++) { if(queue_write_ptr < QUEUE_SIZE) { main_queue[queue_write_ptr++] = Buf[i]; } // Add queue overflow handling if needed } // Re-enable interrupts __enable_irq(); // Acknowledge the transfer to receive more data USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]); USBD_CDC_ReceivePacket(&hUsbDeviceFS); return (USBD_OK); }
RTOS setup (e.g., FreeRTOS):
Use RTOS-provided thread-safe queue functions designed for interrupt contexts. For FreeRTOS, use xQueueSendFromISR instead of the regular xQueueSend:
QueueHandle_t main_data_queue; int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; // Send data to the queue from interrupt context for(uint32_t i=0; i<*Len; i++) { xQueueSendFromISR(main_data_queue, &Buf[i], &xHigherPriorityTaskWoken); } // Trigger a context switch if a higher-priority task was woken portYIELD_FROM_ISR(xHigherPriorityTaskWoken); USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]); USBD_CDC_ReceivePacket(&hUsbDeviceFS); return (USBD_OK); }
This ensures safe, race-free access to the queue regardless of which context is interacting with it.
内容的提问来源于stack exchange,提问作者John Gaby

