Costruire un Simulatore di Calcolo Spaziale con Claude Code

Volevo capire come funzionano i computer delle navicelle spaziali. La pianificazione in tempo reale, la protezione dalle radiazioni, le reti tolleranti ai ritardi che mantengono i rover su Marte in comunicazione con la Terra. Con Artemis II all’orizzonte (la prima missione lunare con equipaggio in oltre 50 anni) sembrava il momento giusto per approfondire. Così ho costruito un simulatore. Da zero. Con Claude Code come mio pair programmer.

Nessun hardware fisico. Tutto gira in QEMU e Docker su un laptop. Codice su GitHub.

flowchart LR
    QEMU["QEMU (Cortex-M3)"] -->|UART socket| Bridge[uart_bridge.py]
    Bridge -->|bpsendfile| SC[Spacecraft Node]
    SC -->|LTP + 5s delay| GS[Ground Station]

    subgraph Docker
        SC
        GS
    end
flowchart TB
    QEMU["QEMU (Cortex-M3)"] -->|UART socket| Bridge[uart_bridge.py]
    Bridge -->|bpsendfile| SC["Spacecraft Node (Docker)"]
    SC -->|LTP + 5s delay| GS["Ground Station (Docker)"]

Perché Questo Progetto

Volevo imparare qualcosa di genuinamente difficile. Qualcosa in cui i concetti sono sconosciuti e gli strumenti sono implacabili. Compilatori C che puntano ad ARM. Script del linker. I/O mappata in memoria. Tabelle dei vettori di interrupt.

Claude Code ha reso tutto ciò possibile. Non perché scrivesse tutto il codice, ma perché spiegava cosa fa uno script del linker mentre ne stavamo scrivendo uno, così la spiegazione era radicata nel problema reale che stavo risolvendo. Lo stesso per volatile, la preemption per priorità di FreeRTOS e i timer di ritrasmissione LTP.

La Roadmap

Ho suddiviso il progetto in quattro fasi, ognuna costruita sulla precedente:

FaseCosaConcetti Chiave
Bare MetalCross-compilazione ARM, output UART, gestori di interruptI/O mappata in memoria, timer SysTick, assembly di avvio
FreeRTOSTask, code, watchdog, inversione di prioritàScheduling deterministico, protocolli mutex, preemption
DTNRete a due nodi, collegamenti degradati, CFDP, routing con grafo dei contattiBundle Protocol, store-and-forward, affidabilità LTP
IntegrazionePipeline di telemetria completa con ritardi a distanza di MarteBridge UART, tc netem, ION OWLT sincronizzato

Ogni fase ha test automatizzati. Ogni milestone è una PR con CI superato.

Fase 1: Bare Metal

La prima sfida era far girare qualcosa. Cross-compilazione ARM puntando a un Cortex-M3 (MPS2-AN385) in QEMU. Nessun OS, nessuna libreria standard, nessun printf.

Claude mi ha aiutato a capire la sequenza di avvio: la tabella dei vettori, il gestore di reset, la copia di .data da flash a RAM, l’azzeramento di .bss. Cose che accadono prima che main() venga persino eseguita.

La prima vittoria è stata un singolo carattere che appariva su una console UART:

#define UART0_DR  (*(volatile uint32_t *)0x40004000)

void uart_putc(char c) {
    UART0_DR = c;
}

Due righe di C. Nessuna libreria. Solo scrivere un byte in un indirizzo di memoria che casualmente è collegato a una porta seriale. Sembrava parlare direttamente con la macchina.

Da lì: interrupt del timer SysTick, gestori di interrupt, output strutturato. La demo SysTick configura un timer hardware per scattare a 2 Hz e conta 10 tick:

SysTick interrupt demo
======================
Ticking at 2 Hz for 5 seconds (10 ticks)...

  tick 1
  tick 2
  tick 3
  tick 4
  tick 5
  tick 6
  tick 7
  tick 8
  tick 9
  tick 10

