Can the P2 run two Interrupts concurrently?

I have question regarding interrupts in the P2. Essentially need to know which pins I can attach an interrupt to, and can the interrupts run concurrently?

Background: we have two motor encoders attached to D2 and D3 respectively, and each has an ISR to count pulses when the motors are running. If I tell both motors to run for 100 pulses, one will run for 100 pulses, the other only counts 89-91 pulses. If I run each motor separately they will run for 100 pulses.

This suggests to me that the P2 can’t run the two ISRs concurrently, but I’m having a hard time finding that information in the Particle Documentation.

Appreciate your input.

BTW might want to add a tag for the P2. Thanks.

Can you share your ISR code? If you are disabling interrupts that would definitely occur.

If you are not disabling interrupts, then it should work, because when the first ISR returns it should then service the second ISR that occurred while the first ISR was running.


#p2 has been added!

1 Like

I ran a test and the interrupt counts seem to be working for me in the scenario below.

I connected a frequency generator to pins D2 and D3. It was set for 1000 Hz.

I then ran this firmware on the Photon 2 (it’s a P2 on the Photon 2, so they should behave the same).

#include "Particle.h"


SerialLogHandler logHandler;

void isrD2();
void isrD3();

std::atomic<uint32_t> counterD2;
std::atomic<uint32_t> counterD3;

void setup() {
    Particle.connect();, std::memory_order_relaxed);
    pinMode(D2, INPUT_PULLUP);
    attachInterrupt(D2, isrD2, FALLING);, std::memory_order_relaxed);
    pinMode(D3, INPUT_PULLUP);
    attachInterrupt(D3, isrD3, FALLING);

void loop() {
    static unsigned long lastCheck = 0;
    if (millis() - lastCheck >= 1000) {
        lastCheck = millis();

        uint32_t tempD2 = counterD2.fetch_and(0, std::memory_order_relaxed);
        uint32_t tempD3 = counterD3.fetch_and(0, std::memory_order_relaxed);"D2=%lu D3=%lu", tempD2, tempD3);

void isrD2() {
    // This increments the value atomically. Even if the ISR triggers
    // while we're resetting the value from loop, the count will
    // not be lost.
    counterD2.fetch_add(1, std::memory_order_relaxed);

void isrD3() {
    counterD3.fetch_add(1, std::memory_order_relaxed);

And this is the USB serial output:

0000027084 [app] INFO: D2=1000 D3=1000
0000028084 [app] INFO: D2=1000 D3=1000
0000029084 [app] INFO: D2=1000 D3=1000
0000030084 [app] INFO: D2=1000 D3=1000
0000031084 [app] INFO: D2=1000 D3=1000
0000032084 [app] INFO: D2=1000 D3=1000
0000033084 [app] INFO: D2=1000 D3=1000
0000034084 [app] INFO: D2=1000 D3=1000
0000035084 [app] INFO: D2=1000 D3=1000
0000036084 [app] INFO: D2=1000 D3=1000
0000037084 [app] INFO: D2=1000 D3=1000
0000038084 [app] INFO: D2=1000 D3=1000
0000039084 [app] INFO: D2=999 D3=999
0000040084 [app] INFO: D2=1000 D3=1000
0000041084 [app] INFO: D2=1000 D3=1000
0000042084 [app] INFO: D2=1000 D3=1000
0000043084 [app] INFO: D2=1000 D3=1000
0000044084 [app] INFO: D2=1000 D3=1000
0000045084 [app] INFO: D2=1000 D3=1000
0000046084 [app] INFO: D2=1000 D3=1000
0000047084 [app] INFO: D2=1000 D3=1000
0000048084 [app] INFO: D2=1000 D3=1000
0000049084 [app] INFO: D2=1000 D3=1000
0000050084 [app] INFO: D2=1000 D3=1000
0000051084 [app] INFO: D2=1000 D3=1000
0000052084 [app] INFO: D2=999 D3=999
0000053084 [app] INFO: D2=1000 D3=1000
0000054084 [app] INFO: D2=1001 D3=1001
0000055084 [app] INFO: D2=999 D3=999
0000056084 [app] INFO: D2=1000 D3=1000
0000057084 [app] INFO: D2=1000 D3=1000
0000058084 [app] INFO: D2=1000 D3=1000
0000059084 [app] INFO: D2=1000 D3=1000
0000060084 [app] INFO: D2=1000 D3=1000
0000061084 [app] INFO: D2=1000 D3=1000

It’s not exactly 1000 on all logs because the logging is done out of the loop thread with the cloud connection active, so it can be off by a little bit, but the counts seem to be equal.

It’s actually possible for the counts to differ if something else disables interrupts, or there’s a thread swap in between the two fetch_and calls.

That’s also a good way to structure counters in an ISR that are interrupt safe without having to disable interrupts.

Thanks Rick. Very nicely documented. I believe I have implemented the atomic counter as suggested, but am still getting the same behavior i.e. can only run one motor at a time.

My ISR code is below:

void Motor1encoderISR(void)
   // The ISR is used to count encoder pulses when the motor is nudged forward or backward
   // A nudge is equal to 100 encoder counts

   if (fnudge != 0)
        if (fnudge == 1)                // nudge forward
            encodecnt = counterD2.fetch_add(1, std::memory_order_relaxed);
            if ((nudgeCount+NUDGE) <= encodecnt)  
                // Turn motor off
                fnudge = 0;
        if (fnudge == 2)             // nudge backward
            encodecnt = counterD2.fetch_sub(1, std::memory_order_relaxed);
            if ((nudgeCount-NUDGE) >= encodecnt)
                // Turn motor off
                fnudge = 0;

Unless there are glaring errors in how I have implemented your suggestion, I think I need to be looking in a different area.

Appreciate the support!

@EncoderYoda, which library are you using to control the motor? If the controller is connected via I2C, calling motor.stop() is not good in an ISR. This may best be done by setting a flag and stopping the motor in loop().

1 Like

Hi, I’m using a home grown motor controller, but your comment may still be relevant. Thanks for the tip.

1 Like