diff --git a/SDR/common.c b/SDR/common.c new file mode 100644 index 0000000..bffdfbf --- /dev/null +++ b/SDR/common.c @@ -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"); + } +} \ No newline at end of file diff --git a/SDR/common.h b/SDR/common.h new file mode 100644 index 0000000..2c968b9 --- /dev/null +++ b/SDR/common.h @@ -0,0 +1,151 @@ +/** + * common.h — Общие определения и конфигурация для приёмника и передатчика + * OFDM + RS(255,223) FEC + PlutoSDR + * + * Включает: + * - Параметры OFDM-модуляции/демодуляции + * - Параметры помехоустойчивого кодирования Рида-Соломона + * - Настройки радиотракта по умолчанию + * - Общие макросы и прототипы функций + */ + +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ============================================================ +// ПАРАМЕТРЫ OFDM-МОДУЛЯЦИИ (ОБЩИЕ ДЛЯ ПРИЁМНИКА И ПЕРЕДАТЧИКА) +// ============================================================ +#define OFDM_M 64 // Количество поднесущих +#define OFDM_CP 16 // Длина циклического префикса +#define OFDM_TAPER 4 // Длина сглаживающего окна + +// ============================================================ +// ПАРАМЕТРЫ ПОМЕХОУСТОЙЧИВОГО КОДИРОВАНИЯ РИДА-СОЛОМОНА +// ============================================================ +#define RS_DATA 223 // Размер блока данных (байт) +#define RS_ENC 255 // Размер кодированного блока (байт) + +// ============================================================ +// ПАРАМЕТРЫ РАДИОТРАКТА ПО УМОЛЧАНИЮ +// ============================================================ +#define DEFAULT_FREQ 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 \ No newline at end of file diff --git a/SDR/reciever.c b/SDR/reciever.c index a11b07a..0825424 100644 --- a/SDR/reciever.c +++ b/SDR/reciever.c @@ -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 @@ -32,286 +23,389 @@ #include #include -/* ============================================================ - * Значения по умолчанию - * ============================================================ */ +// Параметры приёма по умолчанию +#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); diff --git a/SDR/transmitter.c b/SDR/transmitter.c index e407d78..200958c 100644 --- a/SDR/transmitter.c +++ b/SDR/transmitter.c @@ -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 @@ -32,282 +18,182 @@ #include #include -/* ============================================================ - * Значения по умолчанию - * ============================================================ */ +// Параметры передачи по умолчанию +#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; } \ No newline at end of file