Problem simultaneously using 2 interrupts to read o/p PWM waves

Hello,

I have been facing a struggle trying to read PWM o/p data from 2 sensors using 2 pairs of RISING and FALLING edge interrupts with micros(). Basically I am using the interrupts as a higher temporal resolution proxy for pulseIn() which has given me less stable data (stability is important for my project). I have a board (Nano 33 IOT) that supports external HW interrupts on specific pins (2,3,10... I'm using 2 and 3) and can read PWM duty cycle data in microseconds perfectly fine for each pin in isolation but once I try connecting 2 pins at once and adding 2 other ISRs for the second pin I get unstable data read. I believe there is interference between the 2 ISRs. My objective is to be able to read the 2 o/p PWM waveforms' 'on' dutycycle in microseconds, as cleanly as possible. I'm using an AD2 to simulate the waveforms at 2KHz (max value in serial read is thus 500us).

Error/results: I get strange values that fluctuate all over the place, some values that spike over 500, and sometimes the values for both sensors read the same number which is obviously wrong.

I have tried the following (all of which I haven't gotten to work):
*Setting flags inside the ISRs; flag1 for rising, flag2 for falling (pin 1), and same for pin 2 and made a condition that when each pair is true (in other words when an entire pulse from ONE pin has been detected) I print the microsecond 'on' dutycycle value for that pin.
*Making the interrupts 'sequential', i.e.:

void RisingISR_1() {
  //flag1a = true;
  pTime1 = micros();
  attachInterrupt(digitalPinToInterrupt(pin1),FallingISR_1,FALLING);
  }
void FallingISR_1() {
  dur_pin1 = micros() - pTime1;
  attachInterrupt(digitalPinToInterrupt(pin2),RisingISR_2,RISING);
  }
void RisingISR_2() {
  pTime2 = micros();
  attachInterrupt(digitalPinToInterrupt(pin2),FallingISR_2,FALLING);
  }
void FallingISR_2() {
  dur_pin2 = micros() - pTime2;
  attachInterrupt(digitalPinToInterrupt(pin1),RisingISR_1,RISING);
  } 

such that the interrupts are 'allowed' to activate in order. I only declare the first interrupt ISR in setup to avoid the others being declared at the beginning and interfering. This hasn't worked
*Just declaring all of the interrupts separately and putting the rising edge ISR of the 2 pins in setup; the code looks like so:

void RisingISR_1() {
  pTime1 = micros();
  attachInterrupt(digitalPinToInterrupt(pin1),FallingISR_1,FALLING);
  }
void FallingISR_1() { //ISR that emulates functionality of pulseIn()
  dur_pin1 = micros() - pTime1;
  attachInterrupt(digitalPinToInterrupt(pin1),RisingISR_1,RISING);
  }
void RisingISR_2() {
  pTime2 = micros();
  attachInterrupt(digitalPinToInterrupt(pin2),FallingISR_2,FALLING);
  }
void FallingISR_2() { 
  dur_pin2 = micros() - pTime2;
  attachInterrupt(digitalPinToInterrupt(pin2),RisingISR_2,RISING);
  } 

*Other things including using delay in the ISRs to stop the rest of the code from executing, using detachInterrupt() to detach interrupts from the 'other' pin so that the same pin can be 'locked in' once rising edge has been detected, using attachInterrupt for all the other pins on one ISR, etc.

Can someone please help me debug this and/or give me any tips? It has been giving me hell. Thank you!!

p.s. Please dm me for questions, clarifications, etc. Here is the code (along with some variants commented out. I cannot attach sorry).

#include <stdio.h>

//Interrupt Specific Parameters
volatile unsigned long dur_pin1; 
volatile unsigned long pTime1;
volatile unsigned long dur_pin2; 
volatile unsigned long pTime2;
volatile bool flag1a = false;
volatile bool flag1b = false;
volatile bool flag2a = false;
volatile bool flag2b = false;


//Other Variables
int pin1 = 2; //Declare pin you will use. 2,3,10 work on Nano 33 IOT without register change
int pin2 = 3;
unsigned long FS_threshold = 500; //for threshold cap (not implemented)

void RisingISR_1() { //ISR that emulates functionality of pulseIn()
  //flag1a = true;
  //detachInterrupt(digitalPinToInterrupt(pin2));
  pTime1 = micros();
  attachInterrupt(digitalPinToInterrupt(pin1),FallingISR_1,FALLING);
  }

void FallingISR_1() { //ISR that emulates functionality of pulseIn()
  //flag1b = true;
  dur_pin1 = micros() - pTime1;
  attachInterrupt(digitalPinToInterrupt(pin1),RisingISR_1,RISING);
  }
  
void RisingISR_2() { //ISR that emulates functionality of pulseIn()
  //flag2a = true;
  //detachInterrupt(digitalPinToInterrupt(pin1));
  pTime2 = micros();
  attachInterrupt(digitalPinToInterrupt(pin2),FallingISR_2,FALLING);
  }

void FallingISR_2() { //ISR that emulates functionality of pulseIn()
  //flag2b = true;
  dur_pin2 = micros() - pTime2;
  attachInterrupt(digitalPinToInterrupt(pin2),RisingISR_2,RISING);
  }


void setup() {
  pinMode(pin1, INPUT);
  pinMode(pin2, INPUT);
  Serial.begin(9600); 
  attachInterrupt(digitalPinToInterrupt(pin1),RisingISR_1,RISING);
  //attachInterrupt(digitalPinToInterrupt(pin2),RisingISR_2,RISING);
  //attachInterrupt(digitalPinToInterrupt(pin1),FallingISR_1,FALLING);
  //attachInterrupt(digitalPinToInterrupt(pin2),FallingISR_2,FALLING);
}
 
void loop() {
  //if (flag1a && flag1b) {
      Serial.print(micros()); //Timestamp in microsecnods to be printed with each PWM 'on' measurement 
      //noInterrupts();
      Serial.print(","); //for CSV
      Serial.print(dur_pin1); //HE1 value
      //flag1a = flag1b = false;
      //interrupts();
      //}
    //if (flag2a && flag2b) {
      //noInterrupts();
      Serial.print(","); //for CSV
      Serial.println(dur_pin2); //HE2 Value
      //flag2a = flag2b = false;
      //interrupts();
      //}
    }

Do not use the micros(), start the timer and in each interrupt just catch it counter value.

1 Like

With most types of Arduino, it's important to protect the reading of variables that are updated in interrupt routines:

noInterrupts();
unsigned long d = dur_pin;
interrupts();
Serial.print(d);

However, Nano 33 is a 32-bit MCU and unsigned long is also 32 bits, so maybe this is unnecessary. But maybe worth a try?

Also, I have never seen attatchInterrupt() used inside an interrupt routine before. I don't know for sure whether it's ok or bad to do that. But would it not be simpler to set up the interrupt to trigger on CHANGE, then use digitalRead() in the interrupt routine to figure out if a rising or falling edge was detected and avoid using attatchInterrupt() inside the interrupt code?

attachInterrupt(digitalPinToInterrupt(pin1),ISR_1,CHANGE);
...
void ISR_1() { //ISR that emulates functionality of pulseIn()
  if (digitalRead(pin1)==HIGH)
    pTime1 = micros();
  else
    dur_pin1 = micros() - pTime1;
}
1 Like

Thanks for the suggestion! Though I am a little bit confused. Wouldn't I need to use micros() to get the timer value? i.e. would this look something like:

//...
void RisingISR() {
pTime1 = micros();
}
void FallingISR() {
pTime2 = micros();
}
//...
void loop() {
unsigned long dur_pin1 = pTime2 - pTime1;
}
//...

I'm not sure how I can capture the time in the ISR's.

Thank you for the valuable suggestions! I can easily implement the first one. Actually I am aware of the method you suggest for using CHANGE based interrupts. When looking how to decode a PWM I found this method as well as the method I implemented here under 'external interrupts': Three Ways To Read A PWM Signal With Arduino | BenRipley.com I'm aware it is bad practice to put print() statements in an ISR. To be honest I don't quite understand the nuance of putting attachInterrupt() inside an ISR; I assumed (possibly incorrectly) that doing so would force said attachInterrrupt() call to only occur in the instance the ISR is executed. This would presumable allow me to have more control over my code. That is my guess though. I tried using the if else as you suggested as well however it resulted in more fluctuating data compared to the rising/falling interrupts for some reason.

At least at classical Arduino is Ok for doing that.
See the example to read PWM duties on the Uno based on pair of interrupts and a Timer

uint16_t volatile LL,HL;
int FRQ,DUTY;
void log1(){
    LL=TCNT1;   // get the timer counter value
    TCNT1=0;    // reset the counter
    attachInterrupt(0,log0,FALLING);
}

void log0(){
    HL=TCNT1;
    TCNT1=0;
    attachInterrupt(0,log1,RISING);
}

void setup(){
Serial.begin(9600);
attachInterrupt(0,log1,RISING);
// setup the Timer with 1 tick in 4uS
TCCR1A=0;TCCR1B=0;TCNT1=0;
TCCR1B =(1<<CS10)|(1<<CS11);
pinMode(6, OUTPUT);
analogWrite(6,25);
}

void loop(){
int _HL,_LL;
cli();
_HL=HL;
_LL=LL;
sei();
FRQ=250000/(_HL+_LL);
DUTY=(_HL*100)/(_HL+_LL);
Serial.println(FRQ);
Serial.println(DUTY);
delay(1000);
}

For testing connect output of pin 6 to INT0 input - pin2
Output:

-1
-1
980
10
984
9
984
9

Edit - added analog output to pin6 for test purposes

Interesting. Thanks for the code! So as some points of comparison, my Arduino Nano 33 IOT has TCC0, TCC1, TCC2, and TCC3 clock registers; would these be analogous to the TCCR1A, etc you are using with the Uno? I believe I would want 'counter' (under 'Event generator selection', 0x24) though I am not sure. I also don't know what CS00 and CS01 are in your code; can't find anything analogous to this in the nano33 datasheet. Just out of curiosity, were I to want to have a temporal resolution of 1us instead of 4, how would I change the code you sent?

Sorry for any rudimentary points or questions; I am new to writing to registers in Arduino and have found it very difficult to find any information on how to do this online.

Oh, sorry ... it is mistake, it should be CS10 and CS11. This is a clock selection bits of the Timer1, you can see it in the Chapter 15.11.2 of the Atmega328 datasheet:

As you can see, with CS10 & CS11 bits set, the main clock is divided by 64, therefore with main freq 16MHz it will give us 4uS by tick.
As can be seen from the table, the number of frequency division options is limited. If you wanted to get one tick per 1µs, you need a divisor of 16, but there is no such thing in the table. You must use either a divisor of 8 or 64.

All stated above are correct for atmega328 controller. For Arduino Nano 33 IOT controller SAMD21 is used. I think you could see a similar timer registers settings in mcu's datasheet.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.