[HELP] Analog Comparator not interrupting the sleep mode vs. other methods

Hello! I want to put my Arduino Uno at sleep until a certain voltage is applied to AIN1. This being said, I connected (for test purpose) D7 to 3V3 and D6 to GND. If I am right, the moment I place D6 to 5V, it should wake up.

I read a lot of the old topics about this but it seems that it won’t wake up no matter what I try. I also installed and tried with analogComp library, but I’ll stick with this code for now.

Can anyone help me with some suggestions or troubleshooting? Any input is highly appreciated.

#include <avr/sleep.h>
#include <avr/power.h>

volatile boolean triggered;

ISR (ANALOG_COMP_vect) {
  triggered = true;
}

void setup ()
{
  Serial.begin (115200);
  Serial.println ("Started.");
  ACSR =
    (0 << ACD)   | //Comparator, Enabled
    (0 << ACBG)  | //AIN0 is applied to the positive input
    (0 << ACO)   | // Analog Comparator Output: Off
    (1 << ACI)   | //Clear Pending Interrupt
    (1 << ACIE)  | //Analog Comparator Interrupt, Enabled
    (0 << ACIC)  | //Analog Comparator Input Capture, Disabled
    (1 << ACIS1) | (1 < ACIS0); //Capture on Rising Edge
}

void loop () {

  gotoSleep();

  if (triggered) {
    sleep_disable();
    Serial.println ("Woke up!");
    triggered = false;
  }
}

void gotoSleep() {
  Serial.println("Sleeping");    
  delay(70);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  noInterrupts();
  sleep_enable();
  interrupts();
  sleep_cpu();
}

TL;DR version :slight_smile:

I would question if SLEEP_MODE_PWR_DOWN actually disable the Analog Comparator and assume this might be your problem.

if you have a bit more time:

How would I get to this if I were to analyze your question? well let me think around on how I would think about this to see if that makes sense.

I would first try to see if you set the register correctly.

You want to use the Analog Comparator. typical usage is as follow: sometimes we just need to know wether an analog signal is lower or higher than another signal without the need for comparing two Analog inputs constantly in our code. Only the difference matters and the arduino has two pins we can use to easily detect this: AIN0 and AIN1.

you do

    (0 << ACBG)  | //AIN0 is applied to the positive input

ACBG is the Analog Comparator Bandage Selection bit. When this bit is set, a fixed bandgap reference voltage (VBG) replaces the positive input to the Analog Comparator. When this bit is cleared, AIN0 is applied to the positive input of the Analog Comparator.

In our Arduino AIN0 and AIN1 associated with Digital pin D6 and D7 respectively on a UNO and to PIN 4 and 5 on an Arduino Mega 2560

You said you have a UNO, so indeed pin 6 and 7 are good.

The analog comparator compares the input values on the positive pin AIN0 (D6) and negative pin AIN1 (D7). When the voltage on the positive pin D6 is higher than the voltage on the negative pin D7, the Analog Comparator Output bit (ACO) is set to HIGH.

So you are correct, if your initial situation is

D7 to 3.3V and D6 to 0V then Analog Comparator Output bit (ACO) is set to LOW
D7 to 3.3V and D6 to 5V then Analog Comparator Output bit (ACO) is set to HIGH

AC0 is bit 5 of the ACSR register and is read only. While you try to force this to 0 in your set up function, I think it’s no big deal but best when dealing with register is to use ACSR |= to set bits to 1 and ACSR &= to force some bits to 0

Unless the ACD bit in the ACSR register is set to HIGH the arduino will constantly compare the two values.

As you force that bit to low, this is OK, you are indeed in comparison mode and when the output on the D6 pin is higher then the voltage on the D7 pin, the ACO bit in ACSR is set to HIGH. In this case the interrupt flag ACI will also be set. If the ACIE bit in ACSR and the I bit in the status register SREG is set an interrupt will occur.

So we need the ACIE bit in ACSR to be high, which you do

    (1 << ACIE)  | //Analog Comparator Interrupt, Enabled

but you don’t see to set the I bit in the status register SREG at that stage.

but if I remember correctly in Arduino.h they define

#define interrupts() sei()
#define noInterrupts() cli()

and as you call interrupts(); in your gotoSleep() function before calling sleep_cpu(); you are indeed setting the I bit in SREG

so far so good.

You set

    (0 << ACD)   | //Comparator, Enabled

When changing bit ACD the analog comparator interrupt must be disabled by clearing the ACIE in ACSR otherwise an interrupt can occur when this bit is changed. In your setup you clear ACD (so enable the comparator) but you set ACIE to High. That’s against the spec if you are unlucky and the ACD bit was high before… but by default it’s low and you keep it there so probably not an issue.

You set

    (0 << ACIC)  | //Analog Comparator Input Capture, Disabled

so you disable the Analog Comparator Input Capture. When written to LOW, no connection between the Analog Comparator and the input capture function exists and when written HIGH, the input capture function in Timer/Counter1 can be triggered by the Analog Comparator. The comparator output is in this case directly connected to the input capture front-end logic, making the comparator utilize the noise canceler and edge select features of the Timer/Counter1 Input Capture interrupt and to make the comparator trigger the Timer/Counter1 Input Capture interrupt, the ICIE1 bit in the Timer Interrupt Mask Register (TIMSK1) must be set.

→ You don’t use this nor do you set the ICIE1 bit in TIMSK1, so this is coherent and no Timer/Counter1 Input Capture interrupt is enabled

