Real-time

External ADC

This sketch demonstrates how to use storeRaw() with a timer ISR or DMA for non-blocking ADC reads in real-time applications.

A real-time application is one where correctness depends not just on what the system computes, but on when it delivers the result. Missing a deadline — even by a few microseconds — can mean a dropped MIDI note, a motor control glitch, a skipped sensor sample, or a timing fault in a safety-critical system. The loop() must complete each iteration within a bounded, predictable time window.

Calling analogRead() blocks the CPU for 10-100 microseconds per read. With 8 knobs that's up to 800us of dead time per loop — enough to blow past a real-time deadline and cause exactly the kind of jitter described above.

The solution is to let a DMA controller or timer ISR fill a buffer with ADC readings in the background, call storeRaw() on each CtrlPot from the ISR, and then call process() as normal from the main loop. The library still handles:

  • Q16 fixed-point smoothing (filters jitter from noisy ADC readings)
  • Value mapping (raw 0-1023 to your output range, e.g. 0-127 for MIDI)
  • Change detection (only fires the callback when the value actually changes)

This means you get zero-copy, non-blocking knob processing with no boilerplate smoothing, deduplication, or ready-flag management.


The code

#include <CtrlPot.h>

void onVolumeChange(int value) {
  Serial.print("Volume: ");
  Serial.println(value);
}

void onFilterChange(int value) {
  Serial.print("Filter cutoff: ");
  Serial.println(value);
}

void onResonanceChange(int value) {
  Serial.print("Resonance: ");
  Serial.println(value);
}

void onDriveChange(int value) {
  Serial.print("Drive: ");
  Serial.println(value);
}

CtrlPot volumeKnob(A0, 127, 0.05, onVolumeChange);
CtrlPot filterKnob(A1, 100, 0.05, onFilterChange);
CtrlPot resonanceKnob(A2, 100, 0.05, onResonanceChange);
CtrlPot driveKnob(A3, 255, 0.05, onDriveChange);

// --- DMA / ISR side (runs in background) ---
// On Teensy/STM32/ESP32 you would configure a hardware timer
// or DMA descriptor to trigger this automatically.
// storeRaw() is ISR-safe: it only writes to a volatile buffer and sets a flag.
void timerISR() {
  volumeKnob.storeRaw(analogRead(A0));
  filterKnob.storeRaw(analogRead(A1));
  resonanceKnob.storeRaw(analogRead(A2));
  driveKnob.storeRaw(analogRead(A3));
}

void setup() {
  Serial.begin(9600);

  // In a real project you would configure your timer/DMA here, e.g.:
  // IntervalTimer myTimer;
  // myTimer.begin(timerISR, 1000); // read ADC every 1ms
}

void loop() {
  // Simulate the ISR firing (in a real project the timer does this).
  timerISR();

  // process() detects the pending ISR values and consumes them.
  // No analogRead() calls happen here — zero blocking time.
  volumeKnob.process();
  filterKnob.process();
  resonanceKnob.process();
  driveKnob.process();

  // Your audio/DSP processing goes here — uninterrupted by ADC reads.
}
Previous
Fading LED