STM32: Interrupts with higher priority that DON'T call any FreeRTOS API get stuck somewhere

I have a nucleo and thanks to this book I learned that a ISR can be deferred to a task which is a very nice approach!

However, I also learned that higher prio interrupt (prio < configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY in case of stm32) can be still used but they shall not use any freertos API. I wanted to check if I got the concept right and I run the following code, but the effect when pressing the button is the board to get stuck get, whereas if I use the deferring method everything works fine.

Anyone has any idea why this happens and what I am doing wrong?

/* EXTI interrupt init, this is in gpio.c*/ 
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 3, 0);  // configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY = 5
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); 
/* .......... */
 /* This is in stm32f4xx_it.c */ 
void EXTI15_10_IRQHandler(void) { 
  HAL_GPIO_EXTI_IRQHandler(B1_Pin);
 }

 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { 
  if (GPIO_Pin == B1_Pin) { 
    char MESSAGE[] = "Hello World.\n\r"; 
    serial_port_main(MESSAGE); 
  } 
} 

void serial_port_main(void *pIRQparams) {
   const size_t MESSAGE_SIZE_MAX = 100; 
   char message[MESSAGE_SIZE_MAX]; 
   if (pIRQparams != NULL) { 
     const char *msg = (const char *)pIRQparams; 
     strncpy(message, msg, MESSAGE_SIZE_MAX - 1); 
     message[MESSAGE_SIZE_MAX - 1] = '\0'; 
  } 
  pinout_serial_port(message); 
} 

void pinout_serial_port(const char *pMessage) { 
  HAL_UART_Transmit(&huart2, (uint8_t *)pMessage, strlen(pMessage), HAL_MAX_DELAY); 
}

Seems right. I’d try with less stack usage and less work inside the ISR e.g. simply outputting a single char. Is the button debounced by HW ?
If you have a debugger attached it would be way easier to track and spot the problem.

It seems that it get stuck in the configASSERT, see below:

void vPortEnterCritical( void )
{
	portDISABLE_INTERRUPTS();
	uxCriticalNesting++;

	/* This is not the interrupt safe version of the enter critical function so
	assert() if it is being called from an interrupt context.  Only API
	functions that end in "FromISR" can be used in an interrupt.  Only assert if
	the critical nesting count is 1 to protect against recursive calls if the
	assert function also uses a critical section. */
	if( uxCriticalNesting == 1 )
	{
		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
	}
}

From the comment it seems that I should have called a …FromISR() function but… I am confused.

Confusing … you said you don’t call any FreeRTOS API, right ?
What’s the call stack ?

Well, not from the ISR as you can see from above.
I wish to remark that if instead using the callback HAL_GPIO_EXTI_Callback I defer the task by releasing a semaphore from EXTI15_10_IRQHandler all this machinery work.

I have a periodic task blinking the builtin led every second (no interrupts there) but that is unrelated to this, no?
Here is the call stack (but I am a beginner and I don’t know if that “corrupt stack?” at the end want to say anything):

(gdb) bt
#0  0x08002b14 in prvIdleTask (pvParameters=<optimized out>) at Middlewares/Third_Party/FreeRTOS/Source/tasks.c:3432
#1  0x08003d90 in vPortFree (pv=0x0) at Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c:312
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
  1. Right in ISRs you’ve to use the FromISR API
  2. Still strange… it seems that something in your non-FreeRTOS handler corrupts the application. Could you try to reduce this code to the bare minimum ?
    Since no FreeRTOS code is used the FreeRTOS application part should be affected in any way.

Sure I will try to make a bare minimum but it may take some time…

Actually I just realized that I mentioned half of the story (I apologize but I noticed it only now). I think it is irrelevant but: the function that I called serial_port_main() is in-fact called periodically by the same task that blink the led and it send a string every second on the usart, plus the very same function is called by the interrupt to send “Hello World.” every time I press the button.

I don’t think that it is an issue because I have two function calls of the same function one in a task and one in a section completely independent of freertos.

I will keep you posted…

Remember, that every function called by the ISR function is part of the ISR, so serial_port_main is part of that ISR, and thus so is pinout_serial_port, and if you are saying that has higher priority than it can’t call FreeRTOS APIs, and that include vPortEnterCritical, which it appears to (at least indirectly).

1 Like

I think it IS the issue crashing your application. You can’t use serial_port_main and finally HAL_UART_Transmit from a task AND the ISR. If right in the middle transmitting something via UART controller the interrupt occurs and accesses the same UART also trying to transmit something. It will corrupt the state of the UART and maybe something else (don’t know the internals of the HAL function).

1 Like

Oook! But then question: in this case, would the best be to defer the ISR to a task so I will have two tasks (the ordinary task an the deferred task) accessing concurrently to the UART and then manage the access with a semaphore/mutex?

I think in general the problem of accessing a resource both with task and ISR is tricky…

I fully agree with both points :+1:
(Better use the right tool for resource protection. The right tool is a mutex and for very short lock periods a critical section might be a better, optimized way.)

1 Like

Doing “I/O” on a device that the ISR isn’t controlling is generally a bad idea. ISR should generally be very short, doing just “what is needed” to be done. Printing the message on the button press should be done at “Task Level”, not in the ISR.

Good morning!

