Introduction to interrupts
The objectives of this exercise are:
- To implement a system with an external interrupt
- To program an interrupt service routine
- To program multiple threads
- To prototype a digital logic circuit
- To debounce a mechanical switch
The T1 target system will be required, but only the components listed in appendix B are needed for this lab exercise. Set up each component in accordance with the instructions for your specific T1 version in section A.2. Connect your components in the manner described in the “Online Resources” section of the web page for this lab exercise: https://rtcbook.org/mr.
Introduction
This exercise illustrates the use of interrupts originating from
sources that are external to the target computer. The principal activity
of our main()
program is to print the value of a counter on the LCD. If uninterrupted,
the counter display, which is updated once per second, should continue
for \(60\) counts.
Generally, the “service” of an interrupt may be arbitrarily complex
in both form and function. However, in this exercise, each time an
interrupt request (IRQ) occurs, the interrupt service routine (ISR) will
simply print out the message, “interrupt_
”. A momentary contact switch on an
external circuit will initiate the IRQ. The overall effect will be that
the display will print the count repeatedly, with the word “interrupt_
” interspersed for each push of the
switch.
Although this program is not long, it is essential that we understand the events that take place at the time of the interrupt: (1) an unscheduled external event (the interrupt) occurs that is asynchronous with respect to the activity of the main thread, and (2) in a separate thread, the ISR executes to respond (service) to the interrupt. The fact that the counter display continues to run accurately both before and after the interrupt illustrates that the function of the main program is not altered, regardless of where the interrupt occurs in the execution.
Programming the two threads
The main program runs in the main thread. It will perform the following tasks:
- Open the myRIO session.
- Register the digital input (DI) interrupt.
- Create an interrupt thread to service the interrupt.
- Begin a loop. Each time through the loop, the following happens:
lab5-main-loop
- Wait 1 s by calling the (\(5\) ms)
wait()
function (introduced in lab 3) \(200\) times. - Clear the display and print the value of the count.
- Increment the value of the count.
- Wait 1 s by calling the (\(5\) ms)
- After a count of \(60\), signal the interrupt thread to stop, and wait until it terminates.
- Unregister the interrupt.
- Close the myRIO session.
The ISR runs in an interrupt thread, separate from the main thread. It should begin a loop that terminates only when signaled by the main thread.
Within the loop, it will do the following:
- Wait for an external interrupt to occur on
DIO0
. - Service the interrupt by printing the message “
interrupt_
” on the LCD. - Acknowledge the interrupt.
The following sections show how the main and interrupt threads are
implemented using the description given in subsection
5.4.1. Recall that the threads communicate through a thread resource
structure declared in main()
:
typedef struct {
; // IRQ context reserved
NiFpga_IrqContext irqContext; // IRQ thread ready flag
NiFpga_Bool irqThreadRdyuint8_t irqNumber; // IRQ number value
} ThreadResource;
The main thread
Lst. ¿lst:interrupt_setup? shows a code fragment of
main()
that
performs the two interrupt setup tasks: First, registering the interrupt
to occur on falling-edge transitions on DIO0
of connector A, and assigning it to IRQ
number 2. And second, creating the interrupt thread. Note that the code
implementing the main()
function
activity of displaying the count is placed after the thread is created,
as indicated in the listing.1
int32_t irq_status;
uint8_t irqNumber = 2;
// Specify IRQ channel settings
MyRio_IrqDi irqDI0;
irqDI0.dioCount = IRQDIO_A_0CNT;
irqDI0.dioIrqNumber = IRQDIO_A_0NO;
irqDI0.dioIrqEnable = IRQDIO_A_70ENA;
irqDI0.dioIrqRisingEdge = IRQDIO_A_70RISE;
irqDI0.dioIrqFallingEdge = IRQDIO_A_70FALL;
irqDI0.dioChannel = Irq_Dio_A0;
// Declare the thread resource, and set the IRQ number
ThreadResource irqThread0;
irqThread0.irqNumber = IrqNumber;
// I. Register DI0 IRQ. Terminate if not successful
irq_status = Irq_RegisterDiIrq(&irqDI0,
&(irqThread0.irqContext),
irqNumber, // IrqNumber
1, // Count
Irq_Dio_FallingEdge); // TriggerType
// Set the ready flag to enable the new thread
irqThread0.irqThreadRdy = NiFpga_True;
// II. Create new thread to catch the IRQ
pthread_t thread;
irq_status = pthread_create(&thread,
NULL, // attr default ok
DI_ISR, // start routine
&irqThread0);
// Other main() tasks go here.
irqThread0.irqThreadRdy = NiFpga_False; // signal thread to end
irq_status = pthread_join(thread, NULL);
irq_status = Irq_UnregisterDiIrq(&irqDI0,
irqThread0.irqContext,
irqNumber);
The interrupt thread
This is the separate thread that was named and started by the pthread_create()
function: DI_ISR
. Its overall task is
to perform any necessary functions in response to the interrupt. This
thread will execute until signaled to stop by main()
.
As shown in , the Digital Input ISR thread 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 DI_ISR()
is to cast
its input argument (passed as void *
)
into appropriate form. In our case, we cast the resource
argument back to the 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 condition 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->irqNumber,
threadResource&irqAssert,
(NiFpga_Bool*) &(threadResource->irqThreadRdy));
Notice that it receives the ThreadResource
context and IRQ number
information and returns the irqThreadRdy
flag set in the main()
thread.
Because the Irq_Wait()
times out
automatically after 100 ms, we must check the irqAssert
flag to see if our numbered IRQ
has been asserted.
In addition, after the interrupt is serviced, it must be acknowledged to the scheduler, as in the following code:
if (irqAssert & (1 << threadResource->irqNumber)) {
/* Your interrupt service code here */
(irqAssert);
Irq_Acknowledge}
(after the end of the loop) is to terminate the new thread and return from the function:
(NULL);
pthread_exitreturn NULL;
Laboratory exploration and evaluation
Write and debug the main()
and DI_ISR()
functions
described in subsection L5.2.
TODO TODO
Provide an interrupt signal by connecting the SPDT switch on the
circuit bread board to DIO0
of connector
A, as shown in figure 5.15. Notice that pressing the switch
causes a falling edge signal to be applied to the DIO0
input. Try your program now. What
happens? This undesirable phenomenon is caused by the “bounce” of the
mechanical switch.
Adjust the oscilloscope to trigger only on a high-to-low transition of the \({\mbox{IRQ}}\) signal. Observe several of the transitions. Typically, what length of time is required for the transition to settle at the low level? How many triggers occur during the settling?
TODO explain
Reference the listing: lst. ¿lst:lab-5-main?
Reference a line number: lab1:nifpga_status
Correct the problem by replacing the switch in figure 5.15 with the debouncing circuit shown in figure 5.16. This circuit incorporates a TTL Quad open-collector NAND IC, (e.g., a SN7438N.)
Caution: Be certain that \(V_\text{dd}\) and GND
are connected to the chip before wiring
the rest of the circuit.
Try your program again.
Consider the switch-debouncing circuit of figure 5.13 and the traces of the outputs/states \(Q\) and \(Q^*\) shown in figure 5.14. Explain why each \(Q\) and \(Q^*\) state transition occurs and how this debounces the circuit that you built in problem L5.3.
The loss of contact with \(\overline{R}\) does not affect \(Q^*\) or \(Q\). Initial contact with \(\overline{S}\) draws it low and therefore \(Q\) high and \(Q^*\) low. The bounce doesn’t affect the outputs. This logic is then mirrored in the transition from contact with \(\overline{S}\) to \(\overline{R}\), with its ensuing bounce. The traces are shown in .
In your own words, explain how the main program thread configures the interrupt thread, how it communicates with the interrupt thread during execution, and how the interrupt thread functions.
It is best practice to check the value of
irq_status
after each function call.↩︎
Online Resources for Section L5
No online resources.