Каналы и CRSF
Каналы и протокол CRSF
Цель занятия: обучающиеся назначают и переназначают каналы на пульте RadioMaster Boxer, читают значения каналов в коде и находят расхождение между ожидаемым и фактическим назначением по дампу.
Назначение каналов — то место, где обучающийся руками задаёт контракт между пультом и прошивкой: какой стик или тумблер приходит в код каким числом. Это единственная часть системы, где «как договорились, так и будет», и потому единственная, где ошибка проявляется как полная бессмыслица в поведении аппарата.
Что такое канал
Пульт передаёт 16 чисел, много раз в секунду. Каждое число — положение одного органа управления: стика, тумблера, потенциометра. Эти числа и есть каналы.
Пульт не знает, что означают эти числа. Он просто отправляет: «канал 3 = 1700, канал 5 = 1900». Смысл им придаёт приёмная сторона — прошивка аппарата.
| Канал | Обычное назначение | Орган на Boxer |
|---|---|---|
| CH1 | Крен (roll, aileron) | Правый стик, влево-вправо |
| CH2 | Тангаж (pitch, elevator) | Правый стик, вперёд-назад |
| CH3 | Газ (throttle) | Левый стик, вверх-вниз |
| CH4 | Рыскание (yaw, rudder) | Левый стик, влево-вправо |
| CH5 | ARM — разрешение моторов | Тумблер SA |
| CH6–CH16 | Режимы, подвес, доп. функции | SB…SH, потенциометры S1, S2 |
Диапазон значения канала — 1000…2000, в микросекундах (наследие старых серво-сигналов). 1000 — минимум, 1500 — центр, 2000 — максимум.
Цепочка от пульта до кода
Пульт формирует значения каналов
Прошивка пульта (EdgeTX на Boxer) берёт положения стиков, применяет к ним кривые и микшеры и раскладывает результат по 16 каналам.
Передатчик отправляет радиопакет
Модуль ExpressLRS упаковывает каналы и отправляет по радио на 2,4 ГГц сотни раз в секунду.
Приёмник принимает и выдаёт в UART
Приёмник ELRS на борту распаковывает радиопакет и выдаёт его по проводу (UART) в формате CRSF на скорости 420000 бод.
Контроллер разбирает CRSF
Наш код читает байты из UART, находит начало пакета, проверяет целостность и распаковывает 16 чисел. Дальше они идут в микшер.
Устройство пакета CRSF
Пакет с каналами выглядит так:
┌──────┬──────┬──────┬────────────────────────────┬──────┐
│ 0xC8 │ LEN │ 0x16 │ 22 байта данных каналов │ CRC8 │
└──────┴──────┴──────┴────────────────────────────┴──────┘
синх длина тип полезная нагрузка контроль| Поле | Значение | Смысл |
|---|---|---|
| Синхробайт | 0xC8 | Признак начала пакета |
| Длина | обычно 24 | Сколько байт идёт дальше (тип + данные + CRC) |
| Тип | 0x16 | «Это пакет с каналами» (RC Channels Packed) |
| Данные | 22 байта | 16 каналов, упакованных по 11 бит |
| CRC8 | 1 байт | Контрольная сумма, полином 0xD5 |
Почему 22 байта, а не 32
16 каналов по 11 бит = 176 бит = ровно 22 байта. Одиннадцати бит хватает на 2048 градаций — этого более чем достаточно для положения стика, а экономия эфира позволяет отправлять пакеты чаще. В радиосвязи каждый байт стоит времени в эфире, а частота обновления напрямую влияет на отзывчивость управления.
Плата за экономию: значения каналов не выровнены по границам байт. Канал может начаться в середине одного байта и закончиться в другом.
байт 0 байт 1 байт 2 байт 3
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ CH1 ... │ │..CH1 CH2│ │ CH2 CH3│ │ CH3 ...│
└────────┘ └────────┘ └────────┘ └────────┘
8 бит 3+5 бит 6+2 бит ...Отсюда — сдвиги и маски при распаковке.
Разбор кода: распаковка каналов
Первые три канала из рабочей прошивки ERLS_Rover:
_channels[0] = ((uint32_t)payload[0] | ((uint32_t)payload[1] << 8)) & 0x7FF;
_channels[1] = (((uint32_t)payload[1] >> 3) | ((uint32_t)payload[2] << 5)) & 0x7FF;
_channels[2] = (((uint32_t)payload[2] >> 6) | ((uint32_t)payload[3] << 2)
| ((uint32_t)payload[4] << 10)) & 0x7FF;Разобрать вслух — это и есть занятие:
& 0x7FF— маска на 11 младших бит (0x7FF= 2047 = 11 единиц). Отрезает всё лишнее, что попало в переменную сверху.- CH1 лежит в байте 0 целиком и в трёх младших битах байта 1. Сдвиг
<< 8ставит байт 1 выше байта 0. - CH2 начинается с бита 3 байта 1:
>> 3выбрасывает биты, уже отданные CH1. - CH3 собирается из трёх байт: остаток байта 2, весь байт 3 и два бита байта 4. Именно поэтому там три слагаемых.
uint32_t обязательно? Без него
payload[4] << 10 вычисляется в 8-битном типе, старшие биты уезжают за границу
и теряются — канал 3 начинает врать в верхней части диапазона. Это классическая
ошибка переполнения при битовых сдвигах, и она ловится только на краях диапазона:
в центре стика всё выглядит правильно.Приведение к привычному диапазону
Сырое 11-битное значение приходит в диапазоне 172…1811 (так устроен CRSF). Переводим в привычные микросекунды:
_channels[i] = map(_channels[i], 172, 1811, 1000, 2000);
_channels[i] = constrain(_channels[i], 1000, 2000);constrain нужен, потому что map не ограничивает результат: при значении чуть
за границей диапазона (а такое бывает при точной подстройке концов на пульте)
на выходе получится 2003 или 998, и дальше это уедет в микшер.
Конечный автомат приёма
Байты приходят потоком, без границ. Чтобы найти в потоке пакет, используется конечный автомат — та же конструкция, что и в задачах на перекрёстки в робототехнике.
stateDiagram-v2
[*] --> WAIT_SYNC
WAIT_SYNC --> WAIT_LEN: байт == 0xC8
WAIT_SYNC --> WAIT_SYNC: любой другой байт
WAIT_LEN --> WAIT_DATA: длина в пределах 2..62
WAIT_LEN --> WAIT_SYNC: длина неправдоподобна
WAIT_DATA --> WAIT_DATA: копим байты
WAIT_DATA --> WAIT_SYNC: пакет собран (CRC проверен)
case WAIT_SYNC:
if (b == 0xC8) { bytesRead = 0; parserState = WAIT_LEN; }
break;
case WAIT_LEN:
if (b >= 2 && b <= 62) { payloadLen = b; bytesRead = 0; parserState = WAIT_DATA; }
else { parserState = WAIT_SYNC; } // мусор — ищем синхробайт заново
break;Проверка b >= 2 && b <= 62 — защита от мусора: байт 0xC8 может случайно
встретиться внутри данных, и тогда автомат «зацепится» не за то место. Неверная
длина это обнаружит, и поиск начнётся заново.
Зачем проверять CRC
if (calculate_crc8(crsfBuffer, payloadLen - 1) == crsfBuffer[payloadLen - 1]) {
parseCrsfChannels(&crsfBuffer[1]);
lastPacketTime = millis();
}Радиоканал — среда с помехами. Пакет может прийти искажённым, и без проверки искажённое значение попадёт прямо в моторы. Контрольная сумма — это фильтр между помехой и винтами. Пакет с неверным CRC просто отбрасывается: лучше пропустить одно обновление (следующее придёт через пару миллисекунд), чем исполнить случайную команду.
Failsafe: что делать, когда связи нет
if (millis() - lastPacketTime > 500 && linkUp) {
linkUp = false;
_channels[0] = 1500; // крен — центр
_channels[1] = 1500; // тангаж — центр
_channels[2] = 1000; // ГАЗ — МИНИМУМ
_channels[3] = 1500; // рыскание — центр
for (int i = 4; i < 16; i++) _channels[i] = 1500;
}Логика: пакеты приходят сотни раз в секунду, поэтому полсекунды тишины — уже авария. Все управляющие каналы уходят в центр, а газ — в минимум.
Почему газ именно в 1000, а не в 1500. Центр для газа означал бы половину мощности — аппарат без связи продолжил бы лететь. Для наземной техники это «уехал», для летающей — «улетел неизвестно куда на половине тяги». Все каналы в центр, газ в минимум — это не стилистика, а единственный безопасный выбор.
Для летающего аппарата failsafe устроен сложнее: мгновенное снятие газа на высоте означает падение. Там применяют возврат домой или контролируемое снижение — см. радиолинию и планирование маршрута.
Практика: назначение каналов
Включить дамп каналов
В прошивке включаем вывод всех 16 каналов в консоль. В ERLS_Rover это
#define CHANNEL_DEBUG_DUMP 1.
Найти каждый орган управления
Двигаем по одному органу и смотрим, какое число меняется. Записываем таблицу: «стик газа → CH3», «тумблер SA → CH5». Так составляется фактическая карта каналов — не та, что «должна быть», а та, что есть.
Сверить с ожиданием
Сравниваем полученную карту с тем, что ожидает код. Расхождение — источник почти всех «аппарат не слушается» на этом этапе.
Переназначить и проверить снова
Меняем назначение на пульте (в EdgeTX — раздел микшеров) и повторяем дамп. Убеждаемся, что контракт выполняется. Устройство пульта и порядок настройки — в пульте: EdgeTX и Boxer.
crsf_visualizer.py — Python-стенд,
повторяющий декодер прошивки построчно. Он позволяет провести это занятие, даже
если аппарат разобран: приёмник подключается к компьютеру, и каналы видно на
экране. Хороший способ занять группу, когда аппаратов меньше, чем обучающихся.Диагностика
| Симптом | Причина | Проверка |
|---|---|---|
| В консоли пусто | Нет питания приёмника, перепутаны RX/TX, не та скорость | Проверить 420000 бод, поменять RX/TX местами |
| Числа хаотично скачут | Не проверяется CRC или неверная скорость UART | Включить проверку CRC, сверить скорость |
| Все каналы 1500, газ 1000 | Сработал failsafe — связи нет | Проверить привязку и питание приёмника |
| Один канал врёт на краях | Переполнение при сдвиге | Проверить приведение к uint32_t |
| Реагирует не тот орган | Расхождение карты каналов | Дамп и сверка назначения |
| Значения не доходят до 1000/2000 | Не настроены концы на пульте | Подстроить endpoints |
| Газ и крен «поменялись местами» | Расхождение AETR/TAER | Порядок каналов |
Приёмка занятия
- Объясняет, зачем нужны синхробайт, длина и CRC, и что произойдёт без каждого.
- По дампу составляет карту каналов и находит подставленное расхождение.
- Объясняет, почему в failsafe газ уходит в минимум, а не в центр.
- Отвечает, почему CH3 распаковывается из трёх байт, а CH1 — из двух.