Analog Comparator Timing Issues

I am in the process of writing a library for the Arduino that will allow dual channel software FSK demodulation. I have made it work using one channel using the dedicated AIN0 and AIN1 pins but to make the dual channel work I changed over to using the ADC mux as the negative input to the comparator.

I wrote some basic code and hooked up a 1kHz sine wave into my analog 0 input pin and setup a pot on the AIN0 pin (comparator positive input). I hooked up my scope and saw what I expected for the most part but there is one oddity. Note that I attached scope images below for reference.

Basically what is happening is the D13 pin (which I am using solely for monitoring/debugging purposes) does go high for a small period of time as I expected but the rising edge of D13 does not line up with where the sine wave intersects the positive comparator input (AIN0) which is coming from the pot. As I turn the pot up and down (between 0V and 5V) the threshold changes on the scope and the D12 pulse moves slightly in one direction or the other but it doesn’t directly line up with the rising edge of the pulse

Any thoughts?

void setup() {
  // put your setup code here, to run once:
  pinMode(13, OUTPUT);

  //setup analog comparator w/ mux
  PRR &= ~(1 << PRADC); //disable the power reduction ADC bit so that the input MUX can be used (page 234)
  ADMUX &= ~((1 << MUX0) | (1 << MUX1) | (1 << MUX2)); //set negative input of comparator to ADC0
  DIDR1 |= (1 << AIN0D); //disable digital input buffer for AIN0 pin
  DIDR0 |= (1 << ADC0D) | (1 << ADC1D); //disable digital input buffer for ADC0 and ADC1
  ADCSRB |= (1 << ACME); //enable analog comparator mux
  ADCSRA &= ~(1 << ADEN); //disable ADC
  ACSR |= (1 << ACIE) | (1 << ACIS1); //enable comparator interrupt and set for falling edge (really rising edge though since the signal is on the negative input)
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(13, LOW);
}

ISR(ANALOG_COMP_vect)
{
  digitalWrite(13, HIGH);
}

digitalWrite takes a bit of time to run. When you’re blinking leds it’s too short to notice. On a scope looking at things in microseconds it becomes apparent. Try using direct port manipulation to turn D13 on and it might line up better.

ISR(ANALOG_COMP_vect)
{
  PORTB |= (1 << 5);
}

Or if this is the only thing happening on PORTB even faster is

ISR(ANALOG_COMP_vect)
{
  PORTB = 0b00100000;
}

You don’t mention the amount of time between the crossing and the pulse. That would be helpful information; please tell us what it is.

The analog comparator has a propagation delay of as much as 500 nanoseconds. You can see that in the datasheet, in the table “Common DC characteristics,” Table 30-1, page 314 in the 10/2014 datasheet. I think that we can expect that value to be worse for slowly-varying signals, with a small overdrive, than for faster-varying signals, with a larger overdrive. But, there are much bigger delays in the code.

There’s a delay between the time the comparator changes state and the time that the ISR executes. The interrupt takes a couple of cycles to synchronize. The processor will complete the current instruction - one or two cycles for the Uno - and then respond. Pushing the program counter takes 4 cycles. The ISR then pushes any registers that it uses. When I compile this code for the Uno, I see that it pushes 15 times before getting ready to call digitalWrite, for another 30 cycles at two cycles per push. It also does a couple of single-cycle instructions, for a total delay of 32 cycles.

Then it does a digitalWrite(), which Delta_G addresses, above. That takes a surprisingly long time. digitalWrite() conveniently abstracts the hardware, but it adds a lot of execution time.

You can improve that by directly writing to the PORT, or by writing to the PIN register. Delta_G describes PORT manipulation, so I’ll skip that. Writing directly to a PIN register inverts the state of any pins to which you write a 1. So, on the Uno, your ISR could be:

ISR(ANALOG_COMP_vect)
{
  PINB = (1 << 5);
}

Pin 13 is bit 5 on PORTB. Writing a byte with bit 5 set will set the bit, if it’s cleared. loop() clears bit 13 all the time, so you can expect it to be cleared when the interrupt happens. That will also reduce the number of registers that the ISR uses, and hence the number of pushes that it will do before executing your code. It’ll also reduce the delay between starting your code and setting the bit to a couple of cycles - much shorter.

To force the state of pin 13 to a 0, you can use:

PORTB &= ~(1 << 5);

That will be quite fast.

