263 lines
16 KiB
Markdown
263 lines
16 KiB
Markdown
# pluto-link — OFDM-радиолиния между двумя PlutoSDR (весь код на ARM внутри Pluto)
|
||
|
||
Цифровая радиолиния точка-точка на базе двух ADALM-Pluto+ (клон, Zynq-7020).
|
||
PHY: OFDM (liquid-dsp) + RS(255,223) FEC. Весь сигнальный тракт исполняется
|
||
на ARM Cortex-A9 **внутри** Pluto; хост-ПК нужен только для сборки, заливки
|
||
бинарей и снятия логов. Прототип в рамках ОКР «R-Link» (двухранговая
|
||
самоорганизующаяся сеть); данный репозиторий покрывает уровень PHY/линка.
|
||
|
||
---
|
||
|
||
## 1. Аппаратная платформа (фактическая, проверено)
|
||
|
||
| Параметр | Значение | Как проверено |
|
||
|---|---|---|
|
||
| Плата | Pluto+ (клон, Zynq-**7020**, AD9361) | `cat /proc/cpuinfo` — 2 ядра |
|
||
| CPU | 2 × Cortex-A9 @ 667 МГц, NEON | `Features: ... neon vfpv3` |
|
||
| RAM | 1 ГБ (доступно ~1000 МБ) | `free` |
|
||
| Прошивка | v0.38-4-g95aad-dirty | баннер SSH |
|
||
| Доступ | SSH root@192.168.2.1 (пароль `analog`), USB-Ethernet | — |
|
||
|
||
⚠️ Стоковый ADALM-Pluto (7010, 512 МБ, 1 ядро) — **другая плата**; все
|
||
бюджеты CPU в этом документе рассчитаны для Pluto+. Не путать.
|
||
|
||
⚠️ В прошивке v0.38 нет sftp-server → заливать файлы только `scp -O`
|
||
(legacy-протокол).
|
||
|
||
## 2. Бюджет CPU — главное ограничение проекта
|
||
|
||
Замерено офлайн-бенчмарком `src/ofdm_bench.c` прямо на ARM Pluto
|
||
(одно ядро, static, `-O3 -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard
|
||
-ffast-math`, liquid-dsp 1.8.0 **с FFTW/NEON** — проверено по
|
||
`HAVE_LIBFFTW3F` в config.log):
|
||
|
||
| Режим ofdmflexframesync | Пропускная способность | Вывод |
|
||
|---|---|---|
|
||
| Поиск преамбулы (шум) | **5.32 Msps** | дежурный режим, не лимитирует |
|
||
| Приём кадров (duty ~57%) | **2.34 Msps** | узкое место |
|
||
|
||
Следствия (зафиксированы как проектные решения):
|
||
|
||
- **Рабочий sample rate = 1.92 MSPS.** Запас 1.22× на ядре синхронизатора;
|
||
второе ядро — под IIO, RS-декод, ввод-вывод.
|
||
- **3.84 MSPS запрещён**: система работает на редких пакетах и лавинообразно
|
||
разваливается под нагрузкой (0.61× от realtime). Это худший вид отказа —
|
||
«на демо работало».
|
||
- Полезная скорость ≈ **1.3–1.5 Мбит/с** (~160–190 КБ/с). Файл 10 МБ ≈ 1 мин.
|
||
- Параметры замера: M=64, CP=16, taper=4, QPSK, CRC32, payload 1275 Б.
|
||
Полный протокол — `docs/BENCHMARK.md`. При любом изменении PHY-параметров
|
||
бенчмарк перегоняется **до** изменения кода тракта.
|
||
|
||
## 3. Структура репозитория
|
||
|
||
```
|
||
pluto-link/
|
||
├── README.md # этот файл
|
||
├── CLAUDE.md # краткие правила для Claude Code
|
||
├── makefile # цели: all, tx, rx, bench, deploy, clean
|
||
├── toolchain.env # CROSS=arm-linux-gnueabihf-, SYSROOT=$HOME/xarm
|
||
├── src/
|
||
│ ├── common.h # параметры OFDM/RS, дефолты, прототипы
|
||
│ ├── common.c # pluto_init (local:), pluto_configure
|
||
│ ├── transmitter.c # stdin → OFDM → AD9361 TX
|
||
│ ├── receiver.c # AD9361 RX → OFDM sync → stdout
|
||
│ └── ofdm_bench.c # бенчмарк CPU (не удалять!)
|
||
├── scripts/
|
||
│ ├── build_deps.sh # кросс-сборка FFTW + liquid-dsp → $HOME/xarm
|
||
│ ├── deploy.sh # scp -O бинарей в /tmp обеих плат
|
||
│ └── test_file_link.sh # e2e: файл → эфир → md5-сверка
|
||
└── docs/
|
||
├── BENCHMARK.md # методика и результаты замеров CPU
|
||
└── NOTES.md # FDD, регуляторика, известные грабли
|
||
```
|
||
|
||
## 4. Среда разработки и сборка
|
||
|
||
### 4.1 Хост
|
||
|
||
Любой Linux; проверено на Raspberry Pi OS 64-bit (aarch64). Кросс-тулчейн:
|
||
|
||
```bash
|
||
sudo apt install -y build-essential autoconf automake libtool git wget \
|
||
gcc-arm-linux-gnueabihf
|
||
```
|
||
|
||
### 4.2 Зависимости (один раз, ~20 мин)
|
||
|
||
`scripts/build_deps.sh` собирает в `$HOME/xarm` статические armhf-библиотеки.
|
||
**Порядок важен**: liquid зависит от FFTW и libfec, поэтому они собираются до него.
|
||
|
||
1. **FFTW 3.3.10**: `--enable-single --enable-neon --enable-static`
|
||
2. **libfec** (jgaeddert): Reed-Solomon для liquid. liquid **не реализует RS
|
||
сам** — оборачивает libfec. Без неё `fec_create(LIQUID_FEC_RS_M8)` вернёт
|
||
NULL и `-c rs8` молча пойдёт БЕЗ FEC. Собирать строго ДО liquid.
|
||
3. **liquid-dsp 1.8.0**: с `CPPFLAGS=-I$HOME/xarm/include
|
||
LDFLAGS=-L$HOME/xarm/lib` и обходом autoconf-кросс-проблемы:
|
||
`ac_cv_func_malloc_0_nonnull=yes ac_cv_func_realloc_0_nonnull=yes`
|
||
4. **libiio v0.25**: только local backend (network/usb/xml/iiod отключены).
|
||
5. **libad9361-iio**: FIR-фильтр для sample rate < 2.083 MSPS
|
||
(`ad9361_set_bb_rate`). Без него AD9361 отвергает 1.92 MSPS (EINVAL).
|
||
|
||
После сборки liquid **обязательно** проверить, что подхвачены и FFTW, и libfec:
|
||
`grep -E "HAVE_LIBFFTW3F 1|HAVE_LIBFEC 1" config.log` → обе строки. Без FFTW
|
||
liquid молча падает на встроенный FFT (бюджет CPU невалиден); без libfec нет RS.
|
||
|
||
⚠️ При обновлении зависимостей liquid надо пересобирать через `make clean`:
|
||
его makefile не отслеживает зависимость от `config.h`, и `configure`+`make`
|
||
без очистки лишь переустановят старую `libliquid.a` (без FEC). Проверка перед
|
||
деплоем: `arm-linux-gnueabihf-nm $XARM/lib/libliquid.a | grep -c init_rs_char`
|
||
→ ненулевое = RS-символы libfec действительно связаны.
|
||
|
||
### 4.3 Ключевые решения по сборке
|
||
|
||
- **Только статическая линковка** (`-static`). Причина: не зависим от libc
|
||
прошивки Pluto, не таскаем .so, деплой = один файл. Цена +2 МБ — ничто
|
||
при 1 ГБ RAM.
|
||
- **Флаги обязательны**: `-O3 -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard
|
||
-ffast-math`. Без NEON бюджет CPU из раздела 2 не выполняется.
|
||
- Одной командой:
|
||
|
||
```bash
|
||
arm-linux-gnueabihf-gcc -O3 -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard \
|
||
-ffast-math -static src/receiver.c src/common.c -o receiver \
|
||
-I$HOME/xarm/include -L$HOME/xarm/lib \
|
||
-lad9361 -liio -lliquid -lfec -lfftw3f -lm
|
||
```
|
||
|
||
Порядок библиотек критичен при статической линковке: зависимый идёт раньше
|
||
(`-lad9361` перед `-liio`, `-lliquid` перед `-lfec`).
|
||
|
||
### 4.4 Деплой
|
||
|
||
```bash
|
||
scp -O transmitter receiver root@192.168.2.1:/tmp/ # Pluto A
|
||
scp -O transmitter receiver root@<pluto_B>:/tmp/ # Pluto B
|
||
```
|
||
|
||
rootfs Pluto живёт в RAM: `/tmp` очищается при ребуте — это нормально,
|
||
`deploy.sh` заливает заново.
|
||
|
||
## 5. Правила кода (конституция)
|
||
|
||
1. Язык — **C**. Python/GNU Radio в тракте данных запрещены.
|
||
2. Железо — только **libiio, контекст `local:`** (код исполняется на Pluto).
|
||
Сетевые контексты `ip:` — только для отладочных утилит с хоста.
|
||
3. Бюджет: считай, что у тебя **одно ядро 667 МГц** на DSP; второе занято
|
||
IIO/FEC/IO. Любая новая нагрузка в тракте — сначала через ofdm_bench.
|
||
4. Тяжёлые зависимости (FFmpeg, cJSON и т.п.) — запрещены. JSON руками,
|
||
видео — raw UDP.
|
||
5. Порядок отладки: **кабель+аттенюатор → антенны на столе → дистанция**.
|
||
Никогда не отлаживать новый код сразу «в воздухе».
|
||
6. Изменил PHY-параметры (M, CP, модуляция, rate) — перегони бенчмарк и
|
||
обнови `docs/BENCHMARK.md` в том же коммите.
|
||
|
||
## 6. Параметры радиотракта
|
||
|
||
| Параметр | Значение | Примечание |
|
||
|---|---|---|
|
||
| Sample rate | **1 920 000** | см. раздел 2; 3.84 MSPS запрещён |
|
||
| Полоса | 1 500 000 | ~0.78 × rate |
|
||
| Частота (симплекс-тест) | 915 МГц | обе платы на одной частоте |
|
||
| FDD (этап 2) | A: TX 915/RX 868; B: TX 868/RX 915 | разнос 47 МГц |
|
||
| TX gain (кабель) | −40 дБ | + аттенюатор 30–40 дБ обязателен |
|
||
| TX gain (антенны) | −20…0 дБ | начинать с минимума |
|
||
| RX gain | 50 дБ, manual | фиксированный |
|
||
| OFDM | M=64, CP=16, taper=4, QPSK | liquid defaults + CRC32 |
|
||
| FEC | RS(255,223), payload ≤ 1024 Б | 5 блоков на кадр |
|
||
|
||
⚠️ **FDD без дуплексеров**: собственный TX 915 МГц глушит свой RX 868 МГц
|
||
широкополосным шумом. Симптом: loopback работает, дуплекс в эфире — нет.
|
||
Меры: max tx_attenuation, разнос/кросс-поляризация антенн, SAW-фильтр
|
||
868 МГц на RX. Подробнее — `docs/NOTES.md`.
|
||
|
||
⚠️ **Регуляторика**: 868 МГц — SRD-диапазон (ограничения полосы/мощности/
|
||
duty cycle), 915 МГц в регионе ETSI занят GSM-900 uplink. Работа — кабель
|
||
или минимальная мощность на столе, без внешних усилителей.
|
||
|
||
## 7. Формат кадра
|
||
|
||
```
|
||
OFDM-кадр liquid: [преамбула][заголовок 12 Б][payload ≤ 1275 Б][CRC32]
|
||
|
||
Заголовок (12 байт):
|
||
0-1 0xF0 0xAA сигнатура
|
||
2-5 seq (uint32 BE) порядковый номер кадра
|
||
6-7 len (uint16 BE) длина исходных данных до FEC
|
||
8-9 nblocks число RS-блоков
|
||
10 last_block_bytes хвост последнего блока (0 = полный)
|
||
11 резерв
|
||
```
|
||
|
||
⚠️ **Критично**: у liquid заголовок пользователя по умолчанию **8 байт**.
|
||
Байты 8–11 передаются только после явного вызова на обеих сторонах:
|
||
|
||
```c
|
||
ofdmflexframegen_set_header_len(fg, 12); // TX
|
||
ofdmflexframesync_set_header_len(fs, 12); // RX
|
||
```
|
||
|
||
Без этого RX читает `hdr[10]` за пределами буфера (маскируется обрезкой
|
||
по `original_len`, но это мина).
|
||
|
||
## 8. Запуск
|
||
|
||
### 8.1 Передача файла Pluto→Pluto
|
||
|
||
```bash
|
||
scp -O test.bin root@192.168.2.1:/tmp/
|
||
|
||
# Pluto B — приёмник
|
||
ssh root@<pluto_B> '/tmp/receiver -f 915000000 -r 1920000 -b 1500000 -c rs8 \
|
||
> /tmp/out.bin'
|
||
|
||
# Pluto A — передатчик (кабель: -g -40 + аттенюатор!)
|
||
ssh root@192.168.2.1 'cat /tmp/test.bin | /tmp/transmitter -f 915000000 \
|
||
-r 1920000 -b 1500000 -g -40 -p 0 -c rs8'
|
||
|
||
# сверка
|
||
md5sum test.bin && ssh root@<pluto_B> 'md5sum /tmp/out.bin'
|
||
```
|
||
|
||
### 8.2 Бенчмарк CPU (перед любым изменением PHY)
|
||
|
||
```bash
|
||
scp -O ofdm_bench root@192.168.2.1:/tmp/
|
||
ssh root@192.168.2.1 '/tmp/ofdm_bench 1.92'
|
||
# критерий: "сигнал+кадры" ≥ 1.2× цели на одном ядре
|
||
```
|
||
|
||
## 9. Известные проблемы и техдолг
|
||
|
||
| # | Проблема | Статус |
|
||
|---|---|---|
|
||
| 1 | Заголовок 12 Б vs 8 Б по умолчанию у liquid (раздел 7) | **чинить до передачи файлов** |
|
||
| 2 | `recovered[4096]` в receiver при лимите nblocks≤100 (22 КБ) → переполнение стека на битом заголовке. Лимит должен быть `4096/RS_DATA = 18` | чинить |
|
||
| 3 | `fec_decode()` liquid не сообщает о неисправимых RS-блоках → счётчик `rs_saved` фиктивен. Достоверный критерий — только CRC/md5 поверх данных | знать; добавить CRC после декода |
|
||
| 4 | Внешний NCO-CFO в receiver: `set_phase(0)` на границах буферов рвёт фазу; ofdmflexframesync и так компенсирует CFO сам | убрать или обосновать замером |
|
||
| 5 | TX пушит весь буфер 16384 сэмпла при кадре ~10300 → ~35% эфира впустую + паузы `-p` | оптимизация: push ровно idx сэмплов |
|
||
| 6 | Нет ARQ: потерянный кадр = молчаливая дыра в файле. seq в заголовке есть, но RX его игнорирует | минимум: детектор пропусков seq с логом |
|
||
| 7 | Файл `reciever.c` → переименовать в `receiver.c` (make его ждёт) | тривиально |
|
||
|
||
## 10. Дорожная карта
|
||
|
||
1. ✅ Бенчмарк CPU на ARM Pluto → выбран rate 1.92 MSPS
|
||
2. Перенос TX/RX на Pluto (`local:`), фиксы #1, #2, #7
|
||
3. Файл через кабель + аттенюатор, md5 на 1 и 10 МБ
|
||
4. Антенны на столе: замер скорости, PER, счётчик потерянных seq (#6)
|
||
5. FDD 868/915 двусторонняя + меры из docs/NOTES.md
|
||
6. UDP-туннель поверх линка; затем web-настройка (libmicrohttpd)
|
||
7. Видео (raw UDP, пакеты ≤1472 Б) — при устойчивом PER
|
||
|
||
## 11. Диагностика (шпаргалка)
|
||
|
||
| Симптом | Первое, что проверить |
|
||
|---|---|
|
||
| scp: `sftp-server not found` | использовать `scp -O` |
|
||
| RX молчит | частоты TX/RX, `-g` TX, антенны/кабель, RX gain |
|
||
| CRC бьётся, кадры находятся | SNR: снизить полосу, амплитуду TX (`-a 0.15`) |
|
||
| md5 не сходится, CRC ок | потерянные кадры → смотреть разрывы seq (#6) |
|
||
| Работает на малом трафике, падает под нагрузкой | CPU-лимит: rate > 1.92 MSPS? второй процесс на ядре 0? |
|
||
| Loopback ок, дуплекс в эфире нет | самоглушение FDD (раздел 6) |
|
||
|
||
---
|
||
*Замеры и решения актуальны на 2026-07-10, прошивка v0.38, liquid-dsp 1.8.0.* |