A bare-metal Swift project for the M5Stack NanoC6 (ESP32-C6), running without any C, assembly, or ESP-IDF — pure Embedded Swift from bootloader to LED blink.
- Boots directly into Swift
@main— no C startup code - Disables all 4 watchdog timers via volatile register access
- Drives GPIO7 (blue status LED) through direct IO_MUX / GPIO register manipulation
- Outputs serial messages over USB Serial JTAG
- Implements microsecond delays using the SYSTIMER peripheral
- Enables the hardware RNG via SAR ADC entropy source, with a ChaCha20-based CSPRNG providing
arc4random_bufforHashablesupport (verified with Dieharder test suite) - Implements complete IEEE 754 software floating-point (Double & Float: arithmetic, conversion, comparison, ceil/floor) on the FPU-less RV32IMC
- Implements 64-bit integer arithmetic builtins (
__muldi3,__udivdi3,__umoddi3,__divdi3,__moddi3) forString(Int)and other 64-bit operations - Provides runtime stubs (
posix_memalign,free,memset,memcpy,memmove,__ashldi3,__lshrdi3,__ashrdi3) entirely in Swift, with a free-list heap allocator (supporting real deallocation and coalescing) and memory/float/integer primitives isolated in dedicated targets
├── Sources/
│ ├── HeapAllocator/ # Free-list allocator (posix_memalign/free)
│ │ └── HeapAllocator.swift
│ ├── MemoryPrimitives/ # memset/memcpy/memmove + shift stubs (isolated target)
│ │ └── MemoryPrimitives.swift
│ ├── SoftFloat/ # IEEE 754 software floating-point builtins
│ │ ├── SoftDouble.swift # Double-precision arithmetic, conversion, ceil/floor
│ │ └── SoftFloat.swift # Single-precision + Float↔Double conversion
│ ├── SoftInt/ # 64-bit integer arithmetic builtins
│ │ └── SoftInt.swift # mul/div/mod for 64-bit integers on RV32
│ ├── Bootloader/ # 2nd stage bootloader (pure Swift)
│ │ ├── Bootloader.swift # Entry point — flash read, MMU setup, jump to app
│ │ ├── ClockConfig.swift # PLL clock initialization (XTAL → 160MHz)
│ │ ├── FlashRead.swift # SPI flash reading via direct SPI1 registers
│ │ ├── FlashConfig.swift # SPI clock and read mode configuration
│ │ ├── MMU.swift # Flash MMU page table configuration
│ │ ├── Watchdog.swift # WDT disable
│ │ └── Delay.swift # SYSTIMER-based delay
│ ├── Application/ # Main application
│ │ ├── Application.swift # @main entry point — LED blink loop
│ │ └── Support/
│ │ ├── Startup.swift # .bss clearing (clearBSS)
│ │ ├── Watchdog.swift # WDT disable (TIMG0/1, LP_WDT, SWD)
│ │ ├── Delay.swift # SYSTIMER-based microsecond delay
│ │ ├── Serial.swift # USB Serial JTAG output
│ │ ├── Random.swift # ChaCha20 CSPRNG + HW RNG entropy source
│ │ ├── ChaCha20.swift # ChaCha20 block cipher
│ │ └── VolatileRegister.swift
│ └── Registers/ # Auto-generated register definitions (SVD2Swift)
│ ├── Device.swift
│ ├── GPIO.swift
│ ├── IO_MUX.swift
│ ├── SYSTIMER.swift
│ └── USB_DEVICE.swift
├── Tools/
│ ├── elf2image.swift # ELF to ESP flash image converter
│ ├── write-flash.swift # Flash writer via serial (SLIP protocol)
│ ├── image-info.swift # Image header inspector
│ └── gen-partition-table.swift # Partition table generator
├── linker/
│ ├── esp32c6.ld # Application linker script
│ └── bootloader.ld # Bootloader linker script
├── toolset.json # Application compiler & linker flags
├── toolset-bootloader.json # Bootloader compiler & linker flags
├── Makefile # Build & flash automation
├── Package.swift
└── docs/ # Detailed documentation for each subsystem
| Tool | Version | Notes |
|---|---|---|
| Swift 6.3 toolchain | org.swift.630202603201a |
Must support Embedded Swift & RISC-V |
| macOS | — | Uses xcrun for toolchain discovery |
No ESP-IDF installation required. The Tools/ directory contains pure Swift replacements for esptool.py (ELF-to-image conversion, flash writing, image inspection).
Install swiftly (Swift toolchain manager), then install Swift 6.3:
# Install swiftly
curl -L https://swiftlang.github.io/swiftly/swiftly-install.sh | bash
# Install Swift 6.3
swiftly install 6.3
# Set the toolchain for this project
export TOOLCHAINS=org.swift.630202603201amake buildThis runs swift build for the riscv32-none-none-eabi target, then converts the ELF to an ESP flash image using Tools/elf2image.swift.
make flashWrites the bootloader, partition table, and application to the NanoC6 using Tools/write-flash.swift.
screen /dev/cu.usbmodem* 115200You should see Swift: blinking messages and the blue LED toggling every 500ms.
The 2nd stage bootloader (also written in Swift, Sources/Bootloader/) handles low-level initialization (BSS clearing, PLL clock setup to 160MHz, flash SPI configuration, segment loading, Flash MMU), then jumps to the application's @main entry point. Everything is pure Swift:
- Disable watchdogs — All 4 watchdogs (TIMG0/1 MWDT, RWDT, SWD) are fully disabled including stage actions and flashboot mode, following ESP-IDF's approach
- Enable entropy source & CSPRNG — Configure the SAR ADC to feed noise into the hardware RNG, then seed a ChaCha20-based CSPRNG that backs
arc4random_bufforHashablesupport - Configure GPIO7 — Set IO_MUX to GPIO function, route through GPIO matrix, enable output
- Blink loop — Toggle output via W1TS/W1TC registers with SYSTIMER-based delays
Register access uses apple/swift-mmio with definitions generated from the ESP32-C6 SVD file.
The following Swift language features have been verified to work on this bare-metal target (ESP32-C6, RV32IMC, no OS):
| Feature | Notes |
|---|---|
final class (ARC) |
Reference counting works without atomics on single-core |
enum + associated values |
Pattern matching with switch |
Optional |
if let, guard let, ?? |
| Generics | Monomorphized at compile time |
| Protocols + default implementations | As generic constraints |
| Closures (non-escaping) | |
indirect enum |
Heap-allocated recursive data structures |
| Operator overloading | Custom +, ==, etc. |
@propertyWrapper |
e.g. clamping wrapper |
Result<Success, Failure> |
With Error-conforming type |
deinit |
Class destructor called correctly |
Computed properties / didSet |
|
@dynamicMemberLookup |
Detailed write-ups for each subsystem are in the docs/ directory:
| Doc | Topic |
|---|---|
| 01-toolchain | Toolchain setup |
| 02-bootloader-flash-image | Boot sequence & flash image format |
| 03-linker-script | Memory map & linker script design |
| 04-startup | Pure Swift startup & watchdog disabling |
| 05-heap-and-memory-stubs | Heap allocator & memory stubs |
| 06-swift-mmio | swift-mmio integration & SVD2Swift |
| 07-gpio | GPIO driver implementation |
| 08-delay-serial | SYSTIMER delay & USB Serial JTAG output |
| 09-build-system | Build pipeline (SwiftPM + toolset + Make) |
| 10-tools | Swift-based tools (elf2image, write-flash, image-info, gen-partition-table) |
| 11-rng-and-hashable | Hardware RNG, ChaCha20 CSPRNG & Hashable support |
| 12-soft-float | Software floating-point builtins (Double & Float) |
| 13-soft-int | Software integer arithmetic builtins (64-bit mul/div/mod) |
- pico-bare-swift — Inspiration for the
@mainbare-metal pattern andVolatileMappedRegisterusage - esp32-c6-swift-baremetal — Reference for ESP32-C6 + Swift bare-metal approach
- apple/swift-mmio — MMIO register access framework and SVD2Swift code generator
This project is licensed under the Apache License 2.0.
