Embedded Expertise

Task Priorities in FreeRTOS – Part 2: Beyond the Basics

In our previous article, we established a foundation for task prioritization in FreeRTOS, suggesting a three-tier system for most applications. However, certain applications may require an additional layer of complexity. In this article, we’ll introduce a dedicated priority level for a specific yet common type of tasks: device drivers.

Understanding the Device Driver Challenge

While the three-tier structure provides a solid framework, it’s essential to recognize the unique demands placed on device drivers.

Device drivers often interact with hardware components that require timely responses. To ensure optimal system performance, we need to introduce a more granular approach to task prioritization. This involves incorporating a dedicated priority level for device drivers.

Introducing the Device Driver Priority Level

By establishing a distinct priority level for device drivers, we can effectively address the real-time requirements of hardware interactions. This level should be positioned above the standard application tasks but below the critical system tasks.

Top Half and Bottom Half: A Balanced Approach

To further optimize device driver performance, we can employ the concept of top and bottom halves.

  • Top Half (Interrupt Handler): Executes interrupt-time It captures essential data and schedules a bottom half task for further processing. Two things to remember:
    • The handler  must be prompt to return: the other interrupts may be masked!
    • The interrupt handler is not a task!
  • Bottom Half (Driver Task): Runs at the device driver priority level, allowing for more complex data processing without blocking the system.

How Top and Bottom Halves Work Together

The top half and bottom half form a cooperative duo to efficiently handle device interrupts. Here’s how they interact:

  1. Interrupt Occurs: When a device generates an interrupt, the system pauses its current task to execute the interrupt handler (top half).
  2. Data Capture: The top half quickly captures essential data from the device and stores it in a  queue (or another structure type). The availability of data in the queue will cause the bottom half to move from the blocked state to the ready state.
  3. Bottom Half Scheduling: Remember the taskYield() function discussed in the previous article? Here the top half often uses its sister taskYIELDFromISR() to immediately request scheduling. Due to its higher priority, the bottom half is likely to be scheduled next.
  4. Bottom Half Processing: The bottom half retrieves the data from the queue and performs the necessary processing.

 

By using taskYIELDFromISR(), the top half efficiently hands over control to the bottom half, maximizing system responsiveness.

Expanding Our Priority Levels

To accommodate the new device driver priority level, we need to adjust our priority definitions. Here’s the updated code:

// Idle task has lowest priority  (0) and is already defined somewhere else
#define configPRIORITY_BACKGROUND (1) // Low priority tasks, lowest urgency
#define configPRIORITY_NORMAL (2) // Normal application tasks, mid-range prio
#define configPRIORITY_DRIVER (3) // Device driver tasks
#define configPRIORITY_RT (4) // Hard real-time tasks, strictest deadlines
#define configMAX_PRIORITIES (5)

Expanding More: Implementing Protocols

Protocol tasks act as intermediaries, facilitating data transfer between device drivers and application tasks. They are responsible for formatting data, error checking, and ensuring reliable communication.

The Importance of proper prioritization:

  • Data Flow: Protocol tasks must have sufficient priority to prevent data backlogs and ensure timely processing of incoming data.
  • Device Driver Interaction: A protocol task should not starve a device driver of processing time, as this could lead to data loss or system instability.
  • Application Responsiveness: While protocol tasks are important, they should not unduly delay application tasks.
  • Improved determinism: By assigning a dedicated priority to the protocols the system also becomes more deterministic since the scheduler does not have to cycle through the tasks of the same priority.

 

By placing the protocol task priority level between device drivers and application tasks, we achieve a balance that allows for efficient data transfer while maintaining overall system responsiveness. This leads us to the new priority definition code:

// Idle task has lowest priority  (0) and is already defined somewhere else
#define configPRIORITY_BACKGROUND (1) // Low priority tasks, lowest urgency
#define configPRIORITY_NORMAL (2) // Normal application tasks, mid-range prio
#define configPRIORITY_PROTOCOL (3) // Protocol layer tasks
#define configPRIORITY_DRIVER (4) // Device driver tasks
#define configPRIORITY_RT (5) // Hard real-time tasks, strictest deadlines
#define configMAX_PRIORITIES (6)

In essence, protocol tasks act as a bridge, connecting the high-speed world of device drivers with the more relaxed pace of application tasks.

While a dedicated priority level for protocol tasks offers significant benefits in some types of embedded systems, I do recognize that not all applications require this level of complexity. Combining protocol-related activities within existing task priorities might be sufficient for simpler applications with minimal communication needs. However, a dedicated protocol task priority level can significantly enhance system performance and determinism for systems with complex communication protocols or demanding performance requirements.

Wrap-Up

By carefully considering device driver priorities and employing techniques like top and bottom halves, you can significantly improve the performance and responsiveness of your FreeRTOS-based system. By striking the right balance between real-time requirements and overall system efficiency, you’ll create a more robust and reliable application.

In the next article, we will discuss the priority level for less time-constrained but still critical tasks. It will provide an additional layer of flexibility in task management. This will allow for better resource allocation and further improved system responsiveness.