Search

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:

Terminal window
mkdir draft-embedded
cd draft-embedded
touch main.draft

Configure the project to target embedded:

draft.toml
[project]
name = "draft-embedded"
version = "0.1.0"
[target]
std = false
platform = "stm32f4"

Build for the target:

Terminal window
draft build --target stm32f4

GPIO

General Purpose Input/Output lets you control hardware pins.

import gpio
import 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)
again
fin 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
again
fin action.

Interrupts

Interrupts let your code respond immediately to hardware events.

import gpio
import 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)
again
fin 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 core
import 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 gpio
import i2c
import uart
import delay
const TEMP_SENSOR_ADDR = 0x48
const LED_PIN = 13
const 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)
again
fin action.

Try It Yourself

  1. Flash the firmware to your microcontroller.
  2. Open a serial monitor at 115200 baud.
  3. Verify temperature readings appear every second.

Checkpoint

  1. How do you configure a pin as output?

    • pin.mode = gpio.OUTPUT
    • gpio.output(pin)
    • pin.set_mode(OUTPUT)
  2. What does interrupt.attach do?

    • Configures a timer
    • Attaches an interrupt handler to a pin
    • Enables global interrupts
  3. Which module provides I2C communication?

    • i2c
    • wire
    • bus
Answers
  1. pin.mode = gpio.OUTPUT
  2. Attaches an interrupt handler to a pin
  3. 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