Chapter 11. eCos Examples

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?

the movie Goodfellas

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.

Introduction

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).

Tip

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.

Task Mechanics

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.

Tip

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.

Mutex Task Synchronization

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.

Warning

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);
    }
}
            
            
            

Semaphore Task Synchronization

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.

Arcom board add-on module’s SW0 button
Figure 11-1. Arcom board add-on module’s SW0 button

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.

Message Passing

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.

eCos Interrupt Handling

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
35.170.81.33