attachInterrupt/detachInterrupt crash

I thought I remember reading about this before, but I am having a crash on the GIGA
using attachInterrupt, followed by detachInterrupt and maybe it is when I then later try to reattach the interrupt.

My quick search in this forum, I did not find any hits, but I probably missed it.
The following code is a quick and dirty extract from some other code (SoftwareSerial),
that I am playing with which crashes.

#define rxpin 2
void setup() {
    // put your setup code here, to run once:
    while (!Serial && millis() < 4000) {}
    Serial.begin(9600);
    delay(1000);
    Serial.println("Before Serial1 begin"); Serial.flush();
    Serial1.begin(9600);  // 
    Serial.println("Before SerialSoft begin"); Serial.flush();
    pinMode(rxpin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(rxpin), start_bit_falling_edge, FALLING);
    pinMode(LED_BUILTIN, OUTPUT);
}

volatile bool isr_called = false;
void start_bit_falling_edge() {
    isr_called = true;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    detachInterrupt(digitalPinToInterrupt(rxpin));
}

void loop() {
    // first of simple TX only... 
    int ich = Serial.read();
    if (ich != -1) Serial1.write(ich);

//    ich = SerialSoft.read();
//    if (ich != -1) SerialSoft.write(ich);

    // See if We received anything on Serial1...
    ich = Serial1.read();
    if (ich != -1) Serial.write(ich);
    if (isr_called) {
        Serial.println("ISR Was called"); Serial.flush();
        delay(50); // make sure it has time to finish the whole byte
        isr_called = false;
        //attachInterrupt(digitalPinToInterrupt(rxpin), start_bit_falling_edge, FALLING);
        Serial.println("ISR Attached again"); Serial.flush();
    }
}

Note If I comment out the detachInterrupt it does not crash...
My current hookup has a jumper wire between pins 1 and 2, and it crashes when
I enter something into serial monitor. I commented out the simulated restart (2nd attach) as I thought it was the thing that was faulting.

The idea is. With the RX code for SoftwareSerial I am playing with. Is that we set an ISR
on the RX Pin, that when it receives the Start bit, the ISR is triggered, it turns off that ISR, and turns on a Timer Interrupt, where at each of these interrupts is samples the RX pin. When it receives the stop bit, it turns off the timer and turns back the pin interrupt.

Other side note: I was trying to see if there is an open issue on this, but I am not sure where these are located, my first guess was at:
arduino/mbed-os: Arm Mbed OS is a platform operating system designed for the internet of things (github.com)

But this does not show any issues. Maybe at:
ARMmbed/mbed-os: Arm Mbed OS is a platform operating system designed for the internet of things (github.com)
But would think that might be too generic a location? Also did not find any hits
for detachInterrupt and one closed in 2019 on attachInterrupt

Thanks

IF you detach an interrupt inside the interrupt, you destroy the ability of the interrupt code to return to the instruction following the interrupt taking place.

2 Likes

try

#define rxpin 2
void setup() {
  Serial.begin(9600);
  while (!Serial && millis() < 4000);
  Serial.println("Before Serial1 begin");
  Serial1.begin(9600);  //
  Serial.println("Before SerialSoft begin"); Serial.flush();
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(rxpin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(rxpin), start_bit_falling_edge, FALLING);
}