In IDLE mode, we stop the CPU but allow the SPI, USART, Analog Comparator, ADC, 2-wire Serial Interface, Timer/Counters, Watchdog, and the interrupt system to continue operating. so in that sleep mode, your IRQ should trigger.

But your sleep mode is SLEEP_MODE_PWR_DOWN though.

In this mode, the external Oscillator is stopped, while the external interrupts, the 2-wire Serial Interface address watch, and the Watchdog continue operating (if enabled). Only an External Reset, a Watchdog System Reset, a Watchdog Interrupt, a Brown-out Reset, a 2-wire Serial Interface address match, an external level interrupt on INT0 or INT1, or a pin change interrupt can wake up the MCU. This sleep mode basically halts all generated clocks, allowing operation of asynchronous modules only.

Question: by going to SLEEP_MODE_PWR_DOWN, are you actually powering down the Analog Comparator by setting the ACD bit in the Analog Comparator Control and Status Register – ACSR to reduce power consumption?? This would create indeed the situation you see.

→ and that’s where reading the Atmel doc at least once in your life (and having a good memory) helps

if you go to the section 10.10 Minimizing Power Consumption

paragraph 10.10.2 Analog Comparator

When entering Idle mode, the Analog Comparator should be disabled if not used.
When entering ADC Noise Reduction mode, the Analog Comparator should be disabled.
In other sleep modes, the Analog Comparator is automatically disabled.
However, if the Analog Comparator is set up to use the Internal Voltage Reference as input, the Analog Comparator should be disabled in all sleep modes. Otherwise, the Internal Voltage Reference will be enabled, independent of sleep mode. Refer to ”Analog Comparator” on page 234 for details on how to configure the Analog Comparator.

So I would think this is probably your pb. One way to check is to use the POWER_MODE_IDLE and see if your code work.

give it a try…

First of all, thank you very much for the detailed explanation, highly appreciated as I am a beginner with sleep and interrupts.

I wanted to make an edit (but you were faster) and say that I guess it was not working because of the POWER_DWN mode, and that I tried with IDLE, case in which it indeed wakes up, but is also printing a lot of "Sleeping"s. I guess (only a guess) that is because Serial communication is considered an interrupt too?

I changed the code a bit, so instead of Serial.prints I now turn on the internal LED. When using with IDLE, it is working right, but when I try with ADC (or any other mode that automatically disables the comparator) it doesn’t. So I tried to re-enable “manually” the comparator [line 43 in code] but without success (I guess I am doing it wrong). Is there any other way I can turn it on?

Thank you very much!

#include <avr/sleep.h>
#include <avr/power.h>

volatile boolean triggered;

ISR (ANALOG_COMP_vect) {
  triggered = true;
}

void setup ()
{

  pinMode(13, OUTPUT);

  ACSR =
    (0 << ACD)   | //Comparator, Enabled
    (0 << ACBG)  | //AIN0 is applied to the positive input
    (0 << ACO)   | // Analog Comparator Output: Off
    (1 << ACI)   | //Clear Pending Interrupt
    (1 << ACIE)  | //Analog Comparator Interrupt, Enabled
    (0 << ACIC)  | //Analog Comparator Input Capture, Disabled
    (0 << ACIS1) | (0 << ACIS0); //Capture on Toggle
}

void loop () {
  digitalWrite(13, LOW);
  gotoSleep();

  if (triggered) {
    sleep_disable();
    digitalWrite(13, HIGH);
    delay(500);
    triggered = false;

  }
}

void gotoSleep() {
  set_sleep_mode(SLEEP_MODE_ADC);
  noInterrupts();
  sleep_enable();
  interrupts();
  ACSR = B00011000;
  sleep_cpu();
}

EDIT: One more funny thing… If I’m not keeping my left hand on the power source of Arduino while moving the jumper cable, it works the other way around: turns on LED only while jumper cable is in air… What am I missing?

beige22:

I tried with IDLE, case in which it indeed wakes up, but is also printing a lot of "Sleeping"s.

or possibly to what I wrote above


When changing bit ACD the analog comparator interrupt must be disabled by clearing the ACIE in ACSR otherwise an interrupt can occur when this bit is changed. In your setup you clear ACD (so enable the comparator) but you set ACIE to High. That’s against the spec if you are unlucky and the ACD bit was high before… but by default it’s low and you keep it there so probably not an issue.

Oh, so I should do something like this?

ACSR = B00010000; // ACIE bit on 0 and ACD on 0
ACSR = B00011000; // ACIE bit on 1 and ACD on 0

Or should I set only the ACIE bit? I can't remember the instruction right now, but I guess it was something like ACSR = bit(3)?

Update: I tried the method above, still not working. Therefore, I was thinking to approach the problem this way, using the interrupt pins instead of the analog comparator:

  • I want to wake up the Arduino when a sensor is outputting a voltage a above 1.026V (on a range of 0.9 to 2.8V)
  • the logical one on the digital pins would be (for 3v3) at 0.6 * 3.3 = 1.98V
  • 1.98 - 1.026 = 0.954V
  • I make a voltage divider that gives me aprox 0.955V using 540 and 220 resistors
  • The sum of the output voltage of the sensor and the voltage I made using the divider would serve as digital HIGH on input digital pins

Possible problems I see:

  • Let’s say I’ll be using a 3.7V Lipo battery. If the voltage drops, the voltage level of the digital HIGH would drop also, and I might end up waking up even when it is not needed.

Any other cons for this method or any suggestions for implementing this?
Thank you!

P.S. I know that if I am using a 3.7, the HIGH starts at 2.22 not at 1.98 volts.