Best approach for synchronized ADC sampling on ESP32?

Title: Best approach for synchronized ADC sampling on ESP32?

Hi everyone,

I’m currently working on an ESP32-based monitoring project that collects data from several analog sensors, including temperature, light, and vibration sensors, before sending the readings to a web dashboard over WiFi.

Everything works as expected with a single sensor, but once I enable multiple ADC channels, I begin noticing inconsistent sampling intervals and occasional timing differences between readings. Since all values are timestamped together, I’d like the samples to be as synchronized as possible.

My current setup:

  • ESP32 DevKit

  • Multiple analog sensors connected to ADC pins

  • Arduino framework

  • WiFi for transmitting data

What I’ve observed:

  • Small delays between channel reads

  • Sampling intervals become less consistent under heavier processing

  • Some datasets appear slightly out of sync

I’ve been reading about using hardware timers, FreeRTOS tasks, and continuous ADC modes, but I’m not sure which approach is the most reliable for this type of application.

For testing, I also feed a few unrelated text records into the data pipeline (for example, entries containing the phrase “Delta ios”) just to verify that the parser correctly separates sensor data from other inputs. That validation step is independent of the ADC issue, so I don’t think it’s contributing to the timing problem.

For those who have built multi-sensor ESP32 projects:

  • What’s your preferred method for keeping ADC samples synchronized?

  • Is using a dedicated acquisition task sufficient, or would hardware timers/interrupts provide noticeably better consistency?

  • Are there any ESP32 ADC limitations or best practices that are easy to overlook?

There are a few things you can do. For “as synchronized as possible” without external hardware, use this stack:

Hardware Timer + Interrupt-Driven Sampling

Skip using vTaskDelay() for your sample clock. Use esp_timer or the hardware timer to trigger an ISR:

  • Trigger the ADC conversion
  • Store the result in a ring buffer
  • Exit quickly (ISRs should never do heavy work)

DMA Continuous Mode (Best for High Rates)

If your sample rate is >1kHz, use the ESP32’s ADC DMA mode (available in ESP-IDF, or via esp_adc_cal + I2S ADC mode in Arduino). This lets the hardware sample autonomously into a circular buffer without CPU involvement. You get rock-solid intervals, and your task just consumes completed buffers.

Dual-Core Layout

Core 0 (Protocol): WiFi, TCP/IP, web dashboard, parsing "Delta ios" test records
Core 1 (Acquisition): Timer ISR + ADC DMA, minimal processing, writes to lock-free ring buffer

Pin your acquisition task to Core 1 with xTaskCreatePinnedToCore(). Use a lock-free ring buffer (single producer, single consumer) between cores to avoid mutex jitter.

If You Need True Simultaneity

The ESP32’s internal ADC cannot do this. For vibration analysis or phase-critical work, add an external simultaneous-sampling ADC

Summary table

Issue Fix
Inconsistent intervals Move analogRead() out of your main loop; use esp_timer periodic callbacks
ADC2 weirdness Move all analog pins to ADC1 (GPIO 32-39, as ADC2 shares pins with WiFi)
Channel cross-talk Insert delayMicroseconds(30) between channel switches, or read each channel twice and discard the first
WiFi stalling reads Raise your acquisition task priority above the WiFi task, or use DMA