Nucleo stm32f446re gets stuck when using snprintf from a deferred task

I am working on a simple app that periodically sends a string over the usart2 and it immediately sends another string when the builtin button is pressed.

The callback associated to the push-button ISR defer wake up task that should preempt the current running task, as per book.

However, when I use snprintf the board gets stuck in attempting to take the (already taken) semaphore in the deferred task. It follows some (pseudo) code to show what I am doing:

void periodic_task_1sec{
// ...
for(;;){
// ...
  usart2_step(PERIODIC_TASK);
  // ...
  }
}

//...
void usart2_step(enum WhoIsCalling caller) {
  char msg[MSG_LENGTH_MAX];
  char a[] = "Button Pressed!";

  switch (caller) {
  case PERIODIC_TASK:
    (void)snprintf(msg, MSG_LENGTH_MAX, "pippo: %s \n", "pluto");
    transmit(msg);
    break;
  case IRQ_BUILTIN_BUTTON:
    // If I use the following two lines everything works!
    /* strncpy(msg, "Button pressed!\r\n", MSG_LENGTH_MAX - 1); */
    /* msg[MSG_LENGTH_MAX - 1] = '\0'; */

    // If I use this line everything gets stuck
    (void)snprintf(msg, MSG_LENGTH_MAX, "pippo: %s \n", a);

    transmit(msg);
    break;
    }

void transmit(char *pMsg) {
  if (xSemaphoreTake(mutex_tx_process, pdMS_TO_TICKS(5)) == pdTRUE) {
    HAL_UART_Transmit(&huart2, (uint8_t *)pMsg, strlen(pMsg), portMAX_DELAY);
    xSemaphoreGive(mutex_tx_process);
  }
}

// Interrupt handling

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
  if (GPIO_Pin == B1_Pin) {
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(xSemaphoreBuiltinButton, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    // Handle EXTI line 13 interrupt
    // This code will be executed when an interrupt occurs on GPIO pin 13
  }
}

// Functions associated to deferred task woken by the callback function
void BuiltinButtonDeferred(void *pVParameters) {
  (void)pVParameters;

  for (;;) {
    xSemaphoreTake(xSemaphoreBuiltinButton, portMAX_DELAY);
    usart2_step(IRQ_BUILTIN_BUTTON);
  }
}

I attached a debugger and after the call to transmit(msg) in the case IRQ_BUILTIN_BUTTON: the system gets stuck in the line xSemaphoreTake(xSemaphoreBuiltinButton, portMAX_DELAY); of the BuiltinButtonDeferred. If I stop the debugger, I end up in this line configASSERT( pxQueue->uxItemSize == 0 );.

I am not sure, but it seems that after having taken the xSemaphoreBuiltinButton, the program seems to try to take it again. But if I push again the builtin button in reality nothing happens.

Surprisingly, if instead I use the following:

case IRQ_BUILTIN_BUTTON:
    // If I use the following two lines everything works!
    strncpy(msg, "Button pressed!\r\n", MSG_LENGTH_MAX - 1); 
    msg[MSG_LENGTH_MAX - 1] = '\0'; 

    // If I use this line everything gets stuck
    // (void)snprintf(msg, MSG_LENGTH_MAX, "pippo: %s \n", a);

    transmit(msg);
    break;

where snprintf is not used, then everything works smoothly.

The priority of the perodic task is 2, whereas the priority of the deferred task is configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY.

The subject has been mentioned many times: most implementations of the [vsn]printf() family can not be used in ISR’s. Also I saw the functions are stack hungry.

I always use printf-stdarg.c. Just add it to your project and it will be used.

It is interrupt save and it uses a minimum of stack. The only thing it misses is a proper implementation of %f / %g: printing floating points.

1 Like

And this priority is related to HW interrupts and NOT to logical, FreeRTOS managed task priorities.
Did you verify that your task stacks are large enough resp. did you enable stack checking (define set to 2) ?

Yes, I am aware that configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY is related to HW interrupts. However, and if I understood it correctly, according to fig 7.14 of this guide, I gave my deferred task the maximum priority such that it can make API calls, being the deferred task called from an ISR:

  xTaskCreate(BuiltinButtonDeferred, "BuiltinButtonPressed", 128, NULL,
              configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY,
              &xTaskBuitinButtonDeferred);

Not sure if I am mixing apples and oranges though.

Regarding the stack checking, not really because I didn’t know you can do that (sorry I am still a beginner, I will look into that). But I have raised the stack from 128 (which works in the working case depicted so far) to 1024.

Thanks, I will give a shot this evening (CET + 1) and I will let you know.

yes, you are. Task priorities are different from interrupt priorities, as Hartmut pointed out. You may want to re-read the documentation and/or the book for clarification.

This way you can enable stack checking for development.

1 Like

A TASK that has been signaled by an ISR through something like a semaphore is still a TASK and not an ISR. You are in the ISR from the point of entering the ISR from the interupt until that function returns from the interrupt. The DEFERED execution via giving the semaphore, then continues as a task, not an ISR. (likely the portYieldFromISR call will change what task the ISR returns to).

Tasks of all priority can use the FreeRTOS API.

1 Like

@RAc wrote:
A TASK that has been signaled by an ISR through something like a semaphore is still a TASK and not an ISR
[/quote]

You’re right Richard. I mistakenly thought that usart2_step() was called from an interrupt context because of the word IRQ_BUILTIN_BUTTON.

I did not write that, but I also agree with Richard’s explanation. The OS doesn’t care if a task interacts with an ISR, surfs bit coins, implements an http server or knits a sweater. A task is a task and thus is subjected to task priorities and task scheduling rules, period.

Everyone above posted solid advice. I would highly recommend…

  • Your buffer write call being within a FreeRTOS task instead of an ISR (which I think it currently is based off your example).
  • Updating your deferred task priority to configMAX_PRIORITIES - 1 at most. This is explained on this page or in section 4.4.1 of the book you linked. While the priority is silently capped, it’s better to be consistent with the actual priority intended.
1 Like

Now this part is interesting. Assuming your ISR is functioning, the button press should interrupt the FreeRTOS Kernel activity. When your interrupt occurs the periodic task should be running and the deferred task should be blocked on the semaphore. Once the ISR completes then the periodic task should be preempted by the deferred task which should then take the semaphore and transmit over the UART.

If what you’re saying is true - that it never transmits over the UART again even if the button is pressed - then at least one possibility is that your ISR is not signalling correctly (though this looks correct in your HAL_GPIO_EXTI_Callback) or your ISR is not completing.

Yes, the buffer is called from the task. My rule is that the ISR shall also wake up a task and nothing more. I actually have improved my example by replacing the semaphore with a task notification. :slight_smile:

I think it was the latter. Now I have fixed the problem and improved the code in many aspects (for example as said before by replacing the semaphore with task notifications) and I have a very high pile of items in my todo list unfortunately. I will see if I can squeeze some time to revert the SW and plugin a debugger to verify.

Thank you for taking the time to report back! It’s always nice to hear that you were able to get unblocked :slight_smile: