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.
208 lines
16 KiB
Markdown
208 lines
16 KiB
Markdown
# 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@<ip> 'cat /tmp/tx.log /tmp/rx.log'
|
||
``` |