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 <noreply@anthropic.com>
This commit is contained in:
26
.gitattributes
vendored
Normal file
26
.gitattributes
vendored
Normal file
@@ -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
|
||||||
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@@ -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
|
||||||
19
.vscode/c_cpp_properties.json
vendored
Normal file
19
.vscode/c_cpp_properties.json
vendored
Normal file
@@ -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
|
||||||
|
}
|
||||||
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"files.eol": "\n",
|
||||||
|
"files.encoding": "utf8",
|
||||||
|
"C_Cpp.default.cStandard": "gnu11",
|
||||||
|
"C_Cpp.default.intelliSenseMode": "linux-gcc-arm"
|
||||||
|
}
|
||||||
56
CLAUDE.md
Normal file
56
CLAUDE.md
Normal file
@@ -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@<pluto> '/tmp/ofdm_bench 1.92'`
|
||||||
|
E2E-тест: `scripts/test_file_link.sh`
|
||||||
|
Пароль root на Pluto: `analog`. rootfs в RAM — /tmp очищается при ребуте.
|
||||||
246
README.md
Normal file
246
README.md
Normal file
@@ -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@<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.*
|
||||||
84
docs/BENCHMARK.md
Normal file
84
docs/BENCHMARK.md
Normal file
@@ -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 <ip_платы>
|
||||||
|
ssh root@<ip_платы> '/tmp/ofdm_bench 1.92'
|
||||||
|
# критерий приёмки: "сигнал+кадры" ≥ 1.2× цели на одном ядре
|
||||||
|
```
|
||||||
107
docs/NOTES.md
Normal file
107
docs/NOTES.md
Normal file
@@ -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@<ip> 'cat /tmp/tx.log /tmp/rx.log'
|
||||||
|
```
|
||||||
66
makefile
Normal file
66
makefile
Normal file
@@ -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
|
||||||
85
scripts/build_deps.sh
Normal file
85
scripts/build_deps.sh
Normal file
@@ -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 <<EOF
|
||||||
|
set(CMAKE_SYSTEM_NAME Linux)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR arm)
|
||||||
|
set(CMAKE_C_COMPILER ${CROSS}gcc)
|
||||||
|
set(CMAKE_C_FLAGS_INIT "$XCFLAGS")
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
EOF
|
||||||
|
cmake -B build-armhf -DCMAKE_TOOLCHAIN_FILE=toolchain-armhf.cmake \
|
||||||
|
-DCMAKE_INSTALL_PREFIX="$XARM" -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DBUILD_SHARED_LIBS=OFF \
|
||||||
|
-DWITH_LOCAL_BACKEND=ON -DWITH_NETWORK_BACKEND=OFF \
|
||||||
|
-DWITH_USB_BACKEND=OFF -DWITH_SERIAL_BACKEND=OFF \
|
||||||
|
-DWITH_XML_BACKEND=OFF -DHAVE_DNS_SD=OFF \
|
||||||
|
-DWITH_TESTS=OFF -DWITH_EXAMPLES=OFF -DWITH_DOC=OFF \
|
||||||
|
-DCSHARP_BINDINGS=OFF -DPYTHON_BINDINGS=OFF
|
||||||
|
cmake --build build-armhf -j"$(nproc)"
|
||||||
|
cmake --install build-armhf
|
||||||
|
cd "$WORK"
|
||||||
|
# Если cmake ругается на libxml2: XML backend в этой версии отключается
|
||||||
|
# флагом выше; если версия libiio его требует — собери libxml2 static
|
||||||
|
# (--without-python --without-zlib --without-lzma) в $XARM и повтори.
|
||||||
|
else
|
||||||
|
echo "уже собран, пропускаю"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== [4/4] Итог ==="
|
||||||
|
ls -la "$XARM/lib/" | grep -E "libfftw3f|libliquid|libiio" || true
|
||||||
|
echo "Готово. Дальше: make check-env && make"
|
||||||
42
scripts/deploy.sh
Normal file
42
scripts/deploy.sh
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# deploy.sh — заливка бинарей на Pluto (обе или одна плата)
|
||||||
|
# Использование:
|
||||||
|
# scripts/deploy.sh # обе платы из toolchain.env
|
||||||
|
# scripts/deploy.sh 192.168.2.1 # только одна плата
|
||||||
|
# scripts/deploy.sh <ipA> <ipB> # явные адреса
|
||||||
|
# scripts/deploy.sh bench <ip> # только бенчмарк
|
||||||
|
# Прошивка 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 — после перезагрузки залить заново."
|
||||||
68
scripts/test_file_link.sh
Normal file
68
scripts/test_file_link.sh
Normal file
@@ -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
|
||||||
106
src/common.c
Normal file
106
src/common.c
Normal file
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/common.h
Normal file
61
src/common.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* common.h — Общие определения и конфигурация для приёмника и передатчика
|
||||||
|
* OFDM + RS(255,223) FEC + PlutoSDR
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COMMON_H
|
||||||
|
#define COMMON_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <iio.h>
|
||||||
|
#include <liquid/liquid.h>
|
||||||
|
|
||||||
|
// Параметры 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
|
||||||
168
src/ofdm_bench.c
Normal file
168
src/ofdm_bench.c
Normal file
@@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <complex.h>
|
||||||
|
#include <liquid/liquid.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
205
src/receiver.c
Normal file
205
src/receiver.c
Normal file
@@ -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;
|
||||||
|
}
|
||||||
185
src/transmitter.c
Normal file
185
src/transmitter.c
Normal file
@@ -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;
|
||||||
|
}
|
||||||
20
toolchain.env
Normal file
20
toolchain.env
Normal file
@@ -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" автоматически
|
||||||
Reference in New Issue
Block a user