FREE Reverse Engineering Self-Study Course HERE
VIDEO PROMO HERE
Embedded Rust w/ bare-metal button driver for the ESP32C3.
Complete Component Kit BUY
FULL TUTORIAL HERE
- ESP32-C3 DevKit
- Development board based on the ESP32-C3 microcontroller (RISC-V 32-bit @ 160MHz).
- Features: Onboard USB-JTAG debugger, WiFi/BLE connectivity, 4MB Flash, 400KB SRAM.
- Ideal for bare-metal Rust development and low-level embedded systems learning.
- USB-C Cable
- For powering the board and programming via USB-JTAG.
- Push Button
- Momentary tactile switch for GPIO input.
- One side connects to 3.3V, other side to GPIO1.
- LED
- Standard LED with current-limiting resistor (220Ω-1kΩ).
- Anode (longer lead) to GPIO0, cathode to GND.
- 10kΩ Resistor
- Pull-down resistor for button (GPIO1 to GND).
- Keeps GPIO1 LOW when button not pressed.
- Breadboard and Jumper Wires
- For prototyping connections between ESP32-C3, button, LED, and resistors.
- Requires access to 3.3V, GND, GPIO0, and GPIO1 pins on the DevKit.
# Install the RISC-V target and Rust nightly
rustup toolchain install nightly
rustup target add riscv32imc-unknown-none-elf --toolchain nightly
rustup default nightly
# Connect your ESP32-C3 DevKit via USB
# Build and flash (debug build for development)
cargo run
# Or build and flash optimized release build
cargo run --releaseThat's it! The firmware will build and flash automatically with bootloader and partition table.
- Custom RISC-V Entry Point: RISC-V _start function with proper initialization
- Startup Handler: Manual .data/.bss initialization and stack pointer setup
- Exception Handlers: Machine-mode trap handlers for RISC-V exceptions
- Peripheral Interrupt Handlers: ESP32-C3 specific peripheral interrupt handlers
- Button Input Driver: GPIO input with IO_MUX configuration for reading button state
- LED Output Driver: GPIO output control for LED indication
- No Runtime Dependencies: Pure bare-metal implementation without runtime crates
- Flash Script: Automated build, image creation, and flashing with bootloader support
- VS Code Debugging: Full GDB debugging support via OpenOCD and USB-JTAG
- Button: GPIO 1 (GPIO input with pull-down, active-high)
- LED: GPIO 0 (GPIO output)
- UART0 TX: GPIO 21 (available for debugging)
- UART0 RX: GPIO 20 (available for debugging)
3.3V -------- [Button] -------- GPIO1 -------- [10kΩ] -------- GND
GPIO0 -------- [220Ω] -------- [LED+] -------- [LED-] -------- GND
- Button connects GPIO1 to 3.3V when pressed (active-high)
- 10kΩ pull-down resistor keeps GPIO1 LOW when button not pressed
- LED turns ON when GPIO0 is HIGH
This project targets the RISC-V 32-bit architecture with M and C extensions.
# Install the RISC-V target (if not already installed)
rustup target add riscv32imc-unknown-none-elf --toolchain nightly
# Build release
cargo build --release --target riscv32imc-unknown-none-elf
# Build debug (for debugging)
cargo build --target riscv32imc-unknown-none-elfThis project includes a flash script that handles:
- Building the Rust binary (release or debug)
- Converting ELF to ESP32-C3 bootable image format
- Flashing bootloader, partition table, and application
# Flash debug build (for development and debugging)
cargo run
# Flash release build (optimized for speed and size)
cargo run --release
# Or use the script directly
./scripts/flash.sh # release
./scripts/flash.sh debug # debugThe script requires:
- esptool: Install via
pip install esptool - Bootloader: Pre-built bootloader in
bootloader/bootloader.bin - Partition Table: Custom partition layout in
partition/partitions.csv
Memory Layout:
0x0000: Bootloader0x8000: Partition table0x10000: Application (your code)
If you need to flash manually:
# Build
cargo build --release --target riscv32imc-unknown-none-elf
# Create ESP32-C3 image
esptool.py --chip esp32c3 elf2image \
target/riscv32imc-unknown-none-elf/release/esp32c3_bm_button_driver \
-o target/app.bin
# Flash all components
esptool.py --chip esp32c3 --port /dev/cu.usbmodem3101 write_flash \
0x0 bootloader/bootloader.bin \
0x8000 partition/partitions.bin \
0x10000 target/app.binThis project includes full debugging support using OpenOCD and GDB.
- ESP-IDF toolchain installed and sourced
- OpenOCD from ESP-IDF (included in toolchain)
- VS Code with Native Debug extension
- Flash debug build:
cargo run
- Start OpenOCD:
- In VS Code:
Tasks: Run Task→OpenOCD - Or from terminal:
./scripts/openocd-wrapper.sh
- In VS Code:
- Start debugging:
- Press
F5or click Run → Start Debugging - Debugger will connect, reset the target, and stop at startup
- Press
- Debug features:
- Set breakpoints by clicking line numbers
- Step through code (F10 = Step Over, F11 = Step Into)
- Inspect variables and registers
- View call stack
Note: Debug builds have
opt-level = 0for better stepping but run slower. Release builds are optimized and faster but harder to debug.
- Custom Startup Code: Complete control over RISC-V reset sequence and memory initialization
- Type Safety: Rust's type system prevents many common embedded bugs
- Explicit Memory Management: Manual .data/.bss initialization with volatile reads/writes
- Direct GPIO Control: Raw register access for GPIO 8 LED control
- VS Code Integration: Full debugging support with GDB and OpenOCD
- Automated Flashing: Simple script handles bootloader and image conversion
View the sections and symbols:
# View section headers
cargo readobj --bin esp32c3_bm_button_driver --target riscv32imc-unknown-none-elf -- --section-headers
# View symbol table
cargo nm --bin esp32c3_bm_button_driver --target riscv32imc-unknown-none-elf
# Disassemble
cargo objdump --bin esp32c3_bm_button_driver --target riscv32imc-unknown-none-elf -- -dcargo doc --no-deps --document-private-items --open- FLASH: 4MB starting at 0x42000000 (instruction bus)
- DRAM: 400KB starting at 0x3FC80000
- IRAM: 384KB starting at 0x40380000
Defined in
memory.ldfor the ESP32-C3.
