Analog Comparator Interrupts

Hi

Noob here

I'm trying to create a project that responds to a transition at the analog comparator. This is a trivial sample of the problem.

// Declare a volatile boolean variable to indicate when the interrupt has occurred
volatile boolean comparatorTriggered = false;

// Interrupt Service Routine (ISR) for the Analog Comparator
ISR(ANALOG_COMP_vect) {
  comparatorTriggered = true; // Set the flag when the interrupt fires
}

void setup() {
  Serial.begin(115200); // Initialize serial communication

  // Disable digital input buffer on comparator pins (optional, but good practice for noise reduction)
  // AIN0 (PD6) is the positive input, AIN1 (PD7) is the negative input
  DIDR1 |= (1 << AIN1D) | (1 << AIN0D); 

  // Configure Analog Comparator Control and Status Register (ACSR)
  // ACSR = 0b00000000; // Reset ACSR for clarity

  // Enable Analog Comparator Interrupt (ACIE)
  ACSR |= (1 << ACIE); 

  // Select interrupt mode (e.g., rising edge, falling edge, or toggle)
  // ACIS1 and ACIS0 bits control this:
  // 00 = Interrupt on Toggle (any change)
  // 01 = Interrupt on Falling Edge
  // 10 = Interrupt on Rising Edge
  // 11 = Reserved
  // For this example, we'll use rising edge (10)
  ACSR |= (1 << ACIS1); // Set ACIS1
  ACSR &= ~(1 << ACIS0); // Clear ACIS0

  // Set the negative input of the comparator to the internal 1.1V reference
  // ACME (Analog Comparator Multiplexer Enable) in ADCSRB must be set for this
  ADCSRB |= (1 << ACME); 
  // ADMUX register selects the ADC channel to be connected to the negative input of the comparator
  // For internal 1.1V reference, we set MUX bits to 1110 (0x0E)
  ADMUX = 0x0E; 

  // Ensure Global Interrupts are enabled
  sei(); 

  Serial.println("Analog Comparator Interrupt Example");
  Serial.println("AIN0 (PD6) is positive input, compared against internal 1.1V reference.");
  Serial.println("Interrupt will trigger on rising edge when AIN0 > 1.1V.");
}

void loop() {
  if (comparatorTriggered) {
    Serial.println("Comparator Interrupt Triggered!");
    comparatorTriggered = false; // Reset the flag
    // You can add more actions here, like turning on an LED
  }
  // Your main program logic can run here without constantly polling the comparator
  delay(100); // Small delay to avoid excessive serial output
}

The problem is that each line that tries to set the registers causes and error and the ISR declaration causes another error.

Am I missing a library or something?

Thanks

Which board; looks like the code is for an AVR processor.

I suggest that you post the full output of a compile, again using code tags so we know what kind of errors you get.

Can you post your annotated schematic, be sure it shows power sources. Also define the transient, What processor are you using?

Hi

Sorry - it’s an R4 WiFi.

I’m out of the office right now so I don’t have access to the schematics.

The problem is that the code will not compile. It throws errors with the bit twiddlers and the ISR declaration.

Cheers

Mike

The posted code was written for an AVR processor, likely an ATmega, and won't compile or work with any other type of processor. It will certainly not work on the R4 WiFi.

Where did you find the code?

Could you please explain what you are trying to accomplish?
In most cases you do not need any registers and ports...
Unless you really need some fast actions on a slow board (like UNO R3)...
Using digitalWrite() will make your code much easier and more portable...

I got the ISR and bit twiddling from snippets on the web purporting to be for the R4 WiFi.

I am monitoring the current draw on a bilge pump using an ACS712 and I want to trigger an interrupt when the pump turns on and then off by comparing the voltage from the ACS712 and a reference that I will create using a voltage divider.

Any assistance and/or suggestions would be greatly appreciated.

Many thanks.

What does the interrupt do?
Why not use polling?
How fo you turn off the pump?
With a relay, a mosfet? A SSR maybe?
What type of pump is it? (Ac/dc, 3phase?,voltage, wattage).
Do you want to implement a run dry protection?

analogRead() should work fine in your application.

Well, they're not. R4 WiFi code would look entirely different.
(The code looks approximately OK for an Uno R3.)

The pump is independently powered and has its own auto switch based on the water level. I'm just measuring the current draw to tell when it turns on and off.

Would it be possible for you to share what it should look like? Or is that against forum etiquette? I’m not sure - I’m a noobie

Cheers

Mike

I’d start with a much simplier design. Just use AnalogRead() to read the current and your reference voltage. If it is higher, the pump is on, if it is lower, the pump is off. Do this repeatedly inside loop().

Is there a reason something this simple would not work?

They might have been for the R3 WiFi. Certainly not for the R4 WiFi, which has a completely different processor. Unfortunately, Arduino decided to sow great confusion by naming their new board in such a way as to imply that it had some relation to the Uno R3.

As others have said, there is no obvious need for register access or interrupts to do what you want to do.

I don't know what it should look like, but the registers associated with the "low power analog comparator" have entirely different names, like:

            /* Clear the Operation enable and output enable for channel 0. */
            R_ACMPLP->COMPMDR_b.C0ENB = 0U;
            R_ACMPLP->COMPOCR_b.C0OE  = 0U;

This is from the renesas library for dealing with the comparator: fsp/ra/fsp/src/r_acmplp/r_acmplp.c at master · renesas/fsp · GitHub
This isn't very Arduino-like, but it might give you an idea what it looks like.

Okay! I appreciate your response but I think I’ll take others’ advice and use analogRead in the Loop. It actually makes my flow a little easier.

