Skip to content

Bus_SPI (ESP32): read the SPI data registers with 32-bit loads only - #415

Merged
lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:spi_wreg_read
Nov 11, 2026
Merged

lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:spi_wreg_read

Conversation

@ainyan03

Copy link
Copy Markdown
Contributor

The SPI data registers (W0..W15) only take 32-bit accesses. #409 moved the writes in Bus_SPI to 32-bit stores, but the reads in readBytes() / readPixels() and both lgfx::spi::writeBytes() / lgfx::spi::readBytes() still used memcpy().

What goes wrong

  • ESP32-S3: an 8- or 16-bit load from the registers reads 0. readBytes() into a misaligned buffer returns zeros for some bytes, and lgfx::spi::readBytes() returns zeros at the end of a read whose length is not a multiple of 4.
  • ESP32-P4 / ESP32-C5 (Arduino builds): memcpy() to a misaligned destination uses a misaligned load from the registers and faults (Load address misaligned). Chips with GDMA always take the CPU path in readBytes(), so reading more than 32 bytes into a misaligned buffer crashes.
  • ESP32, C3, C6, H2 and C61 read correctly with the current code.

Change

  • Read helpers that use 32-bit loads only (wreg_read_aligned / wreg_read_bytes); a misaligned destination goes through an aligned 64-byte buffer.
  • The write helpers move from Bus_SPI.inl to common.inl so that lgfx::spi:: uses them too.
  • lgfx::spi::writeBytes() now reads only the source words that hold the bytes to send (before: up to 3 bytes past the end of the source). The data sent is unchanged.

Tested

  • ESP32, ESP32-S3, C3, C6, H2, P4, C5, C61: reads match for destination offsets 0-3 and lengths 1-64 (byte-exact, no change to the registers).
  • readBytes() (no DMA) into buffers at offsets 0-3, lengths 1-60, on ESP32 / S3 / C5 / C6: no fault, no write outside the buffer (before: ESP32-C5 faulted at offsets 1-3).
  • Cost of a 64-byte read into a misaligned buffer: 299-622 cycles depending on the chip, about the same as the previous memcpy().
  • Local builds for the upstream CI versions and chips, and fork CI.

The W0..W15 data registers only take 32-bit accesses, but readBytes(),
readPixels() and lgfx::spi::readBytes() still read them with memcpy(),
and lgfx::spi::writeBytes() still wrote them with memcpy(). The access
width and alignment memcpy() uses depend on the libc build:

- On ESP32-S3 an 8- or 16-bit load from the registers reads 0.
  readBytes() into a misaligned buffer returned zeros for some bytes,
  and lgfx::spi::readBytes() returned zeros at the end of a read whose
  length is not a multiple of 4.
- On ESP32-P4 and ESP32-C5 (Arduino builds), memcpy() to a misaligned
  destination uses a misaligned load from the registers, which faults
  (Load address misaligned). Chips with GDMA always take the CPU path in
  readBytes(), so reading more than 32 bytes into a misaligned buffer
  crashed.

The registers are now read through helpers that use 32-bit loads only;
a misaligned destination goes through an aligned buffer. The write
helpers move from Bus_SPI.inl to common.inl so that lgfx::spi:: uses
them as well. lgfx::spi::writeBytes() now reads only the source words
that hold the bytes to send, instead of up to 3 bytes past the end; the
data sent is unchanged.

Checked on ESP32, ESP32-S3, C3, C6, H2, P4, C5 and C61: reads match for
destination offsets 0-3 and lengths 1-64, and readBytes() into a
misaligned buffer no longer faults on ESP32-C5.
@lovyan03
lovyan03 merged commit 239fef0 into m5stack:develop Nov 11, 2026
26 checks passed
@ainyan03
ainyan03 deleted the spi_wreg_read branch November 11, 2026 09:18
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