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

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

13 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) различает экземпляры.

Прошивка STM

Плата — WeAct STM32G431CB. Нужен PlatformIO Core (pio); если не установлен: pip install platformio (в репозитории он лежит в ~/.platformio/penv/bin/pio). Все команды выполняются из каталога прошивки device_firmware/cart_firmware.

Две роли — две отдельные прошивки на две платы. По умолчанию загрузчик — ST-Link по SWD (upload_protocol = stlink в platformio.ini).

1. Подключить ST-Link к плате по SWD (4 провода):

ST-Link WeAct G431CB
SWDIO PA13 (SWDIO)
SWCLK PA14 (SWCLK)
GND GND
3V3 3V3

2. Собрать и прошить роль (прошивать по одной плате за раз, один ST-Link):

# плата-передатчик (к ней подключён пульт)
pio run -e cart_tx -t upload
# плата-приёмник (к ней подключён провод драйвера колёс)
pio run -e cart_rx -t upload
  • Только собрать без заливки: pio run -e cart_tx. Артефакты — .pio/build/<env>/firmware.{elf,bin}.
  • Если подключено несколько ST-Link, указать конкретный: pio run -e cart_tx -t upload --upload-port <serial-ST-Link>.
  • Сменить роль платы — просто залить другой env, флеш перезаписывается целиком.

3. Физические подключения по ролям (распиновка общая, см. выше по документу):

CART-TX CART-RX
Стик X / Y PA0 / PA1 (АЦП) — не используется
Выход SBUS PA9 → локальный драйвер (как раньше) PA9 → провод драйвера колёс
USB → ПК (bridge --role tx) → ПК (bridge --role rx)

4. Проверить роль после прошивки. Подключить плату по USB к ПК и прочитать product string (роль зашита в неё):

python3 -c "from serial.tools import list_ports; \
[print(p.device, p.product, p.serial_number) for p in list_ports.comports()]"
# ожидаемо: /dev/ttyACM0 CART-TX <UID> либо CART-RX

Либо просто запустить tools/bridge.py соответствующей роли — на старте он делает handshake и логирует подтверждённые роль, версию прошивки и UID.

USB CDC теперь несёт бинарный протокол, а не текстовый лог. pio device monitor покажет «мусор» — это нормально. Отладочный текст идёт кадрами LOG, которые печатает bridge.py.

Кадр поверх 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.