Lab 14: Handling Interrupts on the PIC32

In this lab, you will add an interrupt handler routine to an existing PIC32 program. Interrupts make it possible to comput one thing in the “foreground” while an I/O operation happens in the “background.” When that operation completes—or in this case, when the user provides an input—the foreground operation is interrupted. After handling this event, your program can pick up where it left off.

Assigned: Tuesday, December 6

Due: This lab does not need to be submitted.

Collaboration: You may choose your partner(s) for this lab.

Resources

Preparation

Carefully read all of the instructions before beginning the lab.

Part A: Setting up

Skim through the program provided below. This program counts up (in binary) using the first four pins in PORTA.

# Janet Davis, 8 December 2013
# YOUR NAME HERE
# Count in binary on four LEDs.
# When a switch is flipped, briefly twinkle the LEDs, then resume
# counting.
# Illustrates the use of interrupt-driven port I/O.

.set noreorder          # Avoid reordering instructions
.set noat
.global main            # main marks the program entry point

.text                   # Start generating instructions

# setPortABits(int bits)
# This function sets bits as specified in I/O port A.
#
# parameter:        Bit mask in $a0
# produces:         nothing
# pre-condition:    $ra contains return address
# post-condition:   PORTD contains the bits specified in $a0
.ent setPortABits
setPortABits:
    # function body
    la        $t0, PORTA      # Load address of Port A
    sw        $a0, 0($t0)     # Set specified bits in Port A
    jr        $ra             # return to caller
    nop
.end setPortABits

# delay(iterations)
# This function runs the specified number of iterations of a delay loop.
#
# parameter:        32-bit value in $a0
# produces:         nothing
# pre-condition:    $ra contains return address
# post-condition:   Time has elapsed proportionate to the number of
# iterations.
.ent delay
delay:
delayloop:
    addi    $a0, $a0, -1
    bne     $a0, $zero, delayloop
    nop
    # return to caller
    jr        $ra
    nop
.end delay

# main loop
.ent main
main:
    # Set PORT A bits 0-3 to output
    la      $t9, TRISA
    li      $t0, 0x0
    sw      $t0, 0($t9)

    # Set PORT B bit 5 to input, remainder to output
    la      $t9, TRISB
    ori     $t0, $zero, (1 << 5)
    sw      $t0, 0($t9)

    # Count in binary on four LEDs
    li      $s0, 0          # Initalize counter
    li      $s1, 16         # Counter reset value
outerloop:
    add     $a0, $s0, $zero
    jal     setPortABits    # Display counter value on Port A LEDs
    nop
    li      $a0, 0x200000   # Delay
    jal     delay
    nop

    addi    $s0, $s0, 1     # Add one to counter

    bne     $s0, $s1, outerloop
    nop
    li      $s0, 0          # If counter was 16, reset to 0.
    j outerloop             # Loop forever
    nop

.end main

Create a new MPLAB project and add the code above as a new source file. To run this program, first connect the pins RA0, RA1, RA2, and RA3 to logic indicators on your protoboard. You should also connect pin 8 to ground on your protoboard. Turn the protoboard on, build your MPLAB project, and program the device. Verify that the four LEDs count up to 15 in binary. Make sure your logic indicators are set to TTL levels. You may want to reorder your connections to the logic indicators to display the bits in order.

Part B: Installing an interrupt handler

We are going to use a switch connected to PORTB to make the lights blink briefly before returning to the counting procedure. Unlike our previous lab with switches and the PIC32, this time we will use interrupts to respond to switch changes instead of polling the switch state in a loop.

The first step in handling interrupts is to write the interrupt handler routine. The procedure below is an interrupt handler, with some missing pieces:

# handleCNInterrupt
# Handles a Change Notice interrupt
.ent handleCNInterrupt
handleCNInterrupt:
    # Interrupt prologue
    rdpgpr  $sp, $sp        # Restore stack pointer from shadow register set
    
    # TODO: Save registers here

    # Read PORTB to clear change condition
    la      $t9, PORTB
    lw      $t0, 0($t9)

    # Clear CNBIF - Change Notice B Interrupt Flag -
    # to signal this interrupt has been handled.
    la      $t9, IFS1CLR
    li      $t0, (1 << 14)
    sw      $t0, 0($t9)

    # Save the current state of the Port A LEDs
    la      $t9, LATA
    lw      $s0, 0($t9)

    # TODO: Blink ten times very fast

    # Restore the previous state of the LEDs
    la      $t9, LATA
    sw      $s0, 0($t9)
    
    # TODO: Restore registers here
    
    eret                    # Return from exception
    nop

.end handleCNInterrupt

Notice the three TODO comments in the interrupt handler; you will need to fill in the code to blink all four LEDs ten times, as well as code to save and restore all registers used by the interrupt handler but don’t fill this in yet. Unlike a procedure call, interrupt handlers do not follow calling conventions; an interrupt handler can execute at any point so every register must be restored to their original state, even temporary registers like $t0.

Before filling in the missing code, you should add the following directive before the .text section in your program:

# Install our interrupt handler at position 0 in the exception handler
# vector.
.section .vector_0, code
j handleCNInterrupt
nop

You will also need to enable interrupts. Add this code before the outerloop label in main.

# Disable global interrupts
di

# Set "ON" bit in the Change Notice configuration register for Port B
la      $t9, CNCONBSET
ori     $t0, $zero, (1 << 15)
sw      $t0, 0($t9)

# Set CNBIE - Change Notice B Interrupt Enable
la      $t9, IEC1SET
li      $t0, (1 << 14)
sw      $t0, 0($t9)

# Enable interrupts for pin RB5
la      $t9, CNENB
li	    $t0, (1 << 5)
sw      $t0, 0($t9)

# Set interrupt priority to 7 (default is 0 => no interrupt!)
# Bits 18, 19, 20 of IPC8
# Cannot load immediate to high order bits, so load and then shift
la      $t9, IPC8SET
addi    $t0, $zero, 0x7
sll     $t0, $t0, 18
sw      $t0, 0($t9)

# Enable global interrupts
ei

Part C: Filling in the interrupt handler

What happens when you run this program without adding the blinking code? You may observe some strange behavior. Why is this happening?

Fill in the missing code to blink all four lights, then modify the interrupt handler routine to save and restore all registers you use in the handler.

Part D: A second counter (if you have time)

Modify the provided program to use the logic indicators for two independent 2-bit counters. The first 2-bit counter should automatically count up (or wrap around to zero) after each delay, much like the 4-bit counter provided above. The second counter should use the interrupt handler to count up each time the switch is flipped.

Acknowledgements

This lab was developed by Janet Davis for CSC 211L in 2013. Parts of the lab were inspired by exercises written by Marge Coahran.

license This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.