Skip to content
Draft

Embedded Systems

Draft supports bare-metal and RTOS-based embedded development with no runtime overhead.

Draft’s no-GC, no-runtime design makes it ideal for resource-constrained environments.

Platform Architecture Min RAM Min Flash
ARM Cortex-M ARMv6-M/7-M/8-M 4 KB 16 KB
RISC-V RV32IMAC 4 KB 16 KB
AVR ATmega/ATtiny 1 KB 8 KB
ESP32 Xtensa 32 KB 128 KB
nRF52 ARM Cortex-M4 8 KB 32 KB
Terminal window
draft target add arm-none-eabi
draft target add riscv32-unknown-none
Terminal window
draft new my-embedded --template embedded
cd my-embedded

Project structure:

my-embedded/
├── draft.toml
├── memory.x # Linker script
├── Cargo.toml # (for HAL dependencies)
└── src/
├── main.draft
└── interrupts.draft
[package]
name = "my-embedded"
version = "0.1.0"
[target.thumbv7em-none-eabihf]
runner = "probe-run --chip STM32F407VG"
[profile.release]
opt-level = "s" # Optimize for size
lto = true # Link-time optimization
import gpio
action main()
@led = @gpio#Pin::new(13)
@led#mode(@gpio#OUTPUT)
while true
@led#high()
@time#sleep(milliseconds(500))
@led#low()
@time#sleep(milliseconds(500))
fin while.
fin action.
import gpio
action main()
@button = @gpio#Pin::new(0)
@button#mode(@gpio#INPUT)
@button#pull(@gpio#PULL_UP)
while true
if @button#read() == @gpio#LOW
handle_press()
fin if.
@time#sleep(milliseconds(10))
fin while.
fin action.
import gpio
action main()
@pwm = @gpio#PWM::new(18)
@pwm#frequency(1000) @// 1 kHz
for @duty in 0..100
@pwm#duty(@duty)
@time#sleep(milliseconds(20))
fin for.
fin action.
import adc
action main()
@sensor = @adc#Channel::new(0)
@sensor#resolution(12) @// 12-bit
while true
@value = @sensor#read()
@voltage = @value * 3.3 / 4095
@time#sleep(milliseconds(100))
fin while.
fin action.
import dac
action main()
@dac = @dac#Channel::new(1)
for @value in 0..4095
@dac#write(@value)
@time#sleep(milliseconds(1))
fin for.
fin action.
import uart
action main()
@serial = @uart#UART::new(0)
@serial#baud(115200)
@serial#tx_pin(0)
@serial#rx_pin(1)
@serial#write("Hello, World!\r\n")
while true
if @serial#available() > 0
@data = @serial#read()
@serial#write(@data) @// Echo
fin if.
fin while.
fin action.
import i2c
action main()
@bus = @i2c#I2C::new(0)
@bus#sda_pin(4)
@bus#scl_pin(5)
@bus#frequency(400000) @// 400 kHz
@address = 0x68 @// MPU6050
@// Read WHO_AM_I register
@whoami = @bus#read_register(@address, 0x75)
@io#write_line("WHO_AM_I: " + string(@whoami))
@// Read accelerometer data
@data = @bus#read_registers(@address, 0x3B, 6)
fin action.
import spi
action main()
@bus = @spi#SPI::new(0)
@bus#sck_pin(2)
@bus#mosi_pin(3)
@bus#miso_pin(4)
@bus#frequency(1000000) @// 1 MHz
@cs = @gpio#Pin::new(5)
@cs#mode(@gpio#OUTPUT)
@cs#high()
@// Read device ID
@cs#low()
@bus#transfer([0x9F])
@id = @bus#transfer([0x00, 0x00, 0x00])
@cs#high()
fin action.
import can
action main()
@bus = @can#CAN::new(0)
@bus#baud(500000) @// 500 kbps
@bus#tx_pin(0)
@bus#rx_pin(1)
@msg = @can#Message::new(0x123)
@msg#data([0x01, 0x02, 0x03, 0x04])
@bus#send(@msg)
while true
@received = @bus#receive()
if @received != nil
process_message(@received)
fin if.
fin while.
fin action.
import timer
action main()
@timer = @timer#Timer::new(0)
@timer#frequency(1000) @// 1 kHz
@timer#callback(fn() =>
toggle_led()
fin action.)
@timer#start()
fin action.
import watchdog
action main()
@wd = @watchdog#Watchdog::new()
@wd#timeout(seconds(1))
@wd#start()
while true
@// Do work...
@wd#pet()
fin while.
fin action.
import gpio
import interrupts
action main()
@button = @gpio#Pin::new(0)
@button#mode(@gpio#INPUT)
@interrupts#enable(@button, @interrupts#EDGE_FALLING, fn() =>
handle_button_press()
fin action.)
while true
@// Low power wait
@power#wait_for_interrupt()
fin while.
fin action.
import power
action main()
while true
@// Do work...
@sensor_data = read_sensor()
@radio#send(@sensor_data)
@// Sleep for 10 seconds
@power#sleep(seconds(10))
fin while.
fin action.
import power
import gpio
action main()
@wake_pin = @gpio#Pin::new(0)
@wake_pin#mode(@gpio#INPUT)
@wake_pin#wake_on(@gpio#EDGE_RISING)
while true
@// Enter deep sleep
@power#deep_sleep()
@// Woken up by interrupt
handle_event()
fin while.
fin action.
@// Statically allocated buffer
@buffer: [Byte; 1024]
action main()
for @i in 0..1024
@buffer[@i] = 0
fin for.
fin action.
action process()
@// Allocated on stack
@data: [Byte; 256]
@// ...
fin action.

By default, Draft embedded targets have no heap. All allocation is static or stack-based.

import freertos
action main()
@rtos = @freertos#RTOS::new()
@rtos#task("blink", fn() =>
while true
toggle_led()
@rtos#delay(milliseconds(500))
fin while.
fin action.)
@rtos#task("read_sensor", fn() =>
while true
@value = read_sensor()
@rtos#queue_send("sensor_data", @value)
@rtos#delay(milliseconds(100))
fin while.
fin action.)
@rtos#start()
fin action.
Terminal window
draft run --target thumbv7em-none-eabihf --semihosting
Terminal window
draft debug --swo
Terminal window
draft debug --gdb
Terminal window
@// Flash to device
draft flash --target thumbv7em-none-eabihf
@// Flash with probe
draft flash --probe cmsis-dap
@// Flash and run
draft run --target thumbv7em-none-eabihf
[profile.release]
opt-level = "z" # Optimize for size aggressively
lto = true
panic = "abort"

Typical sizes:

Application Flash RAM
Blink LED 2 KB 256 B
Sensor reader 8 KB 1 KB
Full stack 32 KB 8 KB