Доработка синхронизации
This commit is contained in:
538
SDR/reciever.c
538
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 <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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user