Idle task not yielding immediately after ISR until next tick when preemption enabled

eve-shadow wrote on Sunday, July 28, 2019:

Hi all,
I have run into an interesting issue that seems to be related to the
scheduler. I’m on an STM32F4 (using CubeMX to generate HAL and FreeRTOS
code). For the purposes of this discussion let’s say I have 1 running task
(for gathering read data from a uart). Here is the task code:

for (;;) {
/* unmask receive data register not empty interrupt */
SET_BIT(stx->uart->Instance->CR1, USART_CR1_RXNEIE);
/* unmask receive parity error interrupt */
SET_BIT(stx->uart->Instance->CR1, USART_CR1_PEIE);

/* wait for receive data register not empty interrupt */
rstat = xSemaphoreTake(stx->rxSemaphore, portMAX_DELAY);
assert(rstat == pdPASS);

/* receive byte */
if (stx->uart->Init.Parity == UART_PARITY_NONE) {
buff = (char)(stx->uart->Instance->DR & 0x000000FF);
} else {
buff = (char)(stx->uart->Instance->DR & 0x0000007F);
}

/* place byte in read queue */
rstat = xQueueSendToBack(stx->rxFIFO, &buff, portMAX_DELAY);
assert(rstat == pdPASS);
}

Here is the relevant part of the receive byte ISR:

 if ( ((status_reg & USART_SR_RXNE) != RESET) && ((cr1 & USART_CR1_RXNEIE)
!= RESET) ) {
/* rx data register byte available */
/* mask receive data register not empty interrupt */
CLEAR_BIT(stx->uart->Instance->CR1, USART_CR1_RXNEIE);
/* unblock receive task */
rstat = xSemaphoreGiveFromISR(stx->rxSemaphore, NULL);
assert(rstat == pdPASS);
}

Here is the source of error / confusion. Using this code, sending any more
than 1 byte in a short period of time causes a uart overrun error
interrupt. I have traced the code and the problem appears to be that the
idle task does not yield properly. Here is the sequence of events:

  • read task is blocked on xSemaphoreTake()
  • ISR is called and reaches xSemaphoreGiveFromISR()
  • after this call, the read task is put in the ready state (according to
    Atollic TrueStudio FreeRTOS Task List tool, which displays all running
    tasks, state, stack size, etc.)
  • the ISR exits and we return to line 3258 in tasks.c (inside the idle
    task):
(3258) if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY
] ) ) > ( UBaseType_t ) 1 )
(3259) {
(3260) taskYIELD();
(3261) }
  • idle task now appears to loop between lines 3258 and 3235 (the if
    statement on 3258 evaluates to FALSE and does not taskYIELD() )
    (3235) prvCheckTasksWaitingTermination();
  • the uart read task only resumes at the next TICK event

The overrun error then occurs because by the time the uart read task starts
running, another byte has already been received in the uart. Obviously the
desired behavior is for the idle task to immediately yield to the uart read
tasks. Now, if I disable configUSE_PREEMPTION, everything works and the
idle task yields immediately (line 3243 in tasks.c). Am I misunderstanding
what preemption is supposed to do?

Thanks,
Jacob

richard_damon wrote on Sunday, July 28, 2019:

Your ISR is ignoring the Higher Priority Task was woken signal (and in fact passing NULL pointers there, so you are not invoking premption. Fix that and you will fix your problem.

eve-shadow wrote on Sunday, July 28, 2019:

Ah. Failure to read documentation :). Appreciate it, thanks!

Jacob