Thanks again.

Cheers

Mike

The code compiles for an Arduino Uno R3.
However it does not work as there were several errors, for example:

// Select interrupt mode (e.g., rising edge, falling edge, or toggle)
// ACIS1 and ACIS0 bits control this:
// 00 = Interrupt on Toggle (any change)
// 01 = Interrupt on Falling Edge
// 10 = Interrupt on Rising Edge
// 11 = Reserved
// For this example, we'll use rising edge (10)

does not agree with table 23-2 of the ATmega328 datasheet:

It looks to me as though it is probably AI generated, certainly not tested.

With a few changes I managed to get it to work.

// Declare a volatile boolean variable to indicate when the interrupt has occurred
volatile boolean comparatorTriggered = false;

byte monitorPin = 12;

// Interrupt Service Routine (ISR) for the Analog Comparator
ISR(ANALOG_COMP_vect) {
  comparatorTriggered = true; // Set the flag when the interrupt fires
}

void setup() {
  Serial.begin(9600); // Initialize serial communication
  pinMode(monitorPin, OUTPUT);
  Serial.println("\n");

  // Disable digital input buffer on comparator pins (optional, but good practice for noise reduction)
  // AIN0 (PD6) is the positive input, AIN1 (PD0) is the negative input
  DIDR1 |= (1 << AIN1D) | (1 << AIN0D);
  Serial.print("DIDR1  = ");
  Serial.print(DIDR1);
  Serial.print("\t");
  Serial.println(DIDR1, BIN);

  // Configure Analog Comparator Control and Status Register (ACSR)
  // ACSR = 0b00000000; // Reset ACSR for clarity

  // Enable Analog Comparator Bandgap Select (ACBG) - Added by JohnLincoln
  ACSR |= (1 << ACBG);

  // Enable Analog Comparator Interrupt (ACIE)
  ACSR |= (1 << ACIE);

  // Select interrupt mode (e.g., rising edge, falling edge, or toggle)
  // ACIS1 and ACIS0 bits control this:
  // 00 = Interrupt on Toggle (any change)
  // 01 = Interrupt on Falling Edge
  // 10 = Interrupt on Rising Edge
  // 11 = Reserved
  // For this example, we'll use rising edge (10)

  ACSR |= (1 << ACIS1); // Set ACIS1
  ACSR |= (1 << ACIS0); // Set ACIS0  // JohnLincoln
  // ACSR &= ~(1 << ACIS0); // Clear ACIS0


  Serial.print("ACSR   = ");
  Serial.print(ACSR);
  Serial.print("\t");
  Serial.println(ACSR, BIN);

  ADCSRA = 0b00000111;  // Added JohnLincoln
  Serial.print("ADCSRA = ");
  Serial.print(ADCSRA);
  Serial.print("\t");
  Serial.println(ADCSRA, BIN);


  // Set the negative input of the comparator to the internal 1.1V reference
  // ACME (Analog Comparator Multiplexer Enable) in ADCSRB must be set for this
  ADCSRB |= (1 << ACME);
  Serial.print("ADCSRB = ");
  Serial.print(ADCSRB);
  Serial.print("\t");
  Serial.println(ADCSRB, BIN);

  // ADMUX register selects the ADC channel to be connected to the negative input of the comparator
  // For internal 1.1V reference, we set MUX bits to 1110 (0x0E) - This is wrong, JohnLincoln
  // For internal 1.1V reference, set REFS1 and REFS0 - added JohnLincoln
  // using
  ADMUX = 0b11000000;
  Serial.print("ADMUX  = ");
  Serial.print(ADMUX);
  Serial.print("\t");
  Serial.println(ADMUX, BIN);
  Serial.println("");
  // Ensure Global Interrupts are enabled
  sei();

  Serial.println("Analog Comparator Interrupt Example");
  Serial.println("AIN0 (PD0) is positive input, compared against internal 1.1V reference.");
  Serial.println("Interrupt will trigger on rising edge when AIN0 > 1.1V.");
}

void loop() {
  if (comparatorTriggered) {
    Serial.println("Comparator Interrupt Triggered!");
    comparatorTriggered = false; // Reset the flag
    // You can add more actions here, like turning on an LED
    digitalWrite(monitorPin, HIGH);
    delay(2000);
    digitalWrite(monitorPin, LOW);
  }
  // Your main program logic can run here without constantly polling the comparator
  //delay(100); // Small delay to avoid excessive serial output - makes responding to interrupt sluggish - JohnLincoln
}

Here are the results shown on an oscilloscope:

  • Channel 1 - yellow trace - Ramp voltage from Function Generator, connected to A0.
  • Channel 2 - red trace - Pin 12 output, goes high when interrupt detected.
  • Channel 3 - blue trace - Serial output, changed to 9600Bd to make it visible.

Don't over-think it.
If your loop is running fas (as it should).
Then just detect with analogRead.
Leo..

int threshold = 200; // 0-512
if (abs(analogRead(A0) - 512) > threshold) Serial.println("pump ON");
else Serial.println("pump OFF");

Many thanks. I’m working on that approach now.

Cheers

Mike

Full code would look like this.
A flag is added to only print once when pump state changes.
the abs in the code is there so that polarity of the ADS712 doesn't matter.
Threshold value depends on pump current.
Leo..

int threshold = 200; // 0-512
bool flag;

void setup() {
  Serial.begin(115200);
}

void loop() {
  if (abs(analogRead(A0) - 512) > threshold) {
    if (flag == false) {
      Serial.println("pump ON");
      flag = true;
    } else {
      Serial.println("pump OFF");
      flag = false;
    }
  }
}