ISR to capture 3x AC amplitudes - odd results

Hi all,

I am using an interrupt to capture the values of 3 analogue inputs at a specific point in time, triggered by an external reference signal. These values are then simply sent over serial to the PC.

These 3 signals are 400Hz AC waveforms. I wish to capture their peak amplitude using the interrupt, triggered by a 4th reference signal. I have built an optocoupler circuit for the external interrupt to ensure the signal is as clean and noise-free as, checked it on the oscilloscope, and can confirm it's a nice square wave, with no noise visible (20MHz scope) that would cause unwanted triggering of the interrupt.

It is important that the amplitudes are captured when the interrupt is triggered by the reference signal so that they are all sampled at the same time - understanding that there may be a slight delay between samples due to the UNO analogue inputs having to take turns with the ADC.

My end result is I want to see three "pseudo DC" signals by capturing the same point of their waveforms each sample, these signals will reflect the amplitude at that point in time. However, I;m getting a bit of a mess:

The image above shows how the UNO is sampling the waves. They look like AC waves, but in reality are likely the result of the analogue reads sampling at odd times and capturing various parts of the AC waves.


const byte interruptPin = 2;
const int inPin0 = A0;
const int inPin1 = A1;
const int inPin2 = A2;
volatile int inVal0 = 0;
volatile int inVal1 = 0;
volatile int inVal2 = 0;

void setup() {

  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), readIn, FALLING);
  Serial.begin(9600);
}

void loop() {         // loop seems to be required for UNO to run
}

void readIn() {
  
  inVal0 = analogRead(inPin0);
  inVal1 = analogRead(inPin1);
  inVal2 = analogRead(inPin2);

  Serial.print(inVal0);
  Serial.print(",");
  Serial.print(inVal1);
  Serial.print(",");
  Serial.print(inVal2);
  Serial.print(",");
  Serial.print("1023"); // only used to stop the serial plotter auto-scaling
  Serial.print("\n");
}

I cannot use a bridge rectifier, and smooth these to DC signals as they can be in phase or in anti-phase, which is why I need to sample all three AC signals at the same time, when triggered by the reference signal. The AC signals are decoupled and resistor biased to +2.5V and swing roughly between ~0.5V and ~4.5V.

I did think that as this is all at 400Hz, could I be interrupting my interrupt? Or once an interrupt is busy does it ignore further triggers?

I have tried to eliminate noise issues causing false triggering of the ISR: I am using the internal pullup on the digital pin, with an optocoupler switching down to 0V, triggering on the falling edge which is nice and sharp on my oscilloscope. I have added some decoupling capacitors to the Arduino board 5V power supply rail too just to try and rule out noise on the power supply.

I have tried placing the serial printing within the loop instead of the ISR, which does produce different looking results, but the issue still exists of not sampling the same point of the waveform as I had expected.

Any ideas please? This is my first Arduino interrupt program, so I may have made some blunder somewhere.

Thanks, Scott.

Lots of text....... Schematics of the interface of the interrupt signal would be interesting to see even if You tell it looks good.
What is the interrupt signal frequency?

Could You just print the result of one channel, like inVal0?
That would show things for us I think.
Know that all helpers are newbies versus Your project.

Why run serial at 9600? Stone age speed. Use 115200.

2 Likes

Bad idea to use Serial.print() in an interrupt routine. Capture the readings into 3 volatile global variables, set a flag (also volatile) and do the printing from loop().

Hi Railroader,

Yes, I will provide a hand drawn diagram shortly.

All 4 signals are at 400Hz, that's a square reference and 3x AC signals.

I was just pondering the time it takes for the ADC to perform a read, about 0.1mS according to : analogRead() - Arduino Reference

So I imagine when the interrupt is triggered roughly 0.1mS later we write the first value to inVal0, then at 0.2mS we get inVal1, and at 0.3mS we get inVal2. A 400Hz signal has a 25mS cycle time, so I don't see this 0.2mS difference between the first and last samples as being an issue I need to worry about, it is close enough for accuracy regarding which part of the waveform I am sampling.

Sample of serial data output:

