Embedded code that knows your hardware.

Volt is a typed language for microcontrollers. Realtime pin validation against your chip's datasheet before it builds, with built-in task parallelism and first-class state machines.

main.voltESP32-DevKitC
1hardware {2    pin led = 63    pin relay = 344    pin soil: Analog = 255}6setup {7    wifi.connect("home", "secret")8}9every 1s {10    relay.on()11    led.toggle()12    let v = soil.read()13}
Ready Checking pins against the datasheet… 2 errors, 1 warning Applying hints… Check passed in 16ms

Scroll on, or run it yourself.

Problems 3No problems
  1. led

    ErrorFixed main.volt:2

    GPIO 6 is connected to the SPI flash chip. Using it will crash the ESP32.

    Available output pins: 2, 4, 5, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33

    Moved from GPIO 6 to GPIO 2.

  2. relay

    ErrorFixed main.volt:3

    GPIO 34 is input-only — cannot be used as Output.

    Input-only pins: 34-39.

    Moved from GPIO 34 to GPIO 18.

  3. soil

    WarningFixed main.volt:4

    GPIO 25 is on ADC2, which is BLOCKED while WiFi is active. Analog reads will return errors or garbage.

    Use ADC1 pins instead (GPIO 32-39) which work with WiFi.

    Moved from GPIO 25 to GPIO 34.

ESP32-DevKitC V4 board, top view, with both 19-pin headers labelled EN BOOT ESP32-DevKitC V4 ESP32-WROOM-32 ESPRESSIF 3V3 EN VP VN 34 35 32 33 25 26 27 14 12 GND 13 D2 D3 CMD 5V GND 23 22 TX RX 21 GND 19 18 5 17 16 4 0 2 15 D1 D0 CLK
main.volt
1hardware {2    pin led = 23    pin motor: PWM = 44    pin battery: Analog = 355}67setup {8    wifi.connect("home", "secret") or retry(10)9    mqtt.connect("mqtt://broker.local") or retry(5)10}1112task blink {13    every 500ms { led.toggle() }14}1516task report {17    every 5s {18        mqtt.send_int("volt/battery", battery.read())19    }20}2122task drive {23    every 1ms strict { motor.set_pwm(512) }24}
Task trace over 6 seconds drive fires every millisecond from a hardware timer, a continuous band at this scale; blink toggles the LED every 500 ms; report sends once at 5 seconds and blocks on the network in its own FreeRTOS task. blink still toggles at 5.5 seconds and drive never pauses during report's wait. 0123456 s report blocks on the network; blink and drive keep time drive timer ISR, 1 kHz report FreeRTOS task blink protothread 0.00 s

Write tasks. Volt decides how they run.

There is one task keyword. The compiler reads each body and picks the cheapest way to run it safely, so a blink loop never pays for a FreeRTOS stack and a network call never stalls your motor control.

First-class state machines.

States, guards, timeouts and entry and exit actions are syntax, not a library. Doors reopen when the beam is broken, the emergency stop halts the car from any moving state, and the checker verifies every transition and flags states that can never be reached.

elevator.volt
1// hardware, floor and call-button polling omitted2machine elevator {3    initial state idle {4        on target > floor -> going_up5        on target < floor -> going_down6    }78    state going_up {9        enter { motor_up.on() }10        exit { motor_up.off() }11        on estop.is_pressed() -> stopped12        on floor == target -> doors_open13    }1415    state going_down {16        enter { motor_down.on() }17        exit { motor_down.off() }18        on estop.is_pressed() -> stopped19        on floor == target -> doors_open20    }2122    state doors_open {23        enter { door_open.on() }24        exit { door_open.off() }25        after 5s -> doors_closing26    }2728    state doors_closing {29        enter { door_close.on() }30        exit { door_close.off() }31        on beam.is_pressed() -> doors_open32        after 3s -> idle33    }3435    state stopped {36        on reset.is_pressed() -> idle37    }38}
elevator: idle goes up or down toward the target floor; arriving opens the doors; after 5 s the doors close, reopening if the beam is broken, and after 3 s it returns to idle; the emergency stop halts either moving state until reset idle going_up motor_up.on() stopped going_down motor_down.on() doors_open door_open.on() doors_closing door_close.on() target > floor target < floor estop estop reset floor == target (both) after 5s beam after 3s

Pins that look free. Aren't.

Under the module's metal can, the SPI flash that stores your program is wired to GPIO 6–11. The DevKitC breaks them out as CLK, D0–D3 and CMD. They look like free pins. Wire anything to one and the ESP32 crashes the moment it tries to read its own code.

Volt checks every pin against the datasheet and stops you before you build:

-- HARDWARE ERROR -- main.volt:2

GPIO 6 is connected to the SPI flash chip.
Using it will crash the ESP32.

Coming from Arduino or ESP-IDF

From Arduino

setup works the way you expect. Timed blocks like every 500ms replace millis() bookkeeping, and they run side by side without you writing a scheduler.

From ESP-IDF

Volt builds on ESP-IDF and FreeRTOS. You keep native performance and stop maintaining task handles, stack sizes and pin-mux tables by hand.

The toolchain

volt
One binary to check, build, flash and monitor. No Python to install.
Volt IDE
Live diagnostics as you type, formatting, and a hardware panel for wiring pins and devices.
VoltGrid
A registry of signed, reviewed device libraries you can add to a project.