043 Project 43: TinyGo UART Sensor Loop

Updated

July 30, 2026

043 TinyGo: UART Console + Timed Sensor Loop

Build a periodic control loop: sample a sensor (or simulated ADC), emit CSV over serial, and keep loop timing steady enough for soft realtime logging.

Architecture

  tick every T ms
        │
        ▼
  read sensor / mock ──► filter (EMA) ──► UART CSV line
        │
        └── deadline miss counter++

Step 1: Serial hello

package main

import (
    "fmt"
    "machine"
    "time"
)

func main() {
    machine.Serial.Configure(machine.UARTConfig{BaudRate: 115200})
    for {
        fmt.Fprintf(machine.Serial, "hello tinygo %d\n", time.Now().UnixNano())
        time.Sleep(time.Second)
    }
}

Host:

# Linux example — adjust device
screen /dev/ttyACM0 115200
# or: picocom -b 115200 /dev/ttyACM0

Step 2: Fixed-rate loop with miss detection

package main

import (
    "fmt"
    "machine"
    "time"
)

const period = 50 * time.Millisecond // 20 Hz

func readMockSensor() int {
    // Replace with machine.ADC when available on your board
    return int(time.Now().UnixNano()%1000) / 10 // 0..99 fake
}

func main() {
    machine.Serial.Configure(machine.UARTConfig{BaudRate: 115200})

    var ema float32
    var misses int
    next := time.Now()

    for {
        start := time.Now()
        if start.After(next.Add(period / 2)) {
            misses++
        }

        raw := readMockSensor()
        // EMA: y = αx + (1-α)y
        const alpha = 0.2
        ema = alpha*float32(raw) + (1-alpha)*ema

        fmt.Fprintf(machine.Serial, "t_ns=%d raw=%d ema=%.2f misses=%d\n",
            start.UnixNano(), raw, ema, misses)

        next = next.Add(period)
        if d := time.Until(next); d > 0 {
            time.Sleep(d)
        } else {
            // fell behind — resync
            next = time.Now().Add(period)
            misses++
        }
    }
}

Step 3: Real ADC (when target supports it)

Concept:

import "machine"

var adc machine.ADC

func initADC() {
    adc = machine.ADC{Pin: machine.ADC0} // board-specific
    adc.Configure(machine.ADCConfig{})
}

func readMockSensor() int {
    return int(adc.Get()) // width depends on target
}

Consult your board’s pinout. If no ADC: keep mock and still complete timing/miss metrics.

Step 4: Soft realtime checklist

Practice Why
Fixed period Stable sample rate for filters
Count deadline misses Visibility when loop overruns
Avoid allocs in hot loop GC/heap pressure on tiny heaps
Short UART lines Don’t block forever on full buffers

Optional: build with size report and note RAM pressure.

Learning Goals

  • UART as primary debug channel
  • Timed loops and miss accounting
  • Simple digital filter (EMA)
  • Path to real ADC sensors

Extensions

  1. Change rate via serial command (r 100 → 100 ms).
  2. Threshold alarm: print ALERT when ema exceeds N.
  3. Dual-rate: fast sample, slow print.

Checkpoint

  • Serial output visible on host
  • Fixed-rate loop with miss counter
  • EMA or similar filter
  • Notes: period chosen and why