Multiple attachInterrupt handlers via C++ object freeze program

I’m running into an issue where the program will freeze in setup when you have multiple attachInterrupt calls made via a method in a C++ object. I can only get 3 to attach correctly and move on before the fourth one locks up the program. In some cases with a couple lightweight instructions in the handler, it freezes on the third.

I’ve provided a working example. This is specifically for the Photon with this pin configuration. (These pins are all valid to use together, correct? I’ve checked that multiple times.) I need to use these pins in my board design.

   class Robot {
      public:
        Robot(int readPin) {
          pinMode(readPin, INPUT_PULLUP);
          attachInterrupt(readPin, &Robot::handler, this, RISING);
        }
        void handler() {
          // do something on interrupt
        }
    };
    
    void setup() {
      Serial.begin(9600);
      Serial.println("begin");
      new Robot(D3);
      Serial.println("attached D3");
      new Robot(D4);
      Serial.println("attached D4");
      new Robot(D5);
      Serial.println("attached D5");
      new Robot(D6);
      Serial.println("attached D6");
      new Robot(D7);
      Serial.println("attached D7");
    }
    
    void loop() {
    
    }

Serial output:

-> particle serial monitor                                                                                                                                                              eric@firefly
Opening serial monitor for com port: "/dev/ttyACM0"
begin
attached D3
attached D4
attached D5

Thanks for any help!

Are any of the ISR’s being called while setup() is still running? You probably need to show what you’re doing in the ISR’s. Also, what do you mean by “freeze”? What are you seeing (LED colors) besides the fact that the serial printing stops?

1 Like

The code you pasted looks fine. I ran it on a Photon and got this back and the loop code is running normally for me.

begin
attached D3
attached D4
attached D5
attached D6
attached D7
2 Likes

In my own code ISRs are being called in setup, but after the point at which it freezes. I was only incrementing a (volatile) int with the ISR. With this case and running nothing in the ISR it still freezes. When I say freeze I mean it enters the attachInterrupt and does not return from it. LED color stays teal.

Thanks for running the code. Maybe there’s something wrong with my Photon? As another FYI if I swap orders of the attachInterrupt calls for the different pins, it still freezes after the third.

Are you using SYSTEM_THREAD(ENABLED)?
What SYSTEM_MODE() are you using?
What firmware are you targeting and with what IDE are you building?
Does this happen with the Photon completely free of external circuitry or only in-place?
Where is your volatile var located - is it part of the object, static or global?

2 Likes

Are you using SYSTEM_THREAD(ENABLED)?
I have not made any call to SYSTEM_THREAD.

What SYSTEM_MODE() are you using?
I have not made any call to SYSTEM_MODE which I believe means I’m automatic.

What firmware are you targeting and with what IDE are you building?
0.4.9 and I’ve built it from both the web IDE and Particle Dev on Linux.

Does this happen with the Photon completely free of external circuitry or only in-place?
My Photon is in place and unfortunately soldered in, I should have mentioned this.

Thanks for the help and good questions fellas.

OK, especially this part is interesting

Do you happen to have anything on DAC/A6 or A1 as these are pins sharing EXTI lines with your D3 and D4.

https://docs.particle.io/reference/firmware/photon/#attachinterrupt-

Could you provide some background on your hardware setup then?

2 Likes

It seems like you have interrupts occurring during setup(). It probably shouldn’t be necessary, but just as a test, can you surround the interrupt assigning setup code with a noInterrupts()/interrupts() pair? This would prevent the ISRs from being called during setup. I have no idea if this will have any effect, it’s mostly just curiosity to see if it does.

1 Like

I was just thinking about the same thing. I will try that tonight when I get home. Scruff, I will also try and get you the details about all my pins tonight. I was trying to be careful about choosing pins. My attachInterrupt calls are only on those pins in the example. Nothing is on A6. A1 is a disconnected line (was for an optional additional component for later). Does that statement mean I can’t use A1 at all or I just can’t use A1 for interrupts?

Definetly for the latter, but as a possible cause to your issue, this should at least be considered.

Adding a noInterrupts()/interrupts() pair surrounding the contents of the setup function had the same result, froze at the third. Seems like nothing was affected with it. Shame, good thought though.

Also wanted to make a correction to a previous statement about the LED. I’m not sure if it’s a different scenario now, but the light goes immediately from teal to no light at all. The board stays connected at least to the serial monitor. This is for the example code above.

In terms of hardware setup, I can’t get too detailed, but the basics is that there are 5 555 timers sending inputs to respective pins IN1-5. NOT_USED_YET_1-3 as shown are not currently connected. LEDS1-5 are indicators and I wanted them on pins capable of analogWrite. The WKP_BTN worked as you’d expect as the toggle button to turn on the device.

