Lab Assignment (in C): Interrupts and Binary Semaphores

Objective

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

This lab will utilize:

  • Semaphores
  • Lookup table structures and Function Pointers
    • You will allow the user to register their callbacks
  • Interrupts
    • LPC supports rising and falling edge interrupts on certain port pins
    • These port/pin interrupts are actually OR'd together and use a single CPU interrupt.
    • For the SJ1 board, it is: EINT3 interrupt.
    • On the SJ2 board, GPIO interrupts are handled by a dedicated GPIO interrupt (exception number 54)

Where to start

  • For Part 0, do the following
    • Read the LPC User Manual, particularly ALL information about Table 95: GPIO Interrupt
    • Browse the code, and fully absorb interrupt_vector_table.c and entry_point.c
  • For Part 1, first review the Semaphore Design pattern that will be utilized later

Port Interrupts

You will configure GPIO interrupts.  This is supported for Port0 and Port2 and the following registers are relevant. Note that there is a typo in the LPC user-manual as pointed by the orange notation below.

lpc40xx_gpio_interrupts.png

Assignment

Part 0: Simple Interrupt

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

// Step 1:
void main(void) {
  // Read Table 95 in the LPC user manual and setup an interrupt on a switch connected to Port0 or Port2
  // a) For example, choose SW2 (P0_30) pin on SJ2 board and configure as input (Warning: P0.30, and P0.31 require pull-down resistors)
  // b) Configure the registers to trigger Port0 interrupt (such as falling edge)

  // Install GPIO interrupt function at the CPU interrupt (exception) vector
  // c) Hijack the interrupt vector at interrupt_vector_table.c and have it call our gpio_interrupt()

  // Most important step: Enable the GPIO interrupt exception using the ARM Cortex M API (this is from lpc40xx.h)
  NVIC_EnableIRQ(GPIO_IRQn);
 
  // Toggle an LED in a loop to ensure/test that the interrupt is entering ane exiting
  // For example, if the GPIO interrupt gets stuck, this LED will stop blinking
  while (1) {
    delay__ms(100);
    test_gpio_toggle();
  }
}

// Step 2:
void gpio_interrupt(void) {
  // a) Clear Port0/2 interrupt using CLR0 or CLR2 registers
  // b) Use uart_printf__polled() or blink and LED here to test your ISR
}

Code Block 1. Basic Interrupt Test

 

Part 1:  Interrupt with Binary Semaphore

You will essentially complete Part 0, but with the RTOS using a binary semaphore as a signal to wake a sleeping task. It is recommended to save your code in a separate file (or comment it out), and then start this section of the lab. Do not forget to reference the Semaphore Design Pattern.

SemaphoreHandle_t switch_pressed_signal;

void main(void) {
  switch_pressed_signal = ... ;    // Create your binary semaphore
  configure_your_gpio_interrupt(); // Setup interrupt by re-using code from Part 0
  NVIC_EnableIRQ(GPIO_IRQn);       // Enable interrupt gate for the GPIO
 
  xTaskCreate(sleep_on_sem_task, "sem", (512U * 4) / sizeof(void *), NULL, PRIORITY_LOW, NULL);
  vTaskStartScheduler();
}

// Warning: You can only use uart_printf__polled() inside of an ISR
void gpio_interrupt(void) {
  xSemaphoreGiveFromISR(switch_pressed_signal, NULL);
  clear_gpio_interrupt();
}

void sleep_on_sem_task(void * p) {
  while(1) {
    // Use xSemaphoreTake with forever delay and blink an LED when you get the signal
  }
}

Code Block 2. Interrupt with Binary Semaphore

Part 2: Support GPIO interrupts using function pointers

  

You are designing a library that will allow the programmer to be able to "attach" a function callback to any and each pin on port 0. Implement all methods and it should work as per the description mentioned in the comments above each function declaration.

// Objective of the assignment is to create a clean API to register sub-interrupts like so:
void pin30_isr(void) { }
void pin31_isr(void) { }

// Example usage:
void main(void) {
  gpio0__attach_interrupt(30, GPIO_INTR__RISING_EDGE, pin30_isr);
  gpio0__attach_interrupt(31, GPIO_INTR__FALLING_EDGE, pin31_isr);
}

Here is starter code for you that demonstrates the use of function pointers:

// @file gpio_isr.h

typedef enum {
  GPIO_INTR__FALLING_EDGE,
  GPIO_INTR__RISING_EDGE,
} gpio_interrupt_e;

// Function pointer type (demonstrated later in the code sample)
typedef void (*function_pointer_t)(void);

// Allow the user to attach their callbacks
void gpio0__attach_interrupt(uint32_t pin, gpio_interrupt_e interrupt_type, function_pointer_t callback);

// Our main() should configure interrupts to invoke this dispatcher where we will invoke user attached callbacks
// You can hijack 'interrupt_vector_table.c' or use API at lpc_peripherals.h
void gpio0__interrupt_dispatcher(void) {
}

And here is the sample code for the implementation:

// @file gpio_isr.c
#include "gpio_isr.h"

// Note: You may want another separate array for falling vs. rising edge callbacks
static function_pointer_t gpio0_callbacks[32];

void gpio0__attach_interrupt(uint32_t pin, gpio_interrupt_e interrupt_type, function_pointer_t callback) {
  // 1) Store the callback based on the pin at gpio0_callbacks
  // 2) Configure GPIO 0 pin for rising or falling edge
}

// We wrote some of the implementation for you
void gpio0__interrupt_dispatcher(void) {
  // Check which pin generated the interrupt
  const int pin_that_generated_interrupt = logic_that_you_will_write();
  function_pointer_t attached_user_handler = gpio0_callbacks[pin_that_generated_interrupt];
  
  // Invoke the user registered callback, and then clear the interrupt
  attached_user_handler();
  clear_pin_interrupt(pin_that_generated_interrupt);
}

Code Block 3. GPIO library template

Below image shows the software workflow. Click on the image below to view animation and understand more on how the driver should work.

isr.gif

GPIO ISR Animation

Extra Credit: Go above and beyond

There are a number of ways to go the extra step and separate yourself from an average student. For this lab, you can do several things to earn extra credit:

  • Improve the code quality. Instead of hacky code that barely works, demonstrate your code quality by making your code more robust and clean.
    • For example, extend the API at lpc_peripherals.h such that you do not have to modify interrupt_vector_table.c
  • Go back to the previous lab, and instead of implementing a task that reads input, design it such that it registers a callback instead.
    • For example, you will eliminate read_switch task
  • You can extend your API in Part 2 to also support Port 2

Requirements

  • You should be able to full re-write code for Part 0 or Part 1, meaning that you understand the code that you just wrote. You are encouraged to ask questions for any line of code that is not well understood (or magical).
  • Should be able to specify a callback function for any pin for an exposed GPIO given a rising or falling condition
    • We may ask you to change which 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 pin

You cannot use printf() to print anything from inside an ISR (if FreeRTOS is running). Instead use uart__put() function declared in uart.h

  

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

 What to turn in:

  • Place all relevant source files within a .pdf file.
  • Turn in the screenshots of terminal output.
  
Back to top