Skip to content

feat(esp32): digital ADC + DMA for MCPWM low-side current sense#543

Open
uLipe wants to merge 7 commits into
simplefoc:devfrom
uLipe:feature/esp32_hw_trigger_adc
Open

feat(esp32): digital ADC + DMA for MCPWM low-side current sense#543
uLipe wants to merge 7 commits into
simplefoc:devfrom
uLipe:feature/esp32_hw_trigger_adc

Conversation

@uLipe
Copy link
Copy Markdown
Contributor

@uLipe uLipe commented Jun 2, 2026

Summary

Adds digital SAR ADC + DMA for ESP32 MCPWM low-side current sensing (ported from espFoC isensor_adc), integrated into existing LowsideCurrentSense without API changes.

Three backends, selected at init:

Mode Targets PWM → ADC Sampling
ADC_READ (default) All MCPWM ESP32 MCPWM comparator/timer ISR adcRead(), one phase per ISR (~10 µs each)
DIGI_SW ESP32, S2, … MCPWM ISR esp32_adc_digi_trigger_software() — digi pattern + DMA
DIGI_ETM S3, C6, … ETM (TEZ → ADC_TASK_START0) Same digi + DMA, no CPU trigger

DMA shims: I2S (ESP32), SPI3 (S2), GDMA (S3/C6+). Pattern rate 80 kHz, 2 channels, convert_limit = 2 (aligned with espFoC).

Why this approach

Low-side FOC needs currents at a fixed PWM phase. The default ADC_READ path uses MCPWM callbacks + adcRead(), which:

  • Blocks the ISR during each SAR conversion
  • Round-robins phases across interrupts (latency/jitter between i_a / i_b)
  • Limits headroom at higher PWM frequencies

DIGI_* modes run the hardware pattern sequencer and move results by DMA. On S3/C6, ETM starts the ADC without a CPU ISR on the trigger edge.

Zero-copy DMA path

For DIGI_SW / DIGI_ETM, DMA writes adc_digi_output_data_t[] directly into ESP32CurrentSenseParams::adc_buffer (DMA-capable heap_caps alloc). The EOF ISR only:

  • invalidates cache (where required), and
  • re-arms for the next conversion (ETM mode),

with no per-sample copy loop. _readADCVoltageLowSide() decodes the 12-bit field from the digi format on access.

Problems addressed

  • Shorter MCPWM ISRs on digi paths (no adcRead busy-wait in the trigger ISR on ETM; SW trigger only kicks the controller)
  • Both shunt channels in one DMA frame (2-shunt setups)
  • Lower trigger jitter on ETM-capable chips
  • ADC_READ remains the default and fully supported fallback if digi init fails

Limitations

  • ADC1 only, max 2 pattern channels
  • ESP32-C3 has no MCPWM — 3PWM uses LEDC; this feature applies to MCPWM + low-side builds (e.g. C6, S3)
  • Example DRV8302 pins are for classic ESP32 — remap for your board
  • adc_buffer layout differs between ADC_READ (plain int) and DIGI_* (adc_digi_output_data_t overlay)

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

Test Configuration/Setup:

  • Hardware: ESP32C6 + SimpleFoC shield V2.0.1
  • IDE: Arduino CLI
  • MCU package version arduino-esp32/

uLipe added 6 commits June 1, 2026 21:44
Port chip-specific DMA shims (I2S on ESP32, SPI3 on S2, GDMA on S3/C6)
and shared timing constants aligned with espFoC isensor_adc.
Wire MCPWM timer TEZ/TEP to ADC_TASK_START0 so conversions are not
kicked from CPU ISRs on chips that lack ETM (ESP32, ESP32-S2).
Configure adc_hal digi pattern/DMA, process frames into a shared buffer,
and re-arm after each conversion without blocking MCPWM callbacks.
Select LEGACY, DIGI_SW, or DIGI_ETM paths and keep MCPWM sync logic
out of the core driver files.
Prefer digi+DMA on init; use ETM on S3/C6 or MCPWM ISR software trigger
on ESP32/S2, with legacy adcRead() as fallback.
Link adc_hal DMA directly to ESP32CurrentSenseParams::adc_buffer,
trim the EOF ISR to cache sync and rearm only, and decode samples
on read. Allocate params from DMA-capable heap.
@uLipe
Copy link
Copy Markdown
Contributor Author

uLipe commented Jun 2, 2026

@askuric @runger1101001 PTAL this is something that resulted in a HUGE reduction of CPU busy since mostly of the PWM->ADC pipeline is handled by the hardware, PWM generate event to ETM to start the ADC conversion and the DMA stores the channels converted directly in the adc_buffer. with a single interrupt.

Also the ADC Digi is used, allowing faster sample rates and compatibillity across all esp32.

@uLipe uLipe force-pushed the feature/esp32_hw_trigger_adc branch 2 times, most recently from 8e1e93c to c3c6407 Compare June 2, 2026 03:22
Wrap ESP32 digi ADC sources in ARDUINO_ARCH_ESP32 guards so AVR CI
builds do not pull sdkconfig.h. Select DMA backend per chip (I2S /
SPI3 / GDMA) via internal.h so classic ESP32 and S2 do not compile
GDMA. Include esp32_mcu.h before MCPWM guard in lowside.cpp.
@uLipe uLipe force-pushed the feature/esp32_hw_trigger_adc branch from c3c6407 to a76ec58 Compare June 2, 2026 12:14
@uLipe
Copy link
Copy Markdown
Contributor Author

uLipe commented Jun 2, 2026

@askuric @runger1101001 CI is happy now!

Happy to hear your comments, I hope with this modification to extract the maximum efficiency from ESP32 ADC infrastructure.

Even though it not imply higher sample rates (> 100K, except on esp32 and esp32c5 which is possible to achieve 2MSPS), it will push the ADC to the maximum supported while

  • Improve the precision using the curve fitting
  • Makes it to work at full range from 0 to 3.3V
  • Does most of the heavy job by HW, MCPWM fires the ETM automagically at each cycle, it triggerts two/three conversions of the ADC and store the results directly on the buffer via DMA.
  • And the FoC stack gets only a single interrupt when all this chain is done.

Exceptions apply to esp32 and s2 which uses two interrupts MCPWM fires the scan ADC but the rest of the chain is done via DMA.

AS the PR description says the idea is block the CPU at minimum for the hard real time things while guarantee the timings and sample point of the current measurement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant