使用Claude Code构建航天器计算模拟器

我想了解航天器计算机是如何工作的。实时调度、辐射加固、让火星探测器与地球保持通信的延迟容忍网络。随着阿尔忒弥斯II号(50多年来首次载人登月任务)即将到来,现在似乎是深入研究的好时机。于是我从头开始构建了一个模拟器,以Claude Code作为我的结对编程伙伴。

无需物理硬件。一切都在笔记本电脑上的QEMU和Docker中运行。代码在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)"]

为什么做这个项目

我想学一些真正困难的东西。那种概念陌生、工具严苛的领域。针对ARM的C编译器、链接器脚本、内存映射I/O、中断向量表。

Claude Code使这成为可能。不是因为它写了所有代码,而是因为它在我们编写链接器脚本的过程中解释了链接器脚本的作用,所以解释植根于我正在解决的实际问题。volatile、FreeRTOS优先级抢占和LTP重传定时器也是如此。

路线图

我将项目分为四个阶段,每个阶段都在上一个阶段的基础上构建:

阶段内容核心概念
裸机ARM交叉编译、UART输出、中断处理程序内存映射I/O、SysTick定时器、启动汇编
FreeRTOS任务、队列、看门狗、优先级反转确定性调度、互斥锁协议、抢占
DTN双节点网络、降级链路、CFDP、联系图路由Bundle协议、存储转发、LTP可靠性
集成具有火星距离延迟的完整遥测管道UART桥接、tc netem、同步ION OWLT

每个阶段都有自动化测试。每个里程碑都是通过CI的PR。

第一阶段:裸机

第一个挑战是让任何东西运行起来。在QEMU中针对Cortex-M3(MPS2-AN385)进行ARM交叉编译。没有操作系统,没有标准库,没有printf

Claude帮我理解了启动序列:向量表、复位处理程序、将.data从flash复制到RAM、将.bss清零。这些都发生在main()运行之前。

第一个成果是UART控制台上出现的单个字符:

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

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

两行C代码。没有库。只是将一个字节写入碰巧连接到串口的内存地址。感觉像是在直接与机器对话。

从那里:SysTick定时器中断、中断处理程序、结构化输出。SysTick演示配置硬件定时器以2Hz触发并计数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.

没有丢失的tick,没有多余的tick。CPU在中断之间休眠,硬件在恰好正确的时刻唤醒它。裸机上的确定性执行。

第二阶段:FreeRTOS

裸机工作正常后,我添加了FreeRTOS——一个运行在从医疗设备到卫星的所有设备上的实时操作系统。

练习是逐步构建的:

  1. 不同速率的两个任务。 基本多任务处理。
  2. 基于队列的通信。 在任务之间安全地共享数据。
  3. 看门狗定时器。 检测和从挂起的任务中恢复。
  4. 传感器管道。 四个不同速率的传感器馈送处理链。
  5. 优先级反转。 触发并解决经典的RTOS错误。

这是传感器管道启动的情况。注意10Hz陀螺仪主导数据流,1Hz温度读数在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

这就是基于优先级的抢占实际运行的样子。陀螺仪以优先级4运行,温度传感器以优先级1运行。只有在陀螺仪不占用CPU时,温度读数才能通过。

优先级反转练习是最有教育意义的。我创建了三个任务,其中低优先级任务持有高优先级任务需要的互斥锁,而中优先级任务使两者都陷入饥饿状态。解决方案:优先级继承,FreeRTOS临时提升低优先级任务,使其能更快地释放互斥锁。

这是1997年几乎毁掉火星探路者任务的那个错误。自己构建它使教科书上的解释豁然开朗。

第三阶段:延迟容忍网络

TCP/IP在太空中不起作用。到火星的往返时间从6分钟到44分钟不等。当行星遮蔽信号时,链路会中断数小时。TCP关于持续低延迟连接的假设完全瓦解。

NASA JPL的答案是DTN(延迟容忍网络)。Bundle协议在本地存储数据,并在链路可用时逐跳转发。它正是为打垮互联网的那些条件而设计的。

我在Docker中构建了NASA的ION实现,并运行了逐渐困难的测试:

间歇性链路测试在几行输出中讲述了整个DTN的故事:

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

这就是存储转发的实际运作。bundle在链路完全断开(100%丢包)时发送,在本地节点的队列中等待。当我们清除netem规则并恢复连接的那一刻,LTP重传,bundle到达另一端。TCP早就放弃了。

第四阶段:集成

最后阶段将一切连接起来。FreeRTOS固件在QEMU中生成遥测数据。Python桥接脚本读取UART输出并将其注入为DTN bundle。bundle穿越延迟网络到达地面站。

固件启动后立即开始流传输:

# 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

四个不同速率的四个传感器。可以看到10Hz陀螺仪产生大多数读数,2Hz太阳传感器、1Hz温度和0.5Hz电池交错其中。桥接器每2秒将这些打包成DTN bundle。

火星延迟测试证明整个管道在真实条件下工作:

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,但桥接器退出时只有8个已送达。其余的还在5秒延迟中传输。这个延迟是真实的,不是在软件中模拟的。

火星延迟模拟使用两个同步机制:

  1. tc netem为两个容器添加5秒的实际网络延迟
  2. ION范围表设置相同的5秒单向光传播时间,使LTP重传定时器正确

两者必须一致。如果实际延迟是5秒但ION认为是1秒,LTP会积极地重传并淹没链路。把这个弄对让我从任何关于可靠性的教科书章节中学到了更多关于协议设计的知识。

与Claude Code合作

没有Claude,这个项目会花我数月时间。不是因为代码复杂(大多数文件不到300行),而是因为每个领域的学习曲线都很陡峭。

效果好的方面:

需要谨慎的方面:

数字

指标
总测试数7个测试套件中的50多个断言
语言C、Python、Bash
硬件无(QEMU + Docker)
C代码行数约1,200行(固件 + 裸机)
Python代码行数约1,500行(测试 + 桥接 + DTN脚本)
ION DTN配置6个.rc文件中约350行

试试看

一切都是开源的,可以在任何装有QEMU和Docker的Linux机器上运行:

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

无需物理硬件。无需云服务。只需一台笔记本电脑和对航天器计算机如何工作的好奇心。