Language
Program structure
The parts of a Volt program, what each one is for, and when each runs.
A Volt program is one file, main.volt. It describes the hardware, then the behaviour. Here is one with most of the parts:
hardware {
pin led: Output = 2
}
var count: int = 0
func double(x: int) -> int {
return x * 2
}
every 500ms {
led.toggle()
count = double(count) + 1
}
setup {
log("boot", "ready")
}The parts
| Part | What it’s for |
|---|---|
hardware | The pins, devices and buses, and what’s wired where. See Hardware. |
let, var | Values the whole program shares. Globals always have a starting value. |
enum, struct | Your own types. See Types and variables. |
storage | Values kept in flash, so they survive a restart. See Storage. |
channel | A queue for passing values between tasks. See Channels. |
func | A function you can call from anywhere. |
every | A block that runs on a period: every 500ms { … }. See Timing and tasks. |
on | A block that runs when a condition becomes true. |
task | A group of every blocks with state of their own. |
machine | A state machine. See State machines. |
setup | Runs once, when the board starts, before anything else. |
The parts can come in any order. The formatter (volt fmt, or Format in the IDE) puts setup last.
The order things run in
- The board starts and the hardware is set up: every pin gets its mode, every device is started.
- Globals and
storagevalues are loaded. setupruns, once, to the end.- Every
everyblock, task and state machine starts, each on its own schedule, as below.
There’s no main loop. If you’re used to Arduino, setup is the same idea; instead of loop() with delays, each thing that should happen regularly gets its own every block.
Running side by side
A Volt program usually has several every blocks, each with its own rhythm. They seem to run at the same time, but a microcontroller does one thing at a time. The blocks take turns, and each turn is so short that, to you, they all happen together.
The timeline under this program shows it the way a video editor shows clips. Each lane is one block, and each clip is one turn. The playhead moves in real time, so the clips light up when the board would run them.
hardware {
pin led: Output = 2
pin button: PullUpInput = 23
pin fan: Output = 4
pin probe: Analog = 34
}
every 500ms {
led.toggle()
}
every 100ms {
if button.is_pressed() { fan.on() }
}
every 1s {
let raw: int = probe.read()
log("probe", "{raw}")
}every 500msled.toggle()every 100msif button.is_pressed() { fan.on() }every 1slet raw: int = probe.read()Read it like this:
- The lanes don’t wait for each other. The 100 ms lane keeps its beat whatever the others are doing. You never write the timing yourself: no
delay, no counting loops. The compiler builds the scheduler that wakes each block when its next turn is due. - A clip is short. Toggling an LED or reading a pin takes a few microseconds, not milliseconds, so the clips are drawn wider than they really are. Between clips the chip has nothing to do; the scheduler lets it idle.
- When two lanes are due together, as at
0sand1shere, their turns run one after the other, a few microseconds apart. A block always finishes its turn before the next one starts, so two blocks never trip over a shared variable halfway through. - A turn has to end. A block that loops for a long time holds up every lane. Anything that genuinely waits, like Wi-Fi, MQTT or HTTP, is moved by the compiler onto its own task, so the waiting happens off this timeline.
strictblocks cut in. Anevery 1ms strictblock runs from a hardware timer and interrupts whatever is running, so it keeps exact time. That’s why it has to be short and can’t wait for anything.
Try it. Press Check, then change
every 100mstoevery 250ms, or add a block of your own. The timeline redraws as you type.
Functions
Functions take typed parameters and return a typed value:
func average(a: float, b: float) -> float {
return (a + b) / 2.0
}
func announce(name: string) {
log("hello", "hi {name}")
}
setup {
announce("Volt")
let mid: float = average(1.0, 3.0)
log("math", "mid={mid}")
}A function with no -> returns nothing.
Comments and layout
// comments to the end of the line, and /* … */ can span lines. There are no semicolons: one statement per line, and blocks are wrapped in { }.