RTC Website

Timer interrupts and their programming

TX

In lab 4, we used instruction timing to provide known wait intervals. We found that, although instruction timing produced intervals of accurate duration, it was inconvenient as a means of defining the basic time interval (BTI). If changes in other code execution or input/output (I/O) events occurred, the overall duration of the BTI could vary substantially.

In particular, if the computer is to operate as a dynamic system defined by a difference equation, the inputs and outputs must occur at the constant interval \(T\) used to derive the equation coefficients. Otherwise, the I/O timing would be uncoordinated with the physical phenomena outside the computer. Time (and physics) wait for no one!

Clearly, we require a method of making measurements and providing outputs at intervals that are both precise and independent of other tasks or events happening simultaneously. To accomplish this, we will use the interrupt structure that we explored in lab 5. Recall that the interrupt service routine (ISR) ran as a separate thread, and therefore it will not interfere with threads running elsewhere.

To achieve accurate timing, the interrupts will come, not from a source outside the computer, as in lab 5, but from a hardware timer within the computer itself. The timer operates by counting down the value in a register, one count for each pulse of a precision clock, until zero is reached. So the time interval is defined by the initial value in the register and the frequency of the clock. When the count gets to zero, the interrupt occurs, marking the beginning of the BTI. The timer register is refilled to its initial value for the next BTI, and the countdown begins again.

This technique has all the attributes that we desire: the timing is accurate because of the precise clock, the ISR is independent of other threads, and the timing does not deplete significant computing resources because the timer register counting is carried out by hardware rather than software.

In the following sections, we will see how to set up the timer interrupt and how to define the ISR. The setup process uses the same two steps as in lab 5: registering the interrupt and then creating the new thread.

Main thread: background

Initializing the FPGA Timer interrupt is similar to initializing the Digital Input interrupt.

We will use a separate thread to produce interrupts at periodic intervals. Within main(), we configure the Timer interrupt and create a new thread to respond when the interrupt occurs. The two threads communicate through a globally declared thread resource structure:

typedef struct {
  NiFpga_IrqContext irqContext;  // IRQ context reserved
  NiFpga_Bool irqThreadRdy;      // IRQ thread ready flag
} ThreadResource;

The myRIO C library includes functions to set up the timer interrupt request (IRQ). The IRQ settings symbols associated with the timer interrupt are defined in the header file TimerIRQ.h.

Register the Timer IRQ

The first of these functions reserves the interrupt from the FPGA and configures the Timer and IRQ. Its prototype is

int32_t Irq_RegisterTimerIrq(MyRio_IrqTimer *irqChannel,
                             NiFpga_IrqContext *irqContext,
                             uint32_t timeout);

where the three input arguments are as follows:

  1. irqChannel—A pointer to a structure containing the registers and settings for the IRQ I/O to modify. It is defined in TimerIRQ.h as

    typedef struct {
      uint32_t timerWrite;       // Timer IRQ interval register
      uint32_t timerSet;         // Timer IRQ setting register
      Irq_Channel timerChannel;  // Timer IRQ supported I/O
    } MyRio_IrqTimer;
  2. irqContext—A pointer to a context variable identifying the interrupt to be reserved. It is the first component of the thread resources structure.

  3. timeout—The timeout interval in microseconds.

The returned value is 0 for success.

Create the interrupt thread

A new thread must be configured to service the Timer interrupt. In main(), we will use pthread_create() to set up that thread. Its prototype is

int pthread_create(pthread_t *thread,
                   const pthread_attr_t *attr,
                   void *(*start_routine) (void *),
                   void *arg);

where the four input arguments are

  1. thread—A pointer to a thread identifier.
  2. attr—A pointer to thread attributes. In our case, use NULL to apply the default attributes.
  3. start_routine—The name of the starting function in the new thread.
  4. arg—The sole argument to be passed to the new thread. In our case, it will be a pointer to the thread resource structure defined above and will be used in the second argument of Irq_RegisterTimerIrq.