454,571,507,1023
437,593,506,1023
431,601,505,1023
427,608,504,1023
423,616,504,1023
420,621,503,1023
417,626,503,1023
415,632,503,1023
415,633,502,1023
549,469,514,1023
448,599,504,1023
505,538,508,1023
473,553,508,1023
316,715,499,1023
721,287,525,1023
331,646,507,1023
280,760,496,1023
292,767,495,1023
274,756,497,1023
556,451,516,1023
536,532,509,1023
472,496,513,1023
468,562,506,1023
790,214,529,1023
550,462,515,1023
314,592,510,1023
238,797,498,1023
234,775,502,1023
423,513,513,1023
555,449,517,1023
817,184,531,1023
499,544,508,1023
198,846,491,1023
554,451,517,1023
741,428,510,1023
194,840,492,1023
820,170,532,1023
375,777,490,1023
574,375,526,1023
458,606,499,1023
564,411,523,1023
442,662,494,1023
647,232,532,1023
220,860,490,1023
839,161,532,1023
194,841,492,1023
772,406,511,1023
533,472,514,1023
454,622,497,1023
830,159,533,1023
190,847,492,1023
585,522,508,1023
559,429,521,1023
184,869,490,1023
820,207,521,1023
540,468,514,1023
298,856,489,1023
831,175,530,1023
529,473,513,1023
312,843,489,1023
829,174,528,1023
539,468,514,1023
230,866,489,1023
823,231,518,1023
553,452,517,1023
175,871,490,1023
599,519,508,1023
638,236,533,1023
191,852,496,1023
469,565,505,1023
847,152,533,1023
486,484,513,1023
252,867,489,1023
799,369,512,1023
582,340,529,1023
188,854,493,1023
467,568,504,1023
847,159,532,1023
535,470,514,1023
177,874,489,1023
557,531,508,1023
791,155,534,1023
288,573,512,1023
368,797,489,1023
827,256,516,1023
573,369,527,1023
187,854,493,1023
467,571,504,1023
846,163,532,1023
544,464,514,1023
172,872,490,1023
485,551,507,1023
848,149,533,1023
518,476,513,1023
176,876,489,1023
522,540,508,1023
841,148,533,1023
499,482,513,1023
179,876,489,1023
513,541,508,1023
844,148,533,1023
518,477,513,1023
174,876,489,1023
498,547,508,1023
848,148,533,1023
528,474,514,1023
172,876,489,1023
491,548,508,1023
847,149,533,1023
528,474,513,1023
175,875,490,1023
506,543,508,1023
841,149,533,1023
484,486,513,1023
192,873,489,1023
629,502,509,1023
735,162,533,1023
231,645,510,1023
419,722,491,1023
827,179,524,1023
556,440,520,1023
175,860,490,1023
481,553,507,1023
841,151,533,1023
405,513,513,1023
343,817,489,1023
826,216,520,1023
557,440,519,1023
174,864,491,1023
489,549,507,1023
838,152,534,1023
323,552,512,1023
380,788,490,1023
827,204,521,1023
558,432,520,1023
176,858,490,1023
477,556,507,1023
846,150,533,1023
484,485,513,1023
228,869,489,1023
743,441,510,1023
633,246,533,1023
193,815,502,1023
459,605,499,1023
840,171,531,1023
549,460,515,1023
173,870,490,1023
497,547,508,1023
825,151,533,1023
236,642,510,1023
471,561,506,1023
567,396,524,1023
466,570,504,1023
349,548,512,1023
734,189,532,1023
800,213,529,1023
786,220,527,1023
794,206,530,1023
536,473,513,1023
414,694,495,1023
650,277,528,1023
469,561,507,1023
278,754,497,1023
533,475,513,1023
546,468,513,1023
439,523,512,1023
288,748,497,1023
663,406,513,1023
497,492,513,1023
679,363,517,1023
337,717,499,1023
352,660,504,1023
491,499,512,1023
390,601,508,1023
346,686,500,1023
522,525,509,1023
549,468,513,1023
474,551,508,1023
404,624,504,1023
551,465,514,1023
634,379,519,1023
626,386,519,1023
548,469,514,1023
438,587,507,1023
474,550,509,1023
546,473,513,1023
494,535,509,1023
443,582,506,1023
491,528,511,1023
535,482,513,1023

Thanks, Scott.

No, interrupts are disabled while the interrupt routine is running. Yours will run for wire a long time with all those Serial.print() at 9600 baud.

const byte interruptPin = 2;
const int inPin0 = A0;
const int inPin1 = A1;
const int inPin2 = A2;
volatile int inVal0 = 0;
volatile int inVal1 = 0;
volatile int inVal2 = 0;
volatile bool captured = false;
volatile int missed = 0;

