Skip to content

SDM driver support#5610

Open
ttzytt wants to merge 3 commits into
esp-rs:mainfrom
ttzytt:sdm
Open

SDM driver support#5610
ttzytt wants to merge 3 commits into
esp-rs:mainfrom
ttzytt:sdm

Conversation

@ttzytt
Copy link
Copy Markdown
Contributor

@ttzytt ttzytt commented May 27, 2026

Add initial Sigma-Delta Modulation driver

This is inspired by the old SDM PR: #2371 and to resolve the issue #2370. I used that PR as the main reference and tried to address many of the design comments raised there. Thanks to @katyo for the original implementation and all the discussion around it.

Overview

This PR adds an initial SDM driver. The current goal is to settle the driver structure and API first, before expanding the documentation and polishing all details.

The driver exposes SDM as a peripheral-wide collection that owns GPIO_SD and provides individual channel fields. Users can move individual channels out of the collection, for example:

let sdm = Sdm::new(peripherals.GPIO_SD);
let mut channel0 = sdm.channel0;

This follows the direction discussed in the old PR: the collection itself does not shut the peripheral down on Drop, because the intended usage is to split channels out and potentially move them into different tasks.

Resource Management

Each channel manages its own active peripheral usage through guards. When a channel is connected, it acquires an SDM clock guard. When the channel is dropped, the guard is dropped too, and the clock reference count is released.

The implementation uses GenericPeripheralGuard for the actual peripheral enable/disable reference counting. I used this because it is already the common pattern in other drivers. To make that possible for SDM, the metadata now also adds Peripheral::GpioSd entries for the chips that have SDM hardware.

However, GenericPeripheralGuard only tracks whether the peripheral clock is in use. It does not store SDM-specific state, such as which source clock is currently selected. SDM has a shared source clock for all channels, so this PR also adds a small SDM-specific clock state guarded by NonReentrantMutex.

That state tracks:

  • how many SDM channels currently use the shared SDM clock,
  • which clock source is currently selected,
  • whether a newly connected channel is trying to use a conflicting source.

The public API only allows selecting the clock source at Sdm construction time. Individual channels receive that source internally, but they do not expose a per-channel source selection API. This is intentional because the underlying clock source is shared.

Channel Generation

Different chips expose different numbers of SDM channels. Some have 4 channels, while ESP32/ESP32-S2/ESP32-S3/ESP32-P4 have 8.

Writing all channel fields and match arms manually with cfg attributes gets complicated quickly. To avoid that, this PR follows the style used by dedicated GPIO: the channel count comes from metadata, and metadata generates a for_each_sdm_channel! macro. The SDM driver then uses that macro to generate the Sdm channel fields and the channel-to-output-signal mapping.

Clock Source Metadata

The metadata currently includes the default SDM clock source and the list of available SDM clock sources for each supported chip.

At the moment, the driver still has a handwritten ClockSource enum and handwritten per-chip clock source configuration code. I am not fully sure yet what the best way is to consume these metadata fields directly from driver code.

This part is definitely open for improvement. Feedback on how this should fit with the existing clock-related code would be very helpful.

Example/Testing

This PR also adds an SDM example under examples/peripheral/sdm.

I tested the example on my ESP32-C5 board and the output worked there. I have not tested the other supported chips on hardware yet.

Regarding HIL testing, I'm not sure what to put there for this driver. It does not seem very practical to test the actual SDM waveform. Maybe the HIL test should instead focus on the digital side of the driver, such as connecting a channel, reading back the raw prescaler and pulse density registers, checking prescaler error handling, and verifying that dropping one channel releases the guard state so another channel can still be used. I will add some HIL testing later if that seems like a good idea.

Documentation

The documentation and comment in code is still minimal. I plan to improve it after the overall API shape and resource-management structure have been reviewed.

Copilot AI review requested due to automatic review settings May 27, 2026 02:50
Comment on lines +840 to +841
clock_sources = ["apb"]
default_clock_source = "apb"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:(

Comment thread esp-hal/src/sdm.rs
pub timing: Timing,
/// Pulse density.
///
/// The value ranges from `-128` to `127`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's obvious from the type

Comment thread esp-hal/src/sdm.rs
/// The value ranges from `-128` to `127`.
///
/// The channel must have been successfully connected first.
pub fn set_pulse_density(&mut self, density: i8) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should only be apply_config, and maybe set_duty. Duplicaing the entire config API with independent methods isn't something we would like to do.

Comment thread esp-hal/src/sdm.rs
///
/// This only changes the GPIO matrix route and replaces the previous pin
/// connection.
pub fn reconnect<'d>(&mut self, pin: impl PeripheralOutput<'d>) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a method on Sdm that returns the Channel

Comment thread esp-hal/README.md
| SDIO slave | ❌ | | | [❌][5169] [^1] | ❌ | [❌][5417] [^1] | | ❌ | | |
| SHA | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ⚒️ | ❌ | ⚒️ | ⚒️ |
| SDM | [❌][2370] [^1] | | [❌][2370] [^1] | [❌][2370] [^1] | [❌][2370] [^1] | | [❌][2370] [^1] | [❌][2370] [^1] | [❌][2370] [^1] | [❌][2370] [^1] |
| SDM | ✔️ | | ✔️ | ✔️ | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is every hardware feature immediately supported and tested on all chips?

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.

2 participants