041 Project 41: TinyGo Blink & Toolchain
041 TinyGo: Toolchain, Targets, and Blink
Build your first TinyGo firmware loop: install the toolchain, pick a board target, flash (or run in the WASM/simulator path), and understand how TinyGo differs from desktop Go.
Baseline: TinyGo current stable (track tinygo.org release notes); Go 1.22+ host toolchain recommended for building TinyGo itself if you compile from source.
Why TinyGo
| Desktop Go | TinyGo |
|---|---|
| GC, large runtime, OS threads | Smaller runtime, selective GC / freelist modes by target |
net/http, rich stdlib |
Subset of stdlib + machine / board packages |
go build → OS binary |
tinygo build -target=... → firmware / WASM |
| Soft realtime only | Soft realtime loops + hardware interrupts (not hard RTOS guarantees) |
TinyGo is still Go syntax and modules, but the target decides available packages and size.
Host (dev machine) Device / WASM
───────────────── ─────────────
tinygo build -target=pico ──flash──► MCU firmware
tinygo run -target=wasip1 ─────────► wasmtime / browser
Step 1: Install TinyGo
# macOS (Homebrew example — verify current formula)
brew install tinygo
# or follow https://tinygo.org/getting-started/install/
tinygo version
go version # host Go still useful for module toolsList targets:
tinygo targets | head
tinygo info pico # Raspberry Pi Pico example
tinygo info wasip1 # WASM for host experiments without hardwareStep 2: Module + blink (board-agnostic pattern)
mkdir tiny-blink && cd tiny-blink
go mod init example.com/tiny-blinkmain.go — replace pin with your board’s LED (see board docs):
package main
import (
"machine"
"time"
)
func main() {
led := machine.LED // many boards define LED; else machine.GPIO25 etc.
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
for {
led.High()
time.Sleep(500 * time.Millisecond)
led.Low()
time.Sleep(500 * time.Millisecond)
}
}No hardware? Use WASM smoke build
# Compile-only check that TinyGo accepts the package shape
# (blink needs machine.LED — for pure host, use the wasip1 project below)
tinygo build -o /tmp/check.elf -target=pico .Host-only hello (no GPIO):
// main_wasip1.go — build with -target=wasip1
package main
import "fmt"
func main() {
fmt.Println("tinygo wasip1 ok")
}tinygo run -target=wasip1 .
# or: tinygo build -o hello.wasm -target=wasip1 . && wasmtime hello.wasmStep 3: Flash (hardware)
Examples (exact flags change by board/probe):
# Pico UF2 style (common workflow)
tinygo flash -target=pico .
# Some boards use -port
# tinygo flash -target=arduino-nano33 -port=/dev/ttyACM0 .If flash fails: check udev rules (Linux), cable is data-capable, bootloader mode (Pico BOOTSEL).
Step 4: Size and optimization awareness
tinygo build -o firmware.elf -target=pico -size=full .Note code/data/BSS. Optimization flags (-opt=z etc.) matter on small MCUs—read current TinyGo docs for supported -opt values.
Learning Goals
- Install and verify TinyGo; list targets
- Separate host Go vs device build
- Produce a continuous loop firmware or WASM hello
- Know where to look for pin definitions (
machine, board packages)
Extensions
- Blink two LEDs out of phase.
- Print
time.Now()over USB serial if the target supports it (machine.Serial).
- Compare binary size with
-optvariants.
Common gotchas
| Issue | Fix |
|---|---|
machine.LED undefined |
Wrong target or board; set pin explicitly |
| Flash permission denied | udev / cable / bootloader mode |
Import works in go build but not TinyGo |
Package not in TinyGo stdlib subset |
| Expect hard realtime | Soft RT only; measure jitter on real hardware |
Checkpoint
tinygo versionworks
- Built for at least one target
- Blink or wasip1 hello runs
- Notes: board name + pin used