RTC Website

Introduction to interrupts

TX

The objectives of this exercise are:

  1. To implement a system with an external interrupt
  2. To program an interrupt service routine
  3. To program multiple threads
  4. To prototype a digital logic circuit
  5. 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:

  1. Open the myRIO session.
  2. Register the digital input (DI) interrupt.
  3. Create an interrupt thread to service the interrupt.
  4. Begin a loop. Each time through the loop, the following happens: lab5-main-loop
    1. Wait 1 s by calling the (\(5\) ms) wait() function (introduced in lab 3) \(200\) times.
    2. Clear the display and print the value of the count.
    3. Increment the value of the count.
  5. After a count of \(60\), signal the interrupt thread to stop, and wait until it terminates.
  6. Unregister the interrupt.
  7. 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:

  1. Wait for an external interrupt to occur on DIO0.
  2. Service the interrupt by printing the message “interrupt_” on the LCD.
  3. 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 {
  NiFpga_IrqContext irqContext;  // IRQ context reserved
  NiFpga_Bool irqThreadRdy;      // IRQ thread ready flag
  uint8_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.

Algorithm 11: ISR DI_ISR() pseudocode
Algorithm

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 = (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 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;
Irq_Wait(threadResource->irqContext,
         threadResource->irqNumber,
         &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 */
  Irq_Acknowledge(irqAssert);
}

(after the end of the loop) is to terminate the new thread and return from the function:

pthread_exit(NULL);
return 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.

 Figure 5.15
Figure 5.15: Connecting the interrupt switch to the myRIO.

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.

 Figure 5.16
Figure 5.16: Connecting the interrupt switch, with debouncing, to the myRIO. The pullup resistors connected to the open-collector NAND gate outputs improve performance. We recommend 2.2 kΩ pullup resistors.

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 .

 Figure
Figure : Full illustration of the operation of the debouncing circuit.

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.


  1. It is best practice to check the value of irq_status after each function call.↩︎

Online Resources for Section L5

No online resources.