busio.SPI: treat baudrate as a ceiling, not nearest#11081
Merged
Conversation
Bump the samd-peripherals submodule to pick up adafruit/samd-peripherals#49, which makes busio.SPI treat the requested baudrate as a ceiling (the resulting frequency is the requested value or the nearest lower available value, never higher) instead of rounding to the nearest available baudrate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ESP-IDF driver rounds clock_speed_hz to the nearest achievable frequency, which can be higher than requested and overclock a device. Probe candidate targets through the public API (spi_bus_add_device -> spi_device_get_actual_freq -> spi_bus_remove_device), bisecting downward to find the highest achievable frequency that does not exceed the requested baudrate, without depending on divisor internals that vary between Espressif chips. The reported frequency is read back from the configured device. Tested on ESP32-S3, ESP32-C6, and ESP32. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The actual SPI clock is now set to the requested rate or the nearest lower available rate, never higher. Update the configure() baudrate parameter and the frequency attribute docstrings to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SPI_SETFREQUENCY() picks the nearest available frequency, which overshoots the request at low frequencies (e.g. 100000 -> 102362). Treat baudrate as a ceiling: when the driver overshoots, bisect downward on the requested target for the highest available frequency that does not exceed baudrate. The driver returns the actual frequency each call, so this does not depend on its divisor internals. Tested on SPRESENSE (CXD5602): no frequency exceeds the requested baudrate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MXC_SPI_SetFrequency() floors the clock divisor, so the actual SPI clock can be higher than requested (e.g. a 30 MHz request on a 50 MHz peripheral clock runs at the full 50 MHz). Treat baudrate as a ceiling: when the hardware overshoots, bisect downward on the requested target for the highest rate that does not exceed baudrate. MXC_SPI_GetFrequency() reports the actual configured rate, so this does not depend on the divisor internals. Also store the actual rate so the frequency property reports it instead of the requested value. Not tested on hardware (no MAX32xxx board available); logic mirrors the cxd56 and espressif ceiling fixes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tighten the configure() baudrate wording and document that on Zephyr the ceiling behavior may be violated and the frequency attribute may not reflect the actual baudrate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 30, 2026
tannewt
approved these changes
Jun 30, 2026
tannewt
left a comment
Member
There was a problem hiding this comment.
Thank you! I'm surprised so many ports simply rounded.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note: This PR description was mostly written by Claude (Claude Code), and touched up by me (@dhalbert).
Summary
busio.SPIdocumentsbaudrateas a desired clock rate, and most ports already pick the requested rate or the nearest lower available rate. But several ports rounded to the nearest available rate (or worse), so the actual SPI clock could come out higher than requested and overclock a device. This PR makes the requestedbaudratea ceiling consistently: the resulting clock is the requested rate or the nearest lower available rate, never higher.Per-port status
(f_ref/(2·baud)) − 0.5)ceil(f_ref/(2·baud)) − 1(in thesamd-peripheralssubmodule)spi_get_actual_clock, abs-error min)spi_bus_add_device→spi_device_get_actual_freq→spi_bus_remove_device, bisecting downwardSPI_SETFREQUENCYovershoots at low freqs)MXC_SPI_SetFrequencyfloors the divisor)MXC_SPI_GetFrequencyreadbackNone of the fixes depend on chip-specific divisor internals; each uses the port's public "set then read back the actual rate" path (or, for atmel-samd, the documented BAUD formula).
Submodule
The atmel-samd fix lives in
samd-peripherals(adafruit/samd-peripherals#49, merged); this PR bumps the submodule pointer to pick it up.Docs
shared-bindings/busio/SPI.cnow documents the ceiling behavior on both theconfigure()baudrateparameter and thefrequencyattribute. [I reworked some of this to be less verbose and clearer. --@dhalbert]Testing
Known limitation: Zephyr
The
zephyr-cpport is not addressed. Zephyr'sstruct spi_configonly takes a requestedfrequencyand provides no API to read back the achieved rate (zephyrproject-rtos/zephyr#77922), so the read-back-and-bisect approach used by the other ports is not available. Behavior is per-driver: nRF, STM32, Renesas RA8, and Smartbond already floor, but the RP2040/RP2350 PIO, NXP flexcomm (MCXN947/RW612), and NXP ecspi (MIMXRT1170) drivers round to nearest and can exceed the request. This is now documented as a limitation in thebusio.SPI.configure()documentation.