Files
Pluto-SDR/docs/NOTES.md
Maxim ab1e87a17e Этап 3c — гибрид GF+ARQ, пересборка NACK, приёмка. Техдолг #6 закрыт
ARQ включён и для group-FEC. Паритет и ретрансмит перестали быть альтернативами:
GF гасит одиночные потери без задержки, ARQ добирает то, что паритету не по силам
(>2 стираний/группу). Дыру, закрытую паритетом, RX снимает с NACK сам — лишнего
трафика нет.

receiver.c:
- group_route: кадр ложится в кольцо ДО любого bypass. Кольцо — единственный
  источник порядка и живёт независимо от сборки групп; иначе ретрансмит закрытой
  группы не занял бы свой слот и next_out встал бы на нём навсегда. Паритет
  получает слот нулевой длины: тратит seq, но в файл не идёт.
- group_route: bypass для кадра уже закрытой группы (base < g.base, а также
  base == g.base при !active — группа финализирована по END). Без него ретрансмит
  преждевременно flush-ал активную группу, сбрасывая её стирания, либо «воскрешал»
  закрытую с одним элементом.
- group_flush: под FDD пишет в кольцо ТОЛЬКО восстановленные паритетом кадры
  (g.fixed) и снимает их seq с NACK — принятые уже в кольце. Симплекс сохраняет
  прямой вывод группы: ретрансмитов там нет, переупорядочивать нечего.
- process_end: досрочный group_flush последней группы. Штатно группа закрывается
  первым кадром следующей — для последней его не будет, и её восстановление
  случилось бы только при выходе потока C, то есть уже после COMPLETE.
- process_end/miss_rebuild: NACK пересобирается из кольца на каждом END. Находка
  стресса 3b: miss_add при переполнении MISS_CAP молча терял seq, и вернуть его
  было некому (детектор дыр по нему второй раз не срабатывает) — приём заклинивало
  насмерть. Кольцо знает точно, каких кадров нет.
- arq_check_complete: критерий единый (next_out == total_seq), временный gf_seen
  убран.

transmitter.c: g_arq_on = 1 безусловно при -F; arq_service в GF-цикле как в legacy.
2026-07-16 15:29:25 +03:00

208 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.291.37%, среднее 0.72%
(канал флапает, см. выше).
- **PER₂** (FDD, STATUS в эфире, FBGAIN 80…0, 8 прогонов): потерь 111,
среднее 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 902928 — регион США).
Практический вывод: разработка и демо — **кабель с аттенюатором** или
антенны на столе с минимальной мощностью (TX gain от 40) и без внешних
усилителей. Это ограничение прототипа фиксируется в отчёте проекта;
у целевой системы по ТЗ предполагается лицензированный канал.
## 4. RF-безопасность
- Кабельный тест **только** через аттенюатор 3040 дБ + `-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)` байты 811 (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@<ip> 'cat /tmp/tx.log /tmp/rx.log'
```