This function also returns 0 for success.

Main thread: our case

We combine these ideas into a portion of the main() code needed to initialize the timer IRQ. The code in Listing 6.1 sets up an interrupt triggered by the timer. As shown, after its tasks are completed, main() signals the new thread to terminate by setting the irqThreadRdy flag in the ThreadResource structure. Next, main() waits for the thread to terminate. Finally, the timer interrupt must be unregistered.

int32_t irq_status;
MyRio_IrqTimer irqTimer0;
ThreadResource irqThread0;
pthread_t thread;

irqTimer0.timerWrite = IRQTIMERWRITE;    // IRQ channel Registers
irqTimer0.timerSet = IRQTIMERSETTIME;
timeoutValue = 5;

irq_status = Irq_RegisterTimerIrq(&irqTimer0,
                                  &irqThread0.irqContext,
                                  timeoutValue);

// Set the indicator to allow the new thread
irqThread0.irqThreadRdy = NiFpga_True;

irq_status = pthread_create(&thread,     // Create new thread to catch the IRQ
                            NULL,
                            Timer_ISR,
                            &irqThread0);

/* Other main() tasks go here */

irqThread0.irqThreadRdy = NiFpga_False;  // Signal thread to terminate
irq_status = pthread_join(thread, NULL);

irq_status = Irq_UnregisterTimerIrq(&irqTimer0,
                                    irqThread0.irqContext);

The interrupt thread

This is the separate thread specified as the starting routine in pthread_create() function called in main(): Timer_ISR(). Its task is to perform any necessary functions at the time of each interrupt. The thread will run until it is signaled to stop by main().

As shown in , the timer ISR thread is operationally similar to the digital input ISR of chapter 5. It consists of three tasks: (1) cast the thread resource to a more useful form; (2) service the interrupts, until signaled to stop; and (3) terminate the thread. Let’s examine each task separately.

Algorithm 12: Timer_ISR() pseudocode
Algorithm

in Timer_ISR() is to cast its input argument (passed as void *) into an appropriate form. In our case, we cast the resource argument back to a ThreadResource structure. For example, declare

ThreadResource *threadResource = (ThreadResource*) resource;

is to enter a while loop that processes each interrupt. This loop should continue until the irqThreadRdy flag (set in main()) indicates that the thread should end. For example, the conditional expression of the while loop might be

while (threadResource->irqThreadRdy == NiFpga_True) { // ... loop block

Within the loop, two Loop Tasks are performed for each repetition

Use the Irq_Wait() function to pause the loop while waiting for the interrupt. For our case, the call might be

uint32_t irqAssert = 0;
Irq_Wait(threadResource->irqContext,
         TIMERIRQNO,
         &irqAssert,
         (NiFpga_Bool*) &(threadResource->irqThreadRdy));

Notice that it receives the ThreadResource context and Timer IRQ number information and returns the irqThreadRdy flag set in the main thread.

After the Iqr_Wait() function completes, check whether the timer IRQ has been asserted using an if condition:

if (irqAssert & (1 << TIMERIRQNO)) {
  // interrupt servicing code here
}

If true, schedule the next interrupt by writing the time interval into the IRQTIMERWRITE register and setting the IRQTIMERSETTIME flag. That is,

NiFpga_WriteU32(myrio_session,
                IRQTIMERWRITE,
                timeoutValue);
NiFpga_WriteBool(myrio_session,
                 IRQTIMERSETTIME,
                 NiFpga_True);

Here, timeoutValue is the number of microseconds (uint32_t) until the next interrupt. Because the Irq_Wait() times out automatically after \(100\) ms, we must check the irqAssert flag to see if our Timer IRQ has been asserted.

In addition, after the interrupt is serviced, it must be acknowledged to the scheduler using

Irq_Acknowledge(irqAssert);

(after the end of the loop) Terminate the thread and return from the ISR function:

pthread_exit(NULL);
return NULL;

Online Resources for Section 6.6

No online resources.