Skip to content

Instantly share code, notes, and snippets.

@rfairley
Last active January 28, 2019 05:01
Show Gist options
  • Save rfairley/d56f63faaac63cb2e288a3e51d19f23f to your computer and use it in GitHub Desktop.
Save rfairley/d56f63faaac63cb2e288a3e51d19f23f to your computer and use it in GitHub Desktop.
async-programming-io-wiki.md

Interrupts and concurrency

  1. All things that may be written to inside an ISR must be declared volatile
  2. Task notifications should be preferred to semaphores for synchronization
  3. Use mutexes for all cases when the goal is controlling access to a shared resource. Note that mutexes in FreeRTOS have priority inheritance mechanisms, thus avoiding the priority inversion problem
  4. Always use osDelay instead of HAL_Delay for code running on FreeRTOS. The former calls vTaskDelay internally and allows the scheduler to run another task whereas the latter will block on the delay and will not allow the processor to execute other tasks. Calls to vTaskDelayUntil are also acceptable for implementing "delays" for time-triggered threads

IO

Non-blocking I/O should be used for any code integrated with FreeRTOS. I/O should use the DMA controller whenever large amounts of data of a known size are to be transmitted or received. For cases where an unknown about of data is to be transmitted or received, interrupt-based I/O is appropriate.

Sometimes when using non-blocking I/O, you don’t want a task to continue execution until the data transfer is done. A task notification and a callback function can be used together to ensure desired behaviour:

  1. First, perform the non-blocking I/O call. This will initiate the transfer of bits, then continue executing the task while the bits are transferred in parallel
  2. Call xTaskNotifyWait. This will block the task, meaning it will stop executing to let other tasks run until the binary semaphore is given
  3. Implement the callback function. This differs slightly depending on which communication module is being used, but it is required that certain names are used for the callback functions (will get to this in a bit). Regardless, each callback function consists of checking the module instance, and then giving the semaphore from ISR. The callback function is called at the end of a non-blocking transfer after all bytes have been received (for DMA) or after all bits have been received (for interrupt-based)

Example:

/**
 * @brief   receives a character of data via the UART2 module using interrupt-
 *          based I/O
 *
 *          This function never returns
 */
void DataReceiptTask(void){
    // Task initialization...

    uint32_t notification;
    for(;;){
        HAL_UART_Receive_IT(&huart2, rxChar, 1);

        status = xTaskNotifyWait(
            0,
            NOTIFIED_FROM_RX_ISR,
            &notification,
            MAX_DELAY_TIME
        );

        if(status != pdTRUE){
	    // xTaskNotifyWait timed out; handle error here
	}

        // Process received data
        // ... Your code here
    }
}

/**
  * @brief  This function is called whenever a reception from a UART
  *         module is completed. For this program, the callback behaviour
  *         consists of unblocking the thread which initiated the I/O and
  *         yielding to a higher priority task from the ISR if there are
  *         any that can run
  * @param  huart pointer to a UART_HandleTypeDef structure that contains
  *         the configuration information for UART module corresponding to
  *         the callback
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef* huart){
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    if(huart -> instance == USART2){
        xTaskNotifyFromISR(
            DataReceiptTaskHandle,
            NOTIFIED_FROM_RX_ISR,
            eSetBits,
            &xHigherPriorityTaskWoken
        );
    }
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

A few things to note here:

  1. The xTaskNotifyWait timeout argument is MAX_DELAY_TIME. In this example, this would be a user-defined constant equal to the maximum acceptable wait time. Any longer than this, and program execution resumes in this thread, but with status equal to an error code, which enables us to react to the issue
  2. In the callback function, we use an if statement so that the callback can be implemented differently for each instance of the communication module
  3. We must use xTaskNotifyFromISR in the callback functions

NOTE: The callback function for, say, the USART modules, will not be included in usart.c by default. You have to write this function yourself. However, if you go to (for example) Drivers/stm32h7xx_HAL_Driver/src/stm32h7xx_hal_uart.c, you can find weak definitions for the callback functions by searching for the text "callbacK'. These weak functions specify the exact form that the callback function must have in order for it to be called. The __weak attribute just means that in order to use this function, you need to provide an implementation for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment