Создание симулятора бортового компьютера космического аппарата с Claude Code
Я хотел понять, как работают компьютеры космических аппаратов. Планирование в реальном времени, защита от радиации, устойчивые к задержкам сети, которые поддерживают связь марсоходов с Землёй. С приближением Artemis 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)"]
Почему именно этот проект
Я хотел изучить что-то действительно сложное. Что-то, где концепции незнакомы, а инструменты беспощадны. Компиляторы C, нацеленные на ARM. Скрипты компоновщика. Ввод/вывод с отображением в память. Таблицы векторов прерываний.
Claude Code сделал это возможным. Не потому что написал весь код, а потому что объяснял, что делает скрипт компоновщика, пока мы его писали, так что объяснение было основано на реальной задаче, которую я решал. То же самое для volatile, вытеснения по приоритету FreeRTOS и таймеров повторной передачи LTP.
Дорожная карта
Я разбил проект на четыре фазы, каждая из которых строилась на предыдущей:
| Фаза | Что | Ключевые концепции |
|---|---|---|
| Bare Metal | ARM-кросс-компиляция, вывод UART, обработчики прерываний | Ввод/вывод с отображением в память, таймер SysTick, стартовый ассемблер |
| FreeRTOS | Задачи, очереди, сторожевые таймеры, инверсия приоритетов | Детерминированное планирование, протоколы мьютекса, вытеснение |
| DTN | Двухузловая сеть, деградированные каналы, CFDP, маршрутизация по графу контактов | Bundle Protocol, хранение и пересылка, надёжность LTP |
| Интеграция | Полный телеметрический конвейер с задержками на расстоянии Марса | Мост UART, tc netem, синхронизированный ION OWLT |
Каждая фаза имеет автоматизированные тесты. Каждый этап — это PR с пройденным CI.
Фаза 1: Bare Metal
Первой задачей было заставить хоть что-то работать. ARM-кросс-компиляция для Cortex-M3 (MPS2-AN385) в QEMU. Нет ОС, нет стандартной библиотеки, нет printf.
Claude помог мне понять последовательность запуска: таблица векторов, обработчик сброса, копирование .data из флэш в ОЗУ, обнуление .bss. Всё это происходит до того, как main() начнёт выполняться.
Первой победой стал единственный символ, появившийся на UART-консоли:
#define UART0_DR (*(volatile uint32_t *)0x40004000)
void uart_putc(char c) {
UART0_DR = c;
}
Две строки C. Никаких библиотек. Просто запись байта по адресу памяти, который оказался подключён к последовательному порту. Это ощущалось как прямой разговор с машиной.
Далее: прерывания таймера SysTick, обработчики прерываний, структурированный вывод. Демонстрация SysTick настраивает аппаратный таймер для срабатывания с частотой 2 Гц и считает 10 тиков:
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.
Ни одного пропущенного тика. Ни одного лишнего тика. Процессор спит между прерываниями, и аппаратура будит его точно в нужный момент. Детерминированное выполнение с bare metal.
Фаза 2: FreeRTOS
После успешной работы bare metal я добавил FreeRTOS — операционную систему реального времени, работающую на всём, от медицинских устройств до спутников.
Упражнения строились постепенно:
- Две задачи с разными частотами. Базовая многозадачность.
- Коммуникация на основе очередей. Безопасный обмен данными между задачами.
- Сторожевой таймер. Обнаружение зависших задач и восстановление после них.
- Сенсорный конвейер. Четыре датчика с разными частотами, питающих цепочку обработки.
- Инверсия приоритетов. Воспроизведение и устранение классической ошибки RTOS.
Вот запуск сенсорного конвейера. Обратите внимание, как гироскоп на 10 Гц доминирует в потоке, с показанием температуры на 1 Гц, протискивающимся на такте 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. Показание температуры проходит только тогда, когда гироскоп не занимает процессор.
Упражнение по инверсии приоритетов было наиболее поучительным. Я создал три задачи, где задача с низким приоритетом удерживает мьютекс, нужный задаче с высоким приоритетом, а задача со средним приоритетом морит голодом обе. Решение: наследование приоритетов, при котором FreeRTOS временно повышает задачу с низким приоритетом, чтобы та могла быстрее освободить мьютекс.
Это та самая ошибка, которая чуть не погубила миссию Mars Pathfinder в 1997 году. Построив её самостоятельно, я понял объяснение из учебника.
Фаза 3: Устойчивые к задержкам сети
TCP/IP не работает в космосе. Время туда и обратно до Марса составляет от 6 до 44 минут. Каналы пропадают на часы, когда планеты заслоняют сигнал. Предположение TCP о непрерывном соединении с низкой задержкой полностью рушится.
Ответ NASA JPL — DTN (Delay-Tolerant Networking). Bundle Protocol хранит данные локально и пересылает их поузлово, когда каналы становятся доступными. Он разработан именно для условий, которые ломают интернет.
Я собрал реализацию NASA ION в Docker и запустил всё более сложные тесты:
- Базовая связность. Два узла обмениваются bundle-пакетами.
- Деградированные каналы.
tc netemдобавляет задержку 500 мс, 25% потери пакетов, полные отключения. - Передача файлов CFDP. Надёжная доставка файлов с проверкой целостности.
- Маршрутизация по графу контактов. Bundle-пакеты в очереди во время разрывов канала, доставка при открытии окон.
Тест прерывистого канала рассказывает всю историю 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 давно бы сдался.
Фаза 4: Интеграция
Финальная фаза соединяет всё вместе. Прошивка FreeRTOS генерирует телеметрию в QEMU. Python-скрипт-мост читает вывод UART и вставляет его как DTN-пакеты. Пакеты проходят через сеть с задержкой и достигают наземной станции.
Прошивка запускается и немедленно начинает передачу:
# 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
Четыре датчика с четырьмя разными частотами. Можно видеть, что гироскоп на 10 Гц производит большинство показаний, с датчиком солнца на 2 Гц, температурой на 1 Гц и батареей на 0,5 Гц, чередующимися между ними. Мост группирует их в DTN-пакеты каждые 2 секунды.
Тест задержки до Марса доказывает, что весь конвейер работает в реалистичных условиях:
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 пакетов, но к моменту завершения моста доставлено только 8. Остальные ещё находились в пути через 5-секундную задержку. Это задержка настоящая, а не симулированная программно.
Симуляция задержки до Марса использует два синхронизированных механизма:
- tc netem добавляет 5 секунд реальной сетевой задержки обоим контейнерам
- Таблицы диапазонов ION устанавливают то же 5-секундное одностороннее время распространения света, чтобы таймеры повторной передачи LTP были корректными
Оба должны совпадать. Если реальная задержка составляет 5 секунд, но ION думает, что 1 секунда, LTP агрессивно повторяет передачу и переполняет канал. Правильная настройка этого научила меня большему о проектировании протоколов, чем любая глава учебника о надёжности.
Работа с Claude Code
Этот проект занял бы у меня месяцы без Claude. Не потому что код сложен (большинство файлов не превышают 300 строк), а потому что кривая обучения для каждой области крутая.
Что работало хорошо:
- Межобластные знания. Проект охватывает C, Python, ARM-ассемблер, Docker, ION DTN, FreeRTOS, QEMU, tc netem и LTP. Ни один человек не знает все эти области хорошо. Claude мог переключаться между ними.
- Этапы, управляемые тестами. Каждая фаза имеет автоматизированные тесты. Claude помогал писать утверждения, проверяющие реальное поведение, а не просто «компилируется».
- Рабочий процесс на основе PR. Каждый этап — отдельная ветка и PR с CI. Поддержка worktree в Claude Code поддерживала это в порядке. Изолированные ветки, никакого случайного перекрёстного заражения.
Что требовало осторожности:
- Встраиваемый C беспощаден. Ошибки на единицу в скриптах компоновщика не дают стека вызовов. Они дают жёсткий сбой или тихое повреждение данных. Предложения Claude нужно было тщательно проверять по документации на оборудование.
- Документация ION DTN скудна. Обучающие данные Claude не включают глубокие внутренности ION. Я больше полагался на исходный код ION и примеры конфигурации, чем на объяснения Claude об ION-специфичном поведении.
Цифры
| Метрика | Значение |
|---|---|
| Всего тестов | 50+ утверждений в 7 наборах тестов |
| Языки | C, Python, Bash |
| Оборудование | Нет (QEMU + Docker) |
| Строк C | ~1 200 (прошивка + bare metal) |
| Строк Python | ~1 500 (тесты + мост + скрипты DTN) |
| Конфигурация ION DTN | ~350 строк в 6 файлах .rc |
Попробуйте
Всё это открытый исходный код, работающий на любой Linux-машине с QEMU и 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
Никакого физического оборудования. Никаких облачных сервисов. Просто ноутбук и любопытство о том, как работают компьютеры космических аппаратов.