Arduino IDE attachInterrupt

I am trying to get interrupts to work, but failing at the moment.

I have set pin 2 to use INPUT_PULLUP and can read a value correctly as I ground the pin.
I have tried using

attachInterrupt(digitalPinToInterrupt(2), isr, CHANGE);

and

attachInterrupt(26, isr, CHANGE);

but it doesn’t seem to be working. The isr function works when I call it from code, and the variable that is changed in it is declared as volatile.

Has anyone else used interrupts through the Arduino IDE and can confirm it works as expected ?

Thanks

Nick

1 Like

Haven’t tried. Do you want to share your code here, or maybe pastebin or github or something like that if it’s too big for here (I guess a simple test will be small).

Here you go. Might be messing up the formatting, can’t tell till I post it

int pin = 2;
volatile boolean triggered = false;
unsigned long last =0;

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(pin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(pin), ISR, RISING);
}

void loop() {
unsigned long current=millis();
if(current-last>999){
Serial.print(digitalRead(pin));
Serial.print(triggered);
Serial.println(" alive");
last = current;
}

if(triggered){
Serial.println(“triggered”);
triggered = false;
}
}

void ISR(){
triggered = true;
}

the text triggered never appears, tried RISING, FALLING and CHANGE, using 26 as the interrupt number instead of calling the function to work out which interrupt is on pin 2. Code compiles uploads, runs and the value displayed changes as I ground the pin, but the value of triggered doesn’t

It’s better if you at least indent code by 4 spaces so the wiki thing knows not to munge the format

int pin = 2;
volatile boolean triggered = false;
unsigned long last =0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(pin), ISR, RISING);
}

void loop() {
  unsigned long current=millis();
  if(current-last>999){
    Serial.print(digitalRead(pin));
    Serial.print(triggered);
    Serial.println(" alive");
    last = current;
  }

  if(triggered){
    Serial.println("triggered");
    triggered = false;
  }
}

void ISR(){
  triggered = true;
}

Same results as you.

Hmmm, thanks for the report! Will try it out on this side as well.

Yes, got the same behavior as you. Two fixes to make to your code:

  1. You need to explicitly enable interrupts, by setting the MIE bit in the MSTATUS CSR (you can read about this CSR in the RISC-V privileged spec).
  2. You should put code inside your interrupt handler to “acknowledge” that you saw the interrupt and clear down the interrupt source. Otherwise, as soon as you return, the handler will just get called again.

Try this modified version (For fun I hooked the WAKE input to pin 2, which means I can use the blue button to trigger the interrupt, and made it turn on the green LED when “triggered” happens):

// Include this file to give you
// all the CSR macros and #defines

#include "encoding.h"

int pin = 2;
// Save the pinmask to efficiently 
// clear down the interrupt.
int pinmask;

volatile boolean triggered = false;
unsigned long last =0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(pin, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);

  digitalWrite(LED_BUILTIN, HIGH);

  // Compute this once so we can efficiently clear down the 
  // interrupt in ISR.
  pinmask = digitalPinToBitMask(pin);
  attachInterrupt(digitalPinToInterrupt(pin), ISR, FALLING);

  // In addition to attaching the interrupt source to a
  // callback, we need to globally enable interrupts.  
  set_csr(mstatus, MSTATUS_MIE);
  
}

void loop() {
  unsigned long current=millis();
  if(current-last>999){
    Serial.print(digitalRead(pin));
    Serial.print(triggered);
    Serial.println(" alive");
    last = current;
  }

  if(triggered){
    Serial.println("triggered");
    triggered = false;
    digitalWrite(LED_BUILTIN, LOW);

  }
}

void ISR(){
  triggered = true;
  // Clear out the interrupt pending bit, or the interrupt will 
  // just happen again as soon as we return,
  // and we'll never get back into our loop() function. 
  // These are write-one-to-clear.
  GPIO_REG(GPIO_FALL_IP) = pinmask;
}

Ok, that works for me.

However:

  1. towards the end of attachInterrupt() the code says:

    // Enable the interrupt in the PLIC (External interrupts are already
    // globally enabled)
    PLIC_enable_interrupt (&g_plic, intnum);

So, the comment is wrong?

  1. This is an Arduino sketch. Such machine-specific details as globally enabling interrupts, and clearing the interrupt afterwards are supposed to be transparently handled by the Arduino library. The user’s routine (“ISR()” here) is not called directly by the hardware. It’s called by code in the Arduino library, namely line 132 of WInterrupts.c in the handle_m_ext_interrupt() function. Which calls PLIC_complete_interrupt() afterwards.

Seems to me that one or other of those functions should be doing this cleanup.

There are 4 things you need to enable to actually get an interrupt. attachInterrupt handles 3 of them. Is there some other Arduino library function for globally enabling/disabling interrupts? In my mind, the global interrupt enable/disable does not really belong in the per-interrupt code.

attachInterrupt handles these 3 tasks:

  1. Need to tell the GPIO to assert an interrupt on the particular edge you care about.
  2. Need to tell the PLIC to raise an “External” interrupt for the GPIO pin.
  3. Set the “external interrupt” enable bit (MEIE) in MIE CSR, to let the PLIC’s interrupts through. (Confusingly, there is both a CSR called MIE , and a bit in MSTATUS called MIE). This is what the comment in attachInterrupt() is referring to – that the MEIE bit is set in MIE register to enable External interrupts (vs. Timer Interrupts, or Software Interrupts).

