Een Ruimtevaartuig-computersimulator Bouwen met Claude Code

Ik wilde begrijpen hoe computers op ruimtevaartuigen werken. De realtime planning, de stralingsbestendigheid, het vertragingstolerante netwerken dat Mars-rovers in contact houdt met de Aarde. Met Artemis II in het verschiet (de eerste bemande maanmissie in meer dan 50 jaar) leek het het juiste moment om dieper te duiken. Dus bouwde ik een simulator. Van de grond af. Met Claude Code als mijn pair programmer.

Geen fysieke hardware. Alles draait in QEMU en Docker op een laptop. Code op 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)"]

Waarom Dit Project

Ik wilde iets echts moeilijks leren. Iets waarbij de concepten onbekend zijn en het gereedschap meedogenloos is. C-compilers die ARM als doelwit hebben. Linkerscripts. Memory-mapped I/O. Interrupt-vectortabellen.

Claude Code maakte dit mogelijk. Niet omdat het alle code schreef, maar omdat het uitlegde wat een linkerscript doet terwijl we er een schreven, zodat de uitleg geworteld was in het echte probleem dat ik oploste. Hetzelfde voor volatile, FreeRTOS prioriteitspreëmptie en LTP-hertransmissietimers.

De Roadmap

Ik verdeelde het project in vier fasen, elk voortbouwend op de vorige:

FaseWatKernconcepten
Bare MetalARM-kruiscompilatie, UART-uitvoer, interrupthandlersMemory-mapped I/O, SysTick-timer, opstartassembler
FreeRTOSTaken, wachtrijen, watchdogs, prioriteitsinversieDeterministisch plannen, mutex-protocollen, preëmptie
DTNTwee-knooppuntennetwerk, gedegradeerde verbindingen, CFDP, contactgrafiekroutingBundle Protocol, opslaan-en-doorsturen, LTP-betrouwbaarheid
IntegratieVolledige telemetriepipeline met Mars-afstandsvertragingenUART-brug, tc netem, gesynchroniseerde ION OWLT

Elke fase heeft geautomatiseerde tests. Elke mijlpaal is een PR met geslaagde CI.

Fase 1: Bare Metal

De eerste uitdaging was om überhaupt iets te laten draaien. ARM-kruiscompilatie gericht op een Cortex-M3 (MPS2-AN385) in QEMU. Geen OS, geen standaardbibliotheek, geen printf.

Claude hielp me de opstartsequentie te begrijpen: de vectortabel, de resethandler, het kopiëren van .data van flash naar RAM, het nullen van .bss. Dingen die gebeuren voordat main() zelfs maar wordt uitgevoerd.

De eerste overwinning was één karakter dat op een UART-console verscheen:

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

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

Twee regels C. Geen bibliotheken. Gewoon een byte schrijven naar een geheugenadres dat toevallig verbonden is met een seriële poort. Het voelde als rechtstreeks praten met de machine.

Daarna: SysTick-timerinterrupts, interrupthandlers, gestructureerde uitvoer. De SysTick-demo configureert een hardwaretimer om te vuren op 2 Hz en telt 10 ticks:

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.

Geen gemiste ticks. Geen extra ticks. De CPU slaapt tussen interrupts door en de hardware wekt hem op op precies het juiste moment. Deterministische uitvoering vanuit bare metal.

Fase 2: FreeRTOS

Met bare metal werkend, voegde ik FreeRTOS toe, een realtime besturingssysteem dat draait op alles van medische apparaten tot satellieten.

De oefeningen werden stapsgewijs opgebouwd:

  1. Twee taken op verschillende snelheden. Basis multitasking.
  2. Wachtrij-gebaseerde communicatie. Data veilig delen tussen taken.
  3. Watchdog-timer. Vastgelopen taken detecteren en herstellen.
  4. Sensorpipeline. Vier sensoren op verschillende snelheden die een verwerkingsketen voeden.
  5. Prioriteitsinversie. De klassieke RTOS-bug triggeren en oplossen.

Hier is de sensorpipeline die opstart. Merk op hoe de 10 Hz gyroscoop de stroom domineert, met de 1 Hz temperatuurmeting ingeklemd op 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

Dat is prioriteitsgebaseerde preëmptie in actie. De gyroscoop draait op prioriteit 4, de temperatuursensor op prioriteit 1. De temperatuurmeting komt er alleen doorheen wanneer de gyroscoop de CPU niet bezet.