Those instructions directly manipulate the hardware. They’re hardware-specific: these work on the Uno, but may not work on other devices. That’s the tradeoff for the speed improvement of writing directly to PORT and PIN registers - we lose portability among the Arduino gizmos, and we have to look up the PORT designations for the target board.

All told, though, you can’t eliminate the propagation delay, the synchronization delay, the time it takes to push the program counter, or the initial work the ISR does to start up. You can reduce the ISR’s startup work by using a simpler ISR, and eliminate the bulk of the delay through digitalWrite(). Eliminating that will improve things quite a bit.

If the MPU is responding to another interrupt - say, the Timer0 interrupt, which updates millis() and micros(), then it will complete that execution, pop the program counter, and execute one instruction before beginning to respond to the comparator ISR. That will appear as seemingly randomly-occurring additional delays. If you’re not using the timing functions, you can eliminate that by turning off the Timer0 overflow interrupt.

Thanks for all the help everyone!

I tried using the code that Delta_G provided that directly sets the PORTB register pin/bit 5. As expected it still worked on my scope (in the sense that it toggles high and low). But unfortunately the high and low pulse still does not line up with where the threshold voltage intersects the sine wave input (both inputs to the comparator).

I also took a glance at the electrical characteristics section of the datasheet and saw that there were propagation delays etc.

But what’s really weird is that as I move the pot (threshold voltage) up and down the pulse does shift left and right in time (relative to a fixed point of the sine wave which is what the scope is triggering off of). So it’s like the pulse does respond but does not line up. And it’s weird also because the pulse is not happening after the rising edge of the sine wave crosses the threshold (which I might expect due to propagation delays etc.) but rather the pulse goes high before the sine wave crosses the threshold voltage almost as if it’s predicting the rising edge somehow but that makes no sense whatsoever.

I will attach more pics below for additional clarity of what’s happening.

In scope1 image it looks as if the pulse happens about 25us before the sine wave crosses the threshold.
scope2 = pulse is 15-20us early
scope3 = pulse is 10us late
scope4 = pulse is 25us late

So it appears to possibly somewht symmetric in it’s offset depending on which side of 2.5V the pot is on… It’s too consistent to be coincidence it seems like. I’m just not sure to to further debug it.

Is it early? Or just really really late from the crossing before?

I had the same thought so I changed the comparator interrupt to fire on only rising, only falling, and then both. When I changed to falling it moved to be very near the falling edge but similarly not lined up with the threshold voltage intersection with the curve.

Same thing on both, it was close on rising and falling but not exact on either one. So I'm pretty sure (but I guess not 100%, but like 99.99% sure) that it is not extremely late but that it is indeed slightly early/slightly late (depending on threshold voltage) for the appropriate edge (in this case rising edge).

It looks like the pulse is initiated when the input rises through around 2.0 to 2.2 volts. I'd recommend checking that the wiring is actually what you think it is.

Have you correctly configured the analog multiplexer and comparator? I'd recheck the code in setup() against the datasheet. Also note you cannot use analogRead() in the rest of the program as it reconfigures the multiplexer...

I looked over both my code and wiring this morning (with a fresh set of eyes) and I didn't see anything obviously wired incorrectly or any registers that are mis-configured. And I do not call analogRead() anywhere in the code, but thanks for the heads up!

I know that's not super helpful, since you all don't haven direct access to my hardware setup etc. But any other thoughts?

I tried looking up any errata for the atmega328p but there was nothing about the analog comparator firing early/late (only a weird issue where setting MUX3 could lock up the chip if set when the comparator was enabled, but I never set MUX3 so it stays at 0).

Maybe my chip comparator is damaged internally? I'm not sure what it could be at this point. Does the atmega328p have a hysteresis on the input maybe?

Tonight I am going to try adding various capacitor and inductors in appropriate locations to see if maybe it's noise issues. Other than that I'm not sure. If anyone else were able to setup a similar configuration and test how their Arduino Uno behaves, that might either affirm or disprove if the issues is present for others or just me. If not no worries I know it does take time, but it could help. Surely I can't be the first one to experience this problem.

My experience with the analog comparator says that it doesn’t have hysteresis.

Here’s what I’m assuming:

  • You’re using an Uno.
  • The ends of the potentiometer are connected to GND and VCC, and the wiper is connected to pin Arduino pin 6. That’s pin 12 on the ATMega328P, AIN0, PD6.
  • A 1kHz sinudoidal source, with a range of 0V to 5V, is connected directly to Arduino pin A0. That’s pin 23 on the IC, ADC0, PC0.
  • Arduino pin 7 has no connection. That’s pin 13 on the IC, AIN1, PD7.

