Files
radar_system/device_firmware/cart_firmware/docs/protocol.md
T
aweandClaude Opus 4.8 7334f89491 cart: документация сетевого моста в protocol.md
Разделы про роли и идентификацию устройства, кадр USB CDC, сетевой слой
(UDP, seq/новая сессия, HMAC, WireGuard), многоуровневый failsafe и
запуск моста.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-10-19 13:13:24 +03:00

9.9 KiB
Raw Blame History

Протокол старого пульта тележки (реверс-инжиниринг)

Снято 2026-08-20 осциллографом Hantek DPO7204C с сигнального провода пульта (канал CH3). Инструменты: tools/capture.py (захват), tools/decode.py (декодер), сырые данные и картинки — в captures/.

Физический уровень

  • Один сигнальный провод, логика 3.3 В.
  • Инвертированный UART (стандартная полярность SBUS): в покое линия низкая (~0 В), импульсы вверх до ~3.3 В.
  • Скорость 100 000 бод, формат 8E2 (8 бит данных, чётность even, 2 стоп-бита), биты LSB-first. Длительность бита 10 мкс.

Кадровый уровень — SBUS

Стандартный кадр Futaba SBUS, 25 байт (3 мс на линии):

Смещение Размер Содержимое
0 1 Заголовок 0x0F
1 22 16 каналов × 11 бит, упакованы подряд LSB-first
23 1 Флаги: bit0=CH17, bit1=CH18, bit2=frame_lost, bit3=failsafe
24 1 Футер 0x00

Распаковка каналов: 22 байта складываются в 176-битное число LSB-first, канал N (N=0..15) = биты [11·N .. 11·N+10], диапазон значений 0–2047.

  • Кадры отправляются каждые 50 мс (20 Гц). Это медленнее стандартного SBUS (7/14 мс) — ответная часть с этим темпом работает.
  • Флаги во всех наблюдениях = 0x00.

Карта каналов (нумерация с 1)

Управление Канал Мин Нейтраль Макс
Стик вперёд/назад 2 433 (назад) 1024 1643 (вперёд)
Стик влево/вправо 4 446 (влево) 1024 1654 (вправо)
Остальные 14 — всегда 1024

Диапазон осей ~±600 от нейтрали (не полная шкала SBUS). Других органов управления на пульте нет.

Эталонный кадр нейтрали (hex):

0F 00 04 20 00 01 08 40 00 02 10 80 00 04 20 00 01 08 40 00 02 10 80 00 00

Воспроизведение на STM32G431

  • USART: 100000 бод, 8 бит + чётность even (в терминах STM32: M=1, 9-bit с PCE=1), 2 стоп-бита, TXINV=1 (аппаратная инверсия TX) — бит-бэнг не нужен.
  • Отправлять 25-байтовый кадр по таймеру каждые 50 мс.
  • Нейтраль: все каналы 1024; управление — каналы 2 и 4 в измеренных диапазонах.

Сетевой мост «пульт → тележка»

Та же команда управления может идти не по проводу рядом, а через локальную сеть/интернет, не ломая прежний проводной режим. Цепочка:

[пульт] ADC→ STM(CART-TX) ─USB─► bridge --role tx ─UDP─► bridge --role rx ─USB─► STM(CART-RX) ─SBUS─► драйвер

STM-TX продолжает гнать SBUS на свой локальный провод как раньше и дополнительно отдаёт те же значения каналов по USB. STM-RX принимает их по USB и выдаёт на свой провод так, будто сигнал пришёл с пульта.

Роли и идентификация устройства

Роль выбирается на сборке (platformio.ini): pio run -e cart_tx или -e cart_rx. Общий код (sbus, usb_link) собирается в оба env, специфичный — через build_src_filter, мёртвого кода неактивной роли нет.

Каждая прошивка публикует USB product string: CART-TX / CART-RX. По ней tools/bridge.py находит нужный порт (serial.tools.list_ports). После открытия порта ПК шлёт HELLO и ждёт IDENT — подтверждение роли, версии протокола и 96-битного UID чипа (на случай неверно прошитого устройства, рассинхрона версий или чужого CDC с тем же VID/PID). Serial-дескриптор (UID) различает экземпляры.

Кадр поверх USB CDC

Единственный источник истины — src/link_protocol.h, зеркало на Python — tools/link_proto.py (сверка: python3 tools/link_proto.py --selftest).

[0xA5][0x5A][VER=1][TYPE][LEN][PAYLOAD..LEN][CRC16_LO][CRC16_HI]
  • CRC16-CCITT (poly 0x1021, init 0xFFFF) по байтам [VER..конец PAYLOAD], LE. Контрольный вектор: crc16("123456789") = 0x29B1.
  • Приёмник (STM и Python) — стейт-машина: ищет синхрослово, читает по LEN, проверяет CRC; при сбое сдвиг на байт и ресинхронизация (устойчивость к мусору и потере байта).
  • Типы: 0x01 CONTROL, 0x02 IDENT, 0x03 HELLO, 0x04 LOG. Отладочный текст идёт только кадром LOG — «голых» Serial.print, ломающих бинарный поток, нет.

Payload CONTROL (LE): seq:u32, t_ms:u32, flags:u8, ch[16]:u16 = 41 байт. ch[] — значения SBUS-каналов 0..2047, ровно те, что TX кладёт на свой провод. Это общая «валюта» всей цепочки: ПК перекладывает payload в UDP и обратно, RX распаковывает и пакует в SBUS. flags bit0 = failsafe.

Сетевой слой (между экземплярами bridge)

  • Транспорт — UDP: для управления 20 Гц свежий кадр ценнее доставленного; TCP при потере встал бы в head-of-line blocking и копил лаг.
  • Полезная нагрузка UDP — тот же CONTROL payload. seq даёт отбрасывание устаревших/повторных пакетов (защита от переупорядочивания и дубликатов UDP) с корректной арифметикой переполнения uint32. Перезапуск источника (seq снова с 0) распознаётся по пачке подряд «несвежих» пакетов и трактуется как новая сессия — иначе поток завис бы в failsafe навсегда. Это best-effort защита от повтора старых команд, не криптографическая: аутентичность даёт HMAC, против целевого replay на недоверенной сети используй WireGuard.
  • Приём из сети и выдача SBUS на провод — разные таймеры: джиттер сети не попадает на провод, STM-RX гонит SBUS строго каждые 50 мс из последнего состояния.
  • Безопасность. Для интернета обязателен общий ключ (--key): каждый UDP- пакет аутентифицируется усечённым HMAC-SHA256 (8 байт), RX отбрасывает неаутентичные и принимает только с ожидаемого адреса (--peer). Рекомендуемый способ наружу — WireGuard-туннель, внутри которого идёт простой UDP. «Голый» неаутентифицированный UDP допустим только в доверенной локальной сети.

FAILSAFE

Потеря связи никогда не оставляет тележку на последней команде — выход всегда нейтраль (все каналы 1024), а не удержание.

  • Главный предохранитель — на самом STM-RX. Если валидный CONTROL по USB не приходил дольше RX_USB_TIMEOUT_MS = 150 мс (3 кадра), STM-RX сам выдаёт на провод нейтраль с флагом failsafe (SBUS bit3). Срабатывает при обрыве USB, зависании или падении ПК-процесса. SBUS на провод идёт непрерывно 20 Гц в любом случае — драйвер не видит пропажи сигнала.
  • Сетевой failsafe на ПК-RX. При тишине из сети дольше 100 мс bridge активно шлёт на STM-RX нейтральные CONTROL с флагом CTRL_FLAG_FAILSAFE в темпе 20 Гц, чтобы причина была явной. STM-RX транслирует этот флаг в бит failsafe SBUS-кадра (byte 23 bit3). Восстановление — по первому свежему валидному пакету.

Запуск

# сторона пульта
.venv/bin/python tools/bridge.py --role tx --peer 10.0.0.2:5005 --key SECRET
# сторона тележки
.venv/bin/python tools/bridge.py --role rx --bind 0.0.0.0:5005 --peer 10.0.0.1 --key SECRET

Локальная проверка без сети: оба экземпляра на одной машине с --peer 127.0.0.1 и двумя STM по USB; SBUS с RX снять tools/decode.py и сверить с осями стика TX.