Done — 5 seconds counted by interrupt.

Nessun tick perso. Nessun tick extra. La CPU dorme tra gli interrupt e l’hardware la sveglia esattamente al momento giusto. Esecuzione deterministica dal bare metal.

Fase 2: FreeRTOS

Con il bare metal funzionante, ho aggiunto FreeRTOS, un sistema operativo in tempo reale che gira su tutto, dai dispositivi medici ai satelliti.

Gli esercizi sono stati costruiti progressivamente:

  1. Due task a ritmi diversi. Multitasking di base.
  2. Comunicazione basata su code. Condivisione sicura dei dati tra task.
  3. Timer watchdog. Rilevare e recuperare da task bloccati.
  4. Pipeline di sensori. Quattro sensori a ritmi diversi che alimentano una catena di elaborazione.
  5. Inversione di priorità. Innescare e risolvere il bug classico degli RTOS.

Ecco la pipeline di sensori che si avvia. Notate il giroscopio a 10 Hz che domina il flusso, con la lettura della temperatura a 1 Hz inserita al tick 1001:

FreeRTOS Sensor Pipeline Demo
=============================

[GYRO]  Sensor online (10 Hz, priority 4)
[PROC]  Processor online (priority 3)
[TELEM] Telemetry online (priority 2)
[TEMP]  Sensor online (1 Hz, priority 1)
[TELEM] #000 GYRO: 300 at tick 100
[TELEM] #001 GYRO: 300 at tick 200
  ...
[TELEM] #009 GYRO: 300 at tick 1000
[TELEM] #010 TEMP: 1991 at tick 1001
[TELEM] #011 GYRO: 300 at tick 1100

Questa è la preemption basata sulla priorità in azione. Il giroscopio gira alla priorità 4, il sensore di temperatura alla priorità 1. La lettura della temperatura passa solo quando il giroscopio non occupa la CPU.

L’esercizio sull’inversione di priorità è stato il più istruttivo. Ho creato tre task in cui un task a bassa priorità tiene un mutex di cui ha bisogno un task ad alta priorità, e un task a priorità media li fa morire di fame entrambi. La soluzione: ereditarietà delle priorità, dove FreeRTOS aumenta temporaneamente il task a bassa priorità in modo che possa rilasciare il mutex più velocemente.

Questo è il bug che quasi uccise la missione Mars Pathfinder nel 1997. Costruirlo da solo ha fatto scattare la spiegazione del libro di testo.

Fase 3: Reti Tolleranti ai Ritardi

TCP/IP non funziona nello spazio. I tempi di andata e ritorno verso Marte vanno da 6 a 44 minuti. I collegamenti cadono per ore quando i pianeti occultano il segnale. L’assunzione di TCP di una connessione continua e a bassa latenza si sgretola completamente.

La risposta di NASA JPL è DTN, o Delay-Tolerant Networking. Il Bundle Protocol archivia i dati localmente e li inoltra hop per hop quando i collegamenti diventano disponibili. È progettato esattamente per le condizioni che mandano in tilt internet.

Ho costruito l’implementazione ION della NASA in Docker ed eseguito test progressivamente più difficili:

Il test del collegamento intermittente racconta tutta la storia DTN in poche righe di output:

Test: intermittent link (send during outage, deliver on recovery)...
    qdisc: qdisc netem root refcnt 2 limit 1000 loss 100%
    confirmed: bundle queued (link is down)
    restoring link...
  PASS: bundle held during outage
  PASS: bundle delivered after link recovery

Questo è lo store-and-forward in azione. Il bundle viene inviato mentre il collegamento è completamente morto, 100% di perdita di pacchetti. Si trova nella coda del nodo locale. Nel momento in cui cancelliamo la regola netem e ripristiniamo la connettività, LTP ritrasmette e il bundle arriva all’altro capo. TCP si sarebbe arreso molto tempo fa.

Fase 4: Integrazione

