core-s3
Rust board support package for the M5Stack CoreS3 K128 (ESP32-S3) with optional support for an M5Stack Gateway H2 Thread/Zigbee co-processor.
The crate is intentionally #![no_std] and keeps the reusable BSP layer modular:
- board metadata and pin/device maps for CoreS3 peripherals
- crate-owned ILI9342C-compatible display bring-up
- lightweight
embedded-graphicswidgets and dirty-region helpers - FT6336U touch parsing and rotation-aware coordinate mapping
- AXP2101 power/battery helpers and AW9523B expander support
- BMI270/BMM150 motion/orientation helpers
- BM8563 RTC helpers with small
no_stddate/time types - ES7210/AW88298 audio configuration helpers
- feature-gated Gateway H2 UART/framing/OpenThread transport surfaces and Spinel HDLC-lite codec behind
gateway-h2 - optional TF-card SD parts compatible with
embedded-sdmmc
Hardware note: v0.4.4 keeps the default ESP32-S3 path on the current stable downstream stack around
esp-hal = "=1.1.2". Display, touch, battery, motion, compass, RTC, audio, and Gateway H2 examples were smoke-tested during v0.3 development. The shared LCD/TF-card SPI API configures GPIO35 as SD MISO, handles the CoreS3 LCD D/C vs TF-card MISO mode switch inside the BSP, keeps TF-card CS asserted across CMD0 response polling for reliable pre-inserted-card acquisition, and restores SD/MISO-safe idle state after LCD transactions for cooperative LCD + SD use.
Peripheral support
| Peripheral | Address / pins | Support |
|---|---|---|
| ILI9342C SPI LCD | MOSI GPIO37, SCLK GPIO36, CS GPIO3, D/C GPIO35, TF CS GPIO4 held high | init, RGB565 drawing, clipping, rotation/MADCTL, dirty-region blits, shared-SPI initializer |
| FT6336U touch | I2C 0x38 on SDA GPIO12/SCL GPIO11 |
touch report parsing, down/up/move, gestures, rotation mapping, hit testing |
| AXP2101 PMIC | I2C 0x34 |
CoreS3 defaults, backlight rail, M5Unified-compatible battery SOC/status helpers, shutdown/sleep prep |
| AW9523B expander | I2C 0x58 |
CoreS3 defaults and safe output helpers, LCD reset pin helper |
| BMI270 IMU | I2C 0x69 |
init/config, accel/gyro raw reads, offsets, basic motion detection |
| BMM150 magnetometer | 0x10 on BMI270 auxiliary sensor-hub I2C |
generic register helper, hard-iron offset, integer heading helper; CoreS3 access path needs BMI270 sensor-hub validation |
| BM8563 RTC | I2C 0x51 |
get/set date-time, alarms, timer metadata |
| ES7210 microphone ADC | I2C 0x40, I2S GPIO0/34/33/13/14 |
configuration helper; I2S DMA remains app/HAL-owned |
| AW88298 speaker amp | I2C 0x36, I2S GPIO0/34/33/13/14 |
configuration helper; I2S DMA remains app/HAL-owned |
| Gateway H2 | UART1, TX GPIO1, RX GPIO2, 115200 baud | UART bring-up, small request/response/event framing, OpenThread/Spinel transport traits, and Spinel HDLC-lite codec |
| TF-card slot | SCLK GPIO36, MOSI GPIO37, MISO GPIO35, CS GPIO4 | slot metadata, card-detect helper, shared-SPI SpiDevice parts, optional embedded-sdmmc::SdCard conversion |
Repository layout
crates/core-s3/ no_std BSP crate
examples/hello_world/ basic LCD validation
examples/downstream_esp_hal_112/ downstream compatibility build for esp-hal 1.1.2
examples/dirty_regions/ dirty-region animation
examples/dual_core/ PRO CPU + APP CPU example
examples/gateway_h2/ Gateway H2 UART scaffold
examples/display_widgets/ widget rendering smoke test
examples/touch_demo/ FT6336U smoke-test shell
examples/battery_status/ AXP2101/battery smoke-test shell
examples/imu/ BMI270 smoke-test shell
examples/compass/ BMM150 smoke-test shell
examples/rtc/ BM8563 smoke-test shell
examples/audio_init/ ES7210/AW88298 smoke-test shell
examples/sd_card/ AW9523B TF-card detect demo
examples/sd_block_probe/ shared-SPI embedded-sdmmc capacity probe
examples/display_sd_coexist/ alternating LCD + raw SD read/write coexistence test
examples/gateway_h2_transport/ H2 framing smoke-test shell
examples/full_board_demo/ board overview smoke-test shell
.github/workflows/ PR validation and firmware release automation
Features
| Feature | Description |
|---|---|
defmt |
Enables defmt formatting for supported dependency-free public types. |
esp-hal |
Enables ESP-HAL-backed CoreS3 bring-up helpers on Xtensa ESP32-S3 targets. |
gateway-h2 |
Exposes core_s3::gateway_h2, Gateway H2 metadata, UART bring-up, Matter/Thread config types, H2 framing, OpenThread/Spinel transport traits, and Spinel HDLC-lite encode/decode helpers. |
sdmmc |
Enables conversion from BSP SD parts into embedded_sdmmc::SdCard<SPI, DELAY>. |
Minimal display example
use ;
use ;
let mut parts = init_display?;
parts.display.clear?;
Label
.draw?;
Widgets and dirty-region sprite
core_s3::display::DirtySprite stores an off-screen framebuffer and tracks only changed rectangles. Drawing through embedded-graphics marks dirty regions automatically; flush_dirty / flush_dirty_at then blit only final pixels for changed regions into the real display target.
core_s3::ui provides small reusable widgets without a GUI framework dependency:
LabelButtonToggleSliderProgressBarBatteryIndicatorStatusBarMenu
Shared LCD + TF-card SPI
CoreS3 routes the LCD and TF-card socket through the same SPI signal group:
SPI2
SCLK GPIO36
MOSI GPIO37
MISO GPIO35
LCD CS GPIO3
TF CS GPIO4
For firmware that needs both devices, use CoreS3::init_shared_spi, store the returned CoreS3SharedSpiParts in a static_cell::StaticCell, then create the LCD and SD chip-select devices independently with CoreS3::init_display_on_shared_spi and CoreS3::init_sd_on_shared_spi.
M5Stack's official CoreS3 PinMap lists LCD D/C on GPIO35 and TF-card MISO on the same GPIO35 pad. M5GFX's CoreS3 panel switches GPIO35 on LCD CS boundaries: LCD CS active routes GPIO35 as D/C output, while LCD CS inactive releases GPIO35 back to SPI MISO. The BSP follows that model with CoreS3-specific LCD and SD SpiDevice wrappers: LCD transactions force TF-card CS high, route GPIO35 as D/C output only while LCD CS is active, then restore LCD CS high, TF-card CS high, GPIO35 SD MISO/input, and SD-safe SPI settings before returning. SD transactions release GPIO35 as a pulled-up MISO input before TF-card CS is active.
For robust acquisition when a card is already inserted at flash/cold-boot/reset time, initialize and probe SD before LCD SPI traffic: create shared SPI and SD parts, initialize internal I2C, call CoreS3::init_core_s3_power(...), CoreS3::power_cycle_tf_card_rail(...), sd_parts.spi_device.prepare_for_card_acquire(), then call CoreS3SdParts::into_sdmmc() and SdCard::num_bytes(). After the SD probe, initialize the LCD with CoreS3::init_display_on_powered_shared_spi(...). Use examples/display_sd_coexist to validate alternating LCD updates and raw SD read/write/readback on hardware.
Downstream firmware can keep using embedded_hal::spi::SpiDevice and, with feature sdmmc, CoreS3SdParts::into_sdmmc() returns an embedded_sdmmc::SdCard<SPI, DELAY> suitable for a real num_bytes() capacity probe.
The BSP intentionally does not provide credential/token/secret abstractions, fake filesystems, plaintext storage policy, or encryption; downstream firmware should encrypt sensitive bytes before writing them to SD.
Power / battery status
core_s3::power::Axp2101::battery_level_percent() reads AXP2101 register 0xA4, matching M5Unified's CoreS3 getBatteryLevel() behavior. Axp2101::status() prefers that gauge SOC when it returns 0..=100; if unavailable, the existing BatteryStatus::percentage falls back to a coarse voltage estimate and sets percentage_estimated = true with state_of_charge = None.
Charging state comes from AXP2101 register 0x01 bits 5:6. External power uses register 0x00 bit 0x20, and battery presence uses register 0x00 bit 0x08. CoreS3/AXP2101 does not expose battery current through this BSP path, so current-based coulomb counting is not available from AXP2101 alone.
Matter / Gateway H2 scope
The BSP does not implement Matter, Thread, Zigbee, OpenThread CLI, or the OpenThread state machine. M5Stack's Gateway H2 Thread Border Router documentation builds ESP-IDF's examples/openthread/ot_rcp firmware for the ESP32-H2 module, so core_s3::gateway_h2::spinel provides the bounded Spinel HDLC-lite byte-stuffing/FCS codec needed by downstream OpenThread host integrations. The downstream application still owns OpenThread host integration, Matter commissioning/runtime, and protocol policy.
Consumer firmware should own:
- Wi-Fi/IP networking
- Matter server/runtime such as
rs-matter - endpoints and clusters
- commissioning and persistence
- Thread/OpenThread/Spinel/Zigbee protocol integration
- Home Assistant behavior
Building
The default ESP32-S3 build is kept compatible with:
= "=1.1.2"
= "=0.15.0"
= "=0.17.0"
= "=0.1.4"
= "=0.10.0"
= "=1.0.0-beta.0"
= "=0.3.0"
= "=0.7.0"
= "1.0"
Install the ESP Rust toolchain with espup, then:
Flash an example with cargo-embed through the workspace runner:
Embed.toml is configured for ESP32-S3 JTAG. GDB is enabled so dynamic examples continue running while the probe session remains attached.
v0.4 migration notes
- Update dependencies from
core-s3 = "0.3"tocore-s3 = "0.4". - Prefer
core-s3 = "0.4.4"or newer for shared LCD + TF-card SPI: v0.4.4 keeps the v0.4.2 pre-inserted-card acquisition fixes and adds cooperative LCD/SD sharing so LCD transactions restore the SD/MISO-safe GPIO35/SPI2 idle state. - ESP-HAL users should pin to the supported
esp-hal = "=1.1.2"family unless they explicitly opt into and validate a newer stack in their application. - Existing
CoreS3::init_displayremains available for display-only firmware. - Firmware that needs both LCD and TF-card access should migrate to
CoreS3::init_shared_spi,CoreS3::init_sd_on_shared_spi,CoreS3::init_internal_i2c,CoreS3::power_cycle_tf_card_rail,CoreS3SharedSdDevice::prepare_for_card_acquire, andCoreS3::init_display_on_powered_shared_spiwhen SD must be acquired before LCD traffic. The olderCoreS3::init_display_on_shared_spiremains available for display-first code. - Use feature
sdmmcif you want BSP SD parts to convert directly intoembedded_sdmmc::SdCard. - Gateway H2 Matter/Thread config types remain configuration-only. Use
gateway_h2::transportfor H2 framing and OpenThread transport traits, and usegateway_h2::spinelfor the Spinel HDLC-lite codec when the H2 is flashed with ESP-IDF OpenThread RCP firmware. - Full Matter server/runtime code still belongs in consumer applications, not this BSP.
v0.3 migration notes
- Update dependencies from
core-s3 = "0.2"tocore-s3 = "0.3". BatteryStatusnow includes percentage estimate, charge state, external-power state, and low-battery state. Code using the old{ millivolts, state }fields should migrate tocharge_stateand the richer status fields.
Unsupported / application-owned functionality
- Camera driver support is metadata-only.
- High-throughput I2S DMA capture/playback is application/HAL-owned.
- Matter, Thread, Zigbee, OpenThread, and Spinel protocol stacks are application-owned.
- Voltage-based battery percentage is approximate and should not be used as a precise fuel gauge.