Volt Docs

Language

Timing and tasks

Run code on a period with every, react to events with on, and group work into tasks. The compiler schedules all of it.

every

every runs a block on a fixed period. A program can have as many as it needs, each at its own rate:

main.volt
hardware {
    pin led: Output = 2
    pin status: Output = 4
}

every 1s { led.toggle() }

every 100ms { status.toggle() }
0.00s
0ms500ms1s1.5s2s
every 1sled.toggle()
every 100msstatus.toggle()

Try it. Press Check, then change 1s to 1. A plain number isn’t a duration; the fix offers 1ms or 1s.

The blocks don’t wait for each other: the 100 ms block keeps its rhythm while the one-second block waits for its next turn. The timeline shows each turn as a clip; Running side by side explains how they share the chip.

when

when skips a turn while its condition is false:

main.volt
var presses: int = 0

every 10s when wifi.is_connected() {
    mqtt.send_int("presses", presses)
}

setup {
    wifi.connect("home", "secret") or retry(5)
    mqtt.connect("mqtt://broker.local") or retry(3)
}

strict

every … strict runs from a hardware timer, so it keeps exact time even while other code is busy. Keep it short, and don’t call anything that waits, like Wi-Fi or sleep; the compiler reports any call that isn’t allowed there.

main.volt
hardware {
    pin motor: PWM = 4
}

every 1ms strict { motor.set_pwm(100) }

on

on runs a block each time a condition becomes true. debounce ignores changes that come quicker than a time you choose, which is what a mechanical button needs:

main.volt
hardware {
    pin button: PullUpInput = 23
    pin led: Output = 2
}

on button.is_pressed() debounce 50ms {
    led.toggle()
}

Tasks

A task groups every blocks with state that belongs only to them:

main.volt
hardware {
    pin button: PullUpInput = 23
}

var presses: int = 0

task buttons {
    var last: int = 1
    every 20ms {
        let now: int = button.read()
        if now == 0 && last == 1 {
            presses += 1
        }
        last = now
    }
}

How it’s scheduled

You don’t choose how things run; the compiler works it out from what each block does:

  • Plain every blocks and tasks share one cooperative scheduler. It’s light, and nothing can interrupt a block halfway through, so shared values are safe.
  • A task that calls something that waits, like Wi-Fi, MQTT, HTTP or a modem, gets its own RTOS task, so the wait doesn’t hold up the rest.
  • strict blocks run from a timer interrupt.

When values are shared between parts that run separately, the compiler makes the access safe.

Waiting

sleep(duration) pauses the block it’s in; the rest of the program keeps running. deep_sleep(duration) powers the chip down, and it starts again from the beginning when it wakes; anything in storage survives.