volatile bool isr_called = false;
void start_bit_falling_edge() {
  if (!isr_called) {
    isr_called = true;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
}

void loop() {
  // first of simple TX only...
  int ich = Serial.read();
  if (ich != -1) Serial1.write(ich);

  //    ich = SerialSoft.read();
  //    if (ich != -1) SerialSoft.write(ich);

  // See if We received anything on Serial1...
  ich = Serial1.read();
  if (ich != -1) Serial.write(ich);
  if (isr_called) {
    Serial.println("ISR Was called");

    Serial.println("ISR Attached again"); Serial.flush();
    delay(50); // make sure it has time to finish the whole byte
    isr_called = false;
  }
}
1 Like

Thanks both of you!

I have thought about doing something similar as well, but I am worried about
how many interrupts that can be fielded without it causing the timing to fail, or fully reduce the speed (Baud rate) that can be received reliably.

The actual code I was playing with looked more like:

void SoftwareSerial::start_bit_begin() {
    //digitalWriteFast(12, HIGH);
    uint32_t ms_per_bit = microseconds_per_bit;
    //ticker.attach(mbed::callback(this, &SoftwareSerial::data_bit_sample, std::chrono::microseconds(ms_per_bit))); 
    ticker.attach(mbed::callback(this, &SoftwareSerial::data_bit_sample), std::chrono::microseconds(ms_per_bit)); 
    detachInterrupt(digitalPinToInterrupt(rxpin));
    rxcount = 0;
    rxbyte = 0;
    //digitalWriteFast(12, LOW);
}

and the ticker like:

void SoftwareSerial::data_bit_sample() {
    //digitalWriteFast(12, HIGH);
    if (digitalRead(rxpin) == HIGH) rxbyte |= (1 << rxcount);
    rxcount = rxcount + 1;
    if (rxcount == 8) {  // last data bit
        uint16_t head = rx_head + 1;
        if (head >= _SS_MAX_RX_BUFF) head = 0;
        if (head != rx_tail) {
            rx_buffer[head] = rxbyte;
            rx_head = head;
        }
    }
    if (rxcount >= 9) {  // stop bit
        ticker.detach();
        attachInterrupt(digitalPinToInterrupt(rxpin), start_bit_falling_edge, FALLING);
    }
    //digitalWriteFast(12, LOW);
}

In both cases, what obviously like instead of detach/attach again, would rather
have diable/enable disable/restart. In the case of ticker it would be good to be able
to effect when the first tick happens.

Will dig some more, maybe there are some code path that for example returns which interrupt that is and an NVIC calls to enable/disable...

what is the idea behind this idea? do you inventing custom SoftwareSerial lib?

I was curious to see how hard it would be to port some of the work that
@PJRC (Paul) did recently on the Teensy version.
PaulStoffregen/SoftwareSerial: SoftwareSerial library used on Teensy (github.com)

Was also a good excuse to learn some more again on how the GIGA works. Besides I have seen a few different threads where people are asking where is the giga version...

Right now, I am trying to remember what I did to setup the Debug version of the mbed-os.
I think it was @ptillisch who told me (or posted) how to do it the first time.
More specifically I have two copies of it currently on my windows machine.
My github copy which is up to sync with the Arduino sources.
and the copy that is under: /hardware/arduino-git/mbed

What I don't remember is if there is some specific place in the github project I copied to the hardware folder and/or some script... I know my one under the sketchbook is probably
out of date.

Not really...

1 Like

Making some progress, probably not as clean as Arduino might want... i.e they maybe should provide a documented way, but not bad:

This is in my simple hacked up code...

#define rxpin 2

#include <mbed.h>
#include <pinDefinitions.h>
mbed::InterruptIn* pin_irq = nullptr;

void setup() {
    // put your setup code here, to run once:
    while (!Serial && millis() < 4000) {}
    Serial.begin(9600);
    delay(1000);
    Serial.println("Before Serial1 begin"); Serial.flush();
    Serial1.begin(9600);  // 
    Serial.println("Before SerialSoft begin"); Serial.flush();
    pinMode(rxpin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(rxpin), start_bit_falling_edge, FALLING);
    pin_irq = digitalPinToInterruptObj(rxpin);
    Serial.print("IRQ OBJ: ");
    Serial.println((uint32_t)pin_irq);
    pinMode(LED_BUILTIN, OUTPUT);
}

volatile bool isr_called = false;
void start_bit_falling_edge() {
    isr_called = true;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    //detachInterrupt(digitalPinToInterrupt(rxpin));
    pin_irq->disable_irq();
}

void loop() {
    // first of simple TX only... 
    int ich = Serial.read();
    if (ich != -1) Serial1.write(ich);

//    ich = SerialSoft.read();
//    if (ich != -1) SerialSoft.write(ich);

    // See if We received anything on Serial1...
    ich = Serial1.read();
    if (ich != -1) Serial.write(ich);
    if (isr_called) {
        Serial.println("ISR Was called"); Serial.flush();
        delay(50); // make sure it has time to finish the whole byte
        isr_called = false;
        //attachInterrupt(digitalPinToInterrupt(rxpin), start_bit_falling_edge, FALLING);
        pin_irq->enable_irq();
        Serial.println("ISR Attached again"); Serial.flush();
    }
}

The less than documented stuff:
After I did the first attachInterrupt, I did:

#include <mbed.h>
#include <pinDefinitions.h>
mbed::InterruptIn* pin_irq = nullptr;
...
    attachInterrupt(digitalPinToInterrupt(rxpin), start_bit_falling_edge, FALLING);
    pin_irq = digitalPinToInterruptObj(rxpin);

Now in the ISR I do:
pin_irq->disable_irq();

and later I do:
pin_irq->enable_irq();

Which is probably a whole lot less overhead than creating and destroying objects
each time.

But it looks like the code is probably working and not faulting!
Next up to put in the larger sketch

Looks like my SoftwareSerial code is somewhat working now on RX at least at 9600.
I have the library built-in to it as tabs. I will probably split them out and add it to my
Giga library which has some other libraries as well.

If anyone wants to take a look at the WIP, I uploaded here an Arduino zip file...

GIGASoftwareSerialTest-241003a.zip (6.4 KB)

1 Like

Next up: I have finally checked the timing and as I suspected (why I said limping along),
I am not properly offsetting the RX read by half bit time...

Currently I am using to setup the timer:
ticker.attach(mbed::callback(this, &SoftwareSerial::data_bit_sample), std::chrono::microseconds(ms_per_bit));

With the Teensy version using intervalTimer, it does:

	if (data_bit_timer.begin(data_bit_sampling_timer, microseconds_start)) {
		data_bit_timer.update(microseconds_per_bit);
		detachInterrupt(rxpin);

So the first timer interval takes the half bit time into account.

Question is, can the Interval of an mbed::Ticker, be updated? Or do I detach it and
do a new attach at the different timing...

Side forum usage question: In cases like this, should I create new thread about mbed::Ticker or continue here?

And if new thread, should I mark this one as solved? That is, I found a work around that
works for me, but my gut tells me, the fault should not happen and there is probably a bug in the Arduino Interrupts wrapper code.

Would it not be much simpler to make the clock rate twice the Baud rate and after the start bit, just use every other clock time to bit check?

1 Like

Another member reminded me, I was playing with Timers before...
In need of timer interrupts on Arduino Giga - Development / Libraries - Arduino Forum

So will look back over this stuff and decide if I will continue with the mbed stuff,
or try to use one of the hardware timers... However this would limit which pins you could use for RX... Maybe...