Learning External interrupts

Hi. I am a complete newbie, please don’t assume I know anything :confused:

To start learning I have signed up for a course on EdX (Microcontroller Applications with RISC-V). Unfortunately the discussion board is no longer active so I am not able to post queries and receive response on the course board.

Using an FE310 (Sparkfun dev board) and FreedomStudio. The course describes the requirements and development of code for simple interrupt drive examples and I have unpacked these in detail and am reasonably comfortable with what “should” happen.

Control is via external switches (push buttons).The first of these turns an LED on (on board LED) using an external interrupt and the second turns it OFF by polling the GPIO. I have metered the voltages on the buttons and can confirm that they do pull the signal low (pull up resistors are enabled so they are normally high).

Running in debug mode the code never reaches the ISR. Any pointers as to what I am missing would be greatly appreciated.

Here is the code:

/******************************************************************************
     Red-V Thing Plus Interrupt_Demo,
     by Eduardo Corpeño

     Using the built-in LED and two external buttons to control it.
     One button is handled by polling, and the other by interrupts.

     Date: June 20, 2022
     Developed using Freedom Studio v4.18.0.2021-04-1 on Windows 10
     LICENSE: This code is released under the MIT License
     (http://opensource.org/licenses/MIT)
******************************************************************************/

#include <metal/gpio.h> //include GPIO library, https://sifive.github.io/freedom-metal-docs/apiref/gpio.html
#include <stdint.h>
#include <metal/csr.h>	//Control and Status Registers (CSRs)

// Macro Functions

#define  Red_V_enable_output(x)        *((uint32_t *) 0x10012008) |= (1<<(x))
#define  Red_V_set_pin(x)              *((uint32_t *) 0x1001200C) |= (1<<(x))
#define  Red_V_clear_pin(x)            *((uint32_t *) 0x1001200C) &= ~(1<<(x))
#define  Red_V_read_pin(x)             (*((uint32_t *) 0x10012000) & (1<<(x)))
#define  Red_V_enable_pullup(x)        *((uint32_t *) 0x10012010) |= (1<<(x))

#define  Red_V_GPIO_set_ie(x)          *((uint32_t *) 0x10012020) |= (1<<(x))
#define  Red_V_GPIO_clear_flag(x)      *((uint32_t *) 0x10012024) |= (1<<(x))

#define  Red_V_PLIC_GPIO_set_priority(pin,p) *((uint32_t *) (0x0C000020+4*(pin))) = (p)
#define  Red_V_PLIC_clear_ie()               *((uint32_t *) 0x0C002000) = 0;\
                                             *((uint32_t *) 0x0C002004) = 0
#define  Red_V_PLIC_set_ie1(x)               *((uint32_t *) 0x0C002000) |= (1<<(x))
#define  Red_V_PLIC_set_ie2(x)               *((uint32_t *) 0x0C002004) |= (1<<(x))

#define  Red_V_PLIC_claim                    *((uint32_t *) 0x0C200004)

void gpio_isr(void) __attribute__((interrupt, aligned(64)));
void gpio_isr(){
      uint32_t plic_id;

      Red_V_set_pin(5);           // Turn LED ON

      Red_V_GPIO_clear_flag(1);  // Level 3: Clear GPIO0_1 flag
      plic_id = Red_V_PLIC_claim; // Level 2: Claim GPIO interrupt
      Red_V_PLIC_claim = plic_id;	//This signals interrupt completion. See 10.8 of the manual.
}

int main (void){
  struct metal_gpio *gpio_0; // Make instance of GPIO
  gpio_0 = metal_gpio_get_device(0);

  // Pins are set when initialized so we must disable it when we use it as an input/output
  metal_gpio_disable_input(gpio_0, 5);

  // Set as gpio as output
  //metal_gpio_enable_output(gpio_0, 5);
  Red_V_enable_output(5);
  // Pins have more than one function, make sure we disconnect anything connected
  metal_gpio_disable_pinmux(gpio_0, 5);

  metal_gpio_enable_input(gpio_0, 0);    // enable input 0
  metal_gpio_enable_input(gpio_0, 1);    // enable input 1

  metal_gpio_disable_output(gpio_0, 0);  // disable output 0
  metal_gpio_disable_output(gpio_0, 1);  // disable output 1

  metal_gpio_disable_pinmux(gpio_0, 0);  // disable alternate functions for pin 0
  metal_gpio_disable_pinmux(gpio_0, 1);  // disable alternate functions for pin 1

  Red_V_enable_pullup(0);   // enable pull-up for pin 0
  Red_V_enable_pullup(1);   // enable pull-up for pin 1

  // Interrupt Configuration

  // Level 3: GPIO0_1 Falling Edge Interrupt Enable
  Red_V_GPIO_set_ie(1);
  Red_V_GPIO_clear_flag(1);

  // Level 2: PLIC Setting for GPIO0
  Red_V_PLIC_GPIO_set_priority(1,7); // Pin 1, priority 7

  // Level 2: PLIC (IE1, bit 9) for GPIO0_1
  Red_V_PLIC_clear_ie(); // Disable all other interrupts
  Red_V_PLIC_set_ie1(9); // Enable GPIO0_1

  // Level 1: Enable interrupts with MIE in mstatus[3] Sec 8.3 of FE310-G002 manual
  volatile uintptr_t saved_config;
  METAL_CPU_GET_CSR(mstatus,saved_config);
  saved_config |= (0x1U<<3);
  METAL_CPU_SET_CSR(mstatus,saved_config);

  // Level 1: Set base vector mtvec
  METAL_CPU_SET_CSR(mtvec,&gpio_isr);	//The & turns a value into a pointer

  // Level 1: Enable interrupts with MEIE in mie[11] Sec 8.3 of FE310-G002 manual
  METAL_CPU_GET_CSR(mie,saved_config);
  saved_config |= (0x1U<<11);
  METAL_CPU_SET_CSR(mie,saved_config);

  while(1){
          if(Red_V_read_pin(0) == 0)     // Read input pin 0
            Red_V_clear_pin(5);         // Turn LED OFF
	}

  return 0; // Unreachable code
}

1 Like

Hi @Ted usually mie is the last thing to enable, certainly after setting up everything else and, most importantly, mtvec. It’s good you remember to keep the handler on an 64-byte boundary. The first two bits determine absolute or vectored mode, you might check with gdb to make sure PC is going where you think.

Here is a discussion and picture of the PLIC interrupt flow and some tips I put together to initialize and set up GPIO pins for PLIC; they are from an assembly code perspective, which I think is clearer to work with.

You may also find my github repo Demonstrating MTVEC helpful: look at the start.s file.

When an interrupt occurs, you should first determine whether it’s a synchronous exception or asynchronous interrupt. Then, in latter case, you should determine whether the source of interrupt is software, timer, or external/PLIC. In that latter case you claim and acknowledge (complete) the interrupt, and jump as directed to the appropriate source ID.

Make sure the jump tables are on 2-byte or four-byte boundaries, depending whether you use compressed or long form versions of the j instruction.

Lastly, don’t forget the mret at the end, which automatically clears the mip flag.

It is easy to mix up the interrupt enable bits of CSR_MIE_MxIE (where x is S, T, or E for Software, Timer, or External, respectively) with CSR_MSTATUS_MIE which is the main on/off switch for the entire interrupt process. It looks like your C-code already does this correctly, though.

1 Like

Thanks for the detailed response. A fair bit of reading to do, may take some time to absorb :face_with_open_eyes_and_hand_over_mouth: