Доработка синхронизации

This commit is contained in:
Maxim
2026-06-03 10:49:38 +03:00
parent b00af27058
commit 9d6452c860
4 changed files with 732 additions and 484 deletions

117
SDR/common.c Normal file
View File

@@ -0,0 +1,117 @@
/**
* 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 ЧАСТОТА Частота несущей в Гц (по умолчанию: %.0f)\n",
(double)DEFAULT_FREQ);
fprintf(stderr, " -r СКОРОСТЬ Частота дискретизации (по умолчанию: %.0f)\n",
(double)DEFAULT_RATE);
fprintf(stderr, " -b ПОЛОСА Полоса пропускания (по умолчанию: %.0f)\n",
(double)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) {
const char *voltage_ch = is_tx ? "voltage0" : "voltage0";
const char *altvoltage_ch = is_tx ? "altvoltage1" : "altvoltage0";
bool output = is_tx ? true : false;
// Установка частоты несущей
iio_channel_attr_write_longlong(
iio_device_find_channel(phy, altvoltage_ch, output),
"frequency", freq);
// Установка частоты дискретизации
iio_channel_attr_write_longlong(
iio_device_find_channel(phy, voltage_ch, output),
"sampling_frequency", rate);
// Установка полосы пропускания
iio_channel_attr_write_longlong(
iio_device_find_channel(phy, voltage_ch, output),
"rf_bandwidth", bw);
if (is_tx) {
// Настройки передатчика
iio_channel_attr_write(
iio_device_find_channel(phy, voltage_ch, output),
"rf_port_select", "A");
iio_channel_attr_write_double(
iio_device_find_channel(phy, voltage_ch, output),
"hardwaregain", gain);
} else {
// Настройки приёмника
iio_channel_attr_write(
iio_device_find_channel(phy, voltage_ch, output),
"gain_control_mode", "manual");
iio_channel_attr_write_double(
iio_device_find_channel(phy, voltage_ch, output),
"hardwaregain", gain);
iio_channel_attr_write(
iio_device_find_channel(phy, voltage_ch, output),
"rf_port_select", "A_BALANCED");
}
}

151
SDR/common.h Normal file
View File

@@ -0,0 +1,151 @@
/**
* common.h — Общие определения и конфигурация для приёмника и передатчика
* OFDM + RS(255,223) FEC + PlutoSDR
*
* Включает:
* - Параметры OFDM-модуляции/демодуляции
* - Параметры помехоустойчивого кодирования Рида-Соломона
* - Настройки радиотракта по умолчанию
* - Общие макросы и прототипы функций
*/
#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 1265000000 // Частота несущей: 1265 МГц
#define DEFAULT_RATE 3840000 // Частота дискретизации: 3.84 МГц
#define DEFAULT_BW 3000000 // Полоса пропускания: 3 МГц
// ============================================================
// АДРЕСА PLUTOSDR ПО УМОЛЧАНИЮ
// ============================================================
#define TX_URI_DEFAULT "ip:192.168.3.1" // Адрес передатчика
#define RX_URI_DEFAULT "ip:192.168.2.1" // Адрес приёмника
// ============================================================
// ОБЩИЕ ВСПОМОГАТЕЛЬНЫЕ МАКРОСЫ
// ============================================================
/**
* Безопасное освобождение памяти с проверкой указателя
*/
#define SAFE_FREE(ptr) do { \
if ((ptr) != NULL) { \
free(ptr); \
(ptr) = NULL; \
} \
} while(0)
/**
* Вывод отладочного сообщения с меткой времени
*/
#define LOG_INFO(fmt, ...) \
fprintf(stderr, "[INFO] " fmt "\n", ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) \
fprintf(stderr, "[ОШИБКА] " fmt "\n", ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) \
fprintf(stderr, "[ВНИМ.] " fmt "\n", ##__VA_ARGS__)
// ============================================================
// СТРУКТУРА ЗАГОЛОВКА КАДРА (12 байт)
// ============================================================
/**
* Формат заголовка кадра:
* Байты 0-1: Сигнатура начала кадра (0xF0, 0xAA)
* Байты 2-5: Порядковый номер кадра (32 бита)
* Байты 6-7: Размер исходных данных (16 бит)
* Байты 8-9: Количество блоков FEC (16 бит)
* Байт 10: Размер последнего блока RS
* Байт 11: Зарезервирован
*/
#define HDR_SIGNATURE_0 0xF0 // Первый байт сигнатуры
#define HDR_SIGNATURE_1 0xAA // Второй байт сигнатуры
#define HDR_SIZE 12 // Размер заголовка в байтах
// ============================================================
// ПРОТОТИПЫ ОБЩИХ ФУНКЦИЙ
// ============================================================
/**
* Инициализация общего обработчика сигналов завершения
* Устанавливает обработчики для SIGINT и SIGTERM
*/
void setup_signal_handlers(void);
/**
* Вывод справки по использованию
*
* @param prog_name Имя исполняемого файла
* @param mode Режим работы: "TX" или "RX"
*/
void print_usage(const char *prog_name, const char *mode);
/**
* Форматированный вывод конфигурации радиотракта
*
* @param mode Режим: "ПЕРЕДАТЧИК" или "ПРИЁМНИК"
* @param freq Частота несущей
* @param rate Частота дискретизации
* @param bw Полоса пропускания
* @param fec_on Флаг активации FEC
*/
void print_config(const char *mode, long long freq, long long rate,
long long bw, int fec_on);
/**
* Инициализация контекста PlutoSDR и базовых устройств
*
* @param uri URI подключения
* @param ctx Указатель на контекст (выход)
* @param phy Указатель на физическое устройство (выход)
* @param dev Указатель на устройство TX/RX (выход)
* @param is_tx Флаг направления (1=TX, 0=RX)
* @return 0 при успехе, -1 при ошибке
*/
int pluto_init(const char *uri, struct iio_context **ctx,
struct iio_device **phy, struct iio_device **dev, int is_tx);
/**
* Настройка параметров радиотракта
*
* @param phy Физическое устройство
* @param freq Частота несущей
* @param rate Частота дискретизации
* @param bw Полоса пропускания
* @param gain Усиление (только для TX и manual RX)
* @param is_tx Флаг направления
*/
void pluto_configure(struct iio_device *phy, long long freq, long long rate,
long long bw, double gain, int is_tx);
#endif // COMMON_H

View File

@@ -1,23 +1,14 @@
/** /**
* rx.c — QPSK приёмник для PlutoSDR (production) * receiver_advanced.c — OFDM + RS(255,223) FEC + PlutoSDR + ЧАСТОТНАЯ КОРРЕКЦИЯ
* *
* Использует framesync64 из liquid-dsp для декодирования кадров. * ОПТИМАЛЬНАЯ КОМАНДА ЗАПУСКА:
* Архитектура: Pluto ADC → int16 I/Q → float complex → framesync64 → callback * ./receiver_advanced -f 1265000000 -r 3840000 -b 2000000 -c rs8 > output.bin
* *
* Зависимости: * ОСОБЕННОСТИ ДАННОЙ ВЕРСИИ:
* - libiio (PlutoSDR API) * - Адаптивная компенсация частотного сдвига (CFO) через NCO
* - libliquid (liquid-dsp, framesync64) * - Ручное управление усилением для максимальной стабильности
* - libm (математика) * - Экспоненциальная фильтрация оценки CFO (α=0.95)
* - libpthread (сигналы) * - Улучшенная работа в условиях нестабильных генераторов
*
* Компиляция:
* gcc -o rx rx.c -liio -lliquid -lm -lpthread -Wall -O2
*
* Примеры запуска:
* ./rx # значения по умолчанию
* ./rx -f 2400000000 -r 4000000 # 2.4 ГГц, 4 MSPS
* ./rx -b 16384 # размер буфера 16384
* ./rx -h # справка
*/ */
#include <stdio.h> #include <stdio.h>
@@ -32,286 +23,389 @@
#include <iio.h> #include <iio.h>
#include <liquid/liquid.h> #include <liquid/liquid.h>
/* ============================================================ // Параметры приёма по умолчанию
* Значения по умолчанию #define DEFAULT_FREQ 1265000000 // Частота несущей: 1265 МГц
* ============================================================ */ #define DEFAULT_RATE 3840000 // Частота дискретизации: 3.84 МГц
#define DEFAULT_BW 3000000 // Полоса пропускания: 3 МГц
#define BUF_SAMPLES 32768 // Размер аппаратного буфера в отсчётах
#define MAX_FRAME_SAMPLES (1 << 16) // Максимальный размер кадра в отсчётах (65536)
#define DEFAULT_CENTER_FREQ 1300000000 /* 1300 МГц */ // Параметры OFDM-демодуляции
#define DEFAULT_SAMPLE_RATE 3840000 /* 3.84 MSPS */ #define OFDM_M 64 // Количество поднесущих
#define DEFAULT_BUF_SAMPLES 16384 /* размер DMA буфера */ #define OFDM_CP 16 // Длина циклического префикса
#define DEFAULT_URI "ip:192.168.2.1" #define OFDM_TAPER 4 // Длина сглаживающего окна
// Параметры помехоустойчивого декодирования Рида-Соломона
#define RS_DATA 223 // Размер блока данных (байт)
#define RS_ENC 255 // Размер кодированного блока (байт)
/* ============================================================ // Параметры частотной коррекции
* Глобальные переменные #define CFO_FILTER_ALPHA 0.95f // Коэффициент фильтрации CFO (0..1)
* ============================================================ */ #define CFO_MAX_VALID 0.5f // Максимальное валидное значение CFO (рад/семпл)
static volatile int stop = 0; /* флаг остановки (SIGINT) */ // Глобальный флаг для корректного завершения программы
static uint64_t total_packets = 0; /* всего обнаружено кадров */ volatile sig_atomic_t stop = 0;
static uint64_t packets_ok = 0; /* успешно декодировано */
static uint64_t total_bytes = 0; /* всего принято байт данных */
static time_t last_packet_time = 0; /* время последнего успешного пакета */
/* ============================================================ // Глобальные счётчики статистики приёма
* Обработчик сигналов static uint64_t calls = 0; // Общее количество вызовов callback
* ============================================================ */ static uint64_t hdr_ok = 0; // Количество корректных заголовков
static uint64_t crc_raw = 0; // Количество кадров с корректной CRC
static uint64_t rs_saved = 0; // Количество кадров, исправленных RS-декодером
static uint64_t crc_final = 0; // Количество успешно выведенных кадров
static uint64_t total_bytes = 0; // Общее количество принятых байт
// Параметры качества сигнала
static float rssi = -100; // Уровень принимаемого сигнала (дБм)
static float evm = 0; // Величина вектора ошибки (%)
// Компоненты системы частотной коррекции
static float cfo_est = 0.0f; // Текущая оценка CFO (радиан/семпл)
static nco_crcf nco = NULL; // NCO (Numerically Controlled Oscillator) для коррекции
// Декодер Рида-Соломона и флаг активации FEC
static fec decoder = NULL;
static int fec_on = 0;
// Буфер для временного хранения отсчётов после коррекции
static liquid_float_complex frame[MAX_FRAME_SAMPLES];
/** /**
* sigint_handler — обработчик Ctrl+C * Обработчик сигналов завершения (SIGINT, SIGTERM)
*/ */
static void sigint_handler(int sig) { void sigint_handler(int s) {
(void)sig; (void)s;
stop = 1; stop = 1;
} }
/* ============================================================
* Callback framesync64
* ============================================================ */
/** /**
* fs_callback — вызывается framesync64 при обнаружении кадра * Callback-функция обработки принятого OFDM-кадра
* *
* Параметры: * Вызывается синхронизатором при успешном обнаружении кадра.
* _header — заголовок кадра (8 байт) * Выполняет:
* _header_valid — флаг валидности заголовка * 1. Обновление метрик качества сигнала (RSSI, EVM)
* _payload — данные полезной нагрузки (64 байта) * 2. Адаптивную фильтрацию оценки частотного сдвига (CFO)
* _payload_len — длина payload * 3. Декодирование Рида-Соломона (если включено)
* _payload_valid — флаг валидности данных (CRC прошёл) * 4. Вывод декодированных данных в stdout
* _stats — статистика кадра (RSSI, CFO, etc.)
* _userdata — пользовательские данные (не используется)
* *
* Возвращает 0 (продолжить обработку) * @param hdr Указатель на заголовок кадра (12 байт)
* @param hdr_valid Флаг валидности заголовка
* @param pay Указатель на полезную нагрузку
* @param pay_len Длина полезной нагрузки в байтах
* @param pay_valid Флаг валидности CRC полезной нагрузки
* @param stats Статистика кадра (RSSI, EVM, CFO)
* @param user Пользовательские данные (не используется)
* @return 0 - продолжить обработку, ненулевое значение - остановить
*/ */
static int fs_callback(unsigned char *_header, int _header_valid, static int callback(unsigned char *hdr, int hdr_valid,
unsigned char *_payload, unsigned int _payload_len, unsigned char *pay, unsigned int pay_len,
int _payload_valid, framesyncstats_s _stats, int pay_valid, framesyncstats_s stats,
void *_userdata) { void *user) {
(void)_header; (void)user;
(void)_header_valid; calls++;
(void)_stats;
(void)_userdata;
total_packets++; // Обновление метрик качества сигнала
rssi = stats.rssi;
evm = stats.evm;
if (_payload_valid) { // Адаптивная фильтрация CFO с защитой от выбросов
packets_ok++; // Используется экспоненциальное сглаживание для стабильной оценки
/* первый байт = количество данных */ if (fabsf(stats.cfo) < CFO_MAX_VALID) {
size_t data_len = _payload[0]; // Рекурсивный фильтр 1-го порядка: y[n] = α*y[n-1] + (1-α)*x[n]
total_bytes += data_len; cfo_est = CFO_FILTER_ALPHA * cfo_est + (1.0f - CFO_FILTER_ALPHA) * stats.cfo;
last_packet_time = time(NULL); // Применяем скорректированную частоту к NCO
nco_crcf_set_frequency(nco, cfo_est);
/* для отладки: вывод первых 8 байт данных */
#ifdef DEBUG
printf("\n[RX] Packet #%llu: %zu bytes: ",
(unsigned long long)total_packets, data_len);
for (size_t i = 1; i <= data_len && i <= 8; i++)
printf("%02X ", _payload[i]);
if (data_len > 8) printf("...");
printf("\n");
#endif
} }
// Проверка валидности заголовка - критически важно для дальнейшей обработки
if (!hdr_valid) return 0;
hdr_ok++;
// Подсчёт кадров с корректной CRC (до FEC-коррекции)
if (pay_valid) crc_raw++;
// Извлечение метаданных из заголовка кадра
uint16_t original_len = (hdr[6] << 8) | hdr[7]; // Исходная длина данных (байт)
uint8_t last_block_bytes = hdr[10]; // Размер последнего RS-блока (байт)
// Ветвление логики в зависимости от режима FEC
if (fec_on && pay_len >= RS_ENC) {
// === РЕЖИМ С КОРРЕКЦИЕЙ ОШИБОК (РИД-СОЛОМОН) ===
size_t nblocks = pay_len / RS_ENC; // Количество RS-блоков
// Защита от некорректных/повреждённых данных
if (nblocks == 0 || nblocks > 100) return 0;
uint8_t recovered[4096]; // Буфер для восстановленных данных
int rs_ok = 1; // Флаг успешности RS-декодирования
// Поблочное декодирование Рида-Соломона
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) {
// RS-декодер успешно исправил ошибки
if (!pay_valid) rs_saved++; // CRC была некорректна, но RS исправил
// Вычисление реальной длины данных (учёт последнего неполного блока)
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) {
// === РЕЖИМ БЕЗ КОРРЕКЦИИ ОШИБОК ===
// Вывод данных только при корректной CRC
fwrite(pay, 1, original_len, stdout);
fflush(stdout); // Немедленный вывод для конвейерной обработки
total_bytes += original_len;
crc_final++;
}
return 0; return 0;
} }
/* ============================================================
* Вывод справки
* ============================================================ */
/** /**
* print_usage — печать справки по аргументам командной строки * Главная функция приёмника
*
* Выполняет:
* 1. Парсинг аргументов командной строки
* 2. Инициализацию PlutoSDR и настройку радиотракта
* 3. Создание системы частотной коррекции (NCO)
* 4. Инициализацию OFDM-синхронизатора и FEC-декодера
* 5. Основной цикл приёма с визуализацией статистики
* 6. Корректное освобождение ресурсов при завершении
*/ */
static void print_usage(const char *prog) {
printf("Usage: %s [options]\n\n", prog);
printf("Options:\n");
printf(" -f FREQ Center frequency in Hz (default: %d)\n", DEFAULT_CENTER_FREQ);
printf(" -r RATE Sample rate in Hz (default: %d)\n", DEFAULT_SAMPLE_RATE);
printf(" -b SAMPS Buffer size in samples (default: %d)\n", DEFAULT_BUF_SAMPLES);
printf(" -u URI IIO context URI (default: %s)\n", DEFAULT_URI);
printf(" -d Debug mode: print packet data\n");
printf(" -h This help\n");
printf("\nExamples:\n");
printf(" %s # defaults\n", prog);
printf(" %s -f 2400000000 -r 4000000 # 2.4 GHz, 4 MSPS\n", prog);
printf(" %s -d # debug mode\n", prog);
}
/* ============================================================
* MAIN
* ============================================================ */
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
/* ---- переменные с значениями по умолчанию ---- */ // Инициализация параметров значениями по умолчанию
long long center_freq = DEFAULT_CENTER_FREQ; long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
long long sample_rate = DEFAULT_SAMPLE_RATE; const char *uri = "ip:192.168.2.1";
int buf_samples = DEFAULT_BUF_SAMPLES; const char *fec_opt = "rs8";
const char *uri = DEFAULT_URI;
int debug_mode = 0;
/* ---- парсинг аргументов ---- */ // Обработка аргументов командной строки
int opt; int opt;
while ((opt = getopt(argc, argv, "f:r:b:u:dh")) != -1) { while ((opt = getopt(argc, argv, "f:r:b:u:c:h")) != -1) {
switch (opt) { switch (opt) {
case 'f': center_freq = atoll(optarg); break; case 'f': freq = atoll(optarg); break; // Частота несущей (Гц)
case 'r': sample_rate = atoll(optarg); break; case 'r': rate = atoll(optarg); break; // Частота дискретизации (Гц)
case 'b': buf_samples = atoi(optarg); break; case 'b': bw = atoll(optarg); break; // Полоса пропускания (Гц)
case 'u': uri = optarg; break; case 'u': uri = optarg; break; // URI подключения к PlutoSDR
case 'd': debug_mode = 1; break; case 'c': fec_opt = optarg; break; // Тип FEC: rs8 или none
case 'h': print_usage(argv[0]); return 0; case 'h':
default: print_usage(argv[0]); return 1; fprintf(stderr, "Использование: %s [опции]\n", argv[0]);
fprintf(stderr, " -f ЧАСТОТА Частота несущей в Гц (по умолчанию: %.0f)\n", (double)DEFAULT_FREQ);
fprintf(stderr, " -r СКОРОСТЬ Частота дискретизации (по умолчанию: %.0f)\n", (double)DEFAULT_RATE);
fprintf(stderr, " -b ПОЛОСА Полоса пропускания (по умолчанию: %.0f)\n", (double)DEFAULT_BW);
fprintf(stderr, " -u URI URI устройства (по умолчанию: ip:192.168.2.1)\n");
fprintf(stderr, " -c РЕЖИМ FEC режим: rs8 или none (по умолчанию: rs8)\n");
fprintf(stderr, "\nОПТИМАЛЬНЫЙ ЗАПУСК:\n");
fprintf(stderr, " %s -f 1265000000 -r 3840000 -b 3000000 -c rs8 > output.bin\n", argv[0]);
return 0;
default: return 1;
} }
} }
/* ---- инициализация ---- */ // Определение режима FEC
signal(SIGINT, sigint_handler); /* Ctrl+C → sigint_handler */ fec_on = !strcmp(fec_opt, "rs8");
/* ---- заголовок программы ---- */ // Установка обработчика сигналов для корректного завершения
printf("══════════════════════════════════════════\n"); signal(SIGINT, sigint_handler);
printf(" QPSK RECEIVER (framesync64)\n"); signal(SIGTERM, sigint_handler);
printf("══════════════════════════════════════════\n");
printf(" Frequency: %.3f MHz\n", center_freq / 1e6);
printf(" Sample rate: %.3f MSPS\n", sample_rate / 1e6);
printf(" Buffer size: %d samples\n", buf_samples);
printf(" URI: %s\n", uri);
if (debug_mode) printf(" Debug: ON\n");
printf("══════════════════════════════════════════\n");
/* ============================================================ // Вывод параметров конфигурации приёмника
* Инициализация PlutoSDR fprintf(stderr, "═══════════════════════════════════════════════════\n");
* ============================================================ */ fprintf(stderr, "ПРИЁМНИК OFDM С ЧАСТОТНОЙ КОРРЕКЦИЕЙ\n");
fprintf(stderr, "Частота: %.1f МГц\n", freq/1e6);
fprintf(stderr, "Дискрет.: %.2f МГц\n", rate/1e6);
fprintf(stderr, "Полоса: %.2f МГц\n", bw/1e6);
fprintf(stderr, "FEC: %s\n", fec_on ? "RS(255,223)" : "ОТКЛЮЧЕН");
fprintf(stderr, "Коррекция: Адаптивная CFO (α=%.2f)\n", CFO_FILTER_ALPHA);
fprintf(stderr, "Усиление: Ручное (50 дБ)\n");
fprintf(stderr, "═══════════════════════════════════════════════════\n");
/* создаём контекст IIO */ // === ИНИЦИАЛИЗАЦИЯ PLUTOSDR ===
struct iio_context *ctx = iio_create_context_from_uri(uri); struct iio_context *ctx = iio_create_context_from_uri(uri);
if (!ctx) { if (!ctx) {
fprintf(stderr, "ERROR: Cannot connect to %s\n", uri); fprintf(stderr, "Ошибка подключения к PlutoSDR по адресу %s\n", uri);
fprintf(stderr, " Check USB cable and try 'iio_info -s'\n");
return 1; return 1;
} }
/* находим устройства */ // Поиск устройств физического уровня и приёмника
struct iio_device *phy = iio_context_find_device(ctx, "ad9361-phy"); struct iio_device *phy = iio_context_find_device(ctx, "ad9361-phy");
struct iio_device *rx = iio_context_find_device(ctx, "cf-ad9361-lpc"); struct iio_device *rx = iio_context_find_device(ctx, "cf-ad9361-lpc");
if (!phy || !rx) { if (!phy || !rx) {
fprintf(stderr, "ERROR: Devices not found\n"); fprintf(stderr, "Радиоустройства не найдены в контексте\n");
iio_context_destroy(ctx);
return 1; return 1;
} }
/* каналы PHY */ // === НАСТРОЙКА РАДИОТРАКТА ===
struct iio_channel *rx_lo = iio_device_find_channel(phy, "altvoltage0", true); // Установка частоты несущей
struct iio_channel *rx_phy = iio_device_find_channel(phy, "voltage0", false); iio_channel_attr_write_longlong(
iio_device_find_channel(phy, "altvoltage0", true),
"frequency", freq);
/* настройка частоты несущей */ // Установка частоты дискретизации
if (iio_channel_attr_write_longlong(rx_lo, "frequency", center_freq) < 0) { iio_channel_attr_write_longlong(
fprintf(stderr, "WARNING: Cannot set RX LO frequency\n"); iio_device_find_channel(phy, "voltage0", false),
} "sampling_frequency", rate);
/* настройка частоты дискретизации */ // Установка полосы пропускания
if (iio_channel_attr_write_longlong(rx_phy, "sampling_frequency", sample_rate) < 0) { iio_channel_attr_write_longlong(
fprintf(stderr, "WARNING: Cannot set sample rate\n"); iio_device_find_channel(phy, "voltage0", false),
} "rf_bandwidth", bw);
/* настройка полосы, порта, АРУ */ // Настройка ручного управления усилением для максимальной стабильности
iio_channel_attr_write_longlong(rx_phy, "rf_bandwidth", sample_rate); // Ручной режим предпочтительнее автоматического при работе с OFDM
iio_channel_attr_write(rx_phy, "gain_control_mode", "slow_attack"); iio_channel_attr_write(
iio_channel_attr_write(rx_phy, "rf_port_select", "A_BALANCED"); iio_device_find_channel(phy, "voltage0", false),
"gain_control_mode", "manual");
iio_channel_attr_write_double(
iio_device_find_channel(phy, "voltage0", false),
"hardwaregain", 50.0); // Оптимальное значение для большинства сценариев
/* включение DMA каналов I и Q */ // Выбор сбалансированного антенного входа
struct iio_channel *rx_i = iio_device_find_channel(rx, "voltage0", false); iio_channel_attr_write(
struct iio_channel *rx_q = iio_device_find_channel(rx, "voltage1", false); iio_device_find_channel(phy, "voltage0", false),
iio_channel_enable(rx_i); "rf_port_select", "A_BALANCED");
iio_channel_enable(rx_q);
/* создание DMA буфера */ // Включение I и Q каналов приёмника
struct iio_buffer *buf = iio_device_create_buffer(rx, buf_samples, false); 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) { if (!buf) {
fprintf(stderr, "ERROR: Cannot create RX DMA buffer\n"); fprintf(stderr, "Ошибка создания буфера приёма\n");
fprintf(stderr, " Try 'sudo' or check if another process uses Pluto\n");
iio_context_destroy(ctx);
return 1; return 1;
} }
/* ============================================================ // === ИНИЦИАЛИЗАЦИЯ FEC-ДЕКОДЕРА ===
* Инициализация liquid-dsp if (fec_on) {
* ============================================================ */ decoder = fec_create(LIQUID_FEC_RS_M8, NULL);
if (!decoder) {
fprintf(stderr, "Ошибка создания FEC-декодера Рида-Соломона\n");
return 1;
}
}
/** // === ИНИЦИАЛИЗАЦИЯ СИСТЕМЫ ЧАСТОТНОЙ КОРРЕКЦИИ ===
* framesync64 — детектор/декодер кадров фиксированной длины // Создание NCO (Numerically Controlled Oscillator)
* // NCO используется для точной компенсации частотного сдвига
* Что он делает: nco = nco_crcf_create(LIQUID_NCO);
* 1. Ищет заголовок (header) во входном потоке nco_crcf_set_frequency(nco, 0.0f); // Начальная частота: 0 (коррекция пока не требуется)
* 2. Синхронизируется по времени и частоте
* 3. Демодулирует QPSK
* 4. Проверяет CRC-32
* 5. Вызывает callback с результатом
*
* На передающей стороне используется framegen64
*/
framesync64 fs = framesync64_create(fs_callback, NULL);
printf("[RX] Listening... (Ctrl+C to stop)\n\n"); // === ИНИЦИАЛИЗАЦИЯ OFDM-СИНХРОНИЗАТОРА ===
// AGC встроен в ofdmflexframesync, дополнительная настройка не требуется
ofdmflexframesync fs = ofdmflexframesync_create(
OFDM_M, OFDM_CP, OFDM_TAPER, NULL, callback, NULL);
time_t last_report = time(NULL); fprintf(stderr, " Приёмник готов. Прослушивание эфира...\n\n");
/* ============================================================ // Заголовок таблицы статистики
* Основной цикл приёма fprintf(stderr, "Кадры | Загол. | CRC | RS испр | Вывод | RSSI dBM | EVM dBm | CFO\n");
* ============================================================ */ fprintf(stderr, "--------+--------+------+---------+--------+-------+-------+----------\n");
time_t last = time(NULL);
// ===========================================
// === ОСНОВНОЙ ЦИКЛ ПРИЁМА =================
// ===========================================
while (!stop) { while (!stop) {
/* ожидание данных от Pluto */ // Заполнение аппаратного буфера данными с SDR
ssize_t n = iio_buffer_refill(buf); ssize_t n = iio_buffer_refill(buf);
if (n < 0) { if (n < 0) {
usleep(500); /* небольшая пауза при ошибке */ usleep(500); // Кратковременная пауза при ошибке чтения
continue; continue;
} }
/* указатель на данные */ // Получение указателя на сырые данные (16-битные I/Q семплы)
int16_t *bb = (int16_t *)iio_buffer_start(buf); int16_t *bb = (int16_t *)iio_buffer_start(buf);
int ns = (iio_buffer_end(buf) - iio_buffer_start(buf)) / sizeof(int16_t) / 2;
/* конвертация int16 → float complex */ // Вычисление количества комплексных отсчётов
liquid_float_complex frame[ns]; 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++) { for (int i = 0; i < ns; i++) {
frame[i] = ((float)bb[2 * i] / 32767.0f) + // Шаг 1: Нормализация для 12-битного АЦП PlutoSDR (4096 уровней → ±2048)
((float)bb[2 * i + 1] / 32767.0f) * _Complex_I; // Делим на 2048 для приведения к диапазону ±1.0
frame[i] = ((float)bb[2*i] / 2048.0f) +
((float)bb[2*i+1] / 2048.0f) * _Complex_I;
// Шаг 2: Применение частотной коррекции через NCO
// mix_down компенсирует частотный сдвиг путём умножения на e^(-j*2π*f_cfo*t)
nco_crcf_mix_down(nco, frame[i], &frame[i]);
// Шаг 3: Продвижение фазы NCO для следующего семпла
nco_crcf_step(nco);
} }
/* передача всего буфера в framesync64 */ // Сброс фазы NCO в 0 для каждого нового блока
framesync64_execute(fs, frame, ns); // Это предотвращает накопление фазовой ошибки между блоками
nco_crcf_set_phase(nco, 0.0f);
/* вывод статистики (раз в секунду) */ // Передача скорректированных отсчётов синхронизатору
time_t now = time(NULL); // Синхронизатор выполняет:
if (now > last_report) { // - Обнаружение OFDM-кадров
double pct = total_packets > 0 ? // - Оценку и компенсацию оставшегося CFO
100.0 * packets_ok / total_packets : 0.0; // - Демодуляцию поднесущих
// - Декодирование данных
// - Вызов callback-функции для каждого обнаруженного кадра
ofdmflexframesync_execute(fs, frame, ns);
/* время с последнего пакета */ // Обновление статистики на экране (раз в секунду)
int idle_sec = (int)(now - last_packet_time); if (time(NULL) > last) {
fprintf(stderr, "\r%6llu | %5llu | %4llu | %6llu | %6llu | %+5.0f | %+5.1f | %+8.4f",
printf("\r[RX] pkts: %5llu | OK: %5llu (%4.1f%%) | " (unsigned long long)calls, // Всего обнаружено кадров
"bytes: %8llu | idle: %d sec ", (unsigned long long)hdr_ok, // Корректных заголовков
(unsigned long long)total_packets, (unsigned long long)crc_raw, // Корректных CRC
(unsigned long long)packets_ok, pct, (unsigned long long)rs_saved, // Исправлено RS-декодером
(unsigned long long)total_bytes, (unsigned long long)crc_final, // Успешно выведено
idle_sec); rssi, // Уровень сигнала (дБм)
fflush(stdout); evm, // Вектор ошибки (%)
last_report = now; cfo_est); // Текущая оценка CFO
fflush(stderr);
last = time(NULL);
} }
} }
/* ============================================================ // === ВЫВОД ИТОГОВОЙ СТАТИСТИКИ ===
* Завершение fprintf(stderr, "\n\n═══════════════════════════════════════════════════\n");
* ============================================================ */ fprintf(stderr, " ПРИЁМ ЗАВЕРШЁН\n");
fprintf(stderr, "═══════════════════════════════════════════════════\n");
fprintf(stderr, "Обнаружено кадров: %llu\n", (unsigned long long)calls);
fprintf(stderr, "Корректных заголовков: %llu (%.1f%%)\n",
(unsigned long long)hdr_ok,
calls > 0 ? 100.0 * hdr_ok / calls : 0.0);
fprintf(stderr, "Корректных CRC: %llu (%.1f%%)\n",
(unsigned long long)crc_raw,
hdr_ok > 0 ? 100.0 * crc_raw / hdr_ok : 0.0);
fprintf(stderr, "Исправлено RS: %llu\n", (unsigned long long)rs_saved);
fprintf(stderr, "Успешно выведено: %llu кадров\n", (unsigned long long)crc_final);
fprintf(stderr, "Всего принято данных: %llu байт (%.2f МБ)\n",
(unsigned long long)total_bytes, total_bytes / 1e6);
fprintf(stderr, "Финальная оценка CFO: %.4f рад/семпл\n", cfo_est);
fprintf(stderr, "═══════════════════════════════════════════════════\n");
printf("\n\n[RX] Stopping...\n"); // === ОСВОБОЖДЕНИЕ РЕСУРСОВ ===
printf(" Total packets: %llu\n", (unsigned long long)total_packets); if (decoder) fec_destroy(decoder);
printf(" OK packets: %llu (%.1f%%)\n", if (nco) nco_crcf_destroy(nco);
(unsigned long long)packets_ok, ofdmflexframesync_destroy(fs);
total_packets > 0 ? 100.0 * packets_ok / total_packets : 0.0);
printf(" Total bytes: %llu\n", (unsigned long long)total_bytes);
/* освобождение ресурсов */
framesync64_destroy(fs);
iio_buffer_destroy(buf); iio_buffer_destroy(buf);
iio_context_destroy(ctx); iio_context_destroy(ctx);

View File

@@ -1,23 +1,9 @@
/** /**
* tx.c — QPSK передатчик для PlutoSDR (production) * transmitter.c — OFDM + RS(255,223) FEC + PlutoSDR
* СИНХРОНИЗИРОВАННАЯ ВЕРСИЯ
* *
* Использует framegen64 из liquid-dsp для формирования кадров. * ОПТИМАЛЬНАЯ КОМАНДА ЗАПУСКА:
* Архитектура: payload → framegen64 → int16 I/Q → Pluto DMA * cat /dev/urandom | ./transmitter -f 1265000000 -r 3840000 -b 2000000 -g -20 -a 0.3 -p 0 -c rs8
*
* Зависимости:
* - libiio (PlutoSDR API)
* - libliquid (liquid-dsp, framegen64)
* - libm (математика)
* - libpthread (сигналы)
*
* Компиляция:
* gcc -o tx tx.c -liio -lliquid -lm -lpthread -Wall -O2
*
* Примеры запуска:
* ./tx # значения по умолчанию
* ./tx -f 2400000000 -r 4000000 -a 0.5 # 2.4 ГГц, 4 MSPS, половинная амплитуда
* ./tx -g -20 -p 1000 # TX gain -20 dB, пауза 1 мс между кадрами
* ./tx -h # справка
*/ */
#include <stdio.h> #include <stdio.h>
@@ -32,282 +18,182 @@
#include <iio.h> #include <iio.h>
#include <liquid/liquid.h> #include <liquid/liquid.h>
/* ============================================================ // Параметры передачи по умолчанию
* Значения по умолчанию #define DEFAULT_FREQ 1265000000 // Частота несущей: 1265 МГц
* ============================================================ */ #define DEFAULT_RATE 3840000 // Частота дискретизации: 3.84 МГц
#define DEFAULT_BW 3000000 // Полоса пропускания: 3 МГц
#define DEFAULT_GAIN -30.0 // Усиление передатчика: -30 дБ
#define DEFAULT_AMP 0.2f // Амплитуда сигнала (коэффициент масштабирования)
#define DEFAULT_PAUSE 2000 // Пауза между кадрами: 2000 мкс
#define BUF_SAMPLES 16384 // Размер буфера в отсчётах
#define MAX_PAYLOAD 1024 // Максимальный размер полезной нагрузки в байтах
#define DEFAULT_CENTER_FREQ 1300000000 /* 1300 МГц */ // Параметры OFDM-модуляции
#define DEFAULT_SAMPLE_RATE 3840000 /* 3.84 MSPS */ #define OFDM_M 64 // Количество поднесущих
#define DEFAULT_TX_GAIN -10.0 /* аттенюация TX, dB */ #define OFDM_CP 16 // Длина циклического префикса
#define DEFAULT_AMPLITUDE 0.8f /* амплитуда I/Q (0..1) */ #define OFDM_TAPER 4 // Длина сглаживающего окна
#define DEFAULT_BUF_SAMPLES 32768 /* размер DMA буфера */
#define DEFAULT_FRAME_PAUS_US 10000 /* пауза между кадрами, мкс */
#define DEFAULT_MIN_PAYLOAD 32 /* минимальный размер данных */
#define DEFAULT_MAX_PAYLOAD 63 /* максимальный (64-1 байт длины) */
#define DEFAULT_URI "ip:192.168.2.1"
/* ============================================================ // Параметры помехоустойчивого кодирования Рида-Соломона
* Заголовок framegen64 (8 байт) #define RS_DATA 223 // Размер блока данных
* Должен совпадать на приёмной стороне #define RS_ENC 255 // Размер кодированного блока
* ============================================================ */
static uint8_t header[] = { // Глобальный флаг для корректного завершения программы
0xf0, 0xaa, 0x24, 0xdb, volatile sig_atomic_t stop = 0;
0x40, 0x01, 0x55, 0x0f void sigint_handler(int s) { (void)s; stop = 1; }
};
/* ============================================================
* Глобальные переменные
* ============================================================ */
static volatile int stop = 0; /* флаг остановки (SIGINT) */
static uint64_t total_frames = 0; /* всего отправлено кадров */
static uint64_t total_bytes = 0; /* всего отправлено байт */
/* ============================================================
* Обработчик сигналов
* ============================================================ */
/**
* sigint_handler — обработчик Ctrl+C
* Устанавливает флаг stop, программа завершается на следующей итерации
*/
static void sigint_handler(int sig) {
(void)sig;
stop = 1;
}
/* ============================================================
* Вывод справки
* ============================================================ */
/**
* print_usage — печать справки по аргументам командной строки
*/
static void print_usage(const char *prog) {
printf("Usage: %s [options]\n\n", prog);
printf("Options:\n");
printf(" -f FREQ Center frequency in Hz (default: %d)\n", DEFAULT_CENTER_FREQ);
printf(" -r RATE Sample rate in Hz (default: %d)\n", DEFAULT_SAMPLE_RATE);
printf(" -g GAIN TX gain in dB, -89..0 (default: %.0f)\n", DEFAULT_TX_GAIN);
printf(" -a AMP Amplitude 0.0-1.0 (default: %.1f)\n", DEFAULT_AMPLITUDE);
printf(" -p USEC Pause between frames, microseconds (default: %d)\n", DEFAULT_FRAME_PAUS_US);
printf(" -m BYTES Min payload size (default: %d)\n", DEFAULT_MIN_PAYLOAD);
printf(" -M BYTES Max payload size (default: %d)\n", DEFAULT_MAX_PAYLOAD);
printf(" -u URI IIO context URI (default: %s)\n", DEFAULT_URI);
printf(" -h This help\n");
printf("\nExamples:\n");
printf(" %s # defaults\n", prog);
printf(" %s -f 2400000000 -r 4000000 # 2.4 GHz, 4 MSPS\n", prog);
printf(" %s -g -20 -a 0.5 # lower gain and amplitude\n", prog);
}
/* ============================================================
* MAIN
* ============================================================ */
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
/* ---- переменные с значениями по умолчанию ---- */ // Инициализация параметров значениями по умолчанию
long long center_freq = DEFAULT_CENTER_FREQ; long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
long long sample_rate = DEFAULT_SAMPLE_RATE; double gain = DEFAULT_GAIN;
double tx_gain = DEFAULT_TX_GAIN; float amp = DEFAULT_AMP;
float amplitude = DEFAULT_AMPLITUDE; const char *uri = "ip:192.168.3.1"; // адрес PlutoSDR передатчика
int buf_samples = DEFAULT_BUF_SAMPLES; int pause_us = DEFAULT_PAUSE, use_fec = 1;
int frame_pause_us = DEFAULT_FRAME_PAUS_US;
int min_payload = DEFAULT_MIN_PAYLOAD;
int max_payload = DEFAULT_MAX_PAYLOAD;
const char *uri = DEFAULT_URI;
/* ---- парсинг аргументов ---- */ // Обработка аргументов командной строки
int opt; int opt;
while ((opt = getopt(argc, argv, "f:r:g:a:p:m:M:u:h")) != -1) { while ((opt = getopt(argc, argv, "f:r:b:g:a:p:u:c:h")) != -1) {
switch (opt) { switch (opt) {
case 'f': center_freq = atoll(optarg); break; case 'f': freq = atoll(optarg); break; // Частота несущей
case 'r': sample_rate = atoll(optarg); break; case 'r': rate = atoll(optarg); break; // Частота дискретизации
case 'g': tx_gain = atof(optarg); break; case 'b': bw = atoll(optarg); break; // Полоса пропускания
case 'a': amplitude = atof(optarg); break; case 'g': gain = atof(optarg); break; // Усиление передатчика
case 'p': frame_pause_us = atoi(optarg); break; case 'a': amp = atof(optarg); break; // Амплитуда сигнала
case 'm': min_payload = atoi(optarg); break; case 'p': pause_us = atoi(optarg); break; // Пауза между кадрами
case 'M': max_payload = atoi(optarg); break; case 'u': uri = optarg; break; // URI подключения к PlutoSDR
case 'u': uri = optarg; break; case 'c': use_fec = strcmp(optarg, "none") ? 1 : 0; break; // Включение/отключение FEC
case 'h': print_usage(argv[0]); return 0; case 'h':
default: print_usage(argv[0]); return 1; fprintf(stderr, "Использование: %s [-f частота] [-r скорость] [-b полоса] [-g усиление] [-a амплитуда] [-p пауза_мкс] [-c rs8|none]\n", argv[0]);
return 0;
default: return 1;
} }
} }
/* ---- валидация параметров ---- */ // Установка обработчиков сигналов для корректного завершения
if (amplitude < 0.0f) amplitude = 0.0f; signal(SIGINT, sigint_handler);
if (amplitude > 1.0f) amplitude = 1.0f; signal(SIGTERM, sigint_handler);
if (min_payload < 1) min_payload = 1; signal(SIGPIPE, SIG_IGN);
if (max_payload > 63) max_payload = 63; /* 64 байта payload - 1 байт длины */
if (min_payload > max_payload) min_payload = max_payload;
/* ---- инициализация ---- */ // Вывод параметров конфигурации передатчика
signal(SIGINT, sigint_handler); /* Ctrl+C → sigint_handler */ fprintf(stderr, "ПЕРЕДАТЧИК: %.1f МГц | скорость=%.2f МГц | полоса=%.2f МГц | усиление=%.0fдБ | амплитуда=%.2f | FEC=%s\n",
srand(time(NULL)); /* случайный seed */ freq/1e6, rate/1e6, bw/1e6, gain, amp, use_fec ? "RS(255,223)" : "ОТКЛ");
/* ---- заголовок программы ---- */ // Инициализация контекста PlutoSDR
printf("══════════════════════════════════════════\n");
printf(" QPSK TRANSMITTER (framegen64)\n");
printf("══════════════════════════════════════════\n");
printf(" Frequency: %.3f MHz\n", center_freq / 1e6);
printf(" Sample rate: %.3f MSPS\n", sample_rate / 1e6);
printf(" TX gain: %.0f dB\n", tx_gain);
printf(" Amplitude: %.2f\n", amplitude);
printf(" Payload: %d-%d bytes\n", min_payload, max_payload);
printf(" Frame pause: %d us\n", frame_pause_us);
printf(" URI: %s\n", uri);
printf("══════════════════════════════════════════\n");
/* ============================================================
* Инициализация PlutoSDR
* ============================================================ */
/* создаём контекст IIO */
struct iio_context *ctx = iio_create_context_from_uri(uri); struct iio_context *ctx = iio_create_context_from_uri(uri);
if (!ctx) { if (!ctx) { fprintf(stderr, "Ошибка подключения к PlutoSDR\n"); return 1; }
fprintf(stderr, "ERROR: Cannot connect to %s\n", uri);
fprintf(stderr, " Check USB cable and try 'iio_info -s'\n");
return 1;
}
/* находим устройства */ // Поиск устройств в контексте
struct iio_device *phy = iio_context_find_device(ctx, "ad9361-phy"); struct iio_device *phy = iio_context_find_device(ctx, "ad9361-phy");
struct iio_device *tx = iio_context_find_device(ctx, "cf-ad9361-dds-core-lpc"); struct iio_device *tx = iio_context_find_device(ctx, "cf-ad9361-dds-core-lpc");
if (!phy || !tx) { if (!phy || !tx) { fprintf(stderr, "Радиоустройства не найдены\n"); return 1; }
fprintf(stderr, "ERROR: Devices not found\n");
iio_context_destroy(ctx);
return 1;
}
/* каналы PHY */ // Настройка всех параметров радиотракта
struct iio_channel *tx_lo = iio_device_find_channel(phy, "altvoltage1", true); iio_channel_attr_write_longlong(iio_device_find_channel(phy, "altvoltage1", true), "frequency", freq);
struct iio_channel *tx_phy = iio_device_find_channel(phy, "voltage0", true); 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);
/* настройка частоты несущей */ // Включение каналов передатчика
if (iio_channel_attr_write_longlong(tx_lo, "frequency", center_freq) < 0) { iio_channel_enable(iio_device_find_channel(tx, "voltage0", true));
fprintf(stderr, "WARNING: Cannot set TX LO frequency\n"); iio_channel_enable(iio_device_find_channel(tx, "voltage1", true));
}
/* настройка частоты дискретизации */ // Создание буфера передачи
if (iio_channel_attr_write_longlong(tx_phy, "sampling_frequency", sample_rate) < 0) { struct iio_buffer *buf = iio_device_create_buffer(tx, BUF_SAMPLES, false);
fprintf(stderr, "WARNING: Cannot set sample rate\n"); if (!buf) { fprintf(stderr, "Ошибка создания буфера передачи\n"); return 1; }
}
/* настройка полосы, порта, усиления */ // Инициализация помехоустойчивого кодирования и OFDM-модулятора
iio_channel_attr_write_longlong(tx_phy, "rf_bandwidth", sample_rate); fec enc = use_fec ? fec_create(LIQUID_FEC_RS_M8, NULL) : NULL;
iio_channel_attr_write(tx_phy, "rf_port_select", "A");
iio_channel_attr_write_double(tx_phy, "hardwaregain", tx_gain);
/* включение DMA каналов I и Q */ ofdmflexframegenprops_s fgp;
struct iio_channel *tx_i = iio_device_find_channel(tx, "voltage0", true); ofdmflexframegenprops_init_default(&fgp);
struct iio_channel *tx_q = iio_device_find_channel(tx, "voltage1", true); fgp.check = LIQUID_CRC_32;
iio_channel_enable(tx_i); ofdmflexframegen fg = ofdmflexframegen_create(OFDM_M, OFDM_CP, OFDM_TAPER, NULL, &fgp);
iio_channel_enable(tx_q);
/* создание DMA буфера (non-cyclic) */ // Счётчики статистики передачи
struct iio_buffer *buf = iio_device_create_buffer(tx, buf_samples, false); uint64_t frames = 0, bytes_tx = 0;
if (!buf) { uint32_t seq = 0;
fprintf(stderr, "ERROR: Cannot create TX DMA buffer\n"); time_t last = time(NULL);
fprintf(stderr, " Try 'sudo' or check if another process uses Pluto\n");
iio_context_destroy(ctx);
return 1;
}
/* указатель на данные буфера */
int16_t *out = (int16_t *)iio_buffer_start(buf);
/* ============================================================
* Инициализация liquid-dsp
* ============================================================ */
/**
* framegen64 — генератор кадров фиксированной длины (64 байта данных)
*
* Что он делает:
* 1. Добавляет 8-байтовый заголовок (header)
* 2. Кодирует 64 байта полезной нагрузки
* 3. Добавляет CRC-32
* 4. Модулирует QPSK с RRC pulse shaping
* 5. Возвращает LIQUID_FRAME64_LEN (обычно 1440) комплексных отсчётов
*
* На приёмной стороне используется framesync64
*/
framegen64 fg = framegen64_create();
/* буфер для payload (64 байта: 1 байт длины + 63 байта данных) */
uint8_t payload[64];
printf("[TX] Running... (Ctrl+C to stop)\n\n");
time_t last_report = time(NULL);
/* ============================================================
* Основной цикл передачи
* ============================================================ */
// Основной цикл передачи
while (!stop) { while (!stop) {
/* очистка DMA буфера */ uint8_t raw[MAX_PAYLOAD];
memset(out, 0, buf_samples * 2 * sizeof(int16_t)); size_t n = fread(raw, 1, MAX_PAYLOAD, stdin);
if (!n) {
/* генерация случайных данных */ if (feof(stdin) || stop) break;
int payload_len = min_payload + (rand() % (max_payload - min_payload + 1)); usleep(10000);
continue;
/* первый байт = количество байт данных (как в HackRF версии) */
payload[0] = (uint8_t)(payload_len - 1);
for (int i = 1; i < payload_len; i++)
payload[i] = rand() & 0xFF;
/* запуск framegen64: payload → модулированный кадр */
unsigned int frame_len = LIQUID_FRAME64_LEN;
liquid_float_complex frame[frame_len];
framegen64_execute(fg, header, payload, frame);
/* копирование float complex → int16 I/Q в DMA буфер */
int sample_idx = 0;
for (unsigned int i = 0; i < frame_len && sample_idx < buf_samples; i++) {
out[2 * sample_idx] = (int16_t)(crealf(frame[i]) * 32767.0f * amplitude);
out[2 * sample_idx + 1] = (int16_t)(cimagf(frame[i]) * 32767.0f * amplitude);
sample_idx++;
} }
/* отправка буфера в Pluto */ uint8_t *tx_payload = NULL;
iio_buffer_push(buf); size_t tx_len = 0;
uint8_t hdr[12] = {0};
/* статистика */ // Кодирование данных с использованием FEC
total_frames++; if (enc) {
total_bytes += (payload_len - 1); /* минус байт длины */ size_t total_blocks = (n + RS_DATA - 1) / RS_DATA;
tx_len = total_blocks * RS_ENC;
/* вывод статистики (раз в секунду) */ tx_payload = malloc(tx_len);
time_t now = time(NULL); for (size_t b = 0; b < total_blocks; b++) {
if (now > last_report) { uint8_t blk[RS_DATA] = {0};
double kbps = total_bytes * 8.0 / 1000.0; size_t bytes = (b == total_blocks - 1) ? n - b * RS_DATA : RS_DATA;
printf("\r[TX] frames: %6llu | bytes: %8llu | ~%.1f kbps | payload: %2d bytes ", memcpy(blk, raw + b * RS_DATA, bytes);
(unsigned long long)total_frames, fec_encode(enc, RS_DATA, blk, tx_payload + b * RS_ENC);
(unsigned long long)total_bytes, }
kbps, // Формирование заголовка кадра
payload_len); hdr[0]=0xF0; hdr[1]=0xAA; // Сигнатура начала кадра
fflush(stdout); hdr[2]=(seq>>24)&0xFF; hdr[3]=(seq>>16)&0xFF;
last_report = now; 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; // Количество блоков FEC
hdr[10]=n%RS_DATA; hdr[11]=0; // Размер последнего блока
} else {
tx_len = n;
tx_payload = malloc(tx_len);
memcpy(tx_payload, raw, n);
// Формирование заголовка кадра без FEC
hdr[0]=0xF0; hdr[1]=0xAA; // Сигнатура начала кадра
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; // Размер исходных данных
} }
/* пауза между кадрами (чтобы не забивать буфер Pluto) */ // Сборка OFDM-кадра
if (frame_pause_us > 0) ofdmflexframegen_assemble(fg, hdr, tx_payload, tx_len);
usleep(frame_pause_us); 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];
// Генерация OFDM-символов и запись в буфер
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);
printf("\n\n[TX] Stopping...\n"); ofdmflexframegen_destroy(fg);
printf(" Total frames: %llu\n", (unsigned long long)total_frames);
printf(" Total bytes: %llu\n", (unsigned long long)total_bytes);
/* освобождение ресурсов */
framegen64_destroy(fg);
iio_buffer_destroy(buf); iio_buffer_destroy(buf);
iio_context_destroy(ctx); iio_context_destroy(ctx);
return 0; return 0;
} }