FreeRTOS
A real time operating system for embedded systems
INTERRUPT MANAGEMENT
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.
Deferred Interrupt Processing
Binary Semaphores used for Synchronization
A Binary Semaphore can be used to unblock a task each time a particular
interrupt occurs
The majority of the interrupt event processing can be implemented within
the synchronized task.
Only a very fast and short portion remaining directly in the ISR.
The interrupt processing is said to have been ‘deferred’ to a ‘handler’ task
The handler task uses a blocking ‘take’ call to a semaphore and enters the
Blocked state to wait for the event to occur
When the event occurs, the ISR uses a ‘give’ operation on the same
semaphore to unblock the task
Interrupt and Handler Task
Deferred Interrupt Processing
In this interrupt Synchronization scenario:
The Binary Semaphore can be considered conceptually as a queue with a
length of one.
By calling xSemaphorTake(), the handler task 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() to
place a semaphore into the queue, making the queue is full.
This causes the handler task to exit the Blocked state and remove the
semaphore, leaving the queue empty once more.
When the handler 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.
Interrupt and Handler Task
Create a Binary Semaphore
Before a semaphore can be used, it must be created. Use the
vSemaphoreCreateBinary()API function to create a binary
semaphore
void vSemaphoreCreateBinary(xSemaphoreHandle xSemaphore);
xSemaphore
A handle to the semaphore being created
‘Take’ a Semaphore
Use xSemaphoreTake()to ‘take’ a semaphore
portBASE_TYPE xSemaphoreTake(xSemaphoreHandle xSemaphore,
portTickType 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 will only be returned if it 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)
portBASE_TYPE xSemaphoreGiveFromISR(
xSemaphoreHandle xSemaphore,
portBASE_TYPE *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
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 )
{
/* 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 an interrupt every 500ms. */
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( "Perodic task - About to generate an interrupt.\r\n" );
digitalWrite(outputPin, LOW);
digitalWrite(outputPin, HIGH);
Serial.print( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
}
} 11
xSemaphoreTake()
static void vHandlerTask( void *pvParameters )
{
xSemaphoreTake( xBinarySemaphore, 0);
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* Use the semaphore to wait for the event. The semaphore was created
before the scheduler was started so before this task ran for the first
time. The task blocks indefinitely meaning this function call will only
return once the semaphore has been successfully obtained - so there is no
need to check the returned value. */
xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
/* 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" );
}
}
12
xSemaphoreGiveFromISR()
static void vExampleInterruptHandler( void )
{
static signed portBASE_TYPE xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
/* 'Give' the semaphore to unblock the task. */
xSemaphoreGiveFromISR( xBinarySemaphore, (signed
portBASE_TYPE*)&xHigherPriorityTaskWoken );
/* xHigherPriorityTaskWoken was initialised to pdFALSE. It will have then
been set to pdTRUE only if reading from or writing to a queue caused a task
of equal or greater priority than the currently executing task to leave the
Blocked state. When this is the case a context switch should be performed.
In all other cases a context switch is not necessary.
NOTE: The syntax for forcing a context switch within an ISR varies between
FreeRTOS ports. The portEND_SWITCHING_ISR() macro is provided as part of
the Cortex-M3 port layer for this purpose. taskYIELD() must never be called
from an ISR! */
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
13
}
void setup( void )
{
Serial.begin(9600);
/* Before a semaphore is used it must be explicitly created. In this example
a binary semaphore is created. */
vSemaphoreCreateBinary( xBinarySemaphore );
/* Check the semaphore was created successfully. */
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", 200, NULL, 3, NULL );
14
xTaskCreate( vPeriodicTask, "Periodic", 200, NULL, 1, NULL );
/* 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(inputPin, vExampleInterruptHandler, RISING);
/* Start the scheduler so the created tasks start executing. */
vTaskStartScheduler();
}
/* If all is well we will never reach here as the scheduler will now be
running the tasks. If we do reach here then it is likely that there was
insufficient heap memory available for a resource to be created. */
for( ;; );
// return 0;
}
16
Sequence of execution
17
18
19
Counting Semaphores
For Binary Semaphore, when interrupts come at a speed faster
than the handler task can process, events will be lost.
Counting semaphore can be thought of as queue that have a
length of more than one
Each time a counting semaphore is ‘given’, another space in its
queue is used, count value is the number of items in the queue.
Counting semaphores are typically used for two things:
Counting events: the count value is the difference between the number of
events that have occurred and the number that have been processed
Resource management: the count value indicates the number of
resources available; a task must first obtains a semaphore before obtains
the control of a resource; a task returns a semaphore when finishing with
the resource
Using a counting semaphore to ‘count’
events
Create a Counting Semaphore
Use xSemaphoreCreateCounting()to create a counting
semaphore
xSemaphoreHandle xSemaphoreCreateCounting(
unsigned portBASE_TYPE uxMaxCount,
unsigned portBASE_TYPE uxInitialCount );
uxMaxCount
The maximum value the semaphore will count to
uxInitialCount
The initial count value of the semaphore after it has been created
Return value
If NULL is returned then the semaphore could not be created because there
was insufficient heap memory available
A non-NULL value being returned indicates that the semaphore was
created successfully. The returned value should be stored as the
handle to the created semaphore.
Example 13. Using a Counting Semaphore to
Synchronize a Task with an Interrupt
static void vExampleInterruptHandler( void )
{
static portBASE_TYPE xHigherPriorityTaskWoken;
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 );
//portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
vPortYield();
}
23
24
USING QUEUES
WITHIN AN INTERRUPT SERVICE ROUTINE
• xQueueSendToFrontFromISR(),
xQueueSendToBackFromISR() and
xQueueReceiveFromISR() are versions of
xQueueSendToFront(), xQueueSendToBack() and
xQueueReceive() respectively that are safe to use
within an interrupt service routine.
• Semaphores are used to communicate
events.
• Queues are used to both communicate
events and transfer data.
25
Example 14. Sending and
static void vIntegerGenerator( void *pvParameters )
Receiving on a
{ Queue from Within an Interrupt
TickType_t xLastExecutionTime;
unsigned long ulValueToSend = 0;
int i;
/* Initialize the variable used by the call to vTaskDelayUntil(). */
xLastExecutionTime = xTaskGetTickCount();
for( ;; )
{
vTaskDelayUntil( &xLastExecutionTime, 200/portTICK_PERIOD_MS );
for( i = 0; i < 5; i++ )
{
xQueueSendToBack( xIntegerQueue, &ulValueToSend, 0 );
ulValueToSend++;
}
Serial.print( "Generator task - About to generate an interrupt.\r\n" );
digitalWrite(outputPin, LOW);
digitalWrite(outputPin, HIGH);
Serial.print( "Generator task - Interrupt generated.\r\n\r\n\r\n" );
}
26
}
static void vExampleInterruptHandler( void )
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
static unsigned long ulReceivedNumber;
/* 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"
};
27
/* 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 );
/* Print out the string received. */
Serial.print( pcString );
}
}
/*-----------------------------------------------------------*/
29
void setup( void )
{
Serial.begin(9600);
xIntegerQueue = xQueueCreate( 10, sizeof( unsigned long ) );
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", 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 );
30
/* 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);
/* Start the scheduler so the created tasks start executing. */
vTaskStartScheduler();
for( ;; );
}
Example 14. If there is a connection between pin 2 and pin 3
Example 14. If there is no connection between pin 2 and pin 3
Sequence of Execution
34