Lab Assignment: Interrupt + Lookup Tables + Binary Semaphores

Objective

To learn how to create a single dynamic user defined interrupt service routine callback driver/library.

This lab will utilize:

  • Semaphores
    • Wait on Semaphore Design pattern
  • Lookup table structures
  • Function pointers
  • Interrupts
    • LPC supports rising and falling edge interrupts on certain pins
    • These port/pin interrupts are actually OR'd together and use a single CPU interrupt called EINT3 (External Interrupt 3)

Port Interrupts

You will configure GPIO interrupts.  This is supported for Port0 and Port2 and the following registers are relevant.

Assignment

Part 1: Simple Interrupt

The first thing you want to do is get a single Port/Pin's interrupt to work.

void interrupt_callback(void)
{
  // Clear the interrupt
  // Maybe uart0_puts() or blink and LED here to test your ISR
}

void main(void)
{
  isr_register(EINT3_IRQn, interrupt_callback);
  
  while (1) {
  }
}

Part 2: Design GPIOInterrupt driver

You are designing a library that will allow the programmer using your library to be able to "attach" a function callback to any and each pin on port 0 or port 2. 

  1. Implement ALL class methods.
  2. All methods must function work as expected by their comment description.
#ifndef LABGPIOINTERRUPTS_H
#define LABGPIOINTERRUPTS_H

typedef enum {
  rising_edge,
  falling_edge,
  both_edges,
} InterruptCondition_E;

class LabGPIOInterrupts
{
private:
    /**
     * Allocate a lookup table matrix here of function pointers (avoid dynamic allocation)
     * Upon attachInterruptHandler(), you will store the user's function callback
     * Upon the EINT3 interrupt, you will find out which callback to invoke based on Port/Pin status
     * Be clever here. How can you do this such that you and the cpu do the least amount of work.
     */

public:
    /**
     * Optional: LabGPIOInterrupts could be a singleton class, meaning, only one instance can exist at a time.
     * Look up how to implement this. It is best to not allocate memory in the constructor and leave complex
     * code to the init() that you call in your main()
     */
    LabGPIOInterrupts();

    /**
     * This should configure NVIC to notice EINT3 IRQs; use NVIC_EnableIRQ()
     */
    void init();

    /**
     * This handler should place a function pointer within the lookup table for the handle_interrupt() to find.
     *
     * @param[in] port         specify the GPIO port
     * @param[in] pin          specify the GPIO pin to assign an ISR to
     * @param[in] pin_isr      function to run when the interrupt event occurs
     * @param[in] condition    condition for the interrupt to occur on. RISING, FALLING or BOTH edges.
     * @return should return true if valid ports, pins, isrs were supplied and pin isr insertion was sucessful
     */
    bool attachInterruptHandler(uint8_t port, uint32_t pin, void (*pin_isr)(void), InterruptCondition_E condition);
    
    /**
     * This function is invoked by the CPU (through c_eint3_handler) asynchronously when a Port/Pin
     * interrupt occurs. This function is where you will check the Port status, such as IO0IntStatF,
     * and then invoke the user's registered callback and find the entry in your lookup table.
     *
     * VERY IMPORTANT!
     *  - Be sure to clear the interrupt flag that caused this interrupt, or this function will be called
     *    repetitively and lock your system.
     *  - NOTE that your code needs to be able to handle two GPIO interrupts occurring at the same time.
     */
    void handle_interrupt(void);
    
    // Optional destructor
    ~LabGPIOInterrupts();
};

#endif

  
  

/* Since we have a C++ class handle an interrupt, we need to setup a C function delegate to invoke it
 * So here is the skeleton code that you can reference
 */

/**
 * Unless you design Singleton class, we need a global instance of our class because 
 * the asynchronous c_eint3_handler() will need to invoke our C++ class instance callback
 * WARNING: You must use this same instance while testing your main()
 */
LabGPIOInterrupts gpio_intr_instance;

// This function will simply delegate the interrupt handling to our C++ class
// The CPU interrupt should be attached to this function through isr_register()
void c_eint3_handler(void)
{
    gpio_intr_instance.handle_interrupt();
}

/**
 * main() should register C function as callback for the EINT3
 * This is because we cannot register a C++ function as a callback through isr_register()
 * 
 * There are workarounds, such as static functions inside a class, but that design is
 * not covered in this assignment
 */
void main(void)
{
    // Init things once
    gpio_intr_instance.init();
  
    // Register C function which delegates interrupt handling to your C++ class function
    isr_register(EINT3_IRQn, c_eint3_handler);
  
    // Create tasks and test your interrupt handler
}

Code Block 1. GPIO Interrupt Driver Template Class

Part 3: Use Driver to Optimize GPIO Application

As the title says, you will attempt to optimize your previous lab by utilizing interrupts and semaphores. This time, you will eliminate the vReadSwitch task, and utilize a interrupt service routine to send semaphores (fromISR) to the vControlLED task.

Requirements

  • Should be able to specify a callback function for any port/pin for an exposed GPIO given a rising, falling, or both condition.
    • We may ask you to change which port and pin causes a particular callback to be executed in your code and then recompile and re-flash your board to and prove it works with any port 0 or port 2 pin.
  • You will need to use two external switches for this lab.
  • The ISR must use a semaphore (fromISR) to the communicate with the vControlLED task.

You cannot use printf() to print anything from inside an ISR (if FreeRTOS is running), but you can use the u0_dbg_printf() API from printf_lib.h.

Note that printing 4 chars inside an ISR can take 1ms, and this is an eternity for the processor and should never be done (other than debug).

Skeleton Test Code:

void user_callback(void)
{
  // This is where you will "send" a Semaphore that the vControlLED task is waiting on
}

void main(void)
{
  // ISA team may modify Port/Pin to test your GPIO Interrupt class
  gpio_intr_instance.attachInterruptHandler(2, 3, user_callback, rising_edge);
}

What to turn in:

  • Place everything inside of main file or include all relevant files.
  • Turn in the screenshots of terminal output. 
Back to top