Here’s the pin layout for reference:

Hope all this info helps. :smile:

Weird…I just touched the D5 pin and 3V3 via my multimeter (reading voltage) and it got past the attachInterrupt. (I realize this might not have been the best idea, but science.) What’s happening with that?

Not sure. The only thing I could imagine is that you stopped a pin from floating (although this should already be achieved with your INPUT_PULLUP).

But another stab in the dark.
What if you put your attachInterrupt() block into the STARTUP() macro?
And also try keeping the object pointers returned by new.

STARTUP(doAttach())

class Robot {
      public:
        bool success = false;
        Robot(int readPin) {
          pinMode(readPin, INPUT_PULLUP);
          success = attachInterrupt(readPin, &Robot::handler, this, RISING);
        }
        void handler() {
          // do something on interrupt
        }
    };
    

Robot* robots[8];
void doAttach()
{
  for (int i=3; i<8; i++)
      robots[i] = new Robot(D0 + i);
}

void setup()
{
  Serial.begin(115200); // this is the standard USB baud rate anyway
  Serial.println("begin");
  for (int i = 3; i < 8; i++)
    Serial.printlnf("%s attached D%d", robots[i]->success ? "Is" : "Not", i);
}
2 Likes

@crearc, apart from @ScruffR’s recommendations, I believe part of the problem is as follows:

  1. You rely on internal pull-ups which don’t get activated until after the code runs. An external pull-up guarantees the initial condition at power up and on reset.
  2. You have not considered the power-on state of the 555s. Ideally, all 555s are disabled until the code has settled and the code actually enables them. You could tie all 555 RESET* lines to a pull-down resistor and a GPIO pin on the Photon that you would pull up to activate them. I suspect the 555s are firing on power up, causing unwanted interrupts.

Power on conditions are always a beeotch to work with. On the Photon, the JTAG interface affects the state of several pins while others are floating. Designing around these "edge"conditions is good and important practice. :wink:

4 Likes

@ScruffR I’ve ran your code, and it seems to stop within doAttach. I am unable to connect via serial and I also get a steady white light which I assume means it never got past. Thanks for trying a stab at an alternative.

@peekay123 thanks for the comments. Very interesting stuff. These were things I did not really consider (especially since this design worked on an Arduino Due, it really threw things off). But it’s nice to learn about these things. So I suppose the fact that pin D3 through D7 are JTAG interface pins as you mentioned is part of the problem (aka all of my pins haha). But this does make a lot of sense, I had not considered power-on state of the 555s before.

Just to be clear about what you’re saying, I made a diagram of what the intended design should be. Let me know if it’s correct. Is 10k ohm good for it? Sadly this a fabricated board so :cry:.

@crearc, the 555 reset pins R are active LOW so what I meant was to tie pins as you did but have a pull-down resistor at the PDR label, not a series resistor (which you need to remove). This will hold all the 555s in reset until you raise 555_POWER HIGH. Assuming the 555s are CMOS, you can use a resistor value of 10K to 100K though 10K is a good value. :smiley:

3 Likes

Ahh sorry, I was being an idiot and didn’t realize how pull down resistors work. I got that straightened away now.

So I’ve desoldered the board from the Photon. Both forms of the test script now work.

Now I’ve done tests to see if can handle all 5 555s and I’m realizing that I don’t think it can handle the number of interrupts. My original ISR had about 4 instructions and only 2 555s max ran smoothly, adding a third locked it up (after passing the attachInterrupt). I reduced it to just 1 instruction of incrementing a counter. Each 555 emits 22k pulses per second. Is it just more than it can handle at about >70k interrupts per second?

I’ve attempted changing the priority to 10 and 8, but no luck with that. Am I just operating at too high of frequency that I need to offload counting to a different dedicated ICs?

The design and convenience of the Wiring API isn’t geared towards very low interrupt latencies. For example, the EXTI interrupt handler has to first determine with GPIO triggered the interrupt, fetch the target object, and the target function then invoke that, and all this happens via a virtual method call.

Using a bare C style function rather than a C++ class will speed things up to some degree, since this avoids some of the overhead by skipping some of the setup steps.

2 Likes

@crearc, with 5 x 22KHz asynchronous inputs on a shared EXTI line, at best there is 45us gap between triggers and at worst, two pulse are closely timed and the trigger is much less than 45us.

As @mat pointed out, writing your own EXTI bare interrupt handler would allow you to optimize its performance. However, since those interrupts share a single EXTI vector, there is the obvious possibility you will miss interrupts. Perhaps the bigger question that needs to be asked is what are you trying to achieve? What is the purpose of the 555s and why the 22KHz rate? There is a possibility we can suggest a different approach to your project. :grinning:

2 Likes