FreeRTOS InterruptManagement
FreeRTOS InterruptManagement
2
Interrupt Management
Topics covered:
❖ Which FreeRTOS API functions can be used from within an interrupt service
routine (ISR).
❖ How a deferred interrupt scheme can be implemented.
❖ How to create and use binary semaphores and counting semaphores.
❖ The differences between binary and counting semaphores.
❖ How to use a queue to pass data into and out of an interrupt service
routine.
The xHigherPriorityTaskWoken Parameter
• Handles to all the various types of FreeRTOS semaphore are stored in a variable
of type SemaphoreHandle_t.
• 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.
• If a non-NULL value is returned, it indicates that the semaphore has been
created successfully. The returned value should be stored as the handle to the
created semaphore.
‘Take’ a Semaphore
Use xSemaphoreTake()to ‘take’ a semaphore:
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait );
❖ xSemaphore
A handle to the semaphore being ‘taken’
❖ xTicksToWait
The maximum amount of time the task should remain in the Blocked state
Setting xTicksToWait to portMAX_DELAY will cause the task to wait
indefinitely
❖ Return value
pdPASS is returned only if the call to xSemaphoreTake() was successful
in obtaining the semaphore.
pdFALSE if the semaphore was not available
‘Give’ a Semaphore
Use xSemaphoreGive()(xSemaphoreGiveFromISR())to
‘give’ a semaphore (when in an ISR)
BaseType_t xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken);
❖ xSemaphore
A handle to the semaphore being ‘given’
❖ pxHigherPriorityTaskWoken
If the handler task has a higher priority than the currently executing task (the task
that was interrupted), this value will be set to pdTRUE
If xSemaphoreGiveFromISR() sets this value to pdTRUE, then normally 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.
❖ Return value
pdPASS will only be returned if successful
pdFAIL if a semaphore is already available and cannot be given
Example 12. Using a Binary Semaphore
to Synchronize a Task with an Interrupt
static void vPeriodicTask(void *pvParameters) {
//const TickType_t xDelay = 500 / portTICK_PERIOD_MS;
/* As per most tasks, this task is implemented within an infinite loop. */
for (;;) {
/* This task is just used to 'simulate' an interrupt. This is done by
periodically generating a software interrupt. */
vTaskDelay(500 / portTICK_PERIOD_MS);
/* Generate the interrupt, printing a message both before hand and
afterwards so the sequence of execution is evident from the output. */
Serial.print("Periodic task - About to generate an interrupt.\r\n");
digitalWrite(interruptPin, LOW);
digitalWrite(interruptPin, HIGH);
delay(20);
Serial.print("Periodic task - Interrupt generated.\r\n\r\n\r\n");
}
}
17
xSemaphoreTake()
static void vHandlerTask(void *pvParameters) {
/* Note that when you create a binary semaphore in FreeRTOS, it is ready
to be taken, so you may want to take the semaphore after you create it
so that the task waiting on this semaphore will block until given by
another task. */
xSemaphoreTake(xBinarySemaphore, 0);
/* To get here the event must have occurred. Process the event (in this
case we just print out a message). */
Serial.print( "Handler task - Processing event.\r\n" );
}
} 18
xSemaphoreGiveFromISR()
static void IRAM_ATTR vExampleInterruptHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xBinarySemaphore, (BaseType_t
*)&xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken == pdTRUE) {
/* Giving the semaphore unblocked a task, and the priority of the
unblocked task is higher than the currently running task - force
a context switch to ensure that the interrupt returns directly to
the unblocked (higher priority) task.
portYIELD_FROM_ISR();
}
}
19
Example 12. Using a Binary Semaphore to
Synchronize a Task with an Interrupt
20
Example 12. Using a Binary Semaphore to
Synchronize a Task with an Interrupt
xBinarySemaphore = xSemaphoreCreateBinary();
if( xBinarySemaphore != NULL )
{
/* Create the 'handler' task. This is the task that will be synchronized
with the interrupt. The handler task is created with a high priority to
ensure it runs immediately after the interrupt exits. In this case a
priority of 3 is chosen. */
xTaskCreate( vHandlerTask, "Handler", 1024, NULL, 3, NULL );
}
for (;;);
}
Example12 – Serial monitor
22
Sequence of execution
23
Improving the Implementation of the Task
Used in Example 12
Example 12 used a binary semaphore to synchronize a task with
an interrupt. The execution sequence was as follows:
1. The interrupt occurred.
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).
Improving the Implementation of the Task
Used in Example 12
The structure of the task used in Example 12 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:
1. 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 below.
2. 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 below.
26
27
• The deferred interrupt handling task used in Example 12 is
structured so that it only processes one event between each call
to xSemaphoreTake(). That was adequate for Example 12, 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()6.
• This is demonstrated by Listing below, which shows how a deferred
interrupt handler for a UART could be structured. In Listing below, 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).
static void vUARTReceiveHandlerTask( void *pvParameters )
{
/* xMaxExpectedBlockTime holds the maximum time expected between two
interrupts. */
const TickType_t xMaxExpectedBlockTime = pdMS_TO_TICKS( 500 );
xHigherPriorityTaskWoken = pdFALSE;
/* 'Give' the semaphore multiple times. The first will unblock the handler
task, the following 'gives' are to demonstrate that the semaphore latches
the events to allow the handler task to process them in turn without any
events getting lost. This simulates multiple interrupts being taken 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 );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
36
Example13 – Serial monitor
37
Deferring Work to the RTOS Daemon Task
39
Example 14. Sending and Receiving on a Queue from
Within an Interrupt
static void vIntegerGenerator( void *pvParameters )
{
TickType_t xLastExecutionTime;
unsigned long ulValueToSend = 0;
int i;
/* The strings are declared static const to ensure they are not allocated to the
interrupt service routine stack, and 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"
};
41
/* Loop until the queue is empty. */
while ( xQueueReceiveFromISR( xIntegerQueue, &ulReceivedNumber,
&xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )
{
/* Truncate the received value to the last two bits (values 0 to 3 inc.), then
send the string that corresponds to the truncated value to the other
queue. */
ulReceivedNumber &= 0x03;
xQueueSendToBackFromISR( xStringQueue, &pcStrings[ ulReceivedNumber ],
&xHigherPriorityTaskWoken );
}
vPortYield();
}
static void vStringPrinter( void *pvParameters )
{
char *pcString;
for( ;; )
{
/* Block on the queue to wait for data to arrive. */
xQueueReceive( xStringQueue, &pcString, portMAX_DELAY );
43
void setup( void )
{
Serial.begin(9600);
/* 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", 200, 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", 200, NULL, 2, NULL );
44
/* Install the interrupt handler. */
pinMode(inputPin, INPUT);
pinMode(outputPin, OUTPUT);
digitalWrite(outputPin, HIGH);
bool tmp = digitalRead(inputPin);
digitalWrite(outputPin, LOW);
if (digitalRead(inputPin) || !tmp) {
Serial.println("pin 2 must be connected to pin 3");
while(1);
}
attachInterrupt(digitalPinToInterrupt(inputPin), vExampleInterruptHandler, RISING);
for( ;; );
}
Example 14. If there is a connection between pin 2 and pin 4
Example 14. If there is no connection between pin 2 and pin 4
Sequence of Execution
48