Interrupts for the target system!timer
In Lab Exercise 5, 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.InterruptsInterrupts!periodicTimer interruptsBasic time interval (BTI)
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 Exercise 6. Recall that the interrupt service routine (ISR) ran as a separate thread, and therefore it will not interfere with threads running elsewhere.Timer interrupts!interrupt thread
To achieve accurate timing, the interrupts will come, not from a source outside the computer, as in Lab Exercise 6, 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.Timer interrupts!interrupt service routine (ISR)Threads
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 Exercise 6: registering the interrupt and then creating the new thread.
Initializing the FPGA Timer interrupt is similar to initializing the Digital Input interrupt.Timer interrupts!main thread
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: Threads!resourcesInterrupts!resources NiFpga_IrqContext NiFpga_Bool ThreadResource
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. Timer interrupts!interrupt request (IRQ) Irq_RegisterTimerIrq() MyRio_IrqTimer MyRio_IrqTimer NiFpga_IrqContext NiFpga_IrqContext MyRio_IrqTimer MyRio_IrqTimer Irq_Channel pthread_create()
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:
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;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.
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
thread—A pointer to a thread identifier.attr—A pointer to thread attributes. In our case, use NULL 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 of Irq_RegisterTimerIrq.This function also returns 0 for success.
We combine these ideas into a portion of the main() code needed to initialize the timer IRQ. The code in Listing 7.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. Timer interrupts!main thread pthread_t IRQTIMERWRITE IRQTIMERWRITE IRQTIMERSETTIME IRQTIMERSETTIME pthread_create() Irq_UnregisterTimerIrq() Irq_UnregisterTimerIrq() NiFpga_IrqContext NiFpga_False NiFpga_True
main().int32_t irq_status;
MyRio_IrqTimer irqTimer0;
ThreadResource irqThread0;
pthread_t thread;
irqTimer0.timerWrite = IRQTIMERWRITE; // IRQ channel Registers
irqTimer0.timerSet = IRQTIMERSETTIME;
timeoutValue = 500; // Sample period in microseconds
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);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().Timer interrupts!interrupt thread
As shown in Algorithm 7.1, the timer ISR thread is operationally similar to the digital input ISR of Chapter 6. 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.
Timer_ISR() pseudocode 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 blockWithin the loop, two Loop Tasks are performed for each repetition
Irq_Wait() TIMERIRQNO TIMERIRQNO NiFpga_WriteU32() NiFpga_WriteBool() NiFpga_IrqContext NiFpga_True NiFpga_Bool
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 Irq_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;Interrupts for the target system!timer