advanced
Embedded Programming
Write firmware for microcontrollers with Draft. Learn bare-metal setup, GPIO, interrupts, and no-std environments.
Learning Objectives
- Set up a no-std bare-metal project
- Control GPIO pins
- Handle interrupts
- Write firmware for a microcontroller
Prerequisites
- Completed Concurrency and Parallelism
- Basic knowledge of electronics and microcontrollers
- A supported microcontroller board (e.g., STM32, Raspberry Pi Pico)
Bare-Metal Setup
Create a no-std project:
mkdir draft-embeddedcd draft-embeddedtouch main.draftConfigure the project to target embedded:
[project]name = "draft-embedded"version = "0.1.0"
[target]std = falseplatform = "stm32f4"Build for the target:
draft build --target stm32f4GPIO
General Purpose Input/Output lets you control hardware pins.
import gpioimport delay
action main() // Configure LED pin as output @led = gpio.pin(13) led.mode = gpio.OUTPUT
loop led.high() delay.ms(500) led.low() delay.ms(500) againfin action.Reading a Button
import gpio
action main() @button = gpio.pin(0) button.mode = gpio.INPUT_PULLUP @led = gpio.pin(13) led.mode = gpio.OUTPUT
loop if button.read() == gpio.LOW then led.high() else led.low() fi againfin action.Interrupts
Interrupts let your code respond immediately to hardware events.
import gpioimport interrupt
action main() @button = gpio.pin(0) button.mode = gpio.INPUT_PULLUP
@led = gpio.pin(13) led.mode = gpio.OUTPUT
// Attach interrupt on falling edge interrupt.attach(button, gpio.FALLING, on_button_press)
loop // Main loop does other work delay.ms(100) againfin action.
action on_button_press() // Toggle LED from interrupt context @led = gpio.pin(13) led.toggle()fin action.Interrupt Best Practices
- Keep interrupt handlers short and fast.
- Use volatile variables to communicate between ISR and main loop.
- Avoid heavy computation inside interrupts.
No-std Environment
In a no-std environment, the standard library is not available. Use core and alloc instead:
import coreimport alloc
action main() // Allocate memory manually @buffer = alloc.alloc(256)
loop i from 0 to 255 buffer[i] = i again
// Process buffer... alloc.free(buffer)fin action.Available Modules in no-std
| Module | Purpose |
|---|---|
core |
Primitive types, iterators, memory ops |
alloc |
Heap allocation (if enabled) |
gpio |
Hardware pin control |
delay |
Busy-wait and timer delays |
uart |
Serial communication |
i2c |
I2C bus communication |
spi |
SPI bus communication |
Complete Example: Temperature Monitor
import gpioimport i2cimport uartimport delay
const TEMP_SENSOR_ADDR = 0x48const LED_PIN = 13const BAUD_RATE = 115200
action main() // Initialize hardware @led = gpio.pin(LED_PIN) led.mode = gpio.OUTPUT
@sensor = i2c.device(0, TEMP_SENSOR_ADDR) @serial = uart.device(1, BAUD_RATE)
serial.write("Temperature Monitor\r\n")
loop // Read temperature from I2C sensor @raw = sensor.read_register(0x00) @temp_celsius = raw / 256.0
// Format output @line = "Temp: " + temp_celsius + " C\r\n" serial.write(line)
// Blink LED led.high() delay.ms(100) led.low() delay.ms(900) againfin action.Try It Yourself
- Flash the firmware to your microcontroller.
- Open a serial monitor at 115200 baud.
- Verify temperature readings appear every second.
Checkpoint
-
How do you configure a pin as output?
pin.mode = gpio.OUTPUTgpio.output(pin)pin.set_mode(OUTPUT)
-
What does
interrupt.attachdo?- Configures a timer
- Attaches an interrupt handler to a pin
- Enables global interrupts
-
Which module provides I2C communication?
i2cwirebus
Answers
pin.mode = gpio.OUTPUT- Attaches an interrupt handler to a pin
i2c
Summary
Embedded Draft lets you write efficient firmware with a simple syntax. You configured GPIO, used interrupts, and built a no-std application that reads from a sensor and outputs to serial.
Next Steps
- Game Development — Build 2D games with Draft.
