Documents/AT91 Timer IRQ

From Nutwiki
Jump to: navigation, search

AT91 Timer Interrupts

About This Document

This document presents a simple application using a hardware timer interrupt. It will also discuss some important background information you need to be aware of, when using preemptive interrupt handling in a cooperative multithreading system.

About Interrupts

Nut/OS provides a number of timing functions, which in general can be reduced to two basic functions.

  1. Event time out
  2. Thread sleep

Under the hood, the second function is a special case of the first one, using a dedicated internal wait queue. Both, event time out and sleeping introduce context switching. The control is passed from the waiting thread to another thread which is ready to run. Due to the cooperative multithreading used in Nut/OS, there's no guaranteed maximum wait time implied by the kernel itself. The currently running thread decides, when it is willing to release CPU control. This simplifies multithreading code, because the running thread only needs to lock access to shared resources when releasing the CPU. On the other hand, real time behaviour is fully under application control. Guaranteeing minimum thread switching times can make application development quite complicated, specifically if very fast responses are required.

Practically there is no plain cooperative context switching with Nut/OS. All implementations provide hardware interrupt handling, which introduces preemptive context switching. Because applications as well as the Nut/OS kernel are coded with cooperative multithreading in mind, two fundamental restrictions apply for code running in interrupt context.

  1. No Nut/OS API calls are allowed except NutEventPostFromIrq(). Though, many C runtime functions can be used, most notably stdio calls like printf() when used with polling devices like devDebug.
  2. As expected for preemptive context switching, access to shared resources is not as simple as with cooperative multithreading. Access must be atomic (non-interruptable) and thus concurrent access must be protected. Furthermore, shared resources, which are modified in interrupt context, must be declared volatile.

The impact of these restrictions on code size and execution speed should not be underestimated. Thus, as a general rule, code running in interrupt context should be limited to a bare minimum. There is a second reason for following this rule. Typically Nut/OS interrupts can't be nested. Thus, if two interrupts appear concurrently, the one with the higher priority will be handled first and handling of the second one will be delayed until the first one had been finished. This is called interrupt latency.

After all this theory let's try a real example.

Application Sample

One way to implement a piece of code, which should cyclically run at fixed time intervalls, is to create a new thread with an endless loop containing the code and a call to NutSleep(). This, of course, has its limits. When the application contains more threads, the sleep time may grow and the deviation may reach unacceptable values.

The following application uses a hardware timer interrupt to maintain more exact timing. To keep the code simple, the interrupt code will just increment a counter variable, while the main thread runs an endless loop, retrieving the value of the counter and clearing it. Here's the application's main routine.

#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>

#include <dev/board.h>
#include <arch/arm.h>
#include <dev/irqreg.h>
#include <sys/timer.h>
#include <sys/version.h>

volatile int counter;

int main(void)
{
    u_long baud = 115200;
    int last;

    /*
     * Register and initialize the UART DEBUG device for stdout.
     */
    NutRegisterDevice(&DEV_DEBUG, 0, 0);
    freopen(DEV_DEBUG_NAME, "w", stdout);
    _ioctl(_fileno(stdout), UART_SETSPEED, &baud);

    /*
     * Print a banner and some system timing information.
     */
    printf("\n\nAT91 Timer Interrupt Sample - Nut/OS %s\n", NutVersionString());
    printf("CPU running at %lu Hz\n", NutGetCpuClock());
    printf("System Timer running at %lu ticks/s\n", NutGetTickClock());
    printf("1 second needs %lu system ticks\n", NutTimerMillisToTicks(1000));

    /*
     * Initialize counter/timer hardware.
     */
    InitApiTimer();

    for(;;) {
        /* 
         * Disable timer interrupt and retrieve and clear counter value. 
         */
        NutIrqDisable(&sig_TC1);
        last = counter;
        counter = 0;
        NutIrqEnable(&sig_TC1);
        printf("%d counter ticks\n", last);

        /*
         * Sleep 1 second.
         */
        NutSleep(1000);
    }
}

Before entering the loop, the application will initialize the timer hardware by calling InitApiTimer(). This part is highly hardware dependant and you should consult the AT91R40008 data sheet for further explanations.

In general it will initialize timer/counter 1 to run in compare mode. In detail, the timer/counter will count the main CPU clock's rising edges divided by 32. If the count reaches 2303, an interrupt occurs and the counter value will start from zero again.

The CPU clock on Ethernut 3 is 73.727263 Mhz by default. Thus, an interrupt occurs on every 2304th rising edge of CPU clock divided by 32. As a result we will get 1000 interrupts per second.