attachInterrupt doesn’t do this:
4) Set the MIE bit in MSTATUS, which is the global, any-interrupts-at-all enabled bit. This isn’t really something you would want to set and clear inside of attachInterrupt or detatchInterrupt.

It is a fair argument that clearing down the GPIO interrupt source might be handled by the code that calls ISR. The GPIO pins are a special case and we’re doing a lot for them that we aren’t doing for the other sources (e.g. PWM/Counter interrupt) at all. Happy for pointers other Arduino code examples to give the expected behavior.

1 Like

Sure, there is noInterrupts() and interrupts(), so that you can disable interrupts during some particularly timing-sensitive bit-banging.

The default though is to start the program with interrupts enabled. On AVRs this is required to make both the serial port and millis() work. AVRs don’t have a big 64 bit cycle counter, so millis() works by having a timer go off once every ms. If you disable interrupts for more than 1 ms then you lose timing accuracy. I believe micros() works by reading the remaining count on the timer and adding to the current millis().

No one in Arduino-land expects to have to globally enable interrupts. They should (almost) always be on.

I’d suggest that you set this MIE bit in MSTATUS at program initialization, as soon as the interrupt vector points to a handler and not just into the weeds.

Things can be different for SDK programs, of course, but this is what Arduino sketches expect.

Thanks for that.

interrupts() and noInterrupts() generate errors if you add them to the code, although the IDE colours them, suggesting they would be fine. Should we post things like this as issues on the repo, raise them here first or is there a list somewhere of what is currently implemented/working. Happy to contribute to something like that as we test stuff if it doesn’t currently exist.

attachInterrupt works with FALLING. Changing the code you posted to use RISING, code runs and the alive messages are sent but the trigger fails, using CHANGE nothing coming out of the serial port.

Changing the code back to FALLING still doesn’t work once it has failed.

If I uploaded RISING I can get the code to work again with FALLING if I load blink example (doesn’t use serial or interrupts) , then load with FALLING.

If I uploaded CHANGE I need to upload FALLING to the board and power cycle to get it work (unplug, not reset button).

realised now I need to change the

GPIO_REG(GPIO_FALL_IP) = pin mask; 

to

GPIO_REG(GPIO_RISE_IP) = pinmask;

and have both if it is set to CHANGE.

You can certainly file issues against the repo! I think it’s still good we did the debugging on the forum because it’s easier for others to find if they encounter the same.

Yes, we should implement the noInterrupts functions and make the clearing of the interrupts inside the handler automatic.

1 Like

Weird, this does not work on my Hifive1 board.
I also found that, I have to comment out the set_csr line in setup() to make all Serial functions in loop() work. Does the serial communication on Hifive1 relies on interrupt?

My Arduino IDE is 1.8.1, following the default setting in the getting start guide. Haven’t tried the manual configuration.

Any comments?

Did you use the code exactly as above? What you’re describing sounds like the ISR may not be clearing the interrupt back down. Or, perhaps there was some other interrupt pending. What happens if you reset your board?

Yes, I tried exactly the same code you posted. The same behavior lasts after reset.

What information should I provide and what should I do to debug this situation?
Might using a manual openOCD help with this?

Appreciate for any help.

This could be a good use of GDB to debug, if you are comfortable with it. There are lots of tools installed with the SiFive BSP which you can use outside of the Arduino IDE. On Linux, these tools are installed at a location like ~/.arduino15/packages/sifive/. Let’s call that path $SIFIVE.

In one terminal window, connect to your board with OpenOCD:

$SIFIVE/tools/openocd/<hash>/bin/openocd -f $SIFIVE/hardware/riscv/1.0.2/freedom-e-sdk/bsp/env/freedom-e300-hifive1/openocd.cfg -d

Just leave that running. In a second terminal window, start up GDB. You will need to know where the Arduino IDE puts the actual ELF file that it loaded to your board. Generally this is at /tmp/arduino/build_#####/<your_sketch_name.ino.elf>. You can find it in the Arduino IDE output when it uploads the file.

$SIFIVE/tools/riscv32-unknown-elf/gcc/<hash>/bin/riscv32-unknown-elf-gdb /tmp/arduino/build_<number>/interrupts_test_from_forums.ino.elf

Once GDB is running, you can debug your code:

(gdb) target extended-remote localhost:3333
...
(gdb) display/i $pc
???
(gdb) p/x $mstatus
???
(gdb) p/x $mie
???

and so on.

This will show you where your code is currently running. Perhaps it is stuck in ISR or elsewhere. When you get GDB running, let us know what kind of results you are seeing and we can debug further.

1 Like

Thanks for the instructions.

I just gave the gdb a quick try. Before line 33 set_csr(), the $mie register was 0x808 and $mstatus was 0x1800. After that, the $mstatus becomes 0x1808.

I added the breakpoint at ISR(), but still nothing happened after I pushed WAKE button.

I think I should try some other sources/pins as the input of interrupt. But before that, any other suggestions? You guys are so responsive and helpful, thanks.

Update:

Once I replace all the BUILTIN_LED to some arbitrary pin, says 7, and connect pin 7 and 2, the ISR() works as expected.