De prioriteitsinversieoefening was het meest leerzaam. Ik creëerde drie taken waarbij een laagprioriteitstaak een mutex vasthoudt die een hoogprioriteitstaak nodig heeft, en een middelprioriteitstaak beide uithongert. De oplossing: prioriteitsovererving, waarbij FreeRTOS tijdelijk de laagprioriteitstaak verhoogt zodat het de mutex sneller kan vrijgeven.

Dit is de bug die de Mars Pathfinder-missie in 1997 bijna het leven kostte. Het zelf bouwen maakte de leerboekuitleg begrijpelijk.

Fase 3: Vertragingstolerant Netwerken

TCP/IP werkt niet in de ruimte. Heen-en-terugtijden naar Mars variëren van 6 tot 44 minuten. Verbindingen vallen uren uit wanneer planeten het signaal occultêren. TCP’s aanname van een continue, lage-latentieverbinding valt volledig uit elkaar.

NASA JPL’s antwoord is DTN, of Vertragingstolerant Netwerken. Het Bundle Protocol slaat data lokaal op en stuurt het hop voor hop door wanneer verbindingen beschikbaar worden. Het is ontworpen voor precies de omstandigheden die het internet kapotmaken.

Ik bouwde NASA’s ION-implementatie in Docker en voerde steeds moeilijkere tests uit:

De onregelmatige verbindingstest vertelt het hele DTN-verhaal in een paar uitvoerregels:

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

Dat is opslaan-en-doorsturen in actie. De bundle wordt verzonden terwijl de verbinding volledig dood is, 100% pakketverlies. Het zit in de wachtrij van het lokale knooppunt. Op het moment dat we de netem-regel wissen en connectiviteit herstellen, hertransmitteert LTP en arriveert de bundle aan de andere kant. TCP zou allang opgegeven hebben.

Fase 4: Integratie

De laatste fase verbindt alles. FreeRTOS-firmware genereert telemetrie in QEMU. Een Python-brugscript leest de UART-uitvoer en injecteert het als DTN-bundles. De bundles doorlopen een vertraagd netwerk om een grondstation te bereiken.

De firmware start op en begint onmiddellijk te streamen:

# 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

Vier sensoren op vier verschillende snelheden. Je kunt de 10 Hz gyroscoop zien die de meeste metingen produceert, met de 2 Hz zonnesensor, 1 Hz temperatuur en 0,5 Hz batterij afgewisseld. De brug batcheert deze elke 2 seconden in DTN-bundles.

De Mars-vertragingstest bewijst dat de volledige pipeline werkt onder realistische omstandigheden:

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 bundles verzonden, maar slechts 8 geleverd tegen de tijd dat de brug afsloot. De rest was nog onderweg door de 5-secondenvertraging. Dat is de vertraging die echt is, niet gesimuleerd in software.

De Mars-vertragingssimulatie gebruikt twee gesynchroniseerde mechanismen:

  1. tc netem voegt 5 seconden echte netwerklatentie toe aan beide containers
  2. ION-bereiktabellen stellen dezelfde 5-seconden eenrichtingslichtlooptijd in zodat LTP-hertransmissietimers correct zijn

Beide moeten overeenkomen. Als de werkelijke vertraging 5 seconden is maar ION denkt dat het 1 seconde is, hertransmitteert LTP agressief en overstroomt de verbinding. Dit goed krijgen leerde me meer over protocolontwerp dan welk leerboekhoofdstuk over betrouwbaarheid ook.

Werken met Claude Code

Dit project zou me maanden hebben gekost zonder Claude. Niet omdat de code complex is (de meeste bestanden zijn minder dan 300 regels) maar omdat de leercurve voor elk domein steil is.

Wat goed werkte:

Wat zorgvuldigheid vereiste:

De Cijfers

MetriekWaarde
Totale tests50+ beweringen in 7 testsuites
TalenC, Python, Bash
HardwareGeen (QEMU + Docker)
Regels C~1.200 (firmware + bare metal)
Regels Python~1.500 (tests + brug + DTN-scripts)
ION DTN-config~350 regels in 6 .rc-bestanden

Probeer Het

Alles is open source en draait op elke Linux-machine met QEMU en 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

Geen fysieke hardware. Geen clouddiensten. Alleen een laptop en nieuwsgierigheid naar hoe computers op ruimtevaartuigen werken.