int InitApiTimer(void)
{
    int dummy;

    /* 
     * Disable the timer/counter by setting the TC_CLKDIS bit in the
     * channel control register.
     */
    outr(TC1_CCR, TC_CLKDIS);

    /* 
     * Setting a specific bit in the interrupt disable register disables 
     * the related interrupt. In fact only the lower 8 bits are specified, 
     * but it doesn't hurt to set all 32 bits.
     */
    outr(TC1_IDR, 0xFFFFFFFF);

    /* 
     * Reading the status register will clear any pending interrupt.
     */
    dummy = inr(TC1_SR);

    /* 
     * In the channel mode register we select the main clock divided by 
     * 32 as our clock source. The value of the counter will be 
     * incremented at a positive edge from the clock source. Furthermore 
     * we enable the compare mode trigger, which will reset the counter
     * when it reaches the value in the compare register.
     * Finally we select the waveform mode.
     */
    outr(TC1_CMR, TC_CLKS_MCK32 | TC_CPCTRG | TC_WAVE | TC_EEVT_XC0);

    /* 
     * Set compare value for 1 ms. 
     */
    outr(TC1_RC, 0x8FF);

    /* 
     * Enable the timer/counter by setting the TC_CLKEN bit in the
     * channel control register.
     */
    outr(TC1_CCR, TC_CLKEN);

    /* 
     * Enable RC compare interrupts by setting the related bit in the
     * interrupt enable register.
     */
    outr(TC1_IER, TC_CPCS);

    /* 
     * We use the Nut/OS API to register our timer interrupt handler.
     */
    NutRegisterIrqHandler(&sig_TC1, ApiTimerIntr, 0);

    /* 
     * Set to lowest priority. 
     */
    NutIrqSetPriority(&sig_TC1, 0);

    /* 
     * Enable interrupts for TC2 by calling the Nut/OS API. This will
     * not modify any timer/counter register but enable the associated
     * interrupt in the advanced interrupt controller.
     */
    NutIrqEnable(&sig_TC1);

    /*
     * Set the software trigger bit in the channel control register.
     * This resets the counter and starts the clock.
     */
    outr(TC1_CCR, TC_SWTRG);

    return 0;
}

Beside configuring the timer/counter hardware, this routine also calls some Nut/OS API procedures.

  • NutRegisterIrqHandler()

registers a handler routine for the specified interrupt (see below).

  • NutIrqSetPriority()

sets the priority of the specified interrupt.

Furthermore, InitApiTimer() as well as main() use routines for enabling/disabling timer/counter 1 interrupts.

  • NutIrqDisable()

Disables the specified interrupt.

  • NutIrqEnable()

Enables the specified interrupt.

These API calls are specifically used by main() for mutual exclusion access to the counter variable. As stated previously, the interrupt handler routine is most simple. On each interrupt it increments the counter variable by 1. This global variable will be modified in interrupt context and thus is declared volatile (see main routine above).

void ApiTimerIntr(void *arg)
{
    counter++;
}

Here's the RS232 output of the program when running on Ethernut 3.

AT91 Timer Interrupt Sample - Nut/OS 4.0.2.1
CPU running at 73727263 Hz
System Timer running at 1000 ticks/s
1 second needs 1000 system ticks
0 counter ticks
985 counter ticks
1000 counter ticks
1000 counter ticks
1000 counter ticks
1000 counter ticks
1000 counter ticks

Note: Nut/OS 4.0.2 contains a bug, which lets the system timer run faster than 1000 ticks per second. This had been corrected in version 4.0.3 by replacing

         outr(TC0_RC, 0x80F);

with

         outr(TC0_RC, NutGetCpuClock() / (32 * NUT_TICK_FREQ));

in arch/arm/dev/ostimer_at91.c.

Realtime Considerations

Interrupt handlers are the key to Nut/OS realtime support. However, at the time of this writing no information exists about guaranteed response times. It is up to the application programmer to do some complex CPU cycle counting or determine an approxiate value by experiment.

Nevertheless, some hints can be followed to avoid large interrupt latencies.

  1. Keep interrupt handling code as short as possible. Consider to move lengthy processing to specific threads, which can be woken up by an event, posted by an interrupt routine via NutEventPostFromIrq(). In the worst case, all enabled interrupt routines may be processed before your own interrupt handler.
  2. By default all interrupts are blocked while the CPU is running in interrupt context. If large code can't be avoided in the handler, you may consider enabling interrupts within your handler. Be aware, that nested interrupts may introduce a number of new problems and so far haven't been tested on Ethernut 3.
  3. Avoid NutEnterCritical() and NutExitCritical() in your application code. If possible, try to disable/enable the specific interrupt instead. Unfortunately many Nut/OS drivers do not yet follow this rule.
  4. If extremely tight timing is required, you may reduce the overhead by using native interrupts. That means, that you do not use NutRegisterIrqHandler(), but instead configure the AT91 interrupt controller directly for your interrupt routine. Nut/OS explicitly allows native interrupts in application code. Actually NutRegisterIrqHandler() doesn't do any magic. It simply stores the name of the registered handler and its associated argument and calls this handler when the specific interrupt occures.