Skip to content

trickart/m5stack-nano-c6-bare-swift

Repository files navigation

M5Stack NanoC6 Bare-Metal Swift

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.

LED blink demo on M5Stack NanoC6

What This Does

  • 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_buf for Hashable support (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) for String(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

Project Structure

├── 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

Prerequisites

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).

Quick Start

1. Install Swift 6.3

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.630202603201a

2. Build

make build

This runs swift build for the riscv32-none-none-eabi target, then converts the ELF to an ESP flash image using Tools/elf2image.swift.

3. Flash

make flash

Writes the bootloader, partition table, and application to the NanoC6 using Tools/write-flash.swift.

4. Monitor

screen /dev/cu.usbmodem* 115200

You should see Swift: blinking messages and the blue LED toggling every 500ms.

How It Works

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:

  1. Disable watchdogs — All 4 watchdogs (TIMG0/1 MWDT, RWDT, SWD) are fully disabled including stage actions and flashboot mode, following ESP-IDF's approach
  2. Enable entropy source & CSPRNG — Configure the SAR ADC to feed noise into the hardware RNG, then seed a ChaCha20-based CSPRNG that backs arc4random_buf for Hashable support
  3. Configure GPIO7 — Set IO_MUX to GPIO function, route through GPIO matrix, enable output
  4. 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.

Supported Swift Language Features

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

Documentation

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)

Acknowledgments

License

This project is licensed under the Apache License 2.0.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages