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:
Maxim
2026-07-10 11:00:38 +03:00
commit 0f207b146d
18 changed files with 1579 additions and 0 deletions

85
scripts/build_deps.sh Normal file
View 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
View 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
View 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 МГц.
#
# ВНИМАНИЕ: сначала кабель + аттенюатор 3040 дБ (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