The analog comparators in popular AVR controllers Atmega328P, Attyin85 etc. don't have any hysteresis which makes them quite useless (bouncing output on slow/noisy signals).
The solution I present here uses a few external resistors and a simple interrupt routine. No RAM is used and the overhead is negligible if the assembler ISR is used.
The exmple code implements a relaxation oscillator experiment that can be set up very quickly.
Arduino IDE instructions:
- create a new sketch.
- delete all the text in (sketch name).ino
- Click the tree dots (top right) and then on "New File"
- Name the file as "main.c" and paste the code listed below.
Brief explanation: The comparator interrupt is enabled on both edges (toggle). The interrupt handler sends the comparator state to pin PB0 as either a low level (comp. low) or a high impedance level (comp. high). This output is connected to the comparator "-" input (AIN0 - pin 6) via a resistive feedback network. The signal (a charging/discharging capacitor in this case) is fed to the comparator "+" input (AIN1 - pin 7).
The attached LTSpice file contains the equivalent electronic circuit using an open collector/drain comparator such as the LM311. The schematic of an Arduino setup is in the same file.
I'm using this technique to design a minimalistic PI that I will be disclosing in further posts.
Atmega328P_Comparator_Hysteresis.zip


The solution I present here uses a few external resistors and a simple interrupt routine. No RAM is used and the overhead is negligible if the assembler ISR is used.
The exmple code implements a relaxation oscillator experiment that can be set up very quickly.
Arduino IDE instructions:
- create a new sketch.
- delete all the text in (sketch name).ino
- Click the tree dots (top right) and then on "New File"
- Name the file as "main.c" and paste the code listed below.
Brief explanation: The comparator interrupt is enabled on both edges (toggle). The interrupt handler sends the comparator state to pin PB0 as either a low level (comp. low) or a high impedance level (comp. high). This output is connected to the comparator "-" input (AIN0 - pin 6) via a resistive feedback network. The signal (a charging/discharging capacitor in this case) is fed to the comparator "+" input (AIN1 - pin 7).
The attached LTSpice file contains the equivalent electronic circuit using an open collector/drain comparator such as the LM311. The schematic of an Arduino setup is in the same file.
I'm using this technique to design a minimalistic PI that I will be disclosing in further posts.
Atmega328P_Comparator_Hysteresis.zip
PHP Code:
/* ============================================================ */
/*
Atmega328P_Comparator_Hysteresis.c
(c) 2023 Oscar Gonzalez
Simulation of an LM311 type comparator (open collector) with positive
feedback for hysteresis. (See LTSpice circuit of the same name).
Comment out statement "#define _USE_ASSEMBLY_INTERRUPTS_" to use the C
version of the interrupt
*/
/* ============================================================ */
#define F_CPU 16000000L
//gcc 7.4.0
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
#define _USE_ASSEMBLY_INTERRUPTS_
void init_comp(){
// PB1 (arduino Pin 10) drives the capacitor
DDRB |= (1 << PB1);
// PB0 (arduino pin 9) outputs the comparator state.
// Open collector output is simulated as follows:
// - write port bit to 0 permanently
// - output low: conf. pin as output DDRB |= (1 << PB0)
// - output high impedance: conf.pin as input DDRB | ~(1 << PB0)
PORTB &= ~(1 << PB0) ;
// disable digital on comparator inputs
DIDR1 | (1 << AIN1D) | (1 << AIN0D);
//Enable comparator interrupt (on toggle by default)
ACSR |= (1 << ACI); // clear interrupt flag
ACSR |= (1 << ACIE);
}
void main(){
cli();
init_comp();
sei();
// start charging the capacitor through PB1 (pin 10)
PORTB |= (1 << PB1);
while(1);
}
#ifdef _USE_ASSEMBLY_INTERRUPTS_
ISR(ANALOG_COMP_vect, ISR_NAKED) {
// As "naked" this interrupt does not push any registers for low latency.
// This is possible because none of the instructions alter the status register
// or any registers used by the C compiler.
asm volatile(
"IN __tmp_reg__, %2\n"
"SBRC __tmp_reg__, 5\n"
"RJMP L1\n"
"SBI %0, 0\n" // comp. output low
"CBI %1, 1\n" // capacitor drive low
"RETI\n"
"L1: CBI %0, 0\n" // comp. output to high impedance
"SBI %1, 1\n" // capacitor drive high
"RETI\n"
:: "I" (_SFR_IO_ADDR(DDRB)), "I" (_SFR_IO_ADDR(PORTB)), "I" (_SFR_IO_ADDR(ACSR))
);
}
#else
ISR(ANALOG_COMP_vect) {
// Send comp output to PB0 as open collector signal
uint8_t comp_state = ACSR & (1 << ACO);
if (comp_state == 0){
DDRB |= (1 << PB0); // comp. output low
PORTB &= ~(1 << PB1); // capacitor drive low
}
else {
DDRB &= ~(1 << PB0); // comp. output to high impedance
PORTB |= (1 << PB1); // capacitor drive high
}
}
#endif
Comment