Chatter and hysteresis
A slow input crossing the knob does not cross once. It wobbles, and a comparator that answers in a microsecond with no hysteresis flips DIG at every wobble. Debouncing DIG thins it out. Reading ANA and switching at two lines either side of the knob cures it, and the sketch can learn where the knob is from DIG itself.
One crossing, many flips
A light fading at dusk, or a potentiometer turned slowly, does not cross the knob cleanly. It drifts toward the line with a little wobble on it: a shadow, a lamp's flicker, noise on the wire. Near the line, each wobble takes it across and back.
The comparator answers in about a microsecond, so it follows every one. DIG flips each time, and anything reading DIG sees several changes for one slow crossing: a lamp that flickers, a counter that counts too many. The trace in the figure is a picture of the behaviour, not a measurement, but the behaviour is real. ON's datasheet adds that the LM393 tends to oscillate during a transition, through stray capacitance from its output back to its inputs, which a slow input makes last longer.
Hysteresis is two lines
The usual cure is hysteresis: switch on at one line and off at another, a little lower. Once the input is past the upper line, a wobble has to carry it all the way below the lower one to change the answer, and a wobble smaller than the gap cannot. The datasheet recommends a few millivolts of it, made by a resistor from the output back to the plus input.
This board has no such resistor. So the sketch does it.
Debounce thins it, hysteresis cures it
The first idea is to debounce DIG: believe a change only after it has held for, say, 40 ms. That removes the fastest flips. But a slow wobble can hold across the line for longer than any sensible window, and then it gets through, as the middle setting of the figure shows.
The cure is to read ANA and compare in the sketch, with two lines. The trick in this sketch is where the lines come from. DIG changes only when the input is at the knob, so every time DIG changes, ANA is the threshold, and the sketch notes it. Then it switches on above the threshold plus 50 mV and off below it minus 50 mV. The knob still sets the point; the sketch only adds the gap.
What you should see
Turn the input block slowly through the knob and back:
above the knob DIG flipped 7 times
below the knob DIG flipped 4 timesOne line per crossing, however many times DIG itself flipped on the way.
The code
Reads ANA in millivolts. Whenever DIG changes, the input is at the knob, so it notes ANA as the threshold. It then switches on only above the threshold plus HYST_MV and off only below it minus HYST_MV, and counts how often DIG flipped meanwhile.
/*
Analog to Digital Signal - hysteresis in the sketch TK29 / /p/tk29
Any analog block pushed into IN, parts facing the same way. Either
switch position: DIG changes at the knob's voltage either way.
Wiring, OUT to your board. Count from the square pad, parts up,
OUT at the bottom:
GND -> GND
VCC -> 5V on an Uno; 3V3 on an ESP32, ESP32-S3 or Pico
(DIG is pulled up to VCC)
DIG -> D2 on an Uno, GPIO 25 on an ESP32, GPIO 7 on an
ESP32-S3, GP15 on a Raspberry Pi Pico
ANA -> A0 on an Uno, GPIO 34 on an ESP32, GPIO 4 on an
ESP32-S3, GP26 on a Raspberry Pi Pico
Arduino IDE
Tools > Board your board, e.g. Arduino Uno
Tools > Port the one that appears when you plug in
Tools > USB CDC On Boot Enabled (ESP32-S3 only)
No library needed.
*/
// DIG_PIN, then ANA_PIN.
// Uno: 2 and A0. ESP32: 25 and 34. ESP32-S3: 7 and 4. Pico: 15 and 26.
const int DIG_PIN = 2;
const int ANA_PIN = A0;
// Uno and Pico only: the ADC's full scale, in mV.
// Uno: 5000. Pico: 3300.
const float FULL_SCALE_MV = 5000.0;
// Switch this far either side of the knob: two lines 100 mV apart.
const float HYST_MV = 50;
int lastDig;
float thresholdMv = -1; // learned from DIG, below
bool above = false; // the steady answer
unsigned long flips = 0; // how often DIG itself changed
float readMilliVolts() {
#if defined(ARDUINO_ARCH_ESP32)
return analogReadMilliVolts(ANA_PIN); // calibrated in the chip
#else
return analogRead(ANA_PIN) * FULL_SCALE_MV / 1023.0;
#endif
}
void setup() {
Serial.begin(115200);
pinMode(DIG_PIN, INPUT); // the block has its own pull-up
lastDig = digitalRead(DIG_PIN);
}
void loop() {
float mv = readMilliVolts();
int dig = digitalRead(DIG_PIN);
if (dig != lastDig) { // DIG changes only at the knob,
lastDig = dig; // so ANA is the threshold now
thresholdMv = mv;
flips++;
}
if (thresholdMv < 0) return; // not crossed yet
bool was = above;
if (!above && mv > thresholdMv + HYST_MV) above = true;
if (above && mv < thresholdMv - HYST_MV) above = false;
if (above != was) {
Serial.print(above ? "above the knob" : "below the knob");
Serial.print(" DIG flipped ");
Serial.print(flips);
Serial.println(" times");
flips = 0;
}
}The knob still sets the threshold: turn it, cross it once, and the sketch has the new one. The switch position does not matter, because DIG changes at the same voltage either way. above means above the knob; to act on darker, use !above.
The same in MicroPython: learn the threshold from DIG, then switch at two lines either side of it, and count DIG's own flips in between.
"""
Analog to Digital Signal - hysteresis, MicroPython TK29 / /p/tk29
Any analog block pushed into IN, parts facing the same way. Either
switch position: DIG changes at the knob's voltage either way.
Wiring, OUT to your board. Count from the square pad, parts up,
OUT at the bottom:
GND -> GND
VCC -> 3V3 (never 5V: DIG is pulled up to VCC)
DIG -> GPIO 25 on an ESP32, GPIO 7 on an ESP32-S3,
GP15 on a Raspberry Pi Pico
ANA -> GPIO 34 on an ESP32, GPIO 4 on an ESP32-S3,
GP26 on a Raspberry Pi Pico
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Save it to the board as main.py to run it on every power-up.
Nothing to install: machine and sys are built in.
"""
import sys
from machine import ADC, Pin
# DIG_PIN, then ANA_PIN.
# ESP32: 25 and 34. ESP32-S3: 7 and 4. Pico: 15 and 26.
DIG_PIN = 7
ANA_PIN = 4
HYST_MV = 50 # two lines 100 mV apart
dig = Pin(DIG_PIN, Pin.IN) # no pull: the block has its own
adc = ADC(Pin(ANA_PIN))
if sys.platform == "esp32": # ESP32 and ESP32-S3
adc.atten(ADC.ATTN_11DB) # the full range, to about 3.1 V
def read_mv():
if sys.platform == "esp32":
return adc.read_uv() / 1000 # calibrated in the chip
return adc.read_u16() * 3300 / 65535
last = dig.value()
threshold = None # learned from DIG
above = False
flips = 0
while True:
mv = read_mv()
now = dig.value()
if now != last: # DIG changes only at the knob
last = now
threshold = mv
flips += 1
if threshold is None:
continue # not crossed yet
was = above
if not above and mv > threshold + HYST_MV:
above = True
elif above and mv < threshold - HYST_MV:
above = False
if above != was:
print("above" if above else "below", "the knob;",
"DIG flipped", flips, "times")
flips = 0MicroPython's loop is slower, so it sees fewer of DIG's flips than the Arduino sketch does, but the steady answer is the same. Raise HYST_MV if it still switches twice. Stop it with Ctrl-C.
When it does not work
The wobble is bigger than the gap. HYST_MV is 50, so the lines are 100 mV apart; a noisy input, or a lamp's own light reaching the sensor, can swing further. Raise HYST_MV to 100 and try again. The wider the gap, the further past the knob it has to go before it switches.
That is by design: the sketch learns the threshold the first time DIG changes, and has no line to switch at until then. If the input starts far from the knob, sweep it through once after power-up, or give thresholdMv a starting value measured with the previous article's sketch.
It would slow the edges down and hide the fastest flips, but a slow wobble across the line gets through anyway, and a slow edge on a digital input can make things worse. Hysteresis is the cure; this board has none, so the sketch supplies it.
Yes. It shows DIG directly, so it chatters along with it. The sketch's decision is steady; the LED on the board is not. Watch the lamp or the serial monitor, not the TK29's LED.
Six symptoms, and the part of the board each one points at.
When the output is wrong →Edit this page — content/books/analog-to-digital-signal/chatter-and-hysteresis.mdx
Questions about this product
See what other owners have asked, and read their solutions.
Analog to Digital Signal
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.