void setup() {

  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), readIn, FALLING);
  Serial.begin(9600);
}

void loop() {         // loop seems to be required for UNO to run
  if (captured) {
    Serial.print(inVal0);
    Serial.print(",");
    Serial.print(inVal1);
    Serial.print(",");
    Serial.print(inVal2);
    Serial.print(",");
    Serial.print("1023"); // only used to stop the serial plotter auto-scaling
    Serial.print(",");
    Serial.print(missed);
    Serial.print("\n");
    captured = false;
  }
}

void readIn() {
  if (captured) {
    missed++;
  }
  else {
    inVal0 = analogRead(inPin0);
    inVal1 = analogRead(inPin1);
    inVal2 = analogRead(inPin2);
    captured = true;
  }
}

Thank you PaulRB, I will try that code now! I can see your thinking there.

For Railroader, here is my opto circuit for the trigger:

Yes of course. Missed that the location of the serial.print was inside the ISR.

Flippin' incredible!!!

Thanks for the help! That modified code has fixed it nicely:

This shows me varying the three AC waveforms, this is exactly what I was trying to achieve!!! Many thanks indeed!!!!

:smiley:

Thank you both, Scott.

That is not 100% safe! Suppose an interrupt occurs when loop has been reading 1 of the 2 bytes in the integer.
I suggest that, in loop, interrupts are disabled, copies of the 3 integers are taken and interrupts are enabled. Then the Serial.print of the copies can be done safely.

2 Likes

This will protect the access of volatile variables and let you know if you are missing any data points while the data is being processed.

const byte interruptPin = 2;
const int inPin0 = A0;
const int inPin1 = A1;
const int inPin2 = A2;

volatile int inVal0 = 0;
volatile int inVal1 = 0;
volatile int inVal2 = 0;
volatile boolean DataReady = false;
volatile unsigned int MissedInterrupts = 0;

void setup()
{
  Serial.begin(115200);
  delay(200);

  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), readIn, FALLING);

}

void loop()           // loop seems to be required for UNO to run
{
  int iv0, iv1, iv2;

  noInterrupts();
  bool ready = DataReady;
  iv0 = inVal0;
  iv1 = inVal1;
  iv2 = inVal2;
  unsigned int missed = MissedInterrupts;
  interrupts();

  if (ready)
  {
    Serial.print(iv0);
    Serial.print(",");
    Serial.print(iv1);
    Serial.print(",");
    Serial.print(iv2);
    Serial.print(",");
    Serial.print("1023"); // only used to stop the serial plotter auto-scaling
    Serial.print(",");
    Serial.print(missed);
    Serial.println();
  }

  noInterrupts();
  DataReady = false;
  interrupts();
}

void readIn()
{
  if (!DataReady)
  {
    inVal0 = analogRead(inPin0);
    inVal1 = analogRead(inPin1);
    inVal2 = analogRead(inPin2);
    DataReady = true;
    MissedInterrupts = 0;
  }
  else
    MissedInterrupts++;
}
1 Like

Note reply #11. I think You will get unexpected glitches now and then if You go on.

Thank you @Railroader , much appreciated, I see @johnwasser has just posted a solution to the potential issue, will try that out.

Hi @johnwasser, really appreciate the help, but for some reason that code produced this output:

