Henry Hill: You’re a pistol, you’re really funny. You’re really funny.Tommy DeVito: What do you mean I’m funny?Henry Hill: It’s funny, you know. It’s a good story, it’s funny, you’re a funny guy.Tommy DeVito: What do you mean, you mean the way I talk? What?Henry Hill: It’s just, you know. You’re just funny, it’s...funny, the way you tell the story and everything.Tommy DeVito: Funny how? What’s funny about it?
In this chapter, we will go through some examples of embedded system code that use the operating system mechanisms we covered in the previous chapter, under the principle that seeing real implementations contributes to a better understanding of the mechanisms. This chapter uses the real-time operating system eCos for the examples. The concepts and techniques in this chapter apply to other RTOSes as well, but different operating systems use different APIs to carry out the techniques. In the next chapter, we will run through the same examples using the popular Linux operating system.
We have decided to use two operating systems for the examples of operating system use—eCos and Linux. Why did we choose these two? Both are open source, royalty-free, feature-rich, and growing in popularity. Learning how to program using them will probably enhance your ability to be productive with embedded systems. In addition, both operating systems are compatible with the free GNU software development tools. And both are up and running on the Arcom board.
eCos was developed specifically for use in real-time embedded systems, whereas Linux was developed for use on PCs and then subsequently ported to various processors used in embedded systems. Some embedded Linux distributions can require a significant amount of resources (mainly memory and processing power), which are not found in most embedded systems. Linux was originally not real-time, but extensions are now available to add real-time features.
In short, eCos is more suited in general for embedded systems work, but the weight of widespread knowledge and support tools surrounding Linux make it attractive to embedded developers, too.
We try to keep function names, and what these functions do, consistent between the different examples in this and the following chapter. This way, you can concentrate on the details related to the operating systems. eCos includes a POSIX API, which supports a subset of POSIX functions. [1] However, in the following eCos examples, the native eCos API is used.
The instructions for setting up the eCos build environment and building the example eCos applications are covered in Appendix D. Additional information about eCos can be found online at http://ecos.sourceware.org as well as in the book Embedded Software Development with eCos, by Anthony Massa (Prentice Hall PTR).
In order to keep the examples in this chapter shorter and easier to read, we don’t bother to check the return values from operating system function calls (although many eCos system calls do not return a value). In general, it is a good idea to validate all return codes. This provides feedback about potential problems and allows you, as the developer, to make decisions in the software based on failed calls to the operating system. Basically, it makes your code more robust and, hopefully, less buggy.
In this first eCos example, we reuse the Blinking LED program that was covered previously. The first thing to learn is how to create a task. This example creates a task to handle the toggling of the LED at a constant rate. First, we declare the task-specific variables such as the stack and its size, the task priority, and OS-specific variables.
eCos uses the term thread instead of task in its API and variable types. These terms mean the same thing in the embedded systems context.
The example program provides two variables to eCos to allow it to
track the task. The variable ledTaskObj
stores information about the task,
such as its current state; ledTaskHdl
is a unique value assigned to the task.
The stack for our task is statically declared as the array
ledTaskStack
, which is 4,096 bytes in
size. An arbitrary priority of 12 is assigned for this task. The code
also defines the number of clock ticks per second, a value specific to
the Arcom board’s eCos setup. This makes it easy to change the tick
interval to tune system performance.
#define TICKS_PER_SECOND (100) #define LED_TASK_STACK_SIZE (4096) #define LED_TASK_PRIORITY (12) /* Declare the task variables. */ unsigned char ledTaskStack[LED_TASK_STACK_SIZE]; cyg_thread ledTaskObj; cyg_handle_t ledTaskHdl;
Next we show the code for performing the toggle. We have attempted
to reuse code from the original Blinking LED example. The ledInit
and ledToggle
LED driver functions remain
unchanged from the code described in Chapter 3.
The task blinkLedTask
immediately enters an infinite loop. The infinite loop is used to keep
the task continually running and blinking the LED. The first routine
called in the infinite loop is cyg_thread_delay
. This is an eCos function
that suspends a task until a specified number of clock ticks have
elapsed. The parameter passed into the delay routine determines how long
to suspend the task and is based on the system clock used in eCos. At
this point, the blinkLedTask
is
blocked and put in the waiting state by the eCos scheduler.
Once the timer expires, the eCos scheduler puts the blinkLedTask
into the ready queue. If no other
higher-priority tasks are ready to execute (which is the situation in
this case), the scheduler runs the blinkLedTask
; the task continues executing
from the point at which it was blocked.
Next, ledToggle
is called in
order to change the state of the LED. When ledToggle
completes and returns, cyg_thread_delay
is called to delay for
another 500 ms. The blinkLedTask
is
placed back in the waiting state until the time elapses again.
#include <cyg/kernel/kapi.h> #include "led.h" /********************************************************************** * * Function: blinkLedTask * * Description: This task handles toggling the green LED at a * constant interval. * * Notes: * * Returns: None. * **********************************************************************/ void blinkLedTask(cyg_addrword_t data) { while (1) { /* Delay for 500 milliseconds. */ cyg_thread_delay(TICKS_PER_SECOND / 2); ledToggle(); } }
Following is the code to create and start the LED task. The first
thing to notice is that instead of the function main
, eCos programs use a function called
cyg_user_start
.
The first job of cyg_user_start
is to initialize the LED by calling the function ledInit
. Next, the blinkLedTask
task is created. In eCos, tasks
created during initialization (when the scheduler is not running) are
initially suspended. To allow the scheduler to run the task, cyg_thread_resume
is called. Additionally, the
scheduler does not run until cyg_user_start
exits; then the eCos scheduler
takes over.
/********************************************************************** * * Function: cyg_user_start * * Description: Main routine for the eCos Blinking LED program. This * function creates the LED task. * * Notes: This routine invokes the scheduler upon exit. * * Returns: None. * **********************************************************************/ void cyg_user_start(void) { /* Configure the green LED control pin. */ ledInit(); /* Create the LED task. */ cyg_thread_create(LED_TASK_PRIORITY, blinkLedTask, (cyg_addrword_t)0, "LED Task", (void *)ledTaskStack, LED_TASK_STACK_SIZE, &ledTaskHdl, &ledTaskObj); /* Notify the scheduler to start running the task. */ cyg_thread_resume(ledTaskHdl); }
The previous example demonstrates how to create, resume, and delay a task in eCos. Other task operations include deleting tasks (which can sometimes occur by returning from the task function), yielding to other tasks in the system, and other mechanisms for suspending/resuming tasks.
Now we attack the next big job in task management, which is to synchronize tasks. As we saw in Chapter 10, a mutex is a common mechanism for getting two independent tasks to cooperate. For our eCos mutex example, two tasks share a common variable—the first task incrementing it and the second decrementing it at set intervals. The mutex is used to protect the shared variable.
Before accessing a shared resource, a task takes the mutex; once finished, the task releases the mutex for other tasks to use. Each operating system defines these two operations in its own way. For example, eCos offers lock (taking the mutex) and unlock (releasing the mutex) functions.
To keep track of the multiple tasks waiting on the same mutex, the mutex structure contains a queue of tasks that are waiting on that particular mutex. This queue is typically sorted by priority so the highest-priority task waiting for the mutex executes first. One possible result of releasing the mutex could be to wake a task of higher priority. In that case, the releasing task would immediately be forced (by the scheduler) to give up control of the processor, in favor of the higher-priority task.
The main function, cyg_user_start
, which is shown next, calls the
cyg_mutex_init
function to initialize
mutexes, such as the one we’ve named sharedVariableMutex
. This mutex is used to
protect the variable gSharedVariable
.
After returning from the mutex initialization call, the mutex is
available to the first task that takes it. Once the mutex is
initialized, the two tasks are created and then started in a manner like
that shown earlier.
It is important to note that the mutex is initialized prior to the creation of any tasks that use this mutex. Any synchronization mechanism used by a task must be initialized prior to its use by the task. If a task were to try to take an uninitialized mutex, undefined behavior or a system crash could result.
You may notice that the function debug_printf
is called at the end of cyg_user_start
. This is eCos’s lightweight
version of printf
.
#include <cyg/kernel/kapi.h> #include <cyg/infra/diag.h> cyg_mutex_t sharedVariableMutex; int32_t gSharedVariable = 0; /********************************************************************** * * Function: cyg_user_start * * Description: Main routine for the eCos mutex example. This function * creates the mutex and then the increment and decrement * tasks. * * Notes: This routine invokes the scheduler upon exit. * * Returns: None. * **********************************************************************/ void cyg_user_start(void) { /* Create the mutex for accessing the shared variable. */ cyg_mutex_init(&sharedVariableMutex); /* Create the increment and decrement tasks. */ cyg_thread_create(INCREMENT_TASK_PRIORITY, incrementTask, (cyg_addrword_t)0, "Increment Task", (void *)incrementTaskStack, INCREMENT_TASK_STACK_SIZE, &incrementTaskHdl, &incrementTaskObj); cyg_thread_create(DECREMENT_TASK_PRIORITY, decrementTask, (cyg_addrword_t)0, "Decrement Task", (void *)decrementTaskStack, DECREMENT_TASK_STACK_SIZE, &decrementTaskHdl, &decrementTaskObj); /* Notify the scheduler to start running the tasks. */ cyg_thread_resume(incrementTaskHdl); cyg_thread_resume(decrementTaskHdl); diag_printf("eCos mutex example.\n"); }
The incrementTask
function
first delays for three seconds. After the delay, the function tries to
take the mutex by calling cyg_mutex_lock
, passing in the mutex it wishes
to acquire. If the mutex is available, cyg_mutex_lock
returns and the task can
proceed. If the mutex is not available, the task blocks at this point
(and is placed in the waiting state by the scheduler) and waits for the
mutex to be released.
Once the incrementTask
task
obtains the mutex, the shared variable gSharedVariable
is incremented and its value
is output. The mutex is then released by calling cyg_mutex_unlock
, again passing in the mutex
to release as a parameter. Unlike the cyg_
mutex_lock
function, the unlock function never
blocks, although it may cause a reschedule.
/********************************************************************** * * Function: incrementTask * * Description: This task increments a shared variable. * * Notes: * * Returns: None. * **********************************************************************/ void incrementTask(cyg_addrword_t data) { while (1) { /* Delay for 3 seconds. */ cyg_thread_delay(TICKS_PER_SECOND * 3); /* Wait for the mutex to become available. */ cyg_mutex_lock(&sharedVariableMutex); gSharedVariable++; diag_printf("Increment Task: shared variable value is %d\n", gSharedVariable); /* Release the mutex. */ cyg_mutex_unlock(&sharedVariableMutex); } }
The decrementTask
function is
similar to the previous increment task. First, the task delays for seven
seconds. Then the task waits to acquire the sharedVariableMutex
. Once the task gets the
mutex, it decrements the gSharedVariable
value and outputs its value.
Finally, the task releases the mutex.
/********************************************************************** * * Function: decrementTask * * Description: This task decrements a shared variable. * * Notes: * * Returns: None. * **********************************************************************/ void decrementTask(cyg_addrword_t data) { while (1) { /* Delay for 7 seconds. */ cyg_thread_delay(TICKS_PER_SECOND * 7); /* Wait for the mutex to become available. */ cyg_mutex_lock(&sharedVariableMutex); gSharedVariable--; diag_printf("Decrement Task: shared variable value is %d\n", gSharedVariable); /* Release the mutex. */ cyg_mutex_unlock(&sharedVariableMutex); } }
The eCos semaphore example is similar to a push button light
switch using an LED for the light. The following example has two tasks:
a producer and a consumer. The producer task, producerTask
, monitors the button labeled SW0
on the Arcom board’s add-on module. Figure 11-1 shows the
button used in this example.
When the SW0 button is pressed, the producer task signals the
consumer task using a semaphore. The consumer, consumerTask
, waits for the semaphore signal
from the producer task. Upon receiving the signal, the consumer task
outputs a message and toggles the green LED.
The main function, cyg_user_start
, starts by initializing the LED
by calling ledInit
. Next, the
semaphore is initialized with a call to cyg_semaphore_init
. The initial value of the
semaphore, semButton
, is set to zero
so that the consumer task that is waiting does not execute until the
semaphore is signaled by the producer task. Lastly, the two tasks are
created and resumed, as in the prior example, and then a message is
output signifying the start of the program.
#include <cyg/kernel/kapi.h> #include <cyg/infra/diag.h> #include "led.h" cyg_sem_t semButton; /********************************************************************** * * Function: cyg_user_start * * Description: Main routine for the eCos semaphore program. This * function creates the semaphore and the producer and * consumer tasks. * * Notes: This routine invokes the scheduler upon exit. * * Returns: None. * **********************************************************************/ void cyg_user_start(void) { /* Configure the green LED control pin. */ ledInit(); /* Create the semaphore with an initial value of zero. */ cyg_semaphore_init(&semButton, 0); /* Create the producer and consumer tasks. */ cyg_thread_create(PRODUCER_TASK_PRIORITY, producerTask, (cyg_addrword_t)0, "Producer Task", (void *)producerTaskStack, PRODUCER_TASK_STACK_SIZE, &producerTaskHdl, &producerTaskObj); cyg_thread_create(CONSUMER_TASK_PRIORITY, consumerTask, (cyg_addrword_t)0, "Consumer Task", (void *)consumerTaskStack, CONSUMER_TASK_STACK_SIZE, &consumerTaskHdl, &consumerTaskObj); /* Notify the scheduler to start running the tasks. */ cyg_thread_resume(producerTaskHdl); cyg_thread_resume(consumerTaskHdl); diag_printf("eCos semaphore example - press button SW0.\n"); }
The producerTask
contains an
infinite loop that first delays for 10 ms and then checks to see whether
the SW0 button has been pressed, by calling the function buttonDebounce
(we will take a closer look at
this function in a moment). The delay interval was selected in order to
ensure the task is responsive when the button is pressed. For additional
information about selecting sampling intervals, read the sidebar later
in this chapter titled “Switch
Debouncing.”
When the SW0 button is pressed, the semaphore, semButton
, is signaled by calling cyg_semaphore_post
, which increments the
semaphore value and wakes the consumer task waiting on this semaphore.
The producer task then returns to monitoring the SW0 button.
#include "button.h" /********************************************************************** * * Function: producerTask * * Description: This task monitors button SW0. Once pressed, the button * is debounced and the semaphore is signaled, waking the * waiting consumer task. * * Notes: * * Returns: None. * **********************************************************************/ void producerTask(cyg_addrword_t data) { int buttonOn; while (1) { /* Delay for 10 milliseconds. */ cyg_thread_delay(TICKS_PER_SECOND / 100); /* Check whether the SW0 button has been pressed. */ buttonOn = buttonDebounce(); /* If button SW0 was pressed, signal the consumer task. */ if (buttonOn) cyg_semaphore_post(&semButton); } }
Now let’s take a look at the function buttonDebounce
. The debounce code is from the June 2004 Embedded Systems Programming article “My Favorite Software Debouncers,” which can be found
online at http://www.embedded.com.
The debounce function is called in the producer task every 10 ms to determine whether the SW0 button has been pressed. As shown in Figure 11-1, button SW0 is located on the add-on module. The Arcom board’s VIPER-Lite Technical Manual and the VIPER-I/O Technical Manual describe how the add-on module’s buttons are connected to the processor. The add-on module schematics, which are found in the VIPER-I/O Technical Manual, can be used to trace the connection from the button back to the processor.
The button SW0 is read from the signal IN0, as shown in the switches section in the VIPER-I/O Technical Manual. According to the VIPER-Lite Technical Manual, the IN0 signal value is retrieved by reading address 0x14500000. The least significant bit at this address contains the current state of the button SW0. The Arcom board’s documentation states that the default voltage level on the button switch is high and that when the button is pressed, the value changes to low.
The debounce function first calls buttonRead
, which returns the current state of
the SW0 button. The current state of the SW0 button is shifted into the
variable buttonState
. When the
leading edge of the switch closure is debounced and detected, TRUE
is returned.
For additional information about debouncing buttons and switches, take a look at the sidebar “Switch Debouncing” later in this chapter.
/********************************************************************** * * Function: buttonDebounce * * Description: This function debounces buttons. * * Notes: * * Returns: TRUE if the button edge is detected, otherwise * FALSE is returned. * **********************************************************************/ int buttonDebounce(void) { static uint16_t buttonState = 0; uint8_t pinState; pinState = buttonRead(); /* Store the current debounce status. */ buttonState = ((buttonState << 1) | pinState | 0xE000); if (buttonState == 0xF000) return TRUE; return FALSE; }
The consumerTask
contains a
simple infinite loop: wait for the semaphore to be signaled, then print
a message once the signal is received. The consumer task waits for the
semaphore signal by calling cyg_semaphore_wait
, which blocks the task if
the value of the semaphore is equal to 0.
Once the semaphore signal is received, the consumer task outputs a
message that the button was pressed and toggles the green LED by calling
ledToggle
. After the LED is toggled,
the consumer task reverts to waiting for another semaphore
signal.
/********************************************************************** * * Function: consumerTask * * Description: This task waits for the semaphore signal from the * producer task. Once the signal is received, the task * outputs a message. * * Notes: * * Returns: None. * **********************************************************************/ void consumerTask(cyg_addrword_t data) { while (1) { /* Wait for the signal. */ cyg_semaphore_wait(&semButton); diag_printf("Button SW0 was pressed.\n"); ledToggle(); } }
We cover another example using semaphores in the section “eCos Interrupt Handling” later in this chapter.
In this section, we’ll discuss a programming technique that’s useful for certain situations where you divide up tasks—in particular, when you can specify a producer task that generates data, and a consumer task that processes the producer’s output. The use of message passing prevents tasks from stepping on each other’s data and also simplifies coding.
The message passing example is similar to the light
switch-semaphore example shown earlier in this chapter, where the
producer task waits for the SW0 button to be pressed, and the consumer
task outputs a message. The difference this time is that a message that
contains the number of times the button has been pressed is passed from
the producer to the consumer. The consumer, consumerTask
, waits for the message from the
producer task, producerTask
. Once the
message is received by the consumer task, it outputs a message and
toggles the green LED.
Message queues in eCos are called mailboxes. The following main
function, cyg_user_start
, starts by
initializing the LED by calling ledInit
. Next, the mailbox is initialized with
a call to cyg_mbox_create
. The
mailbox create function is passed mailboxHdl
(a handle used for subsequent calls
to perform operations with that specific mailbox) and mailbox
(an area of memory for the kernel’s
mailbox structure). Lastly, the two tasks are created and resumed, as we
saw in the prior example, and then a message is output, signifying the
start of the program.
#include <cyg/kernel/kapi.h> #include <cyg/infra/diag.h> #include "led.h" cyg_handle_t mailboxHdl; cyg_mbox mailbox; /********************************************************************** * * Function: cyg_user_start * * Description: Main routine for the eCos mailbox program. This * function creates the mailbox and the producer and * consumer tasks. * * Notes: This routine invokes the scheduler upon exit. * * Returns: None. * **********************************************************************/ void cyg_user_start(void) { /* Configure the green LED control pin. */ ledInit(); /* Create the mailbox for sending messages between tasks. */ cyg_mbox_create(&mailboxHdl, &mailbox); /* Create the producer and consumer tasks. */ cyg_thread_create(PRODUCER_TASK_PRIORITY, producerTask, (cyg_addrword_t)0, "Producer Task", (void *)producerTaskStack, PRODUCER_TASK_STACK_SIZE, &producerTaskHdl, &producerTaskObj); cyg_thread_create(CONSUMER_TASK_PRIORITY, consumerTask, (cyg_addrword_t)0, "Consumer Task", (void *)consumerTaskStack, CONSUMER_TASK_STACK_SIZE, &consumerTaskHdl, &consumerTaskObj); /* Notify the scheduler to start running the tasks. */ cyg_thread_resume(producerTaskHdl); cyg_thread_resume(consumerTaskHdl); diag_printf("eCos mailbox example - press button SW0.\n"); }
The producerTask
shown next
begins by initializing the button press count variable, buttonPressCount
, to zero, and then enters an
infinite loop. In the loop, the task delays for 10 ms and then checks to
see whether the SW0 button has been pressed with a call to the function
buttonDebounce
.
When the SW0 button is pressed, buttonPressCount
is incremented and the value
is sent to the waiting consumer task via the mailbox. In eCos, messages
are sent in mailboxes using the function cyg_mbox_put
, which takes two arguments: the
mailbox handle, in this case mailboxHdl
, and the message to send. If there
is room in the mailbox, the message is placed there immediately;
otherwise, the task blocks until room is available.
#include "button.h" /********************************************************************** * * Function: producerTask * * Description: This task monitors button SW0. Once pressed, the button * is debounced and a message is sent to the waiting * consumer task. * * Notes: This function is specific to the Arcom board. * * Returns: None. * **********************************************************************/ void producerTask(cyg_addrword_t data) { uint32_t buttonPressCount = 0; int buttonOn; while (1) { /* Delay for 10 milliseconds. */ cyg_thread_delay(TICKS_PER_SECOND / 100); /* Check if the SW0 button has been pressed. */ buttonOn = buttonDebounce(); /* If button SW0 was pressed, send a message to the consumer task. */ if (buttonOn) { buttonPressCount++; cyg_mbox_put(mailboxHdl, (void *)buttonPressCount); } } }
The consumerTask
, shown in the
code that follows, contains an infinite loop that waits for an incoming
message and then prints the message once it’s received. The consumer
task waits for an incoming message by calling cyg_mbox_get
and passing in the mailbox
handle, mailboxHdl
. The mailbox get
function call blocks until the producer task sends a message. The
message, which is the button press count, is stored in the local
variable rcvMsg
. After the message is
output and the green LED is toggled, the consumer task returns to
waiting for another message.
/********************************************************************** * * Function: consumerTask * * Description: This task waits for a message from the producer task. * Once the message is received via the mailbox, it * outputs a message and toggles the green LED. * * Notes: * * Returns: None. * **********************************************************************/ void consumerTask(cyg_addrword_t data) { uint32_t rcvMsg; while (1) { /* Wait for a new message. */ rcvMsg = (uint32_t)cyg_mbox_get(&mailboxHdl); diag_printf("Button SW0 pressed %d times.\n", rcvMsg); ledToggle(); } }
Most operating systems, including eCos, have additional API functions for various
synchronization and message passing operations. For example, eCos
includes the functions cyg_mbox_tryput
and cyg_mbox_tryget
that return false if they are
unsuccessful at putting or getting a message in the mailbox, instead of
blocking the task. These functions can be used to do polling (that is,
to check whether a mailbox is available, go off and do other tasks if it
is not, and then return and try again).
There are also functions for which you pass in a timeout value that blocks for a set amount of time while the function attempts to perform the specified operation. Additional information about the eCos RTOS APIs can be found in the eCos Reference online at http://ecos.sourceware.org/docs-latest.
As explained in Chapter 8, it is important to keep interrupt processing down to a minimum so that other (potentially higher-priority) interrupts in the system can be serviced, and so high-priority tasks can run. Thus, programmers typically divide interrupt handling into two categories: a short ISR, and a more leisurely DSR.
The question of how to make the split—what to put in the ISR and what to put in the DSR—depends on the application and the processor. Basically, you should defer whatever you can. OS functions that might block cannot be called from an ISR or DSR. And unlike many other operating systems, eCos in particular does not allow an ISR even to signal a semaphore via a nonblocking call. In eCos, semaphore signaling must be done via a call from the DSR.
In order to get a better understanding of the split interrupt handling scheme, take a look at the following eCos example, which shows the use of an interrupt to handle the timing for the Blinking LED program. In this example, a semaphore is used to signal a task from the interrupt when it is time to toggle the LED.
The initialization sequence between the hardware and software in
the cyg_user_start
function is
important. For example, you wouldn’t want to call timerInit
to start the timer interrupt before you have created and installed an
interrupt handler for the timer.
First, the LED is initialized. Next, the semaphore, ledToggleSemaphore
, which signals the blinkLedTask
when it is time to toggle the
LED, is initialized. Then the blinkLedTask
is created as shown
previously.
Next, the ISR and DSR are created for the timer interrupt with a
call to cyg_interrupt_create
, which
fills in the kernel interrupt structure. The interrupt vector (27 for
Timer 1) is defined by the macro TIMER1_INT
and passed in as the first
parameter to cyg_interrupt_create
.
The next two parameters are the interrupt priority and interrupt-private
data, which are set to zero. The ISR function, timerIsr
, and DSR function, timerDsr
, are passed in next. When the
cyg_interrupt_create
function
returns, it sets the final two arguments: the interrupt handle, timerInterruptHdl
, and interrupt object,
timerInterruptObj
. The interrupt
handle is used in subsequent operations for this interrupt. The
interrupt object provides the kernel with an area of memory containing
the interrupt handler and its associated data (in case any is
necessary).
Next, the interrupt ISR and DSR are attached to the interrupt
source by calling cyg_interrupt_attach
and passing in the handle
from the interrupt create function. As a precaution, cyg_interrupt_acknowledge
is called in case
there is a pending timer interrupt. Lastly, the interrupt is unmasked by
calling cyg_interrupt_unmask
.
The final step in the initialization sequence is to configure the
timer registers and enable the interrupt, which is done in the function
timerInit
:
#include <cyg/kernel/kapi.h> #include "timer.h" #include "led.h" /* Declare the ISR variables. */ cyg_handle_t timerInterruptHdl; cyg_interrupt timerInterruptObj; cyg_vector_t timerInterruptVector = TIMER1_INT; cyg_sem_t ledToggleSemaphore; /********************************************************************** * * Function: cyg_user_start * * Description: Main routine for eCos interrupt Blinking LED program. * This function creates the LED toggle semaphore, the * LED task, and the timer interrupt handler. * * Notes: This routine invokes the scheduler upon exit. * * Returns: None. * **********************************************************************/ void cyg_user_start(void) { /* Configure the green LED control pin. */ ledInit(); /* Create the semaphore for the task signaling. This semaphore * is initialized with a value of 0 so the toggling task must wait * for the first time period to elapse. */ cyg_semaphore_init(&ledToggleSemaphore, 0); /* Create the LED task. */ cyg_thread_create(LED_TASK_PRIORITY, blinkLedTask, (cyg_addrword_t)0, "LED Task", (void *)ledTaskStack, LED_TASK_STACK_SIZE, &ledTaskHdl, &ledTaskObj); /* Notify the scheduler to start running the task. */ cyg_thread_resume(ledTaskHdl); /* Initialize the interrupt for the timer. */ cyg_interrupt_create(timerInterruptVector, 0, 0, timerIsr, timerDsr, &timerInterruptHdl, &timerInterruptObj); cyg_interrupt_attach(timerInterruptHdl); cyg_interrupt_acknowledge(timerInterruptVector); cyg_interrupt_unmask(timerInterruptVector); /* Initialize the timer registers. */ timerInit(); }
eCos provides the functionality of saving and restoring the
processor’s context when an interrupt occurs so that the ISR does not
need to perform these operations. Thus we reduce the workload of the
timer interrupt handler, timerIsr
, to
three critical operations that must be carried out before interrupts are
enabled again.
The first operation masks the timer interrupt, with a call to
cyg_interrupt_mask
passing in the
interrupt vector timerInterruptVector
, until the DSR is run.
This blocks the ISR from being called again until the current interrupt
has been processed.
The second operation performed in the ISR is to acknowledge the interrupt. The interrupt must be
acknowledged in the processor’s interrupt controller and timer
peripheral. The interrupt is acknowledged in the interrupt controller
using the eCos function cyg_interrupt_acknowledge
and passing in the
interrupt vector timerInterruptVector
. The interrupt is
acknowledged in the timer peripheral by writing the TIMER_1_MATCH
(0x02) bit to the timer status
register.
The third operation, performed when returning, is to inform the
operating system that the timer interrupt has been handled (with the
macro CYG_ISR_HANDLED
) and that the
DSR needs to be run (with the macro CYG_ISR_CALL_DSR
). Notifying eCos that the
interrupt has been handled prevents it from calling any other ISRs to
handle the same interrupt.
The following timerIsr
function
shows these operations:
#include <cyg/hal/hal_intr.h> /********************************************************************** * * Function: timerIsr * * Description: Interrupt service routine for the timer interrupt. * * Notes: * * Returns: Bitmask to inform operating system that the * interrupt has been handled and to schedule the * deferred service routine. * **********************************************************************/ uint32_t timerIsr(cyg_vector_t vector, cyg_addrword_t data) { /* Block the timer interrupt from occurring until the DSR runs. */ cyg_interrupt_mask(timerInterruptVector); /* Acknowledge the interrupt in the interrupt controller and the * timer peripheral. */ cyg_interrupt_acknowledge(timerInterruptVector); TIMER_STATUS_REG = TIMER_1_MATCH; /* Inform the operating system that the interrupt is handled by this * ISR and that the DSR needs to run. */ return (CYG_ISR_HANDLED | CYG_ISR_CALL_DSR); }
The DSR function, timerDsr
,
which is shown next, is scheduled to be run by eCos once the ISR
completes. The DSR signals the LED task using the semaphore ledToggleSemaphore
with the function call
cyg_semaphore_post
.
Next, the new timer interval is programmed into the timer match
register, which is set to expire 500 ms from the current timer count.
Finally, before exiting, the DSR unmasks the timer interrupt in the
operating system, by calling cyg_interrupt_unmask
and passing in the
interrupt vector, which reenables the handling of incoming timer
interrupts.
/********************************************************************** * * Function: timerDsr * * Description: Deferred service routine for the timer interrupt. * * Notes: * * Returns: None. * **********************************************************************/ void timerDsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data) { /* Signal the task to toggle the LED. */ cyg_semaphore_post(&ledToggleSemaphore); /* Set the new timer interval. */ TIMER_1_MATCH_REG = (TIMER_COUNT_REG + TIMER_INTERVAL_500MS); /* Enable processing of incoming timer interrupts. */ cyg_interrupt_unmask(timerInterruptVector); }
The blinkLedTask
contains an
infinite loop that waits for the semaphore ledToggle
-
Semaphore
to be signaled by calling cyg_semaphore_wait
. When the semaphore is
signaled by the timer DSR, the task calls ledToggle
to change the state of the
LED.
/********************************************************************** * * Function: blinkLedTask * * Description: This task handles toggling the LED when it is * signaled from the timer interrupt handler. * * Notes: * * Returns: None. * **********************************************************************/ void blinkLedTask(cyg_addrword_t data) { while (1) { /* Wait for the signal that it is time to toggle the LED. */ cyg_semaphore_wait(&ledToggleSemaphore); /* Change the state of the green LED. */ ledToggle(); } }
This concludes our brief introduction to the eCos operating system and its API. Hopefully, these few examples have clarified some of the points made elsewhere in the book. These are valuable programming techniques used frequently in embedded systems.
[1] The eCos POSIX API is currently compatible with the 1003.1-1996 version of the standard and includes elements from the 1004.1-2001 version.
35.170.81.33