I don’t have a sinusoidal source handy - just an Uno and a couple of wires. Running the code below - mostly your code, with the ISR and interrupt enable instructions deleted, and with loop() simply echoing the state of the comparator bit, ACSR bit ACO, to the LED on pin 13, with pin 6 connected to 3.3V, and A0 connected to GND or 5V, I find that the LED follows the voltage on A0 - it’s on when A0 is grounded, and off when it’s at 5V. Here’s the code:

void setup() {
  pinMode(13, OUTPUT);
  //setup analog comparator w/ mux
  PRR &= ~(1 << PRADC); //disable the power reduction ADC bit so that the input MUX can be used (page 234)
  ADMUX &= ~((1 << MUX0) | (1 << MUX1) | (1 << MUX2)); //set negative input of comparator to ADC0
  DIDR1 |= (1 << AIN0D); //disable digital input buffer for AIN0 pin
  DIDR0 |= (1 << ADC0D) | (1 << ADC1D); //disable digital input buffer for ADC0 and ADC1
  ADCSRB |= (1 << ACME); //enable analog comparator mux
  ADCSRA &= ~(1 << ADEN); //disable ADC
//  ACSR |= (1 << ACIE) | (1 << ACIS1); //enable comparator interrupt and set for falling edge (really rising edge though since the signal is on the negative input)
}

void loop() {
  // put your main code here, to run repeatedly:
  if (ACSR & (1 << ACO)) PORTB |= (1 << 5);
  else PORTB &= ~(1 << 5);
}

If I omit the line that turns on ACME in ADCSRB - which, I think, should connect AIN1, pin 7, to the comparator, I find that changes on A0 have no effect, as expected. I think I’ve verified that your code connects the ADMUX output to the inverting input of the comparator, and AIN0 to the non-inverting input.

Looking at your screenshots, I see that the pulse is triggered whenever the sinusoidal source crosses about 2.1V, rising, in every instance. The pulse triggering doesn’t appear to be affected by the potentiometer voltage at all. I don’t think that there’s a timing problem here - I think that the comparator is somehow seeing the wrong reference, maybe a steady-state 2.1V or so.

How are you offsetting the sine signal? Do you have a signal generator with an offset, or are you using an offset network?

Can you post a schematic of what you’ve actually got connected?

All of your assumptions are correct. Except one, which is the reason I believe it was not working all along. I will explain more in a second.

Thanks for testing it out, glad to hear it worked as expected!

I think that the comparator is somehow seeing the wrong reference, maybe a steady-state 2.1V or so.

I think you hit it right on the head. I had the pot center tap plugged into pin 7 not pin 6 (oops). I guess my eyes were not fresh enough this morning when I checked the wiring. I just completely looked over that part and assumed I had it in the correct pin.

I have an external signal generator with a 5Vpp sinusoidal wave and a 2.5 VDC offset so it swings between 5V and 0V, so there’s no offset network.

I can post a schematic if you wish, but I believe the problem to be the incorrect pin. I won’t be able to test it until later tonight. I actually had to get someone to send me a pic of the board because I’m not in the same location as the Arduino is at the moment and that’s how I confirmed I had the wrong pin. Once I can test and confirm it was just the wrong pin I will edit this post to reflect that information.

I did attach the image below.

Thanks again for all your help!

I will refrain from gloating until you tell us whether that fixes it. It would be poor form to gloat here, so I won’t, but I will gleefully describe my mighty exploits to my wife, at home.

If it works, you have to tell me roughly where you are. I think that you’re near the Eastern seaboard in the US, which is more than a thousand miles from me, and the gloating is more satisfying if I can preface it with, “From over a thousand miles away …”

Haha issues fixed. So it was just the wrong pin. (Face palm.) Feel free to tell whomever haha; you did help me solve it though so thanks again even if it was a simple wiring issue!

I'm in the southeast US. So yes you helped me solve a wiring problem from 1000+ miles away. Testament to the internet (and helpful people on forums)!

Me: "From over a thousand miles away, armed with only a few pictures and a complaint, the great detective reconstructs the crime from aught but the dust in the room! I am the man; I am the man, indeed!"

Wife: "Start the dishwasher and take out the trash, will you?"