Mastering The FreeRTOS Real Time Kernel-A Hands-On Tutorial Guide-Trang-208-259
Mastering The FreeRTOS Real Time Kernel-A Hands-On Tutorial Guide-Trang-208-259
Chapter 6
Interrupt Management
182
6.1 Chapter Introduction and Scope
Events
Embedded real-time systems have to take actions in response to events that originate from
the environment. For example, a packet arriving on an Ethernet peripheral (the event) might
require passing to a TCP/IP stack for processing (the action). Non-trivial systems will have to
service events that originate from multiple sources, all of which will have different processing
overhead and response time requirements. In each case, a judgment has to be made as to
the best event processing implementation strategy:
1. How should the event be detected? Interrupts are normally used, but inputs can also
be polled.
2. When interrupts are used, how much processing should be performed inside the
interrupt service routine (ISR), and how much outside? It is normally desirable to keep
each ISR as short as possible.
3. How events are communicated to the main (non-ISR) code, and how can this code be
structured to best accommodate processing of potentially asynchronous occurrences?
FreeRTOS does not impose any specific event processing strategy on the application
designer, but does provide features that allow the chosen strategy to be implemented in a
simple and maintainable way.
It is important to draw a distinction between the priority of a task, and the priority of an
interrupt:
183
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
All architectures on which FreeRTOS will run are capable of processing interrupts, but details
relating to interrupt entry, and interrupt priority assignment, vary between architectures.
Scope
Which FreeRTOS API functions can be used from within an interrupt service routine.
How to use a queue to pass data into and out of an interrupt service routine.
184
6.2 Using the FreeRTOS API from an ISR
Often it is necessary to use the functionality provided by a FreeRTOS API function from an
interrupt service routine (ISR), but many FreeRTOS API functions perform actions that are not
valid inside an ISR—the most notable of which is placing the task that called the API function
into the Blocked state; if an API function is called from an ISR, then it is not being called from a
task, so there is no calling task that can be placed into the Blocked state. FreeRTOS solves
this problem by providing two versions of some API functions; one version for use from tasks,
and one version for use from ISRs. Functions intended for use from ISRs have “FromISR”
appended to their name.
Note: Never call a FreeRTOS API function that does not have “FromISR” in its name from an
ISR.
Having a separate API for use in interrupts allows task code to be more efficient, ISR code to
be more efficient, and interrupt entry to be simpler. To see why, consider the alternative
solution, which would have been to provide a single version of each API function that could be
called from both a task and an ISR. If the same version of an API function could be called
from both a task and an ISR then:
The API functions would need additional logic to determine if they had been called from
a task or an ISR. The additional logic would introduce new paths through the function,
making the functions longer, more complex, and harder to test.
Some API function parameters would be obsolete when the function was called from a
task, while others would be obsolete when the function was called from an ISR.
Each FreeRTOS port would need to provide a mechanism for determining the
execution context (task or ISR).
Architectures on which it is not easy to determine the execution context (task or ISR)
would require additional, wasteful, more complex to use, and non-standard interrupt
entry code that allowed the execution context to be provided by software.
185
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Having two versions of some API functions allows both tasks and ISRs to be more efficient,
but introduces a new problem; sometimes it is necessary to call a function that is not part of
the FreeRTOS API, but makes use of the FreeRTOS API, from both a task and an ISR.
This is normally only a problem when integrating third party code, as that is the only time when
the software’s design is out of the control of the application writer. If this does become an
issue then the problem can be overcome using one of the following techniques:
1. Defer interrupt processing to a task1, so the API function is only ever called from the
context of a task.
2. If you are using a FreeRTOS port that supports interrupt nesting, then use the version
of the API function that ends in “FromISR”, as that version can be called from tasks and
ISRs (the reverse is not true, API functions that do not end in “FromISR” must not be
called from an ISR).
3. Third party code normally includes an RTOS abstraction layer that can be implemented
to test the context from which the function is being called (task or interrupt), and then
call the API function that is appropriate for the context.
If a context switch is performed by an interrupt, then the task running when the interrupt exits
might be different to the task that was running when the interrupt was entered—the interrupt
will have interrupted one task, but returned to a different task.
Some FreeRTOS API functions can move a task from the Blocked state to the Ready state.
This has already been seen with functions such as xQueueSendToBack(), which will unblock a
task if there was a task waiting in the Blocked state for data to become available on the
subject queue.
186
If the priority of a task that is unblocked by a FreeRTOS API function is higher than the priority
of the task in the Running state then, in accordance with the FreeRTOS scheduling policy, a
switch to the higher priority task should occur. When the switch to the higher priority task
actually occurs is dependent on the context from which the API function is called:
A switch to a higher priority task will not occur automatically inside an interrupt. Instead, a
variable is set to inform the application writer that a context switch should be performed.
Interrupt safe API functions (those that end in “FromISR”) have a pointer parameter called
pxHigherPriorityTaskWoken that is used for this purpose.
If a context switch should be performed, then the interrupt safe API function will set
*pxHigherPriorityTaskWoken to pdTRUE. To be able to detect this has happened, the
variable pointed to by pxHigherPriorityTaskWoken must be initialized to pdFALSE before it
is used for the first time.
If the application writer opts not to request a context switch from the ISR, then the higher
priority task will remain in the Ready state until the next time the scheduler runs—which in
the worst case will be during the next tick interrupt.
There are several reasons why context switches do not occur automatically inside the interrupt
safe version of an API function:
187
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
An interrupt may execute more than once before it is necessary for a task to perform any
processing. For example, consider a scenario where a task processes a string that was
received by an interrupt driven UART; it would be wasteful for the UART ISR to switch to
the task each time a character was received because the task would only have processing
to perform after the complete string had been received.
Interrupts can occur sporadically, and at unpredictable times. Expert FreeRTOS users may
want to temporarily avoid an unpredictable switch to a different task at specific points in
their application—although this can also be achieved using the FreeRTOS scheduler
locking mechanism.
3. Portability
It is the simplest mechanism that can be used across all FreeRTOS ports.
4. Efficiency
Ports that target smaller processor architectures only allow a context switch to be
requested at the very end of an ISR, and removing that restriction would require additional
and more complex code. It also allows more than one call to a FreeRTOS API function
within the same ISR without generating more than one request for a context switch within
the same ISR.
As will be seen later in this book, it is possible to add application code into the RTOS tick
interrupt. The result of attempting a context switch inside the tick interrupt is dependent on
the FreeRTOS port in use. At best, it will result in an unnecessary call to the scheduler.
This section introduces the macros that are used to request a context switch from an ISR. Do
not be concerned if you do not fully understand this section yet, as practical examples are
provided in following sections.
188
taskYIELD() is a macro that can be called in a task to request a context switch.
portYIELD_FROM_ISR() and portEND_SWITCHING_ISR() are both interrupt safe versions of
taskYIELD(). portYIELD_FROM_ISR() and portEND_SWITCHING_ISR() are both used in the
same way, and do the same thing1. Some FreeRTOS ports only provide one of the two
macros. Newer FreeRTOS ports provide both macros. The examples in this book use
portYIELD_FROM_ISR().
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
The xHigherPriorityTaskWoken parameter passed out of an interrupt safe API function can be
used directly as the parameter in a call to portYIELD_FROM_ISR().
1 Historically, portEND_SWITCHING_ISR() was the name used in FreeRTOS ports that required
interrupt handlers to use an assembly code wrapper, and portYIELD_FROM_ISR() was the name used
in FreeRTOS ports that allowed the entire interrupt handler to be written in C.
189
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
It is normally considered best practice to keep ISRs as short as possible. Reasons for this
include:
Even if tasks have been assigned a very high priority, they will only run if no interrupts
are being serviced by the hardware.
ISRs can disrupt (add ‘jitter’ to) both the start time, and the execution time, of a task.
The application writer needs to consider the consequences of, and guard against,
resources such as variables, peripherals, and memory buffers being accessed by a
task and an ISR at the same time.
Some FreeRTOS ports allow interrupts to nest, but interrupt nesting can increase
complexity and reduce predictability. The shorter an interrupt is, the less likely it is to
nest.
An interrupt service routine must record the cause of the interrupt, and clear the interrupt.
Any other processing necessitated by the interrupt can often be performed in a task, allowing
the interrupt service routine to exit as quickly as is practical. This is called ‘deferred interrupt
processing’, because the processing necessitated by the interrupt is ‘deferred’ from the ISR to
a task.
Deferring interrupt processing to a task also allows the application writer to prioritize the
processing relative to other tasks in the application, and use all the FreeRTOS API functions.
If the priority of the task to which interrupt processing is deferred is above the priority of any
other task, then the processing will be performed immediately, just as if the processing had
been performed in the ISR itself. This scenario is shown in Figure 48, in which Task 1 is a
normal application task, and Task 2 is the task to which interrupt processing is deferred.
190
2 - The ISR executes, handles 3 - The priority of Task 2 is higher than
the interrupting peripheral, the priority of Task 1, so the ISR returns
clears the interrupt, then directly to Task 2, in which the interrupt
unblocks Task 2. processing is completed.
t1 t2 t3 t4
1 - Task1 is Running when an
interrupt occurs.
In Figure 48, interrupt processing starts at time t2, and effectively ends at time t4, but only the
period between times t2 and t3 is spent in the ISR. If deferred interrupt processing had not
been used then the entire period between times t2 and t4 would have been spent in the ISR.
The processing necessitated by the interrupt is not trivial. For example, if the interrupt
is just storing the result of an analog to digital conversion, then it is almost certain this
is best performed inside the ISR, but if result of the conversion must also be passed
through a software filter, then it may be best to execute the filter in a task.
The following sections describe and demonstrate the concepts introduced in this chapter so
far, including FreeRTOS features that can be used to implement deferred interrupt processing.
191
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
The interrupt safe version of the Binary Semaphore API can be used to unblock a task each
time a particular interrupt occurs, effectively synchronizing the task with the interrupt. This
allows the majority of the interrupt event processing to be implemented within the
synchronized task, with only a very fast and short portion remaining directly in the ISR. As
described in the previous section, the binary semaphore is used to ‘defer’ interrupt processing
to a task1.
As previously demonstrated in Figure 48, if the interrupt processing is particularly time critical,
then the priority of the deferred processing task can be set to ensure the task always pre-
empts the other tasks in the system. The ISR can then be implemented to include a call to
portYIELD_FROM_ISR(), ensuring the ISR returns directly to the task to which interrupt
processing is being deferred. This has the effect of ensuring the entire event processing
executes contiguously (without a break) in time, just as if it had all been implemented within
the ISR itself. Figure 49 repeats the scenario shown in Figure 48, but with the text updated to
describe how the execution of the deferred processing task can be controlled using a
semaphore.
Task1
The deferred processing task uses a blocking ‘take’ call to a semaphore as a means of
entering the Blocked state to wait for the event to occur. When the event occurs, the ISR uses
1It is more efficient to unblock a task from an interrupt using a direct to task notification than it is using a
binary semaphore. Direct to task notifications are not covered until Chapter 9, Task Notifications.
192
a ‘give’ operation on the same semaphore to unblock the task so that the required event
processing can proceed.
‘Taking a semaphore’ and ‘giving a semaphore’ are concepts that have different meanings
depending on their usage scenario. In this interrupt synchronization scenario, the binary
semaphore can be considered conceptually as a queue with a length of one. The queue can
contain a maximum of one item at any time, so is always either empty or full (hence, binary).
By calling xSemaphoreTake(), the task to which interrupt processing is deferred effectively
attempts to read from the queue with a block time, causing the task to enter the Blocked state
if the queue is empty. When the event occurs, the ISR uses the xSemaphoreGiveFromISR()
function to place a token (the semaphore) into the queue, making the queue full. This causes
the task to exit the Blocked state and remove the token, leaving the queue empty once more.
When the task has completed its processing, it once more attempts to read from the queue
and, finding the queue empty, re-enters the Blocked state to wait for the next event. This
sequence is demonstrated in Figure 50.
Figure 50 shows the interrupt ‘giving’ the semaphore, even though it has not first ‘taken’ it, and
the task ‘taking’ the semaphore, but never giving it back. This is why the scenario is described
as being conceptually similar to writing to and reading from a queue. It often causes confusion
as it does not follow the same rules as other semaphore usage scenarios, where a task that
takes a semaphore must always give it back—such as the scenarios described in Chapter 7,
Resource Management.
193
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Task
xSemaphoreTake()
The semaphore is not
available...
...so the task is blocked
waiting for the semaphore
Task
Interrupt!
xSemaphoreGiveFromISR() xSemaphoreTake()
An interrupt occurs...that
‘gives’ the semaphore….
Task
Interrupt!
xSemaphoreGiveFromISR() xSemaphoreTake()
Task
xSemaphoreTake()
Task
194
The xSemaphoreCreateBinary() API Function
FreeRTOS V9.0.0 also includes the xSemaphoreCreateBinaryStatic() function, which allocates the memory
required to create a binary semaphore statically at compile time: Handles to all the various types of
FreeRTOS semaphore are stored in a variable of type SemaphoreHandle_t.
Before a semaphore can be used, it must be created. To create a binary semaphore, use the
xSemaphoreCreateBinary() API function1.
Returned value If NULL is returned, then the semaphore cannot be created because
there is insufficient heap memory available for FreeRTOS to allocate the
semaphore data structures.
A non-NULL value being returned indicates that the semaphore has been
created successfully. The returned value should be stored as the handle
to the created semaphore.
‘Taking’ a semaphore means to ‘obtain’ or ‘receive’ the semaphore. The semaphore can be
taken only if it is available.
All the various types of FreeRTOS semaphore, except recursive mutexes, can be ‘taken’ using
the xSemaphoreTake() function.
1 Some Semaphore API functions are actually macros, not functions. For simplicity, they are all referred
to as functions throughout this book.
195
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Parameter Name/
Description
Returned Value
xTicksToWait The maximum amount of time the task should remain in the Blocked
state to wait for the semaphore if it is not already available.
196
Table 34. xSemaphoreTake() parameters and return value
Parameter Name/
Description
Returned Value
1. pdPASS
2. pdFALSE
If a block time was specified (xTicksToWait was not zero), then the
calling task will have been placed into the Blocked state to wait for the
semaphore to become available, but the block time expired before this
happened.
197
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Parameter Name/
Description
Returned Value
1. pdPASS
2. pdFAIL
198
Example 16. Using a binary semaphore to synchronize a task with an interrupt
This example uses a binary semaphore to unblock a task from an interrupt service routine—
effectively synchronizing the task with the interrupt.
A simple periodic task is used to generate a software interrupt every 500 milliseconds. A
software interrupt is used for convenience because of the complexity of hooking into a real
interrupt in some target environments. Listing 92 shows the implementation of the periodic
task. Note that the task prints out a string both before and after the interrupt is generated.
This allows the sequence of execution to be observed in the output produced when the
example is executed.
/* The number of the software interrupt used in this example. The code shown is from
the Windows project, where numbers 0 to 2 are used by the FreeRTOS Windows port
itself, so 3 is the first number available to the application. */
#define mainINTERRUPT_NUMBER 3
Listing 93 shows the implementation of the task to which the interrupt processing is deferred—
the task that is synchronized with the software interrupt through the use of a binary
semaphore. Again, a string is printed out on each iteration of the task, so the sequence in
which the task and the interrupt execute is evident from the output produced when the
example is executed.
199
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
It should be noted that, while the code shown in Listing 93 is adequate for Example 16, where
interrupts are generated by software, it is not adequate for scenarios where interrupts are
generated by hardware peripherals. A following sub-section describes how the structure of the
code needs to be changed to make it suitable for use with hardware generated interrupts.
/* To get here the event must have occurred. Process the event (in this
Case, just print out a message). */
vPrintString( "Handler task - Processing event.\r\n" );
}
}
Listing 93. The implementation of the task to which the interrupt processing is
deferred (the task that synchronizes with the interrupt) in Example 16
Listing 94 shows the ISR. This does very little other than ‘give’ the semaphore to unblock the
task to which interrupt processing is deferred.
Note how the xHigherPriorityTaskWoken variable is used. It is set to pdFALSE before calling
xSemaphoreGiveFromISR(), then used as the parameter when portYIELD_FROM_ISR() is
called. A context switch will be requested inside the portYIELD_FROM_ISR() macro if
xHigherPriorityTaskWoken equals pdTRUE.
The prototype of the ISR, and the macro called to force a context switch, are both correct for
the FreeRTOS Windows port, and may be different for other FreeRTOS ports. Refer to the
port specific documentation pages on the FreeRTOS.org website, and the examples provided
in the FreeRTOS download, to find the syntax required for the port you are using.
Unlike most architectures on which FreeRTOS runs, the FreeRTOS Windows port requires an
ISR to return a value. The implementation of the portYIELD_FROM_ISR() macro provided
with the Windows port includes the return statement, so Listing 94 does not show a value
being returned explicitly.
200
static uint32_t ulExampleInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
Listing 94. The ISR for the software interrupt used in Example 16
The main() function creates the binary semaphore, creates the tasks, installs the interrupt
handler, and starts the scheduler. The implementation is shown in Listing 95.
The syntax of the function called to install an interrupt handler is specific to the FreeRTOS
Windows port, and may be different for other FreeRTOS ports. Refer to the port specific
documentation pages on the FreeRTOS.org website, and the examples provided in the
FreeRTOS download, to find the syntax required for the port you are using.
201
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
/* Install the handler for the software interrupt. The syntax necessary
to do this is dependent on the FreeRTOS port being used. The syntax
shown here can only be used with the FreeRTOS windows port, where such
interrupts are only simulated. */
vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
Example 16 produces the output shown in Figure 51. As expected, vHandlerTask() enters the
Running state as soon as the interrupt is generated, so the output from the task splits the
output produced by the periodic task. Further explanation is provided in Figure 52.
202
3 - The ISR ‘gives’ the semaphore, causing vHandlerTask() to unblock.
2 - The Periodic task prints its first The ISR then returns directly to vHandlerTask() because the task is the
message then forces an interrupt. The highest priority Ready state task. vHandlerTask() prints out its message
interrupt service routine (ISR) executes before returning to the Blocked state to wait for the next interrupt.
immediately.
Interrupt
Handler
Periodic
Idle
t1 t2 Time
Example 16 used a binary semaphore to synchronize a task with an interrupt. The execution
sequence was as follows:
2. The ISR executed and ‘gave’ the semaphore to unblock the task.
3. The task executed immediately after the ISR, and ‘took’ the semaphore.
4. The task processed the event, then attempted to ‘take’ the semaphore again—entering
the Blocked state because the semaphore was not yet available (another interrupt had
not yet occurred).
The structure of the task used in Example 16 is adequate only if interrupts occur at a relatively
low frequency. To understand why, consider what would happen if a second, and then a third,
interrupt had occurred before the task had completed its processing of the first interrupt:
When the second ISR executed the semaphore would be empty, so the ISR would give
the semaphore, and the task would process the second event immediately after it had
completed processing the first event. That scenario is shown in Figure 53.
203
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
When the third ISR executed, the semaphore would already be available, preventing
the ISR giving the semaphore again, so the task would not know the third event had
occurred. That scenario is shown in Figure 54.
204
Task
xSemaphoreTake()
Interrupt! Task
xSemaphoreGiveFromISR() xSemaphoreTake()
Task
vProcessEvent()
Interrupt! Task
xSemaphoreGiveFromISR() vProcessEvent()
Another interrupt occurs while the task is still processing the The task is still processing
first event. The ISR ‘gives’ the semaphore again, effectively the first event.
latching the event so the event is not lost.
Task
xSemaphoreTake()
When the task has finished processing the first event it calls xSemaphoreTake() again.
Another interrupt has already occurred, so the semaphore is already available.
Task
vProcessEvent()
Figure 53. The scenario when one interrupt occurs before the task has finished
processing the first event
205
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Task
xSemaphoreTake()
Interrupt! Task
xSemaphoreGiveFromISR() xSemaphoreTake()
Task
vProcessEvent()
Interrupt! Task
xSemaphoreGiveFromISR() vProcessEvent()
A second interrupt occurs while the task is still processing The task is still processing
the first event. The ISR ‘gives’ the semaphore again, the first event.
effectively latching the event so the event is not lost.
Interrupt! Task
xSemaphoreGiveFromISR() vProcessEvent()
A third interrupt occurs while the task is still processing the first The task is still processing
event. The ISR cannot give the semaphore again, because the first event.
the semaphore is already available, and the event is lost.
Task
xSemaphoreTake()
When the task has finished processing the first event it calls xSemaphoreTake() again. A
second and third interrupt have already occurred, so the semaphore is already available.
Task
vProcessEvent()
The task takes the semaphore (without entering the Blocked state), so the semaphore
is no longer available. The task then processes the second event.
Task
xSemaphoreTake()
When the task has finished processing the second event it calls xSemaphoreTake() again,
but the semaphore is not available, and the task enters the Blocked state to wait for the
next interrupt - even though the third event has not been processed.
Figure 54 The scenario when two interrupts occur before the task has finished
processing the first event
206
The deferred interrupt handling task used in Example 16, and shown in Listing 93, is
structured so that it only processes one event between each call to xSemaphoreTake(). That
was adequate for Example 16, because the interrupts that generated the events were
triggered by software, and occurred at a predictable time. In real applications, interrupts are
generated by hardware, and occur at unpredictable times. Therefore, to minimize the chance
of an interrupt being missed, the deferred interrupt handling task must be structured so that it
processes all the events that are already available between each call to xSemaphoreTake()1.
This is demonstrated by Listing 96, which shows how a deferred interrupt handler for a UART
could be structured. In Listing 96, it is assumed the UART generates a receive interrupt each
time a character is received, and that the UART places received characters into a hardware
FIFO (a hardware buffer).
The deferred interrupt handling task used in Example 16 had one other weakness; it did not
use a time out when it called xSemaphoreTake(). Instead, the task passed portMAX_DELAY
as the xSemaphoreTake() xTicksToWait parameter, which results in the task waiting
indefinitely (without a time out) for the semaphore to be available. Indefinite timeouts are often
used in example code because their use simplifies the structure of the example, and therefore
makes the example easier to understand. However, indefinite timeouts are normally bad
practice in real applications, because they make it difficult to recover from an error. As an
example, consider the scenario where a task is waiting for an interrupt to give a semaphore,
but an error state in the hardware is preventing the interrupt from being generated:
If the task is waiting without a time out, it will not know about the error state, and will
wait forever.
If the task is waiting with a time out, then xSemaphoreTake() will return pdFAIL when
the time out expires, and the task can then detect and clear the error the next time it
executes. This scenario is also demonstrated in Listing 96.
1 Alternatively, a counting semaphore, or a direct to task notification, can be used to count events.
Counting semaphores are described in the next section. Direct to task notifications are described in
Chapter 9, Task Notifications. Direct to task notifications are the preferred method as they are the most
efficient in both run time and RAM usage.
207
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
208
6.5 Counting Semaphores
Just as binary semaphores can be thought of as queues that have a length of one, counting
semaphores can be thought of as queues that have a length of more than one. Tasks are not
interested in the data that is stored in the queue—just the number of items in the queue.
configUSE_COUNTING_SEMAPHORES must be set to 1 in FreeRTOSConfig.h for counting
semaphores to be available.
Each time a counting semaphore is ‘given’, another space in its queue is used. The number of
items in the queue is the semaphore’s ‘count’ value.
1. Counting events1
In this scenario, an event handler will ‘give’ a semaphore each time an event occurs—
causing the semaphore’s count value to be incremented on each ‘give’. A task will ‘take’ a
semaphore each time it processes an event—causing the semaphore’s count value to be
decremented on each ‘take’. The count value is the difference between the number of
events that have occurred and the number that have been processed. This mechanism is
shown in Figure 55.
Counting semaphores that are used to count events are created with an initial count value
of zero.
2. Resource management.
In this scenario, the count value indicates the number of resources available. To obtain
control of a resource, a task must first obtain a semaphore—decrementing the
semaphore’s count value. When the count value reaches zero, there are no free
resources. When a task finishes with the resource, it ‘gives’ the semaphore back—
incrementing the semaphore’s count value.
1 It is more efficient to count events using a direct to task notification than it is using a counting
semaphore. Direct to task notifications are not covered until Chapter 9.
209
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Counting semaphores that are used to manage resources are created so that their initial
count value equals the number of resources that are available. Chapter 7 covers using
semaphores to manage resources.
Task
[The semaphore count is 0]
xSemaphoreTake()
Task
Interrupt! [The semaphore count is 1]
xSemaphoreGiveFromISR() xSemaphoreTake()
An interrupt occurs...that
‘gives’ the semaphore….
Task
Interrupt! [The semaphore count is 1]
xSemaphoreGiveFromISR() xSemaphoreTake()
Task
[The semaphore count is 0]
vProcessEvent()
Task
Interrupt! [The semaphore count is 2]
xSemaphoreGiveFromISR() vProcessEvent()
Task
[The semaphore count is 1]
xSemaphoreTake()
When the task has finished processing the first event it calls xSemaphoreTake()
again. Another two semaphores are already ‘available’, one is taken without the task
ever entering the Blocked state, leaving one ‘latched’ semaphore still available.
210
The xSemaphoreCreateCounting() API Function
FreeRTOS V9.0.0 also includes the xSemaphoreCreateCountingStatic() function, which allocates the memory
required to create a counting semaphore statically at compile time: Handles to all the various types of
FreeRTOS semaphore are stored in a variable of type SemaphoreHandle_t.
Before a semaphore can be used, it must be created. To create a counting semaphore, use
the xSemaphoreCreateCounting() API function.
Parameter Name/
Description
Returned Value
uxMaxCount The maximum value to which the semaphore will count. To continue the
queue analogy, the uxMaxCount value is effectively the length of the
queue.
uxInitialCount The initial count value of the semaphore after it has been created.
211
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Parameter Name/
Description
Returned Value
Returned value If NULL is returned, the semaphore cannot be created because there is
insufficient heap memory available for FreeRTOS to allocate the
semaphore data structures. Chapter 2 provides more information on
heap memory management.
A non-NULL value being returned indicates that the semaphore has been
created successfully. The returned value should be stored as the handle
to the created semaphore.
Example 17. Using a counting semaphore to synchronize a task with an inter rupt
To simulate multiple events occurring at high frequency, the interrupt service routine is
changed to ‘give’ the semaphore more than once per interrupt. Each event is latched in the
semaphore’s count value. The modified interrupt service routine is shown in Listing 99.
212
static uint32_t ulExampleInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
/* 'Give' the semaphore multiple times. The first will unblock the deferred
interrupt handling task, the following 'gives' are to demonstrate that the
semaphore latches the events to allow the task to which interrupts are deferred
to process them in turn, without events getting lost. This simulates multiple
interrupts being received by the processor, even though in this case the events
are simulated within a single interrupt occurrence. */
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
Listing 99. The implementation of the interrupt service routine used by Example 17
All the other functions remain unmodified from those used in Example 16.
The output produced when Example 17 is executed is shown in Figure 56. As can be seen,
the task to which interrupt handling is deferred processes all three [simulated] events each
time an interrupt is generated. The events are latched into the count value of the semaphore,
allowing the task to process them in turn.
213
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
The deferred interrupt handling examples presented so far have required the application writer
to create a task for each interrupt that uses the deferred processing technique. It is also
possible to use the xTimerPendFunctionCallFromISR()1 API function to defer interrupt
processing to the RTOS daemon task—removing the need to create a separate task for each
interrupt. Deferring interrupt processing to the daemon task is called ‘centralized deferred
interrupt processing’.
Chapter 5 described how software timer related FreeRTOS API functions send commands to
the daemon task on the timer command queue. The xTimerPendFunctionCall() and
xTimerPendFunctionCallFromISR() API functions use the same timer command queue to send
an ‘execute function’ command to the daemon task. The function sent to the daemon task is
then executed in the context of the daemon task.
It removes the need to create a separate task for each deferred interrupt.
Less flexibility
It is not possible to set the priority of each deferred interrupt handling task separately.
Each deferred interrupt handling function executes at the priority of the daemon task. As
described in Chapter 5, the priority of the daemon task is set by the
configTIMER_TASK_PRIORITY compile time configuration constant within
FreeRTOSConfig.h.
Less determinism
1 It was noted in Chapter 5 that the daemon task was originally called the timer service task because it
was originally only used to execute software timer callback functions. Hence, xTimerPendFunctionCall()
is implemented in timers.c, and, in accordance with the convention of prefixing a function’s name with
the name of the file in which the function is implemented, the function’s name is prefixed with ‘Timer’.
214
xTimerPendFunctionCallFromISR() sends a command to the back of the timer command
queue. Commands that were already in the timer command queue will be processed by
the daemon task before the ‘execute function’ command sent to the queue by
xTimerPendFunctionCallFromISR().
Different interrupts have different timing constraints, so it is common to use both methods of
deferring interrupt processing within the same application.
Parameter Name/
Description
Returned Value
215
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Parameter Name/
Description
Returned Value
pvParameter1 The value that will be passed into the function that is executed
by the daemon task as the function’s pvParameter1 parameter.
The parameter has a void * type to allow it to be used to pass
any data type. For example, integer types can be directly cast
to a void *, alternatively the void * can be used to point to a
structure.
ulParameter2 The value that will be passed into the function that is executed
by the daemon task as the function’s ulParameter2 parameter.
216
Table 37. xTimerPendFunctionCallFromISR() parameters and return value
Parameter Name/
Description
Returned Value
1. pdPASS
2. pdFAIL
Example 18 provides similar functionality to Example 16, but without using a semaphore, and
without creating a task specifically to perform the processing necessitated by the interrupt.
Instead, the processing is performed by the RTOS daemon task.
The interrupt service routine used by Example 18 is shown in Listing 102. It calls
xTimerPendFunctionCallFromISR() to pass a pointer to a function called
vDeferredHandlingFunction() to the daemon task. The deferred interrupt processing is
performed by the vDeferredHandlingFunction() function.
The interrupt service routine increments a variable called ulParameterValue each time it
executes. ulParameterValue is used as the value of ulParameter2 in the call to
xTimerPendFunctionCallFromISR(), so will also be used as the value of ulParameter2 in the
call to vDeferredHandlingFunction() when vDeferredHandlingFunction() is executed by the
daemon task. The function’s other parameter, pvParameter1, is not used in this example.
217
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
/* Send a pointer to the interrupt's deferred handling function to the daemon task.
The deferred handling function's pvParameter1 parameter is not used so just set to
NULL. The deferred handling function's ulParameter2 parameter is used to pass a
number that is incremented by one each time this interrupt handler executes. */
xTimerPendFunctionCallFromISR( vDeferredHandlingFunction, /* Function to execute. */
NULL, /* Not used. */
ulParameterValue, /* Incrementing value. */
&xHigherPriorityTaskWoken );
ulParameterValue++;
vDeferredHandlingFunction() must have the prototype shown in Listing 101, even though, in
this example, only one of its parameters is actually used.
Listing 103. The function that performs the processing necessitated by the interrupt
in Example 18.
The main() function used by Example 18 is shown in Listing 104. It is simpler than the main()
function used by Example 16 because it does not create either a semaphore or a task to
perform the deferred interrupt processing.
218
vPeriodicTask() is the task that periodically generates software interrupts. It is created with a
priority below the priority of the daemon task to ensure it is pre-empted by the daemon task as
soon as the daemon task leaves the Blocked state.
/* Install the handler for the software interrupt. The syntax necessary to do
this is dependent on the FreeRTOS port being used. The syntax shown here can
only be used with the FreeRTOS windows port, where such interrupts are only
simulated. */
vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
Example 18 produces the output shown in Figure 57. The priority of the daemon task is higher
than the priority of the task that generates the software interrupt, so
vDeferredHandlingFunction() is executed by the daemon task as soon as the interrupt is
generated. That results in the message output by vDeferredHandlingFunction() appearing in
between the two messages output by the periodic task, just as it did when a semaphore was
used to unblock a dedicated deferred interrupt processing task. Further explanation is
provided in Figure 58.
219
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Interrupt
Daemon Task
Periodic
Idle
t1 t2 Time
220
6.7 Using Queues within an Interrupt Service Routine
Binary and counting semaphores are used to communicate events. Queues are used to
communicate events, and to transfer data.
Parameter Name/
Description
Returned Value
xQueue The handle of the queue to which the data is being sent
(written). The queue handle will have been returned from the
call to xQueueCreate() used to create the queue.
221
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Parameter Name/
Description
Returned Value
pvItemToQueue A pointer to the data that will be copied into the queue.
The size of each item the queue can hold is set when the
queue is created, so this many bytes will be copied from
pvItemToQueue into the queue storage area.
pxHigherPriorityTaskWoken It is possible that a single queue will have one or more tasks
blocked on it, waiting for data to become available. Calling
xQueueSendToFrontFromISR() or
xQueueSendToBackFromISR() can make data available, and
so cause such a task to leave the Blocked state. If calling the
API function causes a task to leave the Blocked state, and the
unblocked task has a priority higher than the currently
executing task (the task that was interrupted), then, internally,
the API function will set *pxHigherPriorityTaskWoken to
pdTRUE.
If xQueueSendToFrontFromISR() or
xQueueSendToBackFromISR() sets this value to pdTRUE,
then a context switch should be performed before the interrupt
is exited. This will ensure that the interrupt returns directly to
the highest priority Ready state task.
1. pdPASS
2. errQUEUE_FULL
222
Considerations When Using a Queue From an ISR
Queues provide an easy and convenient way of passing data from an interrupt to a task, but it
is not efficient to use a queue if data is arriving at a high frequency.
Many of the demo applications in the FreeRTOS download include a simple UART driver that
uses a queue to pass characters out of the UART’s receive ISR. In those demos a queue is
used for two reasons: to demonstrate queues being used from an ISR, and to deliberately load
the system in order to test the FreeRTOS port. The ISRs that use a queue in this manner are
definitely not intended to represent an efficient design, and unless the data is arriving slowing,
it is recommended that production code does not copy the technique. More efficient
techniques, that are suitable for production code, include:
Using Direct Memory Access (DMA) hardware to receive and buffer characters. This
method has practically no software overhead. A direct to task notification1 can then be
used to unblock the task that will process the buffer only after a break in transmission
has been detected.
Copying each received character into a thread safe RAM buffer2. Again, a direct to
task notification can be used to unblock the task that will process the buffer after a
complete message has been received, or after a break in transmission has been
detected.
Processing the received characters directly within the ISR, then using a queue to send
just the result of processing the data (rather than the raw data) to a task. This was
previously demonstrated by Figure 34.
1 Direct to task notifications provide the most efficient method of unblocking a task from an ISR. Direct
to task notifications are covered in Chapter 9, Task Notifications.
223
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
A periodic task is created that sends five numbers to a queue every 200 milliseconds. It
generates a software interrupt only after all five values have been sent. The task
implementation is shown in Listing 107.
for( ;; )
{
/* This is a periodic task. Block until it is time to run again. The task
will execute every 200ms. */
vTaskDelayUntil( &xLastExecutionTime, pdMS_TO_TICKS( 200 ) );
/* Send five numbers to the queue, each value one higher than the previous
value. The numbers are read from the queue by the interrupt service routine.
The interrupt service routine always empties the queue, so this task is
guaranteed to be able to write all five values without needing to specify a
block time. */
for( i = 0; i < 5; i++ )
{
xQueueSendToBack( xIntegerQueue, &ulValueToSend, 0 );
ulValueToSend++;
}
/* Generate the interrupt so the interrupt service routine can read the
values from the queue. The syntax used to generate a software interrupt is
dependent on the FreeRTOS port being used. The syntax used below can only be
used with the FreeRTOS Windows port, in which such interrupts are only
simulated.*/
vPrintString( "Generator task - About to generate an interrupt.\r\n" );
vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
vPrintString( "Generator task - Interrupt generated.\r\n\r\n\r\n" );
}
}
Listing 107. The implementation of the task that writes to the queue in Example 19
The interrupt service routine calls xQueueReceiveFromISR() repeatedly until all the values
written to the queue by the periodic task have been read out, and the queue is left empty. The
last two bits of each received value are used as an index into an array of strings. A pointer to
the string at the corresponding index position is then sent to a different queue using a call to
xQueueSendFromISR(). The implementation of the interrupt service routine is shown in
Listing 108.
224
static uint32_t ulExampleInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulReceivedNumber;
/* The strings are declared static const to ensure they are not allocated on the
interrupt service routine's stack, and so exist even when the interrupt service
routine is not executing. */
static const char *pcStrings[] =
{
"String 0\r\n",
"String 1\r\n",
"String 2\r\n",
"String 3\r\n"
};
/* If receiving from xIntegerQueue caused a task to leave the Blocked state, and
if the priority of the task that left the Blocked state is higher than the
priority of the task in the Running state, then xHigherPriorityTaskWoken will
have been set to pdTRUE inside xQueueReceiveFromISR().
If sending to xStringQueue caused a task to leave the Blocked state, and if the
priority of the task that left the Blocked state is higher than the priority of
the task in the Running state, then xHigherPriorityTaskWoken will have been set
to pdTRUE inside xQueueSendToBackFromISR().
Listing 108. The implementation of the interrupt service routine used by Example 19
225
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
The task that receives the character pointers from the interrupt service routine blocks on the
queue until a message arrives, printing out each string as it is received. Its implementation is
shown in Listing 109.
for( ;; )
{
/* Block on the queue to wait for data to arrive. */
xQueueReceive( xStringQueue, &pcString, portMAX_DELAY );
Listing 109. The task that prints out the strings received from the interrupt service
routine in Example 19
As normal, main() creates the required queues and tasks before starting the scheduler. Its
implementation is shown in Listing 110.
226
int main( void )
{
/* Before a queue can be used it must first be created. Create both queues used
by this example. One queue can hold variables of type uint32_t, the other queue
can hold variables of type char*. Both queues can hold a maximum of 10 items. A
real application should check the return values to ensure the queues have been
successfully created. */
xIntegerQueue = xQueueCreate( 10, sizeof( uint32_t ) );
xStringQueue = xQueueCreate( 10, sizeof( char * ) );
/* Create the task that uses a queue to pass integers to the interrupt service
routine. The task is created at priority 1. */
xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 1, NULL );
/* Create the task that prints out the strings sent to it from the interrupt
service routine. This task is created at the higher priority of 2. */
xTaskCreate( vStringPrinter, "String", 1000, NULL, 2, NULL );
/* Install the handler for the software interrupt. The syntax necessary to do
this is dependent on the FreeRTOS port being used. The syntax shown here can
only be used with the FreeRTOS Windows port, where such interrupts are only
simulated. */
vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
/* If all is well then main() will never reach here as the scheduler will now be
running the tasks. If main() does reach here then it is likely that there was
insufficient heap memory available for the idle task to be created. Chapter 2
provides more information on heap memory management. */
for( ;; );
}
The output produced when Example 19 is executed is shown in Figure 59. As can be seen,
the interrupt receives all five integers, and produces five strings in response. More
explanation is given in Figure 60.
227
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
3 - The interrupt service routine both reads from a queue and writes to a queue, writing a
string to one queue for every integer received from another. Writing strings to a queue
unblocks the StringPrinter task.
Interrupt
StringPrinter
IntegerGenerator
Idle
t1 Time
1 - The Idle task runs most
of the time. Every 200ms it 5 - The IntegerGenerator task is a periodic task so
gets preempted by the blocks to wait for the next time period - once again
IntegerGenerator task. the idle task is the only task able to run. 200ms after
it last started to execute the whole sequence repeats.
228
6.8 Interrupt Nesting
It is common for confusion to arise between task priorities and interrupt priorities. This section
discusses interrupt priorities, which are the priorities at which interrupt service routines (ISRs)
execute relative to each other. The priority assigned to a task is in no way related to the
priority assigned to an interrupt. Hardware decides when an ISR will execute, whereas
software decides when a task will execute. An ISR executed in response to a hardware
interrupt will interrupt a task, but a task cannot pre-empt an ISR.
Ports that support interrupt nesting require one or both of the constants detailed in Table 39 to
be defined in FreeRTOSConfig.h. configMAX_SYSCALL_INTERRUPT_PRIORITY and
configMAX_API_CALL_INTERRUPT_PRIORITY both define the same property. Older
FreeRTOS ports use configMAX_SYSCALL_INTERRUPT_PRIORITY, and newer FreeRTOS
port use configMAX_API_CALL_INTERRUPT_PRIORITY.
Constant Description
229
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Numeric priority
The numeric priority is simply the number assigned to the interrupt priority. For example, if
an interrupt is assigned a priority of 7, then its numeric priority is 7. Likewise, if an interrupt
is assigned a priority of 200, then its numeric priority is 200.
Logical priority
An interrupt’s logical priority describes that interrupt’s precedence over other interrupts.
If two interrupts of differing priority occur at the same time, then the processor will execute
the ISR for whichever of the two interrupts has the higher logical priority before it executes
the ISR for whichever of the two interrupts has the lower logical priority.
An interrupt can interrupt (nest with) any interrupt that has a lower logical priority, but an
interrupt cannot interrupt (nest with) any interrupt that has an equal or higher logical
priority.
The relationship between an interrupt’s numeric priority and logical priority is dependent on the
processor architecture; on some processors, the higher the numeric priority assigned to an
interrupt the higher that interrupt’s logical priority will be, while on other processor architectures
the higher the numeric priority assigned to an interrupt the lower that interrupt’s logical priority
will be.
Interrupts assigned a numeric priority of 7 have a higher logical priority than interrupts
assigned a numeric priority of 1.
230
configMAX_SYSCALL_INTERRUPT_PRIORITY = 3
configKERNEL_INTERRUPT_PRIORITY = 1
Interrupts that use priorities 1 to 3, inclusive, are prevented from executing while the
kernel or the application is inside a critical section. ISRs running at these priorities can
use interrupt-safe FreeRTOS API functions. Critical sections are described in Chapter
7.
Interrupts that use priority 4, or above, are not affected by critical sections, so nothing
the scheduler does will prevent these interrupts from executing immediately—within the
limitations of the hardware itself. ISRs executing at these priorities cannot use any
FreeRTOS API functions.
Typically, functionality that requires very strict timing accuracy (motor control, for
example) would use a priority above configMAX_SYSCALL_INTERRUPT_PRIORITY
to ensure the scheduler does not introduce jitter into the interrupt response time.
Interrupt configuration on Cortex-M processors is confusing, and prone to error. To assist your
development, the FreeRTOS Cortex-M ports automatically check the interrupt configuration,
but only if configASSERT() is defined. configASSERT() is described in section 11.2.
231
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
The ARM Cortex cores, and ARM Generic Interrupt Controllers (GICs), use numerically low
priority numbers to represent logically high priority interrupts. This can seem counter-intuitive,
and is easy to forget. If you wish to assign an interrupt a logically low priority, then it must be
assigned a numerically high value. If you wish to assign an interrupt a logically high priority,
then it must be assigned a numerically low value.
The Cortex-M interrupt controller allows a maximum of eight bits to be used to specify each
interrupt priority, making 255 the lowest possible priority. Zero is the highest priority.
However, Cortex-M microcontrollers normally only implement a subset of the eight possible
bits. The number of bits actually implemented is dependent on the microcontroller family.
When only a subset of the eight possible bits has been implemented, it is only the most
significant bits of the byte that can be used—leaving the least significant bits unimplemented.
Unimplemented bits can take any value, but it is normal to set them to 1. This is demonstrated
by Figure 62, which shows how a priority of binary 101 is stored in a Cortex-M microcontroller
that implements four priority bits.
In Figure 62 the binary value 101 has been shifted into the most significant four bits because
the least significant four bits are not implemented. The unimplemented bits have been set to
1.
Some library functions expect priority values to be specified after they have been shifted up
into the implemented (most significant) bits. When using such a function the priority shown in
Figure 62 can be specified as decimal 95. Decimal 95 is binary 101 shifted up by four to make
binary 101nnnn (where ‘n’ is an unimplemented bit), and with the unimplemented bits set to 1
to make binary 1011111.
Some library functions expect priority values to be specified before they have been shifted up
into the implemented (most significant) bits. When using such a function the priority shown in
Figure 62 must be specified as decimal 5. Decimal 5 is binary 101 without any shift.
232
configMAX_SYSCALL_INTERRUPT_PRIORITY and configKERNEL_INTERRUPT_PRIORITY
must be specified in a way that allows them to be written directly to the Cortex-M registers, so
after the priority values have been shifted up into the implemented bits.
Cortex-M interrupts will default to a priority of zero—the highest possible priority. The
implementation of the Cortex-M hardware does not permit
configMAX_SYSCALL_INTERRUPT_PRIORITY to be set to 0, so the priority of an interrupt
that uses the FreeRTOS API must never be left at its default value.
233