044 Project 44: TinyGo Cooperative Tasks (Soft RT)
044 TinyGo: Cooperative Multi-Tasking Without a Full RTOS
Implement a tiny cooperative scheduler: multiple tasks share one core by yielding, with per-task periods—useful mental model for soft realtime systems and for understanding why preemptive RTOS exists.
Theory (short)
| Model | Description |
|---|---|
| Cooperative | Task runs until it yields; bad task starves others |
| Preemptive RTOS | Timer IRQ switches tasks; needs careful shared data |
| This lab | Cooperative + fixed time slices via time checks |
loop forever:
for each task:
if now >= task.next:
task.run()
task.next += task.period
Step 1: Task table
package main
import (
"fmt"
"machine"
"time"
)
type Task struct {
Name string
Period time.Duration
Next time.Time
Run func()
}
func main() {
machine.Serial.Configure(machine.UARTConfig{BaudRate: 115200})
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
var blinkOn bool
var ticks int
tasks := []Task{
{
Name: "blink",
Period: 500 * time.Millisecond,
Run: func() {
blinkOn = !blinkOn
led.Set(blinkOn)
},
},
{
Name: "heartbeat",
Period: 1 * time.Second,
Run: func() {
ticks++
fmt.Fprintf(machine.Serial, "hb ticks=%d\n", ticks)
},
},
{
Name: "sensor",
Period: 100 * time.Millisecond,
Run: func() {
// pretend sample
_ = time.Now().UnixNano() % 100
},
},
}
now := time.Now()
for i := range tasks {
tasks[i].Next = now.Add(tasks[i].Period)
}
for {
now = time.Now()
for i := range tasks {
t := &tasks[i]
if !now.Before(t.Next) {
t.Run()
t.Next = t.Next.Add(t.Period)
// if still behind, skip ahead
if now.After(t.Next) {
t.Next = now.Add(t.Period)
}
}
}
time.Sleep(1 * time.Millisecond) // cooperative idle
}
}Step 2: Inject a “bad” task
Add a task that time.Sleep(200 * time.Millisecond) occasionally. Observe heartbeat jitter. Lesson: cooperative scheduling fails open when a task hogs the CPU.
Step 3: Metrics
Track:
runsper task
latecount whennow.Sub(scheduled) > threshold
- Print every 5 s over UART
Step 4: Design write-up
In NOTES.md:
- When cooperative is enough (UI blink, slow sensors).
- When you need interrupts/RTOS (motor control, strict deadlines).
- How TinyGo’s model maps to your board.
Learning Goals
- Multi-rate task tables
- Soft deadline tracking
- Starvation under cooperative scheduling
- Honest boundary vs FreeRTOS-class systems
Extensions
- Priority: run shortest period first each cycle.
- Disable a task via serial command.
- Port one task to pin interrupt (from Project 42).
Checkpoint
- ≥3 tasks with different periods
- UART shows heartbeat
- Bad-task experiment documented
- NOTES.md with RT conclusions