Yes, in-fact in my final implementation I will use a deferred task activated by releasing a semaphore from the ISR.

As said at the beginning, this exercise was only for pedagogical purposes and given the discussion we had, it succeeded in its goal. :slight_smile:

But then within this scenario I have a last question:

If the ordinary task that tries to access the UART (let’s call it task A) has priority lower than the task deferred by the ISR (let’s call it task B) and at some point task A is blocking the UART through a mutex and task B preempts task A and try to access the same resource, what happens?

I guess task B get blocked, right?

What is a common practice to accommodate this problem?

That’s right and that’s the purpose of protecting a resource to ensure exclusive access to it.
A mutex implements a feature called priority inheritance which means the task currently owning the mutex might get boosted to the highest priority of all other tasks currently blocked on this mutex. See also Mutex semaphores with priority inheritance for priority inversion avoidance in mutual exclusion using in FreeRTOS real time embedded software applications

Thanks again! I was just reading that page actually.

However, I am struggling with my FreeRTOS installation.
I have included the FreeRTOS version shipped with CubeMX (the one wrapped by CMSIS) but I am calling the FreeRTOS API directly because at some point I want to port my application on another platform with less pain as possible - this explain why in the example in the very top I am calling pinout_serial_port instead of calling directly HAL_UART_Transmit).

I was thinking to use the xTaskPrioritySet/Get() function but they are not recognized by this RTOS installation, in-spite in my config file I have

#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1

and I have #include <task.h>.
Instead, I have xTaskPriorityInherit/Dishinerit but I cannot find the description of these functions in FreeRTOS guide.
I must say that I am very confused…

Priority inheritance is nothing a task has to explicitly control, that would defeat the purpose. The OS does that for you behind the curtains.

Ok, so you are saying that in the following pseudo code the priority of the lower priority task (the one called by the periodic blink_task) will be automatically raised to the priority of the deferred task from the ISR?:

Periodic task blinking the led and calling `serial_port_main(NULL)`. 
static void task_1000ms(void *pVParameters) // This is a task.
{
  const struct TaskParams *params = (struct TaskParams *)pVParameters;
  TickType_t xLastWakeTime;
  xLastWakeTime = xTaskGetTickCount();

  while (1) {
    // Run activities
    blink_main();
    serial_port_main(NULL);
    
    vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(params->PERIOD));
  }
}


/* Defer the interrupt routine to a task */
TaskHandle_t xTaskUsart2TxDeferred;
SemaphoreHandle_t xSemaphoreUsart2Tx;

xSemaphoreUsart2Tx = xSemaphoreCreateCounting(5, 0);
xTaskCreate(Usart2TxDeferred, "Usart2Tx", 128, NULL,
              configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY,
              &xTaskUsart2TxDeferred);


void EXTI15_10_IRQHandler(void) {
  HAL_GPIO_EXTI_IRQHandler(B1_Pin);

  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  xSemaphoreGiveFromISR(xSemaphoreUsart2Tx, &xHigherPriorityTaskWoken);
  portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

void Usart2TxDeferred(void *pVParameters) {

  for (;;) {
    while (xSemaphoreTake(xSemaphoreUsart2Tx, portMAX_DELAY) == pdTRUE) {
      char MESSAGE[] = "Hello World.\n\r";
      serial_port_main(MESSAGE);
    }
  }
}

/* Function called both from a task and from a deferred task */
void serial_port_main(void *pIRQparams) {
    /* If pIRQparams == NULL the function is called by an "ordinary" 
     * task (blink_task just call serial_port_main(). Else, it is called by a 
     * task that has been deferred by an 
     * ISR. In this way this function can be called both periodically (by the 
     * "ordinary" task and aperiodically (by triggering an IRQ) */

    const size_t MESSAGE_SIZE_MAX = 100; 
    char message[MESSAGE_SIZE_MAX]; 
    static SemaphoreHandle_t mutex_serial_pinout

    if (pIRQparams != NULL) { 
       const char *msg = (const char *)pIRQparams; 
       strncpy(message, msg, MESSAGE_SIZE_MAX - 1); 
       message[MESSAGE_SIZE_MAX - 1] = '\0'; 
    }
    else{ 
       message = "Call from blink_task\r\n"; 
    };

    if (xSemaphoreTake(mutex_serial_pinout, 100 / portTICK_PERIOD_MS) == pdTRUE) {
      pinout_serial_port(message); 
      xSemaphoreGive(mutex_serial_pinout);
    }
} 

In your pseudo code I see only one thread calling your serial_main. I assume some other thread also calls that function. Priority inheritance enforces that if the thread currently owning the mutex (A) has a lower priotit than a thread attempting to claim it (B), As priority is temporarily raised to Bs priority.

I apologize! I edited the pseudo-code.
Will it happens automatically the priority inheritance in the pseudo-code that I updated? What if there are MANY ordinary tasks/deferred from ISR tasks trying to access the same resources? For example, imagine that I add a second button that every time is pressed shall send the message “My name is Bob” on the serial port?

Sorry if I am asking too many questions! :slight_smile:

yes, the priority inheritance kicks in every time any task attempts to claim a mutex. You may want to query the forum for priority inheritance, it has been discussed and explained so many times that you save yourself a lot of time and key strokes by just reading what is already there.

1 Like