La fase finale collega tutto. Il firmware FreeRTOS genera telemetria in QEMU. Uno script bridge Python legge l’output UART e lo inietta come bundle DTN. I bundle attraversano una rete ritardata per raggiungere una stazione di terra.

Il firmware si avvia e inizia immediatamente a trasmettere:

# Spacecraft Telemetry Firmware v1.0
# ===================================
# GYRO sensor online (10 Hz, priority 4)
# Processor online (priority 3)
# Telemetry online (priority 2)
# TEMP sensor online (1 Hz, priority 1)
# BATT sensor online (0.5 Hz, priority 1)
# SUN sensor online (2 Hz, priority 1)
$TELEM,0000,GYRO,300,100
$TELEM,0001,GYRO,300,200
  ...
$TELEM,0005,SUN,801,501
  ...
$TELEM,0011,TEMP,1991,1001
$TELEM,0012,SUN,801,1001

Quattro sensori a quattro ritmi diversi. Si può vedere il giroscopio a 10 Hz che produce la maggior parte delle letture, con il sensore solare a 2 Hz, la temperatura a 1 Hz e la batteria a 0,5 Hz intercalati. Il bridge raggruppa questi in bundle DTN ogni 2 secondi.

Il test del ritardo di Marte dimostra che l’intera pipeline funziona in condizioni realistiche:

Running Mars-delay end-to-end tests...
  PASS: tc netem delay 5s applied on spacecraft
  PASS: bridge exited cleanly
  PASS: bridge read telemetry lines
  PASS: bridge sent >= 1 bundle (got 12)
  PASS: ground station received files (got 8)
  PASS: received telemetry lines (got 224)
  PASS: telemetry CSV format valid (5 fields)
  INFO: delay observable (8 delivered at bridge exit < 12 sent)
7 passed, 0 failed

12 bundle inviati, ma solo 8 consegnati quando il bridge è uscito. Il resto era ancora in volo attraverso il ritardo di 5 secondi. Questo è il ritardo che è reale, non simulato nel software.

La simulazione del ritardo di Marte usa due meccanismi sincronizzati:

  1. tc netem aggiunge 5 secondi di latenza di rete reale a entrambi i container
  2. Tabelle di portata ION impostano lo stesso tempo di luce unidirezionale di 5 secondi in modo che i timer di ritrasmissione LTP siano corretti

Entrambi devono concordare. Se il ritardo effettivo è di 5 secondi ma ION pensa che sia di 1 secondo, LTP ritrasmette aggressivamente e inonda il collegamento. Farlo bene mi ha insegnato più sul design dei protocolli di qualsiasi capitolo di libro di testo sull’affidabilità.

Lavorare con Claude Code

Questo progetto mi avrebbe richiesto mesi senza Claude. Non perché il codice sia complesso (la maggior parte dei file è sotto le 300 righe) ma perché la curva di apprendimento per ogni dominio è ripida.

Cosa ha funzionato bene:

Cosa ha richiesto attenzione:

I Numeri

MetricaValore
Test totali50+ asserzioni in 7 suite di test
LinguaggiC, Python, Bash
HardwareNessuno (QEMU + Docker)
Righe di C~1.200 (firmware + bare metal)
Righe di Python~1.500 (test + bridge + script DTN)
Config ION DTN~350 righe in 6 file .rc

Prova

Tutto è open source e gira su qualsiasi macchina Linux con QEMU e Docker:

sudo apt install gcc-arm-none-eabi qemu-system-arm build-essential
git clone https://github.com/granda/spacecraft-computing-sim
cd spacecraft-computing-sim

make -C bare-metal run          # bare metal UART output
make -C freertos run            # FreeRTOS sensor pipeline
make -C dtn test                # two-node DTN network
make -C integration test-bridge # full telemetry pipeline
make -C integration test-mars-delay  # Mars-distance delays

Nessun hardware fisico. Nessun servizio cloud. Solo un laptop e curiosità su come funzionano i computer delle navicelle spaziali.