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;
}
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
Yes, got the same behavior as you. Two fixes to make to your code:
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).
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;
}
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?
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:
Need to tell the GPIO to assert an interrupt on the particular edge you care about.
Need to tell the PLIC to raise an “External” interrupt for the GPIO pin.
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.
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.
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).
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.
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.
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?
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:
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.
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.
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.