commit 0f207b146d05fa3b125c14b2d5fa57ca8c704fe5 Author: Maxim Date: Fri Jul 10 11:00:38 2026 +0300 Initial commit: OFDM-радиолиния на двух Pluto+ (PHY/link, код на ARM) - src: transmitter, receiver, ofdm_bench, common (libiio local: + liquid-dsp) - scripts: кросс-сборка зависимостей, деплой scp -O, e2e-тест с md5 - docs: бенчмарк CPU (выбор rate 1.92 MSPS), грабли FDD/регуляторика - все текстовые файлы нормализованы в LF (.gitattributes принудительно): CRLF в toolchain.env/makefile ломал source и make на Linux - .vscode: IntelliSense в режиме linux-gcc-arm, заголовки из .xarm/include Co-Authored-By: Claude Fable 5 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dc92658 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,26 @@ +# pluto-link: весь код исполняется на Linux (RPi-хост и ARM Pluto). +# Принудительный LF везде — CRLF в makefile/toolchain.env/скриптах ломает +# make и source на плате ("bad interpreter", \r в переменных PLUTO_A/XARM). + +* text=auto eol=lf + +# Исходники и сборка +*.c text eol=lf +*.h text eol=lf +*.sh text eol=lf +makefile text eol=lf +*.env text eol=lf +*.cmake text eol=lf + +# Документация +*.md text eol=lf + +# Конфиги IDE (json читает и Windows — LF безопасен) +*.json text eol=lf + +# Бинарные артефакты (если когда-то попадут в репо — не трогать) +*.bin binary +*.a binary +*.so binary +*.png binary +*.jpg binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..212b0d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# --- артефакты сборки --- +build/ +*.o +*.a +*.elf + +# скомпилированные бинарники в корне (make из README §4.3 кладёт рядом) +/transmitter +/receiver +/ofdm_bench + +# --- локальная копия кросс-заголовков для IntelliSense (см. .vscode/) --- +.xarm/ + +# --- артефакты тестов и логи --- +*.log +link_test.bin +out.bin +test.bin + +# --- редакторы/ОС --- +.vscode/* +!.vscode/c_cpp_properties.json +!.vscode/settings.json +.idea/ +*.swp +*~ +.DS_Store +Thumbs.db diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..9f85b2a --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,19 @@ +{ + "configurations": [ + { + "name": "pluto-armhf", + "includePath": [ + "${workspaceFolder}/src", + "${workspaceFolder}/.xarm/include" + ], + "defines": [], + "cStandard": "gnu11", + "intelliSenseMode": "linux-gcc-arm", + "compilerArgs": [ + "-mfpu=neon", + "-mfloat-abi=hard" + ] + } + ], + "version": 4 +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ac230ba --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.eol": "\n", + "files.encoding": "utf8", + "C_Cpp.default.cStandard": "gnu11", + "C_Cpp.default.intelliSenseMode": "linux-gcc-arm" +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f65aa75 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,56 @@ +# CLAUDE.md — конституция проекта pluto-link + +Прочитай этот файл первым. Полная документация — README.md, docs/. + +## Контекст +Радиолиния точка-точка на двух Pluto+ (клон ADALM-Pluto: Zynq-7020, +2 × Cortex-A9 @ 667 МГц, NEON, 1 ГБ RAM, прошивка v0.38). +Весь сигнальный тракт исполняется НА ARM ВНУТРИ Pluto (libiio, контекст +`local:`). Хост (Raspberry Pi, aarch64) — только кросс-сборка и деплой. +PHY: OFDM через liquid-dsp 1.8.0 + FFTW/NEON, FEC RS(255,223). + +## Жёсткие правила +1. Язык — C. НИКОГДА не предлагай Python, GNU Radio или обработку на хосте. +2. libiio только `local:` в тракте. `ip:` — только для хост-утилит отладки. +3. Статическая линковка, флаги обязательны: + `-O3 -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard -ffast-math -static` +4. Никаких тяжёлых зависимостей (FFmpeg, cJSON, curl). JSON — руками. +5. Бюджет CPU: на DSP доступно ОДНО ядро 667 МГц (второе — IIO/FEC/IO). + Замерено: ofdmflexframesync тянет 2.34 Msps на кадрах, 5.32 Msps на шуме. + Поэтому sample rate = 1 920 000. НИКОГДА не поднимай до 3 840 000 — + система развалится под нагрузкой (0.61× от realtime). +6. Изменил PHY-параметры (M/CP/модуляция/rate) → сначала прогони + src/ofdm_bench.c на плате, обнови docs/BENCHMARK.md в том же коммите. +7. Порядок отладки: кабель+аттенюатор 30–40 дБ (TX gain −40) → антенны + на столе → дистанция. Не отлаживать новый код сразу в эфире. +8. Изменения показывай в виде diff с объяснением каждой правки. + +## Параметры тракта (текущие) +- rate 1 920 000, bw 1 500 000, OFDM M=64 CP=16 taper=4, QPSK, CRC32 +- FEC RS(255,223), payload ≤ 1024 Б исходных (≤ 5 блоков, 1275 Б в эфире) +- Симплекс-тест: обе платы 915 МГц. FDD (этап 2): A TX915/RX868, B зеркально +- RX gain 50 дБ manual; TX gain: кабель −40, антенны от −20 + +## Формат кадра (заголовок 12 Б) +0-1: 0xF0 0xAA | 2-5: seq u32 BE | 6-7: len u16 BE | 8-9: nblocks | +10: last_block_bytes | 11: резерв +КРИТИЧНО: liquid по умолчанию передаёт 8 Б заголовка. Обязательно +`ofdmflexframegen_set_header_len(fg,12)` и +`ofdmflexframesync_set_header_len(fs,12)` на обеих сторонах. + +## IIO-имена (первые грабли) +PHY: `ad9361-phy`; RX DMA: `cf-ad9361-lpc`; TX DMA: `cf-ad9361-dds-core-lpc`. +TX LO: канал `altvoltage1` (output); RX LO: `altvoltage0` (output). +Каналы данных: voltage0/voltage1 (I/Q, int16, диапазон ±2047 — 12 бит). + +## Известный техдолг (README §9) +#1 header 12Б (см. выше) · #2 лимит nblocks ≤ 18, не 100 (стек recovered[4096]) +· #3 fec_decode() liquid не сообщает об ошибках — rs_saved фиктивен +· #4 внешний NCO в receiver вреден, убрать · #5 TX пушит лишние сэмплы +· #6 нет детектора потерь по seq · #7 reciever.c → receiver.c + +## Команды +Сборка: `make` · Деплой: `make deploy` (scp -O! sftp на плате нет) +Бенчмарк: `make bench && scripts/deploy.sh bench && ssh root@ '/tmp/ofdm_bench 1.92'` +E2E-тест: `scripts/test_file_link.sh` +Пароль root на Pluto: `analog`. rootfs в RAM — /tmp очищается при ребуте. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c8beb5 --- /dev/null +++ b/README.md @@ -0,0 +1,246 @@ +# 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 Зависимости (один раз, ~15 мин) + +`scripts/build_deps.sh` собирает в `$HOME/xarm` статические armhf-библиотеки: + +1. **FFTW 3.3.10**: `--enable-single --enable-neon --enable-static` +2. **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` + +После сборки liquid **обязательно** проверить, что FFTW подхвачен: +`grep HAVE_LIBFFTW3F config.log` → должно быть `1`. Без FFTW liquid молча +падает на встроенный FFT и цифры бюджета CPU невалидны. + +### 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 -lliquid -lfftw3f -lm +``` + +### 4.4 Деплой + +```bash +scp -O transmitter receiver root@192.168.2.1:/tmp/ # Pluto A +scp -O transmitter receiver root@:/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@ '/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@ '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.* \ No newline at end of file diff --git a/docs/BENCHMARK.md b/docs/BENCHMARK.md new file mode 100644 index 0000000..4c358c5 --- /dev/null +++ b/docs/BENCHMARK.md @@ -0,0 +1,84 @@ +# BENCHMARK.md — протокол замеров CPU (ofdmflexframesync на ARM Pluto) + +Этот документ — основание для выбора sample rate. При любом изменении +PHY-параметров (M, CP, модуляция, payload) бенчмарк перегоняется и таблица +обновляется в том же коммите (правило №6 CLAUDE.md). + +## Зачем + +`ofdmflexframesync` (liquid-dsp) — самый дорогой блок тракта. Если его +пропускная способность на одном ядре ниже sample rate, приёмник отстаёт от +эфира: IIO-буферы какое-то время амортизируют, затем переполняются и кадры +теряются лавинообразно. Опасность в том, что на редких пакетах система +*выглядит* работающей — отказ проявляется только под нагрузкой. + +## Методика + +Инструмент: `src/ofdm_bench.c`. Генерирует эталонный буфер +(8 кадров + гэпы, duty cycle кадров ~57%), делает int16-версии +«сигнал+шум» (SNR ~28 дБ) и «чистый шум», прогоняет каждый через +синхронизатор ≥5 с. Конвертация int16→float входит в замер (как в +receiver.c). Процесс пинится на ядро 0 (`sched_setaffinity`). + +Меряются два режима, лимитирует худший: +- **шум** — стоимость дежурного поиска преамбулы (>90% времени в линии); +- **сигнал+кадры** — полный цикл демодуляции под нагрузкой. + +Валидация замера: `CRC ok` должен равняться `8 × число проходов` +(все сгенерированные кадры пойманы и декодированы). Иначе сборка кривая +и цифрам верить нельзя. + +## Окружение эталонного замера + +| Параметр | Значение | +|---|---| +| Дата | 2026-07-10 | +| Плата | Pluto+ (Zynq-7020), 2 × Cortex-A9 @ 667 МГц, NEON, 1 ГБ | +| Прошивка | v0.38-4-g95aad-dirty | +| liquid-dsp | 1.8.0, **с FFTW 3.3.10 (NEON)** — `HAVE_LIBFFTW3F 1` в config.log | +| Сборка | `-O3 -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard -ffast-math -static` | +| Кадр | M=64, CP=16, taper=4, QPSK, CRC32, FEC none, payload 1275 Б, header 8 Б | +| Ядро | одно (affinity CPU0) | + +## Результаты + +``` +буфер: 143360 сэмплов, 8 кадров на проход + +шум (поиск): 5.318–5.324 Msps (186 проходов; кадров 0 — норма) +сигнал+кадры: 2.338–2.339 Msps (82 прохода; 656/656 CRC ok — валидно) +``` + +Разброс между тремя запусками < 0.1% — замер чистый. + +## Выводы (проектные решения) + +| Sample rate | Запас (кадры) | Вердикт | +|---|---|---| +| 3.84 MSPS | 0.61× | **ЗАПРЕЩЁН**: не realtime, отказ под нагрузкой | +| 1.92 MSPS | 1.22× | **рабочий**: второе ядро свободно под IIO/RS/IO | +| 1.00 MSPS | 2.34× | резерв, если 1.92 окажется впритык в реальном тракте | + +- Полезная скорость при 1.92 MSPS: ~1.3–1.5 Мбит/с (≈160–190 КБ/с) + после RS(255,223), преамбул и служебки. +- Поиск на шуме (5.32 Msps) не лимитирует ни один из режимов. +- RS-декод в замер не входит: его стоимость мала относительно + синхронизатора и планируется на второе ядро. + +## Неисследованные резервы (если понадобится > 1.92 MSPS) + +1. **M=32/CP=8** — дешевле FFT и эквалайзер; цена: выше доля пилотов, + чувствительнее к CFO. Прогнать бенчмарк с этими параметрами. +2. **Двухпоточный конвейер** — IIO-refill + int16→float на ядре 1, + синхронизатор на ядре 0: ~+10–15% и убирает джиттер refill. +3. **Сравнение NEON vs VFP** — пересборка без `-mfpu=neon` + (с `-mfpu=vfpv3`) даёт цифру вклада векторизации для отчёта. + +## Как воспроизвести + +```bash +make bench +scripts/deploy.sh bench +ssh root@ '/tmp/ofdm_bench 1.92' +# критерий приёмки: "сигнал+кадры" ≥ 1.2× цели на одном ядре +``` \ No newline at end of file diff --git a/docs/NOTES.md b/docs/NOTES.md new file mode 100644 index 0000000..b728aa8 --- /dev/null +++ b/docs/NOTES.md @@ -0,0 +1,107 @@ +# 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. + +## 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' +``` \ No newline at end of file diff --git a/makefile b/makefile new file mode 100644 index 0000000..b47e8c5 --- /dev/null +++ b/makefile @@ -0,0 +1,66 @@ +# pluto-link — кросс-сборка под ARM Pluto (armhf, static) +# Цели: all | tx | rx | bench | deploy | test | clean | help +# Переменные можно переопределить: make PLUTO_A=ip PLUTO_B=ip + +# --- тулчейн (согласовано с toolchain.env) --- +CROSS ?= arm-linux-gnueabihf- +CC := $(CROSS)gcc +XARM ?= $(HOME)/xarm + +CFLAGS := -O3 -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard -ffast-math \ + -Wall -Wextra -static -I$(XARM)/include -Isrc +LDFLAGS := -L$(XARM)/lib +LDLIBS := -lliquid -lfftw3f -lm -lpthread + +# --- платы --- +PLUTO_A ?= 192.168.2.1 +PLUTO_B ?= 192.168.3.1 + +BIN := build +SRC := src + +all: $(BIN)/transmitter $(BIN)/receiver $(BIN)/ofdm_bench + +$(BIN): + mkdir -p $(BIN) + +$(BIN)/transmitter: $(SRC)/transmitter.c $(SRC)/common.c $(SRC)/common.h | $(BIN) + $(CC) $(CFLAGS) $(SRC)/transmitter.c $(SRC)/common.c -o $@ $(LDFLAGS) -liio $(LDLIBS) + +$(BIN)/receiver: $(SRC)/receiver.c $(SRC)/common.c $(SRC)/common.h | $(BIN) + $(CC) $(CFLAGS) $(SRC)/receiver.c $(SRC)/common.c -o $@ $(LDFLAGS) -liio $(LDLIBS) + +$(BIN)/ofdm_bench: $(SRC)/ofdm_bench.c | $(BIN) + $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) $(LDLIBS) + +tx: $(BIN)/transmitter +rx: $(BIN)/receiver +bench: $(BIN)/ofdm_bench + +# --- деплой (scp -O: на прошивке v0.38 нет sftp-server) --- +deploy: all + scripts/deploy.sh $(PLUTO_A) $(PLUTO_B) + +# --- e2e: файл через радио + md5 --- +test: deploy + scripts/test_file_link.sh $(PLUTO_A) $(PLUTO_B) + +check-env: + @which $(CC) >/dev/null || { echo "нет $(CC): sudo apt install gcc-arm-linux-gnueabihf"; exit 1; } + @test -f $(XARM)/lib/libliquid.a || { echo "нет libliquid.a: запусти scripts/build_deps.sh"; exit 1; } + @test -f $(XARM)/lib/libfftw3f.a || { echo "нет libfftw3f.a: запусти scripts/build_deps.sh"; exit 1; } + @echo "OK: тулчейн и зависимости на месте" + +clean: + rm -rf $(BIN) + +help: + @echo "make [all] — собрать transmitter, receiver, ofdm_bench (static armhf)" + @echo "make tx|rx|bench — собрать отдельную цель" + @echo "make deploy — залить бинарники на обе платы (scp -O в /tmp)" + @echo "make test — e2e-тест передачи файла с md5-сверкой" + @echo "make check-env — проверить тулчейн и $(XARM)" + @echo "make clean — удалить build/" + @echo "переменные: PLUTO_A=$(PLUTO_A) PLUTO_B=$(PLUTO_B) XARM=$(XARM)" + +.PHONY: all tx rx bench deploy test check-env clean help \ No newline at end of file diff --git a/scripts/build_deps.sh b/scripts/build_deps.sh new file mode 100644 index 0000000..db03180 --- /dev/null +++ b/scripts/build_deps.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# build_deps.sh — кросс-сборка статических armhf-зависимостей в $XARM +# Собирает: FFTW 3.3.10 (single+NEON), liquid-dsp 1.8.0, libiio (local backend) +# Запуск: scripts/build_deps.sh (один раз, ~15-20 мин на RPi4) +set -euo pipefail +source "$(dirname "$0")/../toolchain.env" + +WORK="$HOME/src" +mkdir -p "$WORK" "$XARM" +cd "$WORK" + +echo "=== [0/4] Проверка тулчейна ===" +which "${CC}" >/dev/null || { + echo "Нет ${CC}. Установи: sudo apt install gcc-arm-linux-gnueabihf"; exit 1; } +sudo apt-get install -y build-essential autoconf automake libtool git wget cmake >/dev/null + +echo "=== [1/4] FFTW 3.3.10 (float + NEON, static) ===" +if [ ! -f "$XARM/lib/libfftw3f.a" ]; then + [ -d fftw-3.3.10 ] || { + wget -nc http://fftw.org/fftw-3.3.10.tar.gz + tar xf fftw-3.3.10.tar.gz + } + cd fftw-3.3.10 + ./configure --host=arm-linux-gnueabihf --prefix="$XARM" \ + --enable-single --enable-neon --enable-static --disable-shared \ + CC="$CC" CFLAGS="$XCFLAGS" + make -j"$(nproc)" && make install + cd "$WORK" +else + echo "уже собран, пропускаю" +fi + +echo "=== [2/4] liquid-dsp 1.8.0 (static, с FFTW) ===" +if [ ! -f "$XARM/lib/libliquid.a" ]; then + [ -d liquid-dsp ] || git clone --depth 1 --branch v1.8.0 \ + https://github.com/jgaeddert/liquid-dsp.git + cd liquid-dsp + ./bootstrap.sh + ./configure --host=arm-linux-gnueabihf --prefix="$XARM" \ + CC="$CC" CFLAGS="$XCFLAGS -ffast-math" \ + CPPFLAGS="-I$XARM/include" LDFLAGS="-L$XARM/lib" \ + ac_cv_func_malloc_0_nonnull=yes ac_cv_func_realloc_0_nonnull=yes + # КРИТИЧНО: без FFTW liquid молча падает на медленный встроенный FFT, + # и бюджет CPU из docs/BENCHMARK.md становится невалидным. + grep -q "HAVE_LIBFFTW3F 1" config.log || { + echo "ОШИБКА: liquid не нашёл кросс-FFTW — проверь \$XARM"; exit 1; } + make -j"$(nproc)" && make install + cd "$WORK" +else + echo "уже собран, пропускаю" +fi + +echo "=== [3/4] libiio v0.25 (static, только local backend) ===" +if [ ! -f "$XARM/lib/libiio.a" ]; then + [ -d libiio ] || git clone --depth 1 --branch v0.25 \ + https://github.com/analogdevicesinc/libiio.git + cd libiio + cat > toolchain-armhf.cmake < # явные адреса +# scripts/deploy.sh bench # только бенчмарк +# Прошивка v0.38: sftp-server отсутствует → обязателен scp -O (legacy). +set -euo pipefail +source "$(dirname "$0")/../toolchain.env" +cd "$(dirname "$0")/.." + +# sshpass, если установлен, избавляет от ввода пароля (пароль: analog) +if command -v sshpass >/dev/null; then + SCP=(sshpass -p "$PLUTO_PASS" scp -O -o StrictHostKeyChecking=no) +else + SCP=(scp -O) +fi + +FILES=(build/transmitter build/receiver build/ofdm_bench) +TARGETS=() + +case "${1:-}" in + bench) FILES=(build/ofdm_bench); TARGETS=("${2:-$PLUTO_A}") ;; + "") TARGETS=("$PLUTO_A" "$PLUTO_B") ;; + *) TARGETS=("$@") ;; +esac + +for f in "${FILES[@]}"; do + [ -f "$f" ] || { echo "нет $f — сначала make"; exit 1; } +done + +for ip in "${TARGETS[@]}"; do + echo "=== деплой на root@$ip:/tmp/ ===" + if ! ping -c1 -W2 "$ip" >/dev/null 2>&1; then + echo " ! $ip не пингуется, пропускаю"; continue + fi + "${SCP[@]}" "${FILES[@]}" "root@$ip:/tmp/" + echo " ok: ${FILES[*]}" +done + +echo "Напоминание: rootfs платы в RAM — после перезагрузки залить заново." \ No newline at end of file diff --git a/scripts/test_file_link.sh b/scripts/test_file_link.sh new file mode 100644 index 0000000..2e59e0f --- /dev/null +++ b/scripts/test_file_link.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# test_file_link.sh — e2e-тест: файл → Pluto A → эфир → Pluto B → md5 +# Использование: scripts/test_file_link.sh [ipA] [ipB] [размер_КБ] +# По умолчанию: адреса из toolchain.env, файл 1024 КБ, симплекс 915 МГц. +# +# ВНИМАНИЕ: сначала кабель + аттенюатор 30–40 дБ (TX gain −40)! +set -euo pipefail +source "$(dirname "$0")/../toolchain.env" + +A="${1:-$PLUTO_A}" # передатчик +B="${2:-$PLUTO_B}" # приёмник +SIZE_KB="${3:-1024}" + +FREQ=915000000 +RATE=1920000 # НЕ поднимать: бюджет CPU, docs/BENCHMARK.md +BW=1500000 +TXGAIN=-40 # для кабеля; антенны: −20…0 +TIMEOUT=$(( SIZE_KB / 100 + 30 )) # ~160 КБ/с + запас + +if command -v sshpass >/dev/null; then + SSH=(sshpass -p "$PLUTO_PASS" ssh -o StrictHostKeyChecking=no) + SCP=(sshpass -p "$PLUTO_PASS" scp -O -o StrictHostKeyChecking=no) +else + SSH=(ssh); SCP=(scp -O) + echo "подсказка: apt install sshpass — и пароль вводить не придётся" +fi + +echo "=== [1/5] Тестовый файл ${SIZE_KB} КБ ===" +dd if=/dev/urandom of=/tmp/link_test.bin bs=1024 count="$SIZE_KB" status=none +MD5_SRC=$(md5sum /tmp/link_test.bin | cut -d' ' -f1) +echo "md5: $MD5_SRC" + +echo "=== [2/5] Заливка файла на Pluto A ($A) ===" +"${SCP[@]}" /tmp/link_test.bin "root@$A:/tmp/" + +echo "=== [3/5] Старт приёмника на Pluto B ($B), таймаут ${TIMEOUT}с ===" +"${SSH[@]}" "root@$B" \ + "rm -f /tmp/out.bin; timeout $TIMEOUT /tmp/receiver -f $FREQ -r $RATE \ + -b $BW -c rs8 -u local: > /tmp/out.bin 2>/tmp/rx.log" & +RX_PID=$! +sleep 3 # дать RX прогреться до старта TX + +echo "=== [4/5] Передача с Pluto A ===" +"${SSH[@]}" "root@$A" \ + "cat /tmp/link_test.bin | /tmp/transmitter -f $FREQ -r $RATE -b $BW \ + -g $TXGAIN -p 0 -c rs8 -u local: 2>/tmp/tx.log" +sleep 3 # дохлебать хвост эфира +"${SSH[@]}" "root@$B" "killall receiver 2>/dev/null || true" +wait "$RX_PID" 2>/dev/null || true + +echo "=== [5/5] Сверка ===" +MD5_DST=$("${SSH[@]}" "root@$B" "md5sum /tmp/out.bin 2>/dev/null | cut -d' ' -f1" || echo "нет_файла") +SIZE_DST=$("${SSH[@]}" "root@$B" "wc -c < /tmp/out.bin 2>/dev/null" || echo 0) + +echo "источник: $MD5_SRC ($((SIZE_KB*1024)) байт)" +echo "приёмник: $MD5_DST ($SIZE_DST байт)" + +if [ "$MD5_SRC" = "$MD5_DST" ]; then + echo "РЕЗУЛЬТАТ: OK — файл передан без искажений" +else + echo "РЕЗУЛЬТАТ: FAIL" + echo "Диагностика (README §11):" + echo " - размер меньше исходного → потерянные кадры (нет ARQ, техдолг #6):" + echo " смотри лог RX и разрывы seq" + echo " - размер равен, md5 нет → техдолг #1 (header_len=12?) или #3" + echo " - размер 0 → RX не поймал ничего: частота/усиление/кабель" + "${SSH[@]}" "root@$B" "tail -5 /tmp/rx.log" || true +fi \ No newline at end of file diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..0998047 --- /dev/null +++ b/src/common.c @@ -0,0 +1,106 @@ +/** + * common.c — Реализация общих функций + */ + +#include "common.h" + +volatile sig_atomic_t stop = 0; + +static void sigint_handler(int s) { + (void)s; + stop = 1; +} + +void setup_signal_handlers(void) { + signal(SIGINT, sigint_handler); + signal(SIGTERM, sigint_handler); + signal(SIGPIPE, SIG_IGN); +} + +void print_usage(const char *prog_name, const char *mode) { + fprintf(stderr, "═══════════════════════════════════════════════════\n"); + fprintf(stderr, "OFDM %s — PlutoSDR Radio Link\n", mode); + fprintf(stderr, "═══════════════════════════════════════════════════\n"); + fprintf(stderr, "Использование: %s [ОПЦИИ]\n\n", prog_name); + fprintf(stderr, "ОБЩИЕ ОПЦИИ:\n"); + fprintf(stderr, " -f ЧАСТОТА Частота несущей в Гц (по умолчанию: %lld)\n", DEFAULT_FREQ); + fprintf(stderr, " -r СКОРОСТЬ Частота дискретизации (по умолчанию: %lld)\n", DEFAULT_RATE); + fprintf(stderr, " -b ПОЛОСА Полоса пропускания (по умолчанию: %lld)\n", DEFAULT_BW); + fprintf(stderr, " -u URI URI PlutoSDR\n"); + fprintf(stderr, " -c РЕЖИМ FEC: rs8 или none (по умолчанию: rs8)\n"); + fprintf(stderr, " -h Показать эту справку\n"); +} + +void print_config(const char *mode, long long freq, long long rate, + long long bw, int fec_on) { + fprintf(stderr, "═══════════════════════════════════════════════════\n"); + fprintf(stderr, "КОНФИГУРАЦИЯ %s\n", mode); + fprintf(stderr, "Частота: %.1f МГц\n", freq / 1e6); + fprintf(stderr, "Дискрет.: %.2f МГц\n", rate / 1e6); + fprintf(stderr, "Полоса: %.2f МГц\n", bw / 1e6); + fprintf(stderr, "OFDM: M=%d, CP=%d, Taper=%d\n", OFDM_M, OFDM_CP, OFDM_TAPER); + fprintf(stderr, "FEC: %s\n", fec_on ? "RS(255,223)" : "ОТКЛЮЧЕН"); +} + +int pluto_init(const char *uri, struct iio_context **ctx, + struct iio_device **phy, struct iio_device **dev, int is_tx) { + *ctx = iio_create_context_from_uri(uri); + if (!*ctx) { + LOG_ERROR("Не удалось подключиться к PlutoSDR по адресу %s", uri); + return -1; + } + + *phy = iio_context_find_device(*ctx, "ad9361-phy"); + *dev = iio_context_find_device(*ctx, + is_tx ? "cf-ad9361-dds-core-lpc" : "cf-ad9361-lpc"); + + if (!*phy || !*dev) { + LOG_ERROR("Радиоустройства не найдены в контексте"); + iio_context_destroy(*ctx); + return -1; + } + + return 0; +} + +void pluto_configure(struct iio_device *phy, long long freq, long long rate, + long long bw, double gain, int is_tx) { + if (is_tx) { + // Настройка передатчика + iio_channel_attr_write_longlong( + iio_device_find_channel(phy, "altvoltage1", true), + "frequency", freq); + iio_channel_attr_write_longlong( + iio_device_find_channel(phy, "voltage0", true), + "sampling_frequency", rate); + iio_channel_attr_write_longlong( + iio_device_find_channel(phy, "voltage0", true), + "rf_bandwidth", bw); + iio_channel_attr_write( + iio_device_find_channel(phy, "voltage0", true), + "rf_port_select", "A"); + iio_channel_attr_write_double( + iio_device_find_channel(phy, "voltage0", true), + "hardwaregain", gain); + } else { + // Настройка приёмника + iio_channel_attr_write_longlong( + iio_device_find_channel(phy, "altvoltage0", true), + "frequency", freq); + iio_channel_attr_write_longlong( + iio_device_find_channel(phy, "voltage0", false), + "sampling_frequency", rate); + iio_channel_attr_write_longlong( + iio_device_find_channel(phy, "voltage0", false), + "rf_bandwidth", bw); + iio_channel_attr_write( + iio_device_find_channel(phy, "voltage0", false), + "gain_control_mode", "manual"); + iio_channel_attr_write_double( + iio_device_find_channel(phy, "voltage0", false), + "hardwaregain", gain); + iio_channel_attr_write( + iio_device_find_channel(phy, "voltage0", false), + "rf_port_select", "A_BALANCED"); + } +} \ No newline at end of file diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..2755359 --- /dev/null +++ b/src/common.h @@ -0,0 +1,61 @@ +/** + * common.h — Общие определения и конфигурация для приёмника и передатчика + * OFDM + RS(255,223) FEC + PlutoSDR + */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Параметры OFDM-модуляции +#define OFDM_M 64 +#define OFDM_CP 16 +#define OFDM_TAPER 4 + +// Параметры Рида-Соломона +#define RS_DATA 223 +#define RS_ENC 255 + +// Параметры радиотракта по умолчанию +#define DEFAULT_FREQ 1265000000LL +#define DEFAULT_RATE 3840000LL +#define DEFAULT_BW 3000000LL + +// Адреса PlutoSDR +#define TX_URI_DEFAULT "ip:192.168.3.1" // адрес Pluto передатчика +#define RX_URI_DEFAULT "ip:192.168.2.1" // адрес Pluto приемника + +// Формат заголовка кадра +#define HDR_SIGNATURE_0 0xF0 +#define HDR_SIGNATURE_1 0xAA +#define HDR_SIZE 12 + +// Макросы логирования +#define LOG_ERROR(fmt, ...) fprintf(stderr, "[ОШИБКА] " fmt "\n", ##__VA_ARGS__) +#define LOG_INFO(fmt, ...) fprintf(stderr, "[INFO] " fmt "\n", ##__VA_ARGS__) + +// Глобальный флаг завершения +extern volatile sig_atomic_t stop; + +// Прототипы общих функций +void setup_signal_handlers(void); +void print_usage(const char *prog_name, const char *mode); +void print_config(const char *mode, long long freq, long long rate, + long long bw, int fec_on); +int pluto_init(const char *uri, struct iio_context **ctx, + struct iio_device **phy, struct iio_device **dev, int is_tx); +void pluto_configure(struct iio_device *phy, long long freq, long long rate, + long long bw, double gain, int is_tx); + +#endif \ No newline at end of file diff --git a/src/ofdm_bench.c b/src/ofdm_bench.c new file mode 100644 index 0000000..6e3a31a --- /dev/null +++ b/src/ofdm_bench.c @@ -0,0 +1,168 @@ +/** + * ofdm_bench.c — офлайн-бенчмарк ofdmflexframesync на ARM (Pluto, Cortex-A9) + * + * Повторяет тракт receiver.c: int16 IQ -> float -> ofdmflexframesync, + * параметры кадра как в transmitter.c (M=64, CP=16, taper=4, QPSK, CRC32). + * Меряет два режима: чистый шум (поиск преамбулы) и сигнал с кадрами. + * + * Сборка (на хосте, кросс): + * arm-linux-gnueabihf-gcc -O3 -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard \ + * -ffast-math -static ofdm_bench.c -o ofdm_bench \ + * -I$HOME/xarm/include -L$HOME/xarm/lib -lliquid -lfftw3f -lm + * + * Запуск на Pluto: + * /tmp/ofdm_bench [целевой_rate_Msps] # по умолчанию 3.84 + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OFDM_M 64 /* как в common.h */ +#define OFDM_CP 16 +#define OFDM_TAPER 4 +#define PAYLOAD_LEN 1275 /* 5 блоков RS(255,223): как при n=1024 в transmitter.c */ +#define N_FRAMES 8 /* кадров в эталонном буфере */ +#define GAP_SAMPLES 7680 /* пауза 2000 мкс @ 3.84 MSPS, как -p 2000 */ +#define AMP 0.30f +#define NOISE_STD 0.012f /* ~28 дБ SNR, "хороший" эфир */ +#define BENCH_SEC 5.0 + +static unsigned det = 0, ok = 0; + +static int cb(unsigned char *h, int hv, unsigned char *p, unsigned int pl, + int pv, framesyncstats_s s, void *u) +{ + (void)h; (void)p; (void)pl; (void)s; (void)u; + det++; + if (hv && pv) ok++; + return 0; +} + +static double tnow(void) +{ + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + return t.tv_sec + 1e-9 * t.tv_nsec; +} + +static double bench(ofdmflexframesync fs, const int16_t *iq, unsigned n, + float complex *work, const char *name, double target_msps) +{ + det = ok = 0; + ofdmflexframesync_reset(fs); + + unsigned long long total = 0; + unsigned passes = 0; + double t0 = tnow(), t1; + + do { + /* конвертация int16 -> float входит в замер, как в receiver.c */ + for (unsigned i = 0; i < n; i++) + work[i] = (iq[2*i] / 2048.0f) + (iq[2*i+1] / 2048.0f) * _Complex_I; + ofdmflexframesync_execute(fs, work, n); + total += n; + passes++; + t1 = tnow(); + } while (t1 - t0 < BENCH_SEC); + + double msps = (double)total / (t1 - t0) / 1e6; + printf("%-16s %8.3f Msps x%.2f от цели кадров: %u найдено / %u CRC ok (%u проходов)\n", + name, msps, msps / target_msps, det, ok, passes); + return msps; +} + +int main(int argc, char **argv) +{ + double target = (argc > 1) ? atof(argv[1]) : 3.84; + + /* пиним на ядро 0 — меряем стоимость на ОДНОМ ядре */ + cpu_set_t cs; + CPU_ZERO(&cs); + CPU_SET(0, &cs); + sched_setaffinity(0, sizeof(cs), &cs); + + srand(1); + + /* --- генератор кадров: параметры как в transmitter.c --- */ + ofdmflexframegenprops_s p; + ofdmflexframegenprops_init_default(&p); + p.check = LIQUID_CRC_32; + p.fec0 = LIQUID_FEC_NONE; + p.fec1 = LIQUID_FEC_NONE; + p.mod_scheme = LIQUID_MODEM_QPSK; + ofdmflexframegen fg = + ofdmflexframegen_create(OFDM_M, OFDM_CP, OFDM_TAPER, NULL, &p); + + printf("liquid-dsp %s | M=%d CP=%d taper=%d | QPSK, CRC32, FEC none | payload %d B\n", + liquid_version, OFDM_M, OFDM_CP, OFDM_TAPER, PAYLOAD_LEN); + printf("цель: %.2f Msps, одно ядро (affinity CPU0)\n\n", target); + + /* --- эталонный буфер: [гап | кадр] x N_FRAMES --- */ + const unsigned sym = OFDM_M + OFDM_CP; + const unsigned cap = N_FRAMES * (GAP_SAMPLES + 220 * sym); + float complex *sig = malloc(cap * sizeof(float complex)); + unsigned n = 0; + + unsigned char hdr[8] = {0xF0, 0xAA, 0, 0, 0, 1, 0, 0}; + unsigned char pay[PAYLOAD_LEN]; + + for (unsigned f = 0; f < N_FRAMES; f++) { + for (unsigned i = 0; i < GAP_SAMPLES && n < cap; i++) + sig[n++] = 0; + + for (unsigned i = 0; i < PAYLOAD_LEN; i++) + pay[i] = rand() & 0xFF; + + ofdmflexframegen_assemble(fg, hdr, pay, PAYLOAD_LEN); + int done = 0; + float complex buf[sym]; + while (!done && n + sym <= cap) { + done = ofdmflexframegen_write(fg, buf, sym); + for (unsigned i = 0; i < sym; i++) + sig[n++] = buf[i] * AMP; + } + } + + /* --- int16-версии, как приходят из IIO: сигнал+шум и чистый шум --- */ + int16_t *iq_sig = malloc((size_t)n * 2 * sizeof(int16_t)); + int16_t *iq_noise = malloc((size_t)n * 2 * sizeof(int16_t)); + for (unsigned i = 0; i < n; i++) { + float complex nz = NOISE_STD * (float)M_SQRT1_2 * + (randnf() + randnf() * _Complex_I); + float complex x = sig[i] + nz; + iq_sig[2*i] = (int16_t)(crealf(x) * 2047); + iq_sig[2*i+1] = (int16_t)(cimagf(x) * 2047); + iq_noise[2*i] = (int16_t)(crealf(nz) * 2047); + iq_noise[2*i+1] = (int16_t)(cimagf(nz) * 2047); + } + float complex *work = malloc((size_t)n * sizeof(float complex)); + + ofdmflexframesync fs = + ofdmflexframesync_create(OFDM_M, OFDM_CP, OFDM_TAPER, NULL, cb, NULL); + + printf("буфер: %u сэмплов, %u кадров на проход\n\n", n, N_FRAMES); + + double m_noise = bench(fs, iq_noise, n, work, "шум (поиск):", target); + double m_sig = bench(fs, iq_sig, n, work, "сигнал+кадры:", target); + + double worst = (m_sig < m_noise) ? m_sig : m_noise; + printf("\nУзкое место: %.3f Msps -> ", worst); + if (worst > 2.0 * target) + printf("OK: запас >2x, realtime на одном ядре реален\n"); + else if (worst > 1.2 * target) + printf("ВПРИТЫК (1.2–2x): пойдёт, но без запаса — думать о снижении rate\n"); + else + printf("НЕ ТЯНЕТ: снижать sample rate или уходить на single-carrier PHY\n"); + + free(sig); free(iq_sig); free(iq_noise); free(work); + ofdmflexframegen_destroy(fg); + ofdmflexframesync_destroy(fs); + return 0; +} \ No newline at end of file diff --git a/src/receiver.c b/src/receiver.c new file mode 100644 index 0000000..94d6977 --- /dev/null +++ b/src/receiver.c @@ -0,0 +1,205 @@ +// receiver.c — OFDM-приёмник с RS(255,223) FEC и частотной коррекцией + + +#include "common.h" + +#define BUF_SAMPLES 32768 +#define MAX_FRAME_SAMPLES (1 << 16) +#define RX_GAIN 50.0 +#define CFO_FILTER_ALPHA 0.95f +#define CFO_MAX_VALID 0.5f + +extern volatile sig_atomic_t stop; + +static uint64_t calls = 0, hdr_ok = 0, crc_raw = 0; +static uint64_t rs_saved = 0, crc_final = 0, total_bytes = 0; +static float rssi = -100, evm = 0; +static float cfo_est = 0.0f; +static nco_crcf nco = NULL; +static fec decoder = NULL; +static int fec_on = 0; +static liquid_float_complex frame[MAX_FRAME_SAMPLES]; + +static int callback(unsigned char *hdr, int hdr_valid, + unsigned char *pay, unsigned int pay_len, + int pay_valid, framesyncstats_s stats, + void *user) { + (void)user; + calls++; + rssi = stats.rssi; + evm = stats.evm; + + // Обновляем CFO только если NCO инициализирован + if (nco && fabsf(stats.cfo) < CFO_MAX_VALID) { + cfo_est = CFO_FILTER_ALPHA * cfo_est + (1.0f - CFO_FILTER_ALPHA) * stats.cfo; + nco_crcf_set_frequency(nco, cfo_est); + } + + if (!hdr_valid) return 0; + hdr_ok++; + if (pay_valid) crc_raw++; + + uint16_t original_len = (hdr[6] << 8) | hdr[7]; + uint8_t last_block_bytes = hdr[10]; + + if (fec_on && decoder && pay_len >= RS_ENC) { + size_t nblocks = pay_len / RS_ENC; + if (nblocks == 0 || nblocks > 100) return 0; + + uint8_t recovered[4096]; + int rs_ok = 1; + for (size_t b = 0; b < nblocks; b++) { + if (fec_decode(decoder, RS_DATA, pay + b * RS_ENC, + recovered + b * RS_DATA) != 0) { + rs_ok = 0; break; + } + } + if (rs_ok) { + if (!pay_valid) rs_saved++; + size_t out_len = (nblocks - 1) * RS_DATA + + (last_block_bytes > 0 ? last_block_bytes : RS_DATA); + if (out_len > nblocks * RS_DATA) out_len = nblocks * RS_DATA; + if (out_len > original_len) out_len = original_len; + if (out_len > 0) { + fwrite(recovered, 1, out_len, stdout); + fflush(stdout); + total_bytes += out_len; + crc_final++; + } + } + } else if (pay_valid && original_len > 0 && original_len <= pay_len) { + fwrite(pay, 1, original_len, stdout); + fflush(stdout); + total_bytes += original_len; + crc_final++; + } + return 0; +} + +int main(int argc, char *argv[]) { + long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW; + const char *uri = RX_URI_DEFAULT; + const char *fec_opt = "rs8"; + + int opt; + while ((opt = getopt(argc, argv, "f:r:b:u:c:h")) != -1) { + switch (opt) { + case 'f': freq = atoll(optarg); break; + case 'r': rate = atoll(optarg); break; + case 'b': bw = atoll(optarg); break; + case 'u': uri = optarg; break; + case 'c': fec_opt = optarg; break; + case 'h': + print_usage(argv[0], "ПРИЁМНИК"); + fprintf(stderr, "\nСПЕЦИФИЧНЫЕ ОПЦИИ RX:\n"); + fprintf(stderr, " Усиление фиксированное: %.0f дБ\n", RX_GAIN); + fprintf(stderr, " Частотная коррекция: адаптивная (α=%.2f)\n", CFO_FILTER_ALPHA); + fprintf(stderr, "\nОПТИМАЛЬНЫЙ ЗАПУСК:\n"); + fprintf(stderr, " %s -f %lld -r %lld -b %lld -c rs8 > output.bin\n", + argv[0], DEFAULT_FREQ, DEFAULT_RATE, DEFAULT_BW); + return 0; + default: return 1; + } + } + + fec_on = !strcmp(fec_opt, "rs8"); + setup_signal_handlers(); + + print_config("ПРИЁМНИК", freq, rate, bw, fec_on); + fprintf(stderr, "Усиление: %.0f дБ (ручное)\n", RX_GAIN); + fprintf(stderr, "CFO фильтр: α=%.2f\n", CFO_FILTER_ALPHA); + fprintf(stderr, "URI: %s\n", uri); + fprintf(stderr, "═══════════════════════════════════════════════════\n"); + + struct iio_context *ctx = NULL; + struct iio_device *phy = NULL, *rx = NULL; + if (pluto_init(uri, &ctx, &phy, &rx, 0) != 0) return 1; + + pluto_configure(phy, freq, rate, bw, RX_GAIN, 0); + + iio_channel_enable(iio_device_find_channel(rx, "voltage0", false)); + iio_channel_enable(iio_device_find_channel(rx, "voltage1", false)); + + struct iio_buffer *buf = iio_device_create_buffer(rx, BUF_SAMPLES, false); + if (!buf) { + fprintf(stderr, "Ошибка создания буфера приёма\n"); + iio_context_destroy(ctx); + return 1; + } + + // Инициализация FEC + if (fec_on) { + decoder = fec_create(LIQUID_FEC_RS_M8, NULL); + if (!decoder) { + fprintf(stderr, "Ошибка создания FEC-декодера\n"); + } + } + + // Инициализация NCO для частотной коррекции + nco = nco_crcf_create(LIQUID_NCO); + if (!nco) { + fprintf(stderr, "Ошибка создания NCO\n"); + } else { + nco_crcf_set_frequency(nco, 0.0f); + } + + ofdmflexframesync fs = ofdmflexframesync_create( + OFDM_M, OFDM_CP, OFDM_TAPER, NULL, callback, NULL); + if (!fs) { + fprintf(stderr, "Ошибка создания OFDM-синхронизатора\n"); + if (decoder) fec_destroy(decoder); + if (nco) nco_crcf_destroy(nco); + iio_buffer_destroy(buf); + iio_context_destroy(ctx); + return 1; + } + + fprintf(stderr, "Приёмник готов. Ожидание сигнала...\n\n"); + fprintf(stderr, "Кадры | Загол. | CRC | RS испр | Вывод | RSSI | EVM | CFO\n"); + fprintf(stderr, "--------+--------+------+---------+--------+-------+-------+----------\n"); + + time_t last = time(NULL); + + while (!stop) { + ssize_t n = iio_buffer_refill(buf); + if (n < 0) { usleep(500); continue; } + + int16_t *bb = (int16_t *)iio_buffer_start(buf); + int ns = (iio_buffer_end(buf) - iio_buffer_start(buf)) / sizeof(int16_t) / 2; + if (ns > MAX_FRAME_SAMPLES) ns = MAX_FRAME_SAMPLES; + + for (int i = 0; i < ns; i++) { + frame[i] = ((float)bb[2*i] / 2048.0f) + + ((float)bb[2*i+1] / 2048.0f) * _Complex_I; + if (nco) { + nco_crcf_mix_down(nco, frame[i], &frame[i]); + nco_crcf_step(nco); + } + } + if (nco) nco_crcf_set_phase(nco, 0.0f); + + ofdmflexframesync_execute(fs, frame, ns); + + if (time(NULL) > last) { + fprintf(stderr, "\r%6llu | %5llu | %4llu | %6llu | %6llu | %+5.0f | %+5.1f | %+8.4f", + (unsigned long long)calls, (unsigned long long)hdr_ok, + (unsigned long long)crc_raw, (unsigned long long)rs_saved, + (unsigned long long)crc_final, rssi, evm, cfo_est); + fflush(stderr); + last = time(NULL); + } + } + + fprintf(stderr, "\n\nПРИЁМ ЗАВЕРШЁН\n"); + fprintf(stderr, "Кадров: %llu | Заголовков: %llu | CRC: %llu | RS испр: %llu | Вывод: %llu | Байт: %llu\n", + (unsigned long long)calls, (unsigned long long)hdr_ok, + (unsigned long long)crc_raw, (unsigned long long)rs_saved, + (unsigned long long)crc_final, (unsigned long long)total_bytes); + + if (decoder) fec_destroy(decoder); + if (nco) nco_crcf_destroy(nco); + ofdmflexframesync_destroy(fs); + iio_buffer_destroy(buf); + iio_context_destroy(ctx); + return 0; +} \ No newline at end of file diff --git a/src/transmitter.c b/src/transmitter.c new file mode 100644 index 0000000..0a1a22d --- /dev/null +++ b/src/transmitter.c @@ -0,0 +1,185 @@ +/** + * transmitter.c — OFDM-передатчик с RS(255,223) FEC для PlutoSDR + * + * ОПТИМАЛЬНАЯ КОМАНДА ЗАПУСКА: + * cat test.bin | ./transmitter -f 1265000000 -r 3840000 -b 2000000 -g -20 -a 0.3 -p 0 -c rs8 + */ + +#include "common.h" + +// Параметры передатчика по умолчанию +#define DEFAULT_GAIN -30.0 // Усиление передатчика: -30 дБ +#define DEFAULT_AMP 0.2f // Амплитуда сигнала +#define DEFAULT_PAUSE 2000 // Пауза между кадрами: 2000 мкс +#define BUF_SAMPLES 16384 // Размер буфера в отсчётах +#define MAX_PAYLOAD 1024 // Максимальный размер полезной нагрузки + +// Внешняя переменная флага завершения (определена в common.c) +extern volatile sig_atomic_t stop; + +int main(int argc, char *argv[]) { + // Инициализация параметров + long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW; + double gain = DEFAULT_GAIN; + float amp = DEFAULT_AMP; + const char *uri = TX_URI_DEFAULT; + int pause_us = DEFAULT_PAUSE, use_fec = 1; + + // Обработка аргументов командной строки + int opt; + while ((opt = getopt(argc, argv, "f:r:b:g:a:p:u:c:h")) != -1) { + switch (opt) { + case 'f': freq = atoll(optarg); break; + case 'r': rate = atoll(optarg); break; + case 'b': bw = atoll(optarg); break; + case 'g': gain = atof(optarg); break; + case 'a': amp = atof(optarg); break; + case 'p': pause_us = atoi(optarg); break; + case 'u': uri = optarg; break; + case 'c': use_fec = strcmp(optarg, "none") ? 1 : 0; break; + case 'h': + print_usage(argv[0], "ПЕРЕДАТЧИК"); + fprintf(stderr, "\nСПЕЦИФИЧНЫЕ ОПЦИИ TX:\n"); + fprintf(stderr, " -g УСИЛЕНИЕ Усиление в дБ (по умолчанию: %.0f)\n", DEFAULT_GAIN); + fprintf(stderr, " -a АМПЛИТ. Амплитуда сигнала (по умолчанию: %.2f)\n", DEFAULT_AMP); + fprintf(stderr, " -p ПАУЗА Пауза между кадрами в мкс (по умолчанию: %d)\n", DEFAULT_PAUSE); + fprintf(stderr, "\nОПТИМАЛЬНЫЙ ЗАПУСК:\n"); + fprintf(stderr, " cat test.bin | %s -f %lld -r %lld -b %lld -c rs8 > /dev/null\n", + argv[0], DEFAULT_FREQ, DEFAULT_RATE, DEFAULT_BW); + return 0; + default: return 1; + } + } + + // Настройка обработчиков сигналов + setup_signal_handlers(); + + // Вывод конфигурации + print_config("ПЕРЕДАТЧИК", freq, rate, bw, use_fec); + fprintf(stderr, "Усиление: %.0f дБ\n", gain); + fprintf(stderr, "Амплитуда: %.2f\n", amp); + fprintf(stderr, "Пауза: %d мкс\n", pause_us); + fprintf(stderr, "URI: %s\n", uri); + fprintf(stderr, "═══════════════════════════════════════════════════\n"); + + // Инициализация PlutoSDR + struct iio_context *ctx = NULL; + struct iio_device *phy = NULL, *tx = NULL; + if (pluto_init(uri, &ctx, &phy, &tx, 1) != 0) return 1; + + // Настройка радиотракта + pluto_configure(phy, freq, rate, bw, gain, 1); + + // Включение каналов передатчика + iio_channel_enable(iio_device_find_channel(tx, "voltage0", true)); + iio_channel_enable(iio_device_find_channel(tx, "voltage1", true)); + + // Создание буфера передачи + struct iio_buffer *buf = iio_device_create_buffer(tx, BUF_SAMPLES, false); + if (!buf) { + LOG_ERROR("Ошибка создания буфера передачи"); + iio_context_destroy(ctx); + return 1; + } + + // Инициализация FEC и OFDM + fec enc = use_fec ? fec_create(LIQUID_FEC_RS_M8, NULL) : NULL; + + ofdmflexframegenprops_s fgp; + ofdmflexframegenprops_init_default(&fgp); + fgp.check = LIQUID_CRC_32; + ofdmflexframegen fg = ofdmflexframegen_create(OFDM_M, OFDM_CP, OFDM_TAPER, NULL, &fgp); + + // Счётчики статистики + uint64_t frames = 0, bytes_tx = 0; + uint32_t seq = 0; + time_t last = time(NULL); + + fprintf(stderr, " Передатчик готов. Ожидание данных...\n\n"); + + // Основной цикл передачи + while (!stop) { + uint8_t raw[MAX_PAYLOAD]; + size_t n = fread(raw, 1, MAX_PAYLOAD, stdin); + if (!n) { + if (feof(stdin) || stop) break; + usleep(10000); + continue; + } + + uint8_t *tx_payload = NULL; + size_t tx_len = 0; + uint8_t hdr[HDR_SIZE] = {0}; + + // Кодирование данных + if (enc) { + size_t total_blocks = (n + RS_DATA - 1) / RS_DATA; + tx_len = total_blocks * RS_ENC; + tx_payload = malloc(tx_len); + + for (size_t b = 0; b < total_blocks; b++) { + uint8_t blk[RS_DATA] = {0}; + size_t bytes = (b == total_blocks - 1) ? n - b * RS_DATA : RS_DATA; + memcpy(blk, raw + b * RS_DATA, bytes); + fec_encode(enc, RS_DATA, blk, tx_payload + b * RS_ENC); + } + + // Формирование заголовка + hdr[0] = HDR_SIGNATURE_0; hdr[1] = HDR_SIGNATURE_1; + hdr[2] = (seq >> 24) & 0xFF; hdr[3] = (seq >> 16) & 0xFF; + hdr[4] = (seq >> 8) & 0xFF; hdr[5] = seq & 0xFF; + hdr[6] = (n >> 8) & 0xFF; hdr[7] = n & 0xFF; + hdr[8] = (total_blocks >> 8) & 0xFF; hdr[9] = total_blocks & 0xFF; + hdr[10] = n % RS_DATA; hdr[11] = 0; + } else { + tx_len = n; + tx_payload = malloc(tx_len); + memcpy(tx_payload, raw, n); + + hdr[0] = HDR_SIGNATURE_0; hdr[1] = HDR_SIGNATURE_1; + hdr[2] = (seq >> 24) & 0xFF; hdr[3] = (seq >> 16) & 0xFF; + hdr[4] = (seq >> 8) & 0xFF; hdr[5] = seq & 0xFF; + hdr[6] = (n >> 8) & 0xFF; hdr[7] = n & 0xFF; + } + + // Сборка OFDM-кадра + ofdmflexframegen_assemble(fg, hdr, tx_payload, tx_len); + free(tx_payload); + + // Формирование выходного сигнала + int16_t *out = (int16_t *)iio_buffer_start(buf); + memset(out, 0, BUF_SAMPLES * 2 * sizeof(int16_t)); + int idx = 0, done = 0; + float complex symb[OFDM_M + OFDM_CP]; + + while (!done && (idx + OFDM_M + OFDM_CP) <= BUF_SAMPLES) { + done = ofdmflexframegen_write(fg, symb, OFDM_M + OFDM_CP); + for (int i = 0; i < OFDM_M + OFDM_CP; i++) { + out[2 * idx] = (int16_t)(crealf(symb[i]) * 32767 * amp); + out[2 * idx + 1] = (int16_t)(cimagf(symb[i]) * 32767 * amp); + idx++; + } + } + if (idx > 0) iio_buffer_push(buf); + + // Обновление статистики + frames++; bytes_tx += n; seq++; + if (time(NULL) > last) { + fprintf(stderr, "\r📤 Передано: %llu кадров, %llu байт", + (unsigned long long)frames, (unsigned long long)bytes_tx); + fflush(stderr); + last = time(NULL); + } + if (pause_us > 0) usleep(pause_us); + } + + fprintf(stderr, "\nПередача завершена: %llu кадров, %llu байт\n", + (unsigned long long)frames, (unsigned long long)bytes_tx); + + // Освобождение ресурсов + if (enc) fec_destroy(enc); + ofdmflexframegen_destroy(fg); + iio_buffer_destroy(buf); + iio_context_destroy(ctx); + return 0; +} \ No newline at end of file diff --git a/toolchain.env b/toolchain.env new file mode 100644 index 0000000..86d5658 --- /dev/null +++ b/toolchain.env @@ -0,0 +1,20 @@ +# toolchain.env — окружение кросс-сборки pluto-link +# Использование в скриптах: source "$(dirname "$0")/../toolchain.env" +# Значения согласованы с makefile (там задаются через ?=). + +export CROSS=arm-linux-gnueabihf- +export CC=${CROSS}gcc +export XARM="$HOME/xarm" # префикс кросс-библиотек (static) + +export XCFLAGS="-O3 -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard -ffast-math" + +# Платы (переопределяй здесь или переменными окружения) +export PLUTO_A="${PLUTO_A:-192.168.2.1}" # передатчик +export PLUTO_B="${PLUTO_B:-192.168.3.1}" # приёмник +export PLUTO_PASS="analog" # дефолтный пароль root + +# Примечания: +# - прошивка v0.38: заливка только `scp -O` (sftp-server на плате нет) +# - rootfs платы в RAM: /tmp очищается при перезагрузке +# - для запуска ssh/scp без ввода пароля: sudo apt install sshpass, +# тогда скрипты используют sshpass -p "$PLUTO_PASS" автоматически \ No newline at end of file