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

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)
* - libliquid (liquid-dsp, framesync64)
* - libm (математика)
* - 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 # справка
* ОСОБЕННОСТИ ДАННОЙ ВЕРСИИ:
* - Адаптивная компенсация частотного сдвига (CFO) через NCO
* - Ручное управление усилением для максимальной стабильности
* - Экспоненциальная фильтрация оценки CFO (α=0.95)
* - Улучшенная работа в условиях нестабильных генераторов
*/
#include <stdio.h>
@@ -32,286 +23,389 @@
#include <iio.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 МГц */
#define DEFAULT_SAMPLE_RATE 3840000 /* 3.84 MSPS */
#define DEFAULT_BUF_SAMPLES 16384 /* размер DMA буфера */
#define DEFAULT_URI "ip:192.168.2.1"
// Параметры OFDM-демодуляции
#define OFDM_M 64 // Количество поднесущих
#define OFDM_CP 16 // Длина циклического префикса
#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; /* всего обнаружено кадров */
static uint64_t packets_ok = 0; /* успешно декодировано */
static uint64_t total_bytes = 0; /* всего принято байт данных */
static time_t last_packet_time = 0; /* время последнего успешного пакета */
// Глобальный флаг для корректного завершения программы
volatile sig_atomic_t stop = 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)sig;
void sigint_handler(int s) {
(void)s;
stop = 1;
}
/* ============================================================
* Callback framesync64
* ============================================================ */
/**
* fs_callback — вызывается framesync64 при обнаружении кадра
* Callback-функция обработки принятого OFDM-кадра
*
* Параметры:
* _header — заголовок кадра (8 байт)
* _header_valid — флаг валидности заголовка
* _payload — данные полезной нагрузки (64 байта)
* _payload_len — длина payload
* _payload_valid — флаг валидности данных (CRC прошёл)
* _stats — статистика кадра (RSSI, CFO, etc.)
* _userdata — пользовательские данные (не используется)
* Вызывается синхронизатором при успешном обнаружении кадра.
* Выполняет:
* 1. Обновление метрик качества сигнала (RSSI, EVM)
* 2. Адаптивную фильтрацию оценки частотного сдвига (CFO)
* 3. Декодирование Рида-Соломона (если включено)
* 4. Вывод декодированных данных в stdout
*
* Возвращает 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,
unsigned char *_payload, unsigned int _payload_len,
int _payload_valid, framesyncstats_s _stats,
void *_userdata) {
(void)_header;
(void)_header_valid;
(void)_stats;
(void)_userdata;
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++;
total_packets++;
// Обновление метрик качества сигнала
rssi = stats.rssi;
evm = stats.evm;
if (_payload_valid) {
packets_ok++;
/* первый байт = количество данных */
size_t data_len = _payload[0];
total_bytes += data_len;
last_packet_time = time(NULL);
/* для отладки: вывод первых 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
// Адаптивная фильтрация CFO с защитой от выбросов
// Используется экспоненциальное сглаживание для стабильной оценки
if (fabsf(stats.cfo) < CFO_MAX_VALID) {
// Рекурсивный фильтр 1-го порядка: y[n] = α*y[n-1] + (1-α)*x[n]
cfo_est = CFO_FILTER_ALPHA * cfo_est + (1.0f - CFO_FILTER_ALPHA) * stats.cfo;
// Применяем скорректированную частоту к NCO
nco_crcf_set_frequency(nco, cfo_est);
}
// Проверка валидности заголовка - критически важно для дальнейшей обработки
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;
}
/* ============================================================
* Вывод справки
* ============================================================ */
/**
* 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[]) {
/* ---- переменные с значениями по умолчанию ---- */
long long center_freq = DEFAULT_CENTER_FREQ;
long long sample_rate = DEFAULT_SAMPLE_RATE;
int buf_samples = DEFAULT_BUF_SAMPLES;
const char *uri = DEFAULT_URI;
int debug_mode = 0;
// Инициализация параметров значениями по умолчанию
long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
const char *uri = "ip:192.168.2.1";
const char *fec_opt = "rs8";
/* ---- парсинг аргументов ---- */
// Обработка аргументов командной строки
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) {
case 'f': center_freq = atoll(optarg); break;
case 'r': sample_rate = atoll(optarg); break;
case 'b': buf_samples = atoi(optarg); break;
case 'u': uri = optarg; break;
case 'd': debug_mode = 1; break;
case 'h': print_usage(argv[0]); return 0;
default: print_usage(argv[0]); return 1;
case 'f': freq = atoll(optarg); break; // Частота несущей (Гц)
case 'r': rate = atoll(optarg); break; // Частота дискретизации (Гц)
case 'b': bw = atoll(optarg); break; // Полоса пропускания (Гц)
case 'u': uri = optarg; break; // URI подключения к PlutoSDR
case 'c': fec_opt = optarg; break; // Тип FEC: rs8 или none
case 'h':
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;
}
}
/* ---- инициализация ---- */
signal(SIGINT, sigint_handler); /* Ctrl+C → sigint_handler */
// Определение режима FEC
fec_on = !strcmp(fec_opt, "rs8");
/* ---- заголовок программы ---- */
printf("══════════════════════════════════════════\n");
printf(" QPSK RECEIVER (framesync64)\n");
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");
// Установка обработчика сигналов для корректного завершения
signal(SIGINT, sigint_handler);
signal(SIGTERM, sigint_handler);
/* ============================================================
* Инициализация 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);
if (!ctx) {
fprintf(stderr, "ERROR: Cannot connect to %s\n", uri);
fprintf(stderr, " Check USB cable and try 'iio_info -s'\n");
fprintf(stderr, "Ошибка подключения к PlutoSDR по адресу %s\n", uri);
return 1;
}
/* находим устройства */
// Поиск устройств физического уровня и приёмника
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) {
fprintf(stderr, "ERROR: Devices not found\n");
iio_context_destroy(ctx);
fprintf(stderr, "Радиоустройства не найдены в контексте\n");
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) {
fprintf(stderr, "WARNING: Cannot set RX LO frequency\n");
}
// Установка частоты дискретизации
iio_channel_attr_write_longlong(
iio_device_find_channel(phy, "voltage0", false),
"sampling_frequency", rate);
/* настройка частоты дискретизации */
if (iio_channel_attr_write_longlong(rx_phy, "sampling_frequency", sample_rate) < 0) {
fprintf(stderr, "WARNING: Cannot set sample rate\n");
}
// Установка полосы пропускания
iio_channel_attr_write_longlong(
iio_device_find_channel(phy, "voltage0", false),
"rf_bandwidth", bw);
/* настройка полосы, порта, АРУ */
iio_channel_attr_write_longlong(rx_phy, "rf_bandwidth", sample_rate);
iio_channel_attr_write(rx_phy, "gain_control_mode", "slow_attack");
iio_channel_attr_write(rx_phy, "rf_port_select", "A_BALANCED");
// Настройка ручного управления усилением для максимальной стабильности
// Ручной режим предпочтительнее автоматического при работе с OFDM
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", 50.0); // Оптимальное значение для большинства сценариев
/* включение DMA каналов I и Q */
struct iio_channel *rx_i = iio_device_find_channel(rx, "voltage0", false);
struct iio_channel *rx_q = iio_device_find_channel(rx, "voltage1", false);
iio_channel_enable(rx_i);
iio_channel_enable(rx_q);
// Выбор сбалансированного антенного входа
iio_channel_attr_write(
iio_device_find_channel(phy, "voltage0", false),
"rf_port_select", "A_BALANCED");
/* создание DMA буфера */
struct iio_buffer *buf = iio_device_create_buffer(rx, buf_samples, false);
// Включение I и Q каналов приёмника
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, "ERROR: Cannot create RX DMA buffer\n");
fprintf(stderr, " Try 'sudo' or check if another process uses Pluto\n");
iio_context_destroy(ctx);
fprintf(stderr, "Ошибка создания буфера приёма\n");
return 1;
}
/* ============================================================
* Инициализация liquid-dsp
* ============================================================ */
// === ИНИЦИАЛИЗАЦИЯ FEC-ДЕКОДЕРА ===
if (fec_on) {
decoder = fec_create(LIQUID_FEC_RS_M8, NULL);
if (!decoder) {
fprintf(stderr, "Ошибка создания FEC-декодера Рида-Соломона\n");
return 1;
}
}
/**
* framesync64 — детектор/декодер кадров фиксированной длины
*
* Что он делает:
* 1. Ищет заголовок (header) во входном потоке
* 2. Синхронизируется по времени и частоте
* 3. Демодулирует QPSK
* 4. Проверяет CRC-32
* 5. Вызывает callback с результатом
*
* На передающей стороне используется framegen64
*/
framesync64 fs = framesync64_create(fs_callback, NULL);
// === ИНИЦИАЛИЗАЦИЯ СИСТЕМЫ ЧАСТОТНОЙ КОРРЕКЦИИ ===
// Создание NCO (Numerically Controlled Oscillator)
// NCO используется для точной компенсации частотного сдвига
nco = nco_crcf_create(LIQUID_NCO);
nco_crcf_set_frequency(nco, 0.0f); // Начальная частота: 0 (коррекция пока не требуется)
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) {
/* ожидание данных от Pluto */
// Заполнение аппаратного буфера данными с SDR
ssize_t n = iio_buffer_refill(buf);
if (n < 0) {
usleep(500); /* небольшая пауза при ошибке */
usleep(500); // Кратковременная пауза при ошибке чтения
continue;
}
/* указатель на данные */
// Получение указателя на сырые данные (16-битные I/Q семплы)
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++) {
frame[i] = ((float)bb[2 * i] / 32767.0f) +
((float)bb[2 * i + 1] / 32767.0f) * _Complex_I;
// Шаг 1: Нормализация для 12-битного АЦП PlutoSDR (4096 уровней → ±2048)
// Делим на 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 */
framesync64_execute(fs, frame, ns);
// Сброс фазы NCO в 0 для каждого нового блока
// Это предотвращает накопление фазовой ошибки между блоками
nco_crcf_set_phase(nco, 0.0f);
/* вывод статистики (раз в секунду) */
time_t now = time(NULL);
if (now > last_report) {
double pct = total_packets > 0 ?
100.0 * packets_ok / total_packets : 0.0;
// Передача скорректированных отсчётов синхронизатору
// Синхронизатор выполняет:
// - Обнаружение OFDM-кадров
// - Оценку и компенсацию оставшегося CFO
// - Демодуляцию поднесущих
// - Декодирование данных
// - Вызов callback-функции для каждого обнаруженного кадра
ofdmflexframesync_execute(fs, frame, ns);
/* время с последнего пакета */
int idle_sec = (int)(now - last_packet_time);
printf("\r[RX] pkts: %5llu | OK: %5llu (%4.1f%%) | "
"bytes: %8llu | idle: %d sec ",
(unsigned long long)total_packets,
(unsigned long long)packets_ok, pct,
(unsigned long long)total_bytes,
idle_sec);
fflush(stdout);
last_report = now;
// Обновление статистики на экране (раз в секунду)
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, // Корректных CRC
(unsigned long long)rs_saved, // Исправлено RS-декодером
(unsigned long long)crc_final, // Успешно выведено
rssi, // Уровень сигнала (дБм)
evm, // Вектор ошибки (%)
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);
printf(" OK packets: %llu (%.1f%%)\n",
(unsigned long long)packets_ok,
total_packets > 0 ? 100.0 * packets_ok / total_packets : 0.0);
printf(" Total bytes: %llu\n", (unsigned long long)total_bytes);
/* освобождение ресурсов */
framesync64_destroy(fs);
// === ОСВОБОЖДЕНИЕ РЕСУРСОВ ===
if (decoder) fec_destroy(decoder);
if (nco) nco_crcf_destroy(nco);
ofdmflexframesync_destroy(fs);
iio_buffer_destroy(buf);
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
*
* Зависимости:
* - 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 # справка
* ОПТИМАЛЬНАЯ КОМАНДА ЗАПУСКА:
* cat /dev/urandom | ./transmitter -f 1265000000 -r 3840000 -b 2000000 -g -20 -a 0.3 -p 0 -c rs8
*/
#include <stdio.h>
@@ -32,282 +18,182 @@
#include <iio.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 МГц */
#define DEFAULT_SAMPLE_RATE 3840000 /* 3.84 MSPS */
#define DEFAULT_TX_GAIN -10.0 /* аттенюация TX, dB */
#define DEFAULT_AMPLITUDE 0.8f /* амплитуда I/Q (0..1) */
#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"
// Параметры OFDM-модуляции
#define OFDM_M 64 // Количество поднесущих
#define OFDM_CP 16 // Длина циклического префикса
#define OFDM_TAPER 4 // Длина сглаживающего окна
/* ============================================================
* Заголовок framegen64 (8 байт)
* Должен совпадать на приёмной стороне
* ============================================================ */
// Параметры помехоустойчивого кодирования Рида-Соломона
#define RS_DATA 223 // Размер блока данных
#define RS_ENC 255 // Размер кодированного блока
static uint8_t header[] = {
0xf0, 0xaa, 0x24, 0xdb,
0x40, 0x01, 0x55, 0x0f
};
/* ============================================================
* Глобальные переменные
* ============================================================ */
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
* ============================================================ */
// Глобальный флаг для корректного завершения программы
volatile sig_atomic_t stop = 0;
void sigint_handler(int s) { (void)s; stop = 1; }
int main(int argc, char *argv[]) {
/* ---- переменные с значениями по умолчанию ---- */
long long center_freq = DEFAULT_CENTER_FREQ;
long long sample_rate = DEFAULT_SAMPLE_RATE;
double tx_gain = DEFAULT_TX_GAIN;
float amplitude = DEFAULT_AMPLITUDE;
int buf_samples = DEFAULT_BUF_SAMPLES;
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;
// Инициализация параметров значениями по умолчанию
long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
double gain = DEFAULT_GAIN;
float amp = DEFAULT_AMP;
const char *uri = "ip:192.168.3.1"; // адрес PlutoSDR передатчика
int pause_us = DEFAULT_PAUSE, use_fec = 1;
/* ---- парсинг аргументов ---- */
// Обработка аргументов командной строки
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) {
case 'f': center_freq = atoll(optarg); break;
case 'r': sample_rate = atoll(optarg); break;
case 'g': tx_gain = atof(optarg); break;
case 'a': amplitude = atof(optarg); break;
case 'p': frame_pause_us = atoi(optarg); break;
case 'm': min_payload = atoi(optarg); break;
case 'M': max_payload = atoi(optarg); break;
case 'u': uri = optarg; break;
case 'h': print_usage(argv[0]); return 0;
default: print_usage(argv[0]); return 1;
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; // URI подключения к PlutoSDR
case 'c': use_fec = strcmp(optarg, "none") ? 1 : 0; break; // Включение/отключение FEC
case 'h':
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;
if (amplitude > 1.0f) amplitude = 1.0f;
if (min_payload < 1) min_payload = 1;
if (max_payload > 63) max_payload = 63; /* 64 байта payload - 1 байт длины */
if (min_payload > max_payload) min_payload = max_payload;
// Установка обработчиков сигналов для корректного завершения
signal(SIGINT, sigint_handler);
signal(SIGTERM, sigint_handler);
signal(SIGPIPE, SIG_IGN);
/* ---- инициализация ---- */
signal(SIGINT, sigint_handler); /* Ctrl+C → sigint_handler */
srand(time(NULL)); /* случайный seed */
// Вывод параметров конфигурации передатчика
fprintf(stderr, "ПЕРЕДАТЧИК: %.1f МГц | скорость=%.2f МГц | полоса=%.2f МГц | усиление=%.0fдБ | амплитуда=%.2f | FEC=%s\n",
freq/1e6, rate/1e6, bw/1e6, gain, amp, use_fec ? "RS(255,223)" : "ОТКЛ");
/* ---- заголовок программы ---- */
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 */
// Инициализация контекста PlutoSDR
struct iio_context *ctx = iio_create_context_from_uri(uri);
if (!ctx) {
fprintf(stderr, "ERROR: Cannot connect to %s\n", uri);
fprintf(stderr, " Check USB cable and try 'iio_info -s'\n");
return 1;
}
if (!ctx) { fprintf(stderr, "Ошибка подключения к PlutoSDR\n"); return 1; }
/* находим устройства */
// Поиск устройств в контексте
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");
if (!phy || !tx) {
fprintf(stderr, "ERROR: Devices not found\n");
iio_context_destroy(ctx);
return 1;
}
struct iio_device *tx = iio_context_find_device(ctx, "cf-ad9361-dds-core-lpc");
if (!phy || !tx) { fprintf(stderr, "Радиоустройства не найдены\n"); return 1; }
/* каналы PHY */
struct iio_channel *tx_lo = iio_device_find_channel(phy, "altvoltage1", true);
struct iio_channel *tx_phy = iio_device_find_channel(phy, "voltage0", true);
// Настройка всех параметров радиотракта
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);
/* настройка частоты несущей */
if (iio_channel_attr_write_longlong(tx_lo, "frequency", center_freq) < 0) {
fprintf(stderr, "WARNING: Cannot set TX LO frequency\n");
}
// Включение каналов передатчика
iio_channel_enable(iio_device_find_channel(tx, "voltage0", true));
iio_channel_enable(iio_device_find_channel(tx, "voltage1", true));
/* настройка частоты дискретизации */
if (iio_channel_attr_write_longlong(tx_phy, "sampling_frequency", sample_rate) < 0) {
fprintf(stderr, "WARNING: Cannot set sample rate\n");
}
// Создание буфера передачи
struct iio_buffer *buf = iio_device_create_buffer(tx, BUF_SAMPLES, false);
if (!buf) { fprintf(stderr, "Ошибка создания буфера передачи\n"); return 1; }
/* настройка полосы, порта, усиления */
iio_channel_attr_write_longlong(tx_phy, "rf_bandwidth", sample_rate);
iio_channel_attr_write(tx_phy, "rf_port_select", "A");
iio_channel_attr_write_double(tx_phy, "hardwaregain", tx_gain);
// Инициализация помехоустойчивого кодирования и OFDM-модулятора
fec enc = use_fec ? fec_create(LIQUID_FEC_RS_M8, NULL) : NULL;
/* включение DMA каналов I и Q */
struct iio_channel *tx_i = iio_device_find_channel(tx, "voltage0", true);
struct iio_channel *tx_q = iio_device_find_channel(tx, "voltage1", true);
iio_channel_enable(tx_i);
iio_channel_enable(tx_q);
ofdmflexframegenprops_s fgp;
ofdmflexframegenprops_init_default(&fgp);
fgp.check = LIQUID_CRC_32;
ofdmflexframegen fg = ofdmflexframegen_create(OFDM_M, OFDM_CP, OFDM_TAPER, NULL, &fgp);
/* создание DMA буфера (non-cyclic) */
struct iio_buffer *buf = iio_device_create_buffer(tx, buf_samples, false);
if (!buf) {
fprintf(stderr, "ERROR: Cannot create TX DMA buffer\n");
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);
/* ============================================================
* Основной цикл передачи
* ============================================================ */
// Счётчики статистики передачи
uint64_t frames = 0, bytes_tx = 0;
uint32_t seq = 0;
time_t last = time(NULL);
// Основной цикл передачи
while (!stop) {
/* очистка DMA буфера */
memset(out, 0, buf_samples * 2 * sizeof(int16_t));
/* генерация случайных данных */
int payload_len = min_payload + (rand() % (max_payload - min_payload + 1));
/* первый байт = количество байт данных (как в 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++;
uint8_t raw[MAX_PAYLOAD];
size_t n = fread(raw, 1, MAX_PAYLOAD, stdin);
if (!n) {
if (feof(stdin) || stop) break;
usleep(10000);
continue;
}
/* отправка буфера в Pluto */
iio_buffer_push(buf);
uint8_t *tx_payload = NULL;
size_t tx_len = 0;
uint8_t hdr[12] = {0};
/* статистика */
total_frames++;
total_bytes += (payload_len - 1); /* минус байт длины */
/* вывод статистики (раз в секунду) */
time_t now = time(NULL);
if (now > last_report) {
double kbps = total_bytes * 8.0 / 1000.0;
printf("\r[TX] frames: %6llu | bytes: %8llu | ~%.1f kbps | payload: %2d bytes ",
(unsigned long long)total_frames,
(unsigned long long)total_bytes,
kbps,
payload_len);
fflush(stdout);
last_report = now;
// Кодирование данных с использованием FEC
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]=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; // Размер исходных данных
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) */
if (frame_pause_us > 0)
usleep(frame_pause_us);
// Сборка 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];
// Генерация 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); // Пауза между кадрами
}
/* ============================================================
* Завершение
* ============================================================ */
printf("\n\n[TX] Stopping...\n");
printf(" Total frames: %llu\n", (unsigned long long)total_frames);
printf(" Total bytes: %llu\n", (unsigned long long)total_bytes);
/* освобождение ресурсов */
framegen64_destroy(fg);
// Завершение работы и освобождение ресурсов
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;
}