Timer interrupts and their programming
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 {
; // IRQ context reserved
NiFpga_IrqContext irqContext; // IRQ thread ready flag
NiFpga_Bool irqThreadRdy} 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, *irqContext, NiFpga_IrqContext uint32_t timeout);
where the three input arguments are as follows:
irqChannel
—A pointer to a structure containing the registers and settings for the IRQ I/O to modify. It is defined inTimerIRQ.h
astypedef struct { uint32_t timerWrite; // Timer IRQ interval register uint32_t timerSet; // Timer IRQ setting register ; // Timer IRQ supported I/O Irq_Channel timerChannel} MyRio_IrqTimer;
irqContext
—A pointer to a context variable identifying the interrupt to be reserved. It is the first component of the thread resources structure.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 usepthread_create()
to set up that thread. Its prototype isint pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
where the four input arguments are
thread
—A pointer to a thread identifier.attr
—A pointer to thread attributes. In our case, useNULL
to apply the default attributes.start_routine
—The name of the starting function in the new thread.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 ofIrq_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
.timerWrite = IRQTIMERWRITE; // IRQ channel Registers
irqTimer0.timerSet = IRQTIMERSETTIME;
irqTimer0= 5;
timeoutValue
= Irq_RegisterTimerIrq(&irqTimer0,
irq_status &irqThread0.irqContext,
);
timeoutValue
// Set the indicator to allow the new thread
.irqThreadRdy = NiFpga_True;
irqThread0
= pthread_create(&thread, // Create new thread to catch the IRQ
irq_status ,
NULL,
Timer_ISR&irqThread0);
/* Other main() tasks go here */
.irqThreadRdy = NiFpga_False; // Signal thread to terminate
irqThread0= pthread_join(thread, NULL);
irq_status
= Irq_UnregisterTimerIrq(&irqTimer0,
irq_status .irqContext); irqThread0
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.
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*) resource; ThreadResource
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;
(threadResource->irqContext,
Irq_Wait,
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,
(myrio_session,
NiFpga_WriteU32,
IRQTIMERWRITE);
timeoutValue(myrio_session,
NiFpga_WriteBool,
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
(irqAssert); Irq_Acknowledge
(after the end of the loop) Terminate the thread and return from the ISR function:
(NULL);
pthread_exitreturn NULL;
Online Resources for Section 6.6
No online resources.