2025-12-01 10:26 PM
This board has STM32F103C8 MCU.
I applied this patch for USB CDC interface, to successfully read/write data between STM32 and laptop via USB cable.
How to make this STM32 enter light sleep mode, while allowing STM32 to maintain virtual com port connection with laptop, and only wake up when I send something to STM32 from my laptop using a terminal app?
I tried debugging by putting wakeup_reason in various USB_CDC functions in
USB_Device/App/usb_device.c
USB_Device/App/usb_cdc_if.c
USB_Device/Targe/usbd_conf.c
But I didn't see wakeup_reason change after wake up.
I tried two ways:
// command c-strings
char *cmd1 = "cmd_sleep";
while (1)
{
// check if there is any data received on USB CDC
bytesAvailable = CDC_GetRxBufferBytesAvailable_FS();
if (bytesAvailable > 0 && (bytesAvailable <= sizeof(TxBuffer)) ) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // led on
HAL_Delay(10); // small delay to ensure interrupt USB library functions will fill out receive buffer
//bytesToRead = bytesAvailable >= 1 ? 1 : bytesAvailable; // until 8 bytes diff between head and tail refer to function CDC_ReadRxBuffer_FS()
bytesAvailable = CDC_GetRxBufferBytesAvailable_FS(); // re-read updated bytes available value
/* check against rxData capacity so we don't overflow */
if (bytesAvailable > MAX_rxData) {
len_needed = snprintf((char*)TxBuffer, sizeof(TxBuffer), "[Ex]bytesAvailable (%hu) exceeds MAX_rxData (%d)\r\n", bytesAvailable, MAX_rxData);
try_cdc_transmit(TxBuffer, (uint16_t)len_needed, 2000);
/* clamp to rxData capacity so we never overflow */
bytesAvailable = MAX_rxData;
}
if (CDC_ReadRxBuffer_FS(rxData, bytesAvailable) == USB_CDC_RX_BUFFER_OK) {
// first try to find cmd1 in rxData buffer
if (find_subbuffer(rxData, (const uint8_t *)cmd1, (size_t)bytesAvailable, strnlen(cmd1, MAX_rxData)) >= 0) {
len_needed = snprintf((char*)TxBuffer, sizeof(TxBuffer), "[Ix]cmd1 received, going sleep\r\n");
try_cdc_transmit(TxBuffer, (uint16_t)len_needed, 2000);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // led on
// preparing to sleep
/* clear any pending USB IRQ to avoid spurious wake */
#ifdef USB_LP_CAN1_RX0_IRQn
NVIC_ClearPendingIRQ(USB_LP_CAN1_RX0_IRQn);
#endif
#ifdef USBWakeUp_IRQn
NVIC_ClearPendingIRQ(USBWakeUp_IRQn);
#endif
HAL_SuspendTick(); // stop SysTick to avoid periodic wakeups */
wakeup_reason = 0; // clear before sleeping
// wait/sleep until interrupt
__WFI();
// wake up STM32
HAL_ResumeTick();
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // led on
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // led off
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // led on
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // led off
len_needed = snprintf((char*)TxBuffer, sizeof(TxBuffer), "[Ix]woke up, reason:%hu\r\n", wakeup_reason);
try_cdc_transmit(TxBuffer, (uint16_t)len_needed,2000);
}
else {
len_needed = snprintf((char*)TxBuffer, sizeof(TxBuffer), "[Ix]couldn't find any command\r\n");
try_cdc_transmit(TxBuffer, (uint16_t)len_needed, 2000);
}
}
else {
len_needed = snprintf((char*)TxBuffer, sizeof(TxBuffer), "[Ex]Error with CDC_ReadRxBuffer_FS\r\n");
try_cdc_transmit(TxBuffer, (uint16_t)len_needed, 2000);
}
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // led off
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
} // closes while loop
/* Try to transmit via CDC, waiting up to timeout_ms for the IN endpoint to accept the transfer */
static int try_cdc_transmit(const uint8_t *buf, uint16_t len, uint32_t timeout_ms)
{
uint32_t start = HAL_GetTick();
uint8_t result_cdc;
do {
result_cdc = CDC_Transmit_FS((uint8_t *)buf, len); // CDC_Transmit_FS expects uint8_t*
if (result_cdc == USBD_OK) return 0; // accepted
if (result_cdc != USBD_BUSY) return -2; // other error
/* USBD_BUSY -> wait a bit (allow USB stack to progress) */
HAL_Delay(1);
} while ((HAL_GetTick() - start) < timeout_ms);
return -1; // timed out
}I send "cmd_sleep" in terminal app to STM32, STM32 goes to sleep, then wakes up immediately (I receive in terminal app the prints).
Another way:
// preparing to sleep
/* save current NVIC enabled state and then globally disable IRQs we don't want */
uint32_t saved_iser[8];
const int ISER_WORDS = 8; /* covers up to 8*32 = 256 IRQs, safe for STM32F1 */
for (int i = 0; i < ISER_WORDS; ++i) {
saved_iser[i] = NVIC->ISER[i]; /* read currently enabled interrupts */
if (saved_iser[i]) {
NVIC->ICER[i] = saved_iser[i]; /* disable those interrupts */
}
NVIC->ICPR[i] = 0xFFFFFFFFu; /* clear all pending IRQs to avoid immediate wake */
}
/* Clear EXTI hardware flag for any NVIC pending for it */
NVIC_ClearPendingIRQ(EXTI15_10_IRQn);
/* Also clear any pending USB IRQ to avoid spurious wake */
#ifdef USB_LP_CAN1_RX0_IRQn
NVIC_ClearPendingIRQ(USB_LP_CAN1_RX0_IRQn);
#endif
#ifdef USBWakeUp_IRQn
NVIC_ClearPendingIRQ(USBWakeUp_IRQn);
#endif
/* Now enable *only* the IRQs that are allowed to wake MCU */
#ifdef USB_LP_CAN1_RX0_IRQn
NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn); /* USB low priority (OUT/IN) */
#endif
#ifdef USBWakeUp_IRQn
NVIC_EnableIRQ(USBWakeUp_IRQn); /* USB wake (if present) */
#endif
HAL_SuspendTick(); // stop SysTick to avoid periodic wakeups */
wakeup_reason = 0; // clear before sleeping
// wait/sleep until interrupt
__WFI();
/* restore original NVIC enables */
for (int i = 0; i < ISER_WORDS; ++i) {
if (saved_iser[i]) {
NVIC->ISER[i] = saved_iser[i]; /* restore previously enabled IRQs */
}
}
HAL_ResumeTick();
// wake up STM32
HAL_ResumeTick();
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // led on
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // led off
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // led on
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // led offBut then a terminal app in laptop freezes if I try to send anything after STM32 went to sleep. Though, virtual com port remains in device manager in Windows.
But I can't send anything, app instantly freezes, I assume because STM32 is unresponsive.
Is it possible that there's another IRQ responsible for maintaining connection, but because it's an IRQ, it causes STM32 to wake up immediately even though you don't send anything over terminal app?
.ioc: