# NOTES.md — грабли, ограничения и почему так Здесь собрано всё, что не является кодом, но экономит дни отладки. ## 1. Плата: Pluto+ ≠ ADALM-Pluto Наши платы — клоны «Pluto+» (Zynq-7020). Отличия от стокового ADALM-Pluto (7010), которые влияют на проект: | | Pluto+ (у нас) | ADALM-Pluto сток | |---|---|---| | SoC | Zynq-7020 | Zynq-7010 | | Ядра ARM | 2 активных | 1 (второе — через хак) | | RAM | 1 ГБ | 512 МБ | | Ethernet | есть гигабитный | нет (только USB) | Следствия: бюджет «одно ядро на DSP, второе на IO» валиден только на наших платах; на стоковом Pluto весь тракт не поместится. Все замеры (docs/BENCHMARK.md) сделаны на Pluto+. Особенности прошивки v0.38: - **нет sftp-server** → только `scp -O` (legacy protocol); - **rootfs в RAM** → всё в /tmp и /root пропадает при ребуте; бинарники заливаются заново (`make deploy`), «установить навсегда» = пересборка прошивки, нам не нужно; - BogoMIPS 333 в /proc/cpuinfo — артефакт таймера, реальная частота 667 МГц; - пароль root: `analog`. ## 2. FDD и самоглушение (главный риск этапа «в воздух») План FDD: A TX 915 / RX 868; B TX 868 / RX 915 (разнос 47 МГц). Проблема: **дуплексеров нет**. Собственный передатчик платы в паре сантиметров от собственного приёмника: - широкополосный шум TX попадает в полосу RX даже при разносе 47 МГц; - сильный сигнал TX блокирует (десенсибилизирует) входной каскад RX. Характерный симптом: симплекс и loopback работают идеально, а в дуплексе RX платы «глохнет», как только её TX активен. Это не баг кода. Меры (по нарастанию): 1. максимальный `tx_attenuation`, какой позволяет линия; 2. физический разнос TX/RX антенн + кросс-поляризация; 3. SAW-фильтр 868 МГц на RX-вход (копеечный, SRD-диапазон); 4. если ничего не помогает — честный TDD на одной частоте. Поэтому порядок работ: **сначала симплекс** (обе платы 915 МГц, полудуплекс по очереди), FDD — отдельным этапом с замерами деградации RX при активном TX. ### 2.1. Предварительные замеры (этап 1, §10 п.7 — FDD-водопровод) Числа сняты на **сконфигурированном**, но молчащем обратном тракте (LO 868 поднят, `hardwaregain` выставлен, но DMA обратного TX ничего не пушит). Полноценный gate с реальным STATUS-трафиком — этап 2, числа уйдут в §2.2. - **Самоглушения от «просто включённого» обратного тракта не видно.** Форвард A→B под FDD (1 МБ, `-p 4000`, group-FEC) байт-в-байт и при fb-TX `-g -89` (≈выкл), и при `-g -20` (штатное). Активный обратный LO 868 в 47 МГц от RX 915 приёмник данных не глушит — на кабеле+аттенюаторе. Это ответ на вопрос PER₁ (десенс от включённого тракта), но НЕ на PER₂ (десенс от реального излучения STATUS) — тот снимается в этапе 2. - **Диагностика: пустой файл на выходе RX ≠ самоглушение.** Симптом «0 кадров, RSSI −100» — это НЕ глушение, а мёртвый вход: остаточный процесс держит RX-DMA (`cf-ad9361-lpc`) от неубитого прошлого запуска. Глушение выглядит ИНАЧЕ — сильный RSSI (−48) при разрушенном EVM (−2.5). **Различать по RSSI.** Лечение: `killall -9 receiver transmitter` перед каждым стартом (уже в `scripts/test_fdd_fwd.sh`). - **Ридбэк LO округляется PLL.** AD9361 — дробный-N синтезатор с шагом ~2 Гц: `868000000` читается как `867999998`. Это норма, не ошибка; сверка ридбэка идёт с допуском `FDD_LO_TOL_HZ`=100 Гц (common.c). - **Канал деградировал против этапа 0.** Эталон регрессии сдвинут `-p 3000` → `-p 4000` (на 3000/3500 теперь Overrun+потери). Направление **B→A** флапает даже на `-p 4000` при СИЛЬНОМ RSSI (−33…−35) — вероятна компрессия входа RX (усиление 50 дБ избыточно). Проверить снижением RX/TX gain до свипа частот. ### 2.2. Gate-замер самоглушения (этап 2, 2026-07-16) — вердикт: FDD принят Условия: кабель+аттенюатор, данные A→B 915 (`TXGAIN −30`, `-p 4000`, GROUPFEC=0 — сырой PER без маскировки), STATUS B→A 868 (10 Гц), 1 МБ = 1024 кадра на прогон. Полные числа — README §12.8. - **PER₀** (симплекс, 3×): потерь 3/5/14 → 0.29–1.37%, среднее 0.72% (канал флапает, см. выше). - **PER₂** (FDD, STATUS в эфире, FBGAIN −80…0, 8 прогонов): потерь 1–11, среднее 0.51%. **PER₂−PER₀ ≈ −0.2 п.п. — излучение STATUS форвард-линк не трогает вообще, ни при каком усилении.** PER₁≈PER₀ снят ранее (§2.1). - **Доставка STATUS B→A**: 0% при FBGAIN ≤ −40 (не пробивает аттенюатор); **плато ~50% при −20…0** (47/52/52/50/51%) — от усиления НЕ зависит. - FBGAIN=0 дал первые «RS битых: 3» на RX данных платы B → лёгкий самодесенс B на максимуме fb-TX. Рабочее окно fb-TX на кабеле: **−20…−10**. **Плато 50% — структурное, не RF.** Гипотеза: fb-RX платы A десенсится её же TX-бёрстом (механизм §2, но на стороне A). Duty данных при `-p 4000` = 57% (кадр 5.33 мс + пауза 4 мс); STATUS ~1.4 мс выживает только попав в паузу — доля доставки ≈ доле тихого времени и потому не зависит от FBGAIN. Проверка при случае: `PAUSE=8000` должен поднять доставку. Приятное следствие: в фазе ретрансмитов и END-хендшейка ARQ плата A передаёт мало → доставка растёт именно там, где обратная связь критична. **Вердикт gate.** Исходный критерий «≥99% доставки» был перестрахован и пересмотрен: STATUS кумулятивен по построению (потеря безвредна — следующий несёт то же состояние), 50% доставки = эффективные ~5 Гц фидбэка, для ARQ с rate-limit 2× периода и TX-окном ~19 с этого с большим запасом достаточно. Рабочий критерий: доставка стабильна (≥30%) И PER₂−PER₀ ≤ +0.3 п.п. И PER₁≈PER₀ — **выполнен, этап 5 (fallback TDD, цена ~8% throughput) не нужен**. Замечание к методике: у приёмника НЕТ флага усиления RX данных (константа `RX_GAIN=50` в коде; `-g` приёмника — усиление fb-TX). Переменная окружения `RX_GAIN` скриптами не читается — прогоны «RX_GAIN=40/30/20» были репликами FBGAIN=0 (они и дали три точки плато). Гипотеза компрессии входа при RSSI −33…−35 (см. §2.1) остаётся непроверенной, но gate она не блокирует. ### 2.3. Находки этапа 3 (ARQ, 2026-07-16) **Клифф канала на кабеле — свипу PER здесь не место.** `TXGAIN −30` даёт PER 0.4%, `−38` — уже `CRC: 0` при живых `Заголовках: 3322`. Порог заголовка и порога payload разнесены: заголовок ofdmflexframe идёт BPSK с собственным FEC, payload — QPSK+RS(255,223), и падает он первым. Практический смысл: **живой счётчик «Заголовков» при нулевом CRC — это не «плохой канал», а его отсутствие**; диагностику по такому прогону строить нельзя, ARQ там нечего вытягивать. Аттенюатор даёт слишком грубый шаг — карта PER снята свипом в README §12.4. **Направления несимметричны.** На одном и том же кабеле A→B даёт `RS битых: 0`, B→A — `RS битых: 20…28` на 1024 кадрах. Разброс устойчив между прогонами, то есть это не статистика, а свойство пары плат/тракта. Для ARQ безразлично (ретрансмит закроет), но при выборе направления для чувствительного трафика и при трактовке PER-замеров это надо помнить: цифры одного направления не переносятся на другое. **Ловушка «битый кадр выпадает из NACK».** `miss_ack(seq)` по факту приёма кадра (до декода) — тихо ломает ARQ: кадр, не прошедший RS/CRC, снимался с NACK навсегда, A его не досылал, а RX считал файл собранным → **A рапортовал успех с битым md5**. Учёт NACK обязан идти ПОСЛЕ декода: `ok → miss_ack`, `!ok → miss_add_sorted`. Битый кадр — такая же дыра, как непришедший, просто обнаруженная не детектором seq. Не выстрелило на приёмке 3a лишь потому, что там случились `RS битых: 0`. **Ловушка «COMPLETE съеден снимком».** STATUS на A забирается снимком (latest-wins, `fb_take` гасит флаг свежести). Если бы COMPLETE читался из снимка, `arq_service` съел бы тот самый STATUS, в котором он приехал, и A ушёл бы в 10-секундный таймаут на ЦЕЛОМ файле. COMPLETE поэтому — отдельная монотонная защёлка, взводимая прямо в `fb_callback`. **Переполнение MISS_CAP — тупик без пересборки.** `miss_add` при переполнении молча теряет seq, и вернуть его в NACK уже некому: детектор дыр по нему второй раз не сработает (`highest_seq` ушёл вперёд). В тяжёлом канале это заклинивало приём насмерть. Лечится тем, что кольцо reorder знает точно, каких кадров нет: `miss_rebuild()` пересобирает NACK из кольца на каждом END (A шлёт их 10 Гц до самого COMPLETE, скан ≤1024 — сотые доли ядра). ## 3. Регуляторика (868/915 МГц в регионе ETSI) - **868 МГц** — SRD-диапазон: ограничения на полосу канала (сотни кГц), ЭИИМ и duty cycle. Наш сигнал 1.5 МГц в них не вписывается. - **915 МГц** — в Европе это uplink GSM-900, не ISM (ISM 902–928 — регион США). Практический вывод: разработка и демо — **кабель с аттенюатором** или антенны на столе с минимальной мощностью (TX gain от −40) и без внешних усилителей. Это ограничение прототипа фиксируется в отчёте проекта; у целевой системы по ТЗ предполагается лицензированный канал. ## 4. RF-безопасность - Кабельный тест **только** через аттенюатор 30–40 дБ + `-g -40`. Прямое соединение TX→RX выжигает входной каскад AD9361. - Диапазон DAC/ADC — 12 бит (±2047 в int16). Клиппинг при амплитуде TX > ~0.5 виден как рост EVM на RX. ## 5. Особенности liquid-dsp, найденные в бою - **Заголовок пользователя по умолчанию 8 байт.** Наш формат — 12 байт. Без `ofdmflexframegen_set_header_len(fg,12)` / `ofdmflexframesync_set_header_len(fs,12)` байты 8–11 (nblocks, last_block_bytes) в эфир не уходят, RX читает мусор за границей буфера. - **`fec_decode()` не сообщает об ошибках**: RS-декодер liquid всегда «успешен», неисправимый блок просто отдаёт мусор. Единственный надёжный критерий целостности — CRC/md5 поверх декодированных данных. - **Без FFTW liquid молча деградирует** на встроенный FFT. Проверка после сборки обязательна: `grep HAVE_LIBFFTW3F config.log`. - `ofdmflexframesync` сам оценивает и компенсирует CFO по преамбуле; внешний NCO-контур поверх него (как в раннем receiver.c) не нужен и с `set_phase(0)` на границах буферов — вреден. ## 6. Почему архитектурные решения именно такие - **Статическая линковка**: независимость от libc прошивки, деплой одним файлом, нет возни с sysroot ADI. Цена ~2 МБ — ничто при 1 ГБ RAM. - **rate 1.92, а не 3.84 MSPS**: замер (BENCHMARK.md) — 2.34 Msps потолок синхронизатора на кадрах. 3.84 даёт отказ «работает на демо, падает под нагрузкой» — худший для радиолинии вид отказа. - **OFDM оставлен, несмотря на цену CPU**: код написан и проверен; single-carrier QPSK дал бы ту же полезную скорость дешевле, но потребовал бы написать и отладить синхронизацию заново. Решение можно пересмотреть, если понадобится rate > 1.92 MSPS (см. резервы в BENCHMARK.md). - **Симплекс раньше FDD**: изолирует проблемы кода от проблем RF (п. 2). ## 7. Быстрые команды-шпаргалки ```bash # что видит плата iio_info -u ip:192.168.2.1 | less iio_attr -u ip:192.168.2.1 -c ad9361-phy altvoltage1 frequency # TX LO # загрузка CPU на плате во время линии (busybox top) ssh root@192.168.2.1 top # логи TX/RX после test_file_link.sh ssh root@ 'cat /tmp/tx.log /tmp/rx.log' ```