L⸮⸮⸮⸮⸮e⸮5⸮⸮⸮e⸮n⸮h⸮ee⸮Y⸮h⸮j⸮⸮C⸮A⸮k⸮h⸮I⸮⸮⸮h⸮⸮⸮hI⸮⸮I⸮⸮e>⸮j⸮A⸮⸮⸮{⸮I⸮^⸮e⸮A⸮G⸮⸮⸮
⸮J⸮L⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮ѻ⸮⸮Q;x[⸮⸮⸮⸮⸮f⸮⸮⸮⸮⸮⸮⸮⸮I⸮⸮՟⸮{⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮M⸮i⸮I⸮⸮Z⸮⸮⸮y⸮i⸮⸮⸮⸮⸮{⸮⸮⸮I⸮⸮⸮⸮⸮o⸮⸮⸮{⸮
⸮{⸮[⸮⸮⸮I⸮⸮⸮⸮⸮[⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮{⸮f⸮⸮⸮Ȩ⸮I⸮}⸮⸮⸮OI⸮A⸮I⸮H⸮[⸮A⸮⸮⸮'⸮⸮⸮⸮⸮⸮⸮⸮n⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮G⸮KI⸮⸮⸮f⸮⸮⸮⸮⸮⸮⸮f⸮g⸮I⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮f⸮I⸮K⸮⸮⸮⸮⸮⸮I⸮⸮A⸮A⸮⸮I⸮g⸮⸮⸮⸮⸮I⸮	⸮⸮⸮n⸮i⸮H⸮i⸮⸮⸮⸮h⸮YI⸮	⸮H⸮M⸮⸮h⸮⸮{⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮
⸮I⸮⸮⸮⸮⸮KI⸮⸮⸮⸮⸮⸮⸮⸮j⸮i⸮⸮⸮⸮⸮I⸮e⸮k⸮⸮⸮⸮⸮k⸮]⸮⸮n⸮⸮⸮⸮⸮ɨ⸮⸮H⸮A⸮⸮⸮⸮⸮I⸮[[⸮⸮I⸮ƨ⸮⸮⸮⸮ɉ?⸮⸮⸮⸮H⸮Ȩ⸮⸮⸮⸮⸮⸮⸮⸮⸮E⸮⸮⸮M⸮n⸮n⸮o⸮n⸮k⸮⸮⸮k⸮j⸮i⸮i⸮H⸮⸮⸮⸮K⸮⸮⸮⸮{⸮k⸮y⸮⸮⸮⸮I⸮I⸮⸮⸮⸮k⸮⸮
⸮⸮⸮
⸮
⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮Ȉ⸮⸮j⸮⸮⸮⸮⸮⸮H⸮H⸮I⸮⸮⸮⸮⸮A⸮⸮⸮⸮⸮k⸮K⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮O⸮_⸮⸮i⸮i⸮
⸮⸮⸮⸮-⸮k⸮
⸮ɨ⸮⸮⸮⸮⸮⸮⸮⸮
⸮⸮⸮⸮G⸮⸮⸮⸮⸮⸮⸮⸮⸮e⸮=⸮Y⸮K⸮{⸮I⸮⸮⸮[⸮H⸮[⸮⸮⸮E⸮⸮YM⸮I⸮}e⸮⸮Q⸮jH⸮⸮o⸮⸮⸮{⸮}⸮⸮⸮⸮⸮y⸮⸮⸮⸮ڍ⸮⸮⸮⸮⸮⸮I⸮⸮⸮g⸮{⸮I⸮⸮⸮⸮⸮H⸮⸮
⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮Ũ⸮*⸮⸮h<)⸮ʨ⸮⸮o
⸮J⸮⸮f⸮⸮k⸮H⸮⸮⸮
⸮e⸮⸮[⸮\⸮X⸮[⸮⸮[⸮H⸮⸮I⸮Yh⸮I⸮⸮⸮⸮h⸮K⸮{⸮$⸮Q
⸮X⸮
1 Like

I would make it global. There's no need to create and destroy it (the variables) each cycle.
Same goes with unsigned int missed; and iv0; iv1; iv2;.

There is also little point in re-disable interrupt after serial print.

All that can be consolidated into one block.

int iv0;
int iv1;
int iv2;
bool ready;
void loop
(void)
{
  noInterrupts();
  //copy volatile data
  ready = DataReady;
  DataReady = false;
  iv0 = inVal0;
  iv1 = inVal1;
  iv2 = inVal2;
  missed = MissedInterrupts;
  interrupts();
  if (ready) {
    //serial print data
    ready = false;
  }
}

An additional check can be performed (in the no-interrupt block) to check the state of DataReady and skip unnecessary coping.

nointerrupt();
if (DataReady) {
//copy data
DataReady = false;
}
interrupts();

That is because your serial monitor is not set at the same baud rate as your software. See the drop down menu at the lower right hand corner of the serial monitor window.

That 100R resistor value in the LED of the opto isolator is a bit too small. Use 220R.

1 Like

Did you set your Serial Monitor to 115200 baud?

  1. No need to make them global when 'static' will do the job.
  2. The compiler will probably use registers for short-term locals.
  3. "Creating" a local is just adding a value to the stack pointer. It doesn't take significant time.

If You want to top the performance regarding the very first sample, make one analogue read in setup! That "warms up" the ADC. The very first sample takes 0.200 mS. Then They sample as You wrote, 0.100 mS.

1 Like

You mean 0.104 mS. :slight_smile:

16 MHz / 128 prescale / 13 cycles per read = 9615.38 Hz

1 Like