PaulStoffregen / RadioHead

Version of RadioHead library for Teensy boards
http://www.airspayce.com/mikem/arduino/RadioHead/
Other
255 stars 156 forks source link

Struggling using RF95 in an Interrupt function (ISR), help please ! #47

Closed xdiediex closed 2 years ago

xdiediex commented 2 years ago

Hello,

I'm struggling using the RH_RF95 with an ISR. I need to send a message when a pin's state change. But when the state change the serial is just freezing and nothing is sent. This issue disapear when I comment this line : rf95.send(data, sizeof(data)); My interrupt PIN is 3 on Arduino Uno, could the issue come from here ? Or maybe it's not possible to use LoRa comm insisde ISR ? Thank you for your time !

Here is my code :

// I2C Master
// Include the required Wire library for I2C
#include <Wire.h>
#include <SPI.h>
#include <RH_RF95.h>

// Singleton instance of the radio driver
RH_RF95 rf95;

const byte interruptPin = 3;

void setup() {
  Serial.begin(9600);
  Wire.begin();    // Start the I2C Bus as Master

  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(interruptPin, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(interruptPin), InterruptVol, CHANGE); 

  //Initialize LoRa Module
  while (!rf95.init()) {
    Serial.println("LoRa radio init failed");
    while (1);
  }

  Serial.println("SETUP FINISHED !");
  Serial.println("");
}

void InterruptVol(){  
  Serial.println("INTERRUPTION");
  alerte = true;
  if(digitalRead(interruptPin)){
    SendAlertMessage(); // LoRa ne marche pas dans fct d'interruption
  } else {
    alerte = false;
    noTone(BUZZER_PIN);
  }
}

void SendAlertMessage(){
  blabla...

  SendLoraData(msgAlerte);
}

void SendLoraData(char data[]) {
    Serial.print("[LoRa] Sending data... ");
    data[strlen(data)]='\0';
    //Serial.println(data); //debug
    rf95.send(data, sizeof(data));
    Serial.println(" Data sent ! ");
    rf95.waitPacketSent(1000);

  delay(1000);
}
uggima commented 2 years ago

Hi. just dropping in quickly. Thinking an interrupt is not really what you would want here. Interrupts are generally VERY short, like shorter than sending the first I of "INTERRUPT" over a 9600 baud serial communication. Also its been a long while since i looked at Serial.println but it might make use of interrupts itself.

If you really want to use an interrupt maybe set a flag in the interrupt routine and check for it in the main loop? Flag being a global variable defined just after #includes so all functions can access the same variable, would also need to be volatile as its being changed within an interrupt. https://www.arduino.cc/reference/en/language/variables/variable-scope-qualifiers/volatile/

xdiediex commented 2 years ago

Hello uggima,

Thanks a lot for your response. Indeed the serial monitor only printed the two first letters of my Serial.println ("IN"). I wasn't aware that interrupts were so short in time. But I'll definitely test the flag solution, see if it's work. I'll let you know !

gitmeister commented 2 years ago

Interrupts are meant to be short but nothing prevents you from abusing this. You could theoretically make all this work as long as you didn't make use of library calls to function that aren't "re-entrant". Consider a printf() being executed in main() that gets interrupted by an ISR which also makes a printf(). If this function makes use of static variables (as opposed to stack-based variables), then your ISR will overwrite the statics, and upon returning, the main() version will probably get hopelessly lost. Re-entrant libraries do exist but they are rare at this level. To make a function re-entrant you'd have to have all variables (including anything else that gets called) stack-based so that when the ISR gets control it'll execute a printf() with fresh variables that, upon returning, will be removed and thus expose the original printf()'s variables. This isn't free of course because your next problem is overflowing the stack as every nested printf() will allocate variables on the stack and with typical Arduino stacks of 2K or so you can see why it's not really practical to even consider this approach when ram memory is limited. So as mentioned previously using a variable to flag the ISR event is the way to trigger main() to treat the event. This requires you to keep main() tight as every delay will add to the latency in treating the event.

uggima commented 2 years ago

Ok, yes you really can do your entire program from within an isr (interrupt service routine). But one thing i was avoiding getting into was registers and the big issue of calling an isr from within an isr.

grabbed from https://stackoverflow.com/questions/5111393/do-interrupts-interrupt-other-interrupts-on-arduino

The AVR hardware clears the global interrupt flag in SREG before entering an interrupt vector. Thus, normally interrupts will remain disabled inside the handler until the handler exits, where the RETI instruction (that is emitted by the compiler as part of the normal function epilogue for an interrupt handler) will eventually re-enable further interrupts. For that reason, interrupt handlers normally do not nest. For most interrupt handlers, this is the desired behaviour, for some it is even required in order to prevent infinitely recursive interrupts (like UART interrupts, or level-triggered external interrupts). In rare circumstances though it might be desired to re-enable the global interrupt flag as early as possible in the interrupt handler, in order to not defer any other interrupt more than absolutely needed. This could be done using an sei() instruction right at the beginning of the interrupt handler, but this still leaves few instructions inside the compiler-generated function prologue to run with global interrupts disabled.

And Serial.print.... uses interrupts, so how it managed to get two letters out, i don't have a clue? but... yeah using interrupts from within interrupts is REALLY not something to rely on.

gitmeister commented 2 years ago

Serial receive is typically interrupt-driven to put characters into a buffer however Serial transmit usually polls to dump the characters into the tx register of the USART hardware which handles the output autonomously.

Interrupts within interrupts is actually fairly common in real-time systems. Clocks and "impatient" hardware usually get the top spots. You can imagine that if system time is maintained in software, having the "tick" being delayed by user ISRs would skew the system time so having ISRs interrupt ISRs is desirable in these cases. Debugging these things, however, usually involves LEDs or an oscilloscope - fun times.

xdiediex commented 2 years ago

Thank you everyone for your answers. I modified my interrupt function (deleted all the Serial.println and simplified the interrupt function) :

char Alerte[7] = {"ALERTE|"};

//Fonction d'interruption pour le vol
void InterruptVol(){  
  alerte = true;
  if(digitalRead(interruptPin)){
    /* Test */
    tone(BUZZER_PIN, 900);
    rf95.send((uint8_t *)Alerte, strlen(Alerte)); 
    rf95.waitPacketSent(1000);

  } else {
    alerte = false;
    noTone(BUZZER_PIN);
  }
}

But still not working (the serial monitor just freeze on send() function), so maybe the issue is coming from the library, or am i doing something wrong ? I think the flag solution work tho but It's not really what I want (because I have to check flag in the loop but I dont want to wait every loop to send my alert message, I need it to be sent when the digital pin change state) .

PS :
rf95.send((uint8_t *)Alerte, strlen(Alerte)); work when not in an interrupt if you were wondering.