Работающая версия с PLuto + liquidDSP
This commit is contained in:
454
SDR/reciever.c
454
SDR/reciever.c
@@ -1,215 +1,319 @@
|
||||
// qpsk_rx_constellation.c
|
||||
/**
|
||||
* rx.c — QPSK приёмник для PlutoSDR (production)
|
||||
*
|
||||
* Использует framesync64 из liquid-dsp для декодирования кадров.
|
||||
* Архитектура: Pluto ADC → int16 I/Q → float complex → framesync64 → callback
|
||||
*
|
||||
* Зависимости:
|
||||
* - 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 # справка
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <math.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>
|
||||
|
||||
#define RX_BUF_SIZE 32768
|
||||
#define SAMP_RATE 3840000.0
|
||||
#define CENTER_FREQ 1300000000.0
|
||||
#define SPS 8
|
||||
#define PLOT_SIZE 40
|
||||
#define ACCUM_COUNT 4000
|
||||
#define DC_ALPHA 0.001
|
||||
/* ============================================================
|
||||
* Значения по умолчанию
|
||||
* ============================================================ */
|
||||
|
||||
volatile int stop = 0;
|
||||
void sigint_handler(int s) { stop = 1; }
|
||||
#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"
|
||||
|
||||
unsigned int qpsk_demod(float i, float q) {
|
||||
if (i > 0 && q > 0) return 0;
|
||||
if (i < 0 && q > 0) return 1;
|
||||
if (i < 0 && q < 0) return 2;
|
||||
return 3;
|
||||
|
||||
/* ============================================================
|
||||
* Глобальные переменные
|
||||
* ============================================================ */
|
||||
|
||||
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; /* время последнего успешного пакета */
|
||||
|
||||
/* ============================================================
|
||||
* Обработчик сигналов
|
||||
* ============================================================ */
|
||||
|
||||
/**
|
||||
* sigint_handler — обработчик Ctrl+C
|
||||
*/
|
||||
static void sigint_handler(int sig) {
|
||||
(void)sig;
|
||||
stop = 1;
|
||||
}
|
||||
|
||||
int main() {
|
||||
signal(SIGINT, sigint_handler);
|
||||
/* ============================================================
|
||||
* Callback framesync64
|
||||
* ============================================================ */
|
||||
|
||||
/**
|
||||
* fs_callback — вызывается framesync64 при обнаружении кадра
|
||||
*
|
||||
* Параметры:
|
||||
* _header — заголовок кадра (8 байт)
|
||||
* _header_valid — флаг валидности заголовка
|
||||
* _payload — данные полезной нагрузки (64 байта)
|
||||
* _payload_len — длина payload
|
||||
* _payload_valid — флаг валидности данных (CRC прошёл)
|
||||
* _stats — статистика кадра (RSSI, CFO, etc.)
|
||||
* _userdata — пользовательские данные (не используется)
|
||||
*
|
||||
* Возвращает 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;
|
||||
|
||||
total_packets++;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Вывод справки
|
||||
* ============================================================ */
|
||||
|
||||
/**
|
||||
* 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(" -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;
|
||||
|
||||
/* ---- парсинг аргументов ---- */
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "f:r:b:u:dh")) != -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;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- инициализация ---- */
|
||||
signal(SIGINT, sigint_handler); /* Ctrl+C → sigint_handler */
|
||||
|
||||
/* ---- заголовок программы ---- */
|
||||
printf("══════════════════════════════════════════\n");
|
||||
printf(" QPSK RX + Costas loop\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");
|
||||
|
||||
struct iio_context *ctx = iio_create_context_from_uri("ip:192.168.2.1");
|
||||
/* ============================================================
|
||||
* Инициализация PlutoSDR
|
||||
* ============================================================ */
|
||||
|
||||
/* создаём контекст IIO */
|
||||
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;
|
||||
}
|
||||
|
||||
/* находим устройства */
|
||||
struct iio_device *phy = iio_context_find_device(ctx, "ad9361-phy");
|
||||
struct iio_device *rx_dma = 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);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct iio_channel *rx_lo = iio_device_find_channel(phy, "altvoltage0", true);
|
||||
/* каналы 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(rx_lo, "frequency", (long long)CENTER_FREQ);
|
||||
iio_channel_attr_write_longlong(rx_phy, "sampling_frequency", (long long)SAMP_RATE);
|
||||
iio_channel_attr_write_longlong(rx_phy, "rf_bandwidth", (long long)(SAMP_RATE * 1.5));
|
||||
|
||||
/* настройка частоты несущей */
|
||||
if (iio_channel_attr_write_longlong(rx_lo, "frequency", center_freq) < 0) {
|
||||
fprintf(stderr, "WARNING: Cannot set RX LO frequency\n");
|
||||
}
|
||||
|
||||
/* настройка частоты дискретизации */
|
||||
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(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");
|
||||
|
||||
struct iio_channel *rx_i = iio_device_find_channel(rx_dma, "voltage0", false);
|
||||
struct iio_channel *rx_q = iio_device_find_channel(rx_dma, "voltage1", false);
|
||||
/* включение 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);
|
||||
|
||||
struct iio_buffer *rxbuf = iio_device_create_buffer(rx_dma, RX_BUF_SIZE, false);
|
||||
if (!rxbuf) { fprintf(stderr, "ОШИБКА: буфер RX\n"); return 1; }
|
||||
|
||||
// Costas loop
|
||||
float costas_phase = 0.0;
|
||||
float costas_freq = 0.0;
|
||||
float costas_alpha = 0.05;
|
||||
float costas_beta = 0.001;
|
||||
|
||||
double dc_i = 0.0, dc_q = 0.0;
|
||||
|
||||
// Поиск timing offset
|
||||
double best_energy[8] = {0};
|
||||
int best_offset = SPS/2;
|
||||
|
||||
printf("[RX] Поиск timing offset...\n");
|
||||
size_t search_samples = 0;
|
||||
while (!stop && search_samples < 50000) {
|
||||
ssize_t n = iio_buffer_refill(rxbuf);
|
||||
if (n < 0) continue;
|
||||
int16_t *buf = (int16_t *)iio_buffer_start(rxbuf);
|
||||
size_t samples = (iio_buffer_end(rxbuf) - iio_buffer_start(rxbuf)) / sizeof(int16_t) / 2;
|
||||
for (size_t i = 0; i < samples; i++) {
|
||||
double i_val = (double)buf[i * 2] / 2048.0;
|
||||
double q_val = (double)buf[i * 2 + 1] / 2048.0;
|
||||
dc_i = dc_i * 0.999 + i_val * 0.001;
|
||||
dc_q = dc_q * 0.999 + q_val * 0.001;
|
||||
best_energy[i % SPS] += (i_val - dc_i) * (i_val - dc_i) + (q_val - dc_q) * (q_val - dc_q);
|
||||
search_samples++;
|
||||
}
|
||||
}
|
||||
|
||||
double max_e = 0;
|
||||
for (int p = 0; p < SPS; p++)
|
||||
if (best_energy[p] > max_e) { max_e = best_energy[p]; best_offset = p; }
|
||||
printf("[RX] Timing offset: %d\n\n", best_offset);
|
||||
|
||||
// Накопление
|
||||
double *i_acc = malloc(sizeof(double) * ACCUM_COUNT);
|
||||
double *q_acc = malloc(sizeof(double) * ACCUM_COUNT);
|
||||
size_t acc_idx = 0;
|
||||
dc_i = dc_q = 0.0;
|
||||
|
||||
printf("[RX] Накопление с Costas loop...\n");
|
||||
|
||||
while (!stop && acc_idx < ACCUM_COUNT) {
|
||||
ssize_t n = iio_buffer_refill(rxbuf);
|
||||
if (n < 0) { usleep(1000); continue; }
|
||||
|
||||
int16_t *buf = (int16_t *)iio_buffer_start(rxbuf);
|
||||
size_t samples = (iio_buffer_end(rxbuf) - iio_buffer_start(rxbuf)) / sizeof(int16_t) / 2;
|
||||
|
||||
for (size_t i = best_offset; i < samples && acc_idx < ACCUM_COUNT; i += SPS) {
|
||||
double i_val = (double)buf[i * 2] / 2048.0;
|
||||
double q_val = (double)buf[i * 2 + 1] / 2048.0;
|
||||
|
||||
dc_i = dc_i * (1.0 - DC_ALPHA) + i_val * DC_ALPHA;
|
||||
dc_q = dc_q * (1.0 - DC_ALPHA) + q_val * DC_ALPHA;
|
||||
i_val -= dc_i;
|
||||
q_val -= dc_q;
|
||||
|
||||
// Costas: вращение
|
||||
float cos_p = cosf(costas_phase);
|
||||
float sin_p = sinf(costas_phase);
|
||||
float i_rot = i_val * cos_p + q_val * sin_p;
|
||||
float q_rot = -i_val * sin_p + q_val * cos_p;
|
||||
|
||||
// Costas phase detector
|
||||
float phase_error = (i_rot > 0 ? q_rot : -q_rot) - (q_rot > 0 ? i_rot : -i_rot);
|
||||
costas_freq += costas_beta * phase_error;
|
||||
costas_phase += costas_freq + costas_alpha * phase_error;
|
||||
while (costas_phase > M_PI) costas_phase -= 2*M_PI;
|
||||
while (costas_phase < -M_PI) costas_phase += 2*M_PI;
|
||||
|
||||
i_acc[acc_idx] = i_rot;
|
||||
q_acc[acc_idx] = q_rot;
|
||||
acc_idx++;
|
||||
}
|
||||
|
||||
if (acc_idx % 200 == 0) {
|
||||
printf("\r Накоплено: %zu | фаза: %+.2f", acc_idx, costas_phase);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n\n[RX] Готово. Фаза: %.2f рад\n", costas_phase);
|
||||
|
||||
// Проверка сигнала
|
||||
double avg_power = 0.0;
|
||||
for (size_t i = 0; i < acc_idx; i++)
|
||||
avg_power += i_acc[i] * i_acc[i] + q_acc[i] * q_acc[i];
|
||||
avg_power /= acc_idx;
|
||||
double avg_power_db = 10.0 * log10(avg_power + 1e-20);
|
||||
printf(" Средняя мощность: %.1f dB\n", avg_power_db);
|
||||
|
||||
if (avg_power_db < -20.0) {
|
||||
printf("\n >>> СИГНАЛ ОТСУТСТВУЕТ! <<<\n");
|
||||
free(i_acc); free(q_acc);
|
||||
iio_buffer_destroy(rxbuf);
|
||||
/* создание DMA буфера */
|
||||
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);
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Созвездие
|
||||
double min_i = 999, max_i = -999, min_q = 999, max_q = -999;
|
||||
for (size_t i = 0; i < acc_idx; i++) {
|
||||
if (i_acc[i] < min_i) min_i = i_acc[i];
|
||||
if (i_acc[i] > max_i) max_i = i_acc[i];
|
||||
if (q_acc[i] < min_q) min_q = q_acc[i];
|
||||
if (q_acc[i] > max_q) max_q = q_acc[i];
|
||||
}
|
||||
/* ============================================================
|
||||
* Инициализация liquid-dsp
|
||||
* ============================================================ */
|
||||
|
||||
double center_i = (max_i + min_i) / 2.0;
|
||||
double center_q = (max_q + min_q) / 2.0;
|
||||
double range = fmax(max_i - min_i, max_q - min_q) * 0.55;
|
||||
/**
|
||||
* framesync64 — детектор/декодер кадров фиксированной длины
|
||||
*
|
||||
* Что он делает:
|
||||
* 1. Ищет заголовок (header) во входном потоке
|
||||
* 2. Синхронизируется по времени и частоте
|
||||
* 3. Демодулирует QPSK
|
||||
* 4. Проверяет CRC-32
|
||||
* 5. Вызывает callback с результатом
|
||||
*
|
||||
* На передающей стороне используется framegen64
|
||||
*/
|
||||
framesync64 fs = framesync64_create(fs_callback, NULL);
|
||||
|
||||
printf(" I: [%.2f, %.2f] Q: [%.2f, %.2f]\n", min_i, max_i, min_q, max_q);
|
||||
printf("[RX] Listening... (Ctrl+C to stop)\n\n");
|
||||
|
||||
int density[PLOT_SIZE][PLOT_SIZE];
|
||||
memset(density, 0, sizeof(density));
|
||||
for (size_t i = 0; i < acc_idx; i++) {
|
||||
int x = (int)((i_acc[i] - center_i + range) / (2.0 * range) * (PLOT_SIZE - 1) + 0.5);
|
||||
int y = (int)((q_acc[i] - center_q + range) / (2.0 * range) * (PLOT_SIZE - 1) + 0.5);
|
||||
if (x >= 0 && x < PLOT_SIZE && y >= 0 && y < PLOT_SIZE) density[y][x]++;
|
||||
}
|
||||
time_t last_report = time(NULL);
|
||||
|
||||
int max_density = 1;
|
||||
for (int y = 0; y < PLOT_SIZE; y++)
|
||||
for (int x = 0; x < PLOT_SIZE; x++)
|
||||
if (density[y][x] > max_density) max_density = density[y][x];
|
||||
/* ============================================================
|
||||
* Основной цикл приёма
|
||||
* ============================================================ */
|
||||
|
||||
const char *shade = " .:-=+*#@";
|
||||
|
||||
printf("\n Q\n ^\n");
|
||||
for (int y = PLOT_SIZE - 1; y >= 0; y--) {
|
||||
printf("%s", (y == PLOT_SIZE/2) ? "0 -" : " |");
|
||||
for (int x = 0; x < PLOT_SIZE; x++) {
|
||||
int level = density[y][x] * 8 / max_density;
|
||||
printf("%c", shade[level]);
|
||||
while (!stop) {
|
||||
/* ожидание данных от Pluto */
|
||||
ssize_t n = iio_buffer_refill(buf);
|
||||
if (n < 0) {
|
||||
usleep(500); /* небольшая пауза при ошибке */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* указатель на данные */
|
||||
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];
|
||||
for (int i = 0; i < ns; i++) {
|
||||
frame[i] = ((float)bb[2 * i] / 32767.0f) +
|
||||
((float)bb[2 * i + 1] / 32767.0f) * _Complex_I;
|
||||
}
|
||||
|
||||
/* передача всего буфера в framesync64 */
|
||||
framesync64_execute(fs, frame, ns);
|
||||
|
||||
/* вывод статистики (раз в секунду) */
|
||||
time_t now = time(NULL);
|
||||
if (now > last_report) {
|
||||
double pct = total_packets > 0 ?
|
||||
100.0 * packets_ok / total_packets : 0.0;
|
||||
|
||||
/* время с последнего пакета */
|
||||
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;
|
||||
}
|
||||
printf("%s\n", (y == PLOT_SIZE/2) ? "-> I" : "");
|
||||
}
|
||||
|
||||
int quad[4] = {0};
|
||||
for (size_t i = 0; i < acc_idx; i++) {
|
||||
double i_n = i_acc[i] - center_i;
|
||||
double q_n = q_acc[i] - center_q;
|
||||
if (i_n > 0 && q_n > 0) quad[0]++;
|
||||
if (i_n < 0 && q_n > 0) quad[1]++;
|
||||
if (i_n < 0 && q_n < 0) quad[2]++;
|
||||
if (i_n > 0 && q_n < 0) quad[3]++;
|
||||
}
|
||||
/* ============================================================
|
||||
* Завершение
|
||||
* ============================================================ */
|
||||
|
||||
printf("\n Квадранты: Q1=%.0f%% Q2=%.0f%% Q3=%.0f%% Q4=%.0f%%\n",
|
||||
100.0*quad[0]/acc_idx, 100.0*quad[1]/acc_idx,
|
||||
100.0*quad[2]/acc_idx, 100.0*quad[3]/acc_idx);
|
||||
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);
|
||||
|
||||
printf("\n[RX] Готово.\n");
|
||||
free(i_acc); free(q_acc);
|
||||
iio_buffer_destroy(rxbuf);
|
||||
/* освобождение ресурсов */
|
||||
framesync64_destroy(fs);
|
||||
iio_buffer_destroy(buf);
|
||||
iio_context_destroy(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,122 +1,313 @@
|
||||
// qpsk_tx.c
|
||||
/**
|
||||
* tx.c — QPSK передатчик для PlutoSDR (production)
|
||||
*
|
||||
* Использует 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 # справка
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <math.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>
|
||||
|
||||
#define SPS 8
|
||||
#define SYM_BUF_SIZE 1024
|
||||
#define BUF_SIZE (SYM_BUF_SIZE * SPS)
|
||||
#define SAMP_RATE 3840000.0
|
||||
#define CENTER_FREQ 1300000000.0
|
||||
#define TX_AMPLITUDE 6000.0
|
||||
/* ============================================================
|
||||
* Значения по умолчанию
|
||||
* ============================================================ */
|
||||
|
||||
static const float rrc_pulse[] = {
|
||||
-0.0012, -0.0018, -0.0007, 0.0032, 0.0097, 0.0175, 0.0247, 0.0293,
|
||||
0.0293, 0.0247, 0.0175, 0.0097, 0.0032, -0.0007, -0.0018, -0.0012
|
||||
#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"
|
||||
|
||||
/* ============================================================
|
||||
* Заголовок framegen64 (8 байт)
|
||||
* Должен совпадать на приёмной стороне
|
||||
* ============================================================ */
|
||||
|
||||
static uint8_t header[] = {
|
||||
0xf0, 0xaa, 0x24, 0xdb,
|
||||
0x40, 0x01, 0x55, 0x0f
|
||||
};
|
||||
#define RRC_LEN 16
|
||||
|
||||
volatile int stop = 0;
|
||||
void sigint_handler(int s) { stop = 1; }
|
||||
/* ============================================================
|
||||
* Глобальные переменные
|
||||
* ============================================================ */
|
||||
|
||||
void qpsk_map(unsigned int bits, float *i, float *q) {
|
||||
static const float map[4][2] = {
|
||||
{ 1.0, 1.0}, {-1.0, 1.0}, {-1.0, -1.0}, { 1.0, -1.0},
|
||||
};
|
||||
*i = map[bits & 3][0];
|
||||
*q = map[bits & 3][1];
|
||||
static volatile int stop = 0; /* флаг остановки (SIGINT) */
|
||||
static uint64_t total_frames = 0; /* всего отправлено кадров */
|
||||
static uint64_t total_bytes = 0; /* всего отправлено байт */
|
||||
|
||||
/* ============================================================
|
||||
* Обработчик сигналов
|
||||
* ============================================================ */
|
||||
|
||||
/**
|
||||
* sigint_handler — обработчик Ctrl+C
|
||||
* Устанавливает флаг stop, программа завершается на следующей итерации
|
||||
*/
|
||||
static void sigint_handler(int sig) {
|
||||
(void)sig;
|
||||
stop = 1;
|
||||
}
|
||||
|
||||
static uint32_t lfsr = 0xACE1u;
|
||||
unsigned int lfsr_bits(int n) {
|
||||
unsigned int val = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
unsigned int bit = (lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5);
|
||||
lfsr = (lfsr >> 1) | ((bit & 1u) << 15);
|
||||
val = (val << 1) | (bit & 1u);
|
||||
}
|
||||
return val;
|
||||
/* ============================================================
|
||||
* Вывод справки
|
||||
* ============================================================ */
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
int main() {
|
||||
signal(SIGINT, sigint_handler);
|
||||
/* ============================================================
|
||||
* MAIN
|
||||
* ============================================================ */
|
||||
|
||||
printf("══════════════════════════════════════════\n");
|
||||
printf(" QPSK TX (непрерывный циклический)\n");
|
||||
printf(" Fs=%.2f MSPS SPS=%d Rs=%d ksym/s\n",
|
||||
SAMP_RATE/1e6, SPS, (int)SAMP_RATE/SPS/1000);
|
||||
printf("══════════════════════════════════════════\n");
|
||||
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;
|
||||
|
||||
struct iio_context *ctx = iio_create_context_from_uri("ip:192.168.2.1");
|
||||
struct iio_device *phy = iio_context_find_device(ctx, "ad9361-phy");
|
||||
struct iio_device *tx_dma = iio_context_find_device(ctx, "cf-ad9361-dds-core-lpc");
|
||||
|
||||
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(tx_lo, "frequency", (long long)CENTER_FREQ);
|
||||
iio_channel_attr_write_longlong(tx_phy, "sampling_frequency", (long long)SAMP_RATE);
|
||||
iio_channel_attr_write_longlong(tx_phy, "rf_bandwidth", (long long)(SAMP_RATE * 1.5));
|
||||
iio_channel_attr_write(tx_phy, "rf_port_select", "A");
|
||||
iio_channel_attr_write_double(tx_phy, "hardwaregain", -5.0);
|
||||
printf("[OK] PHY: LO=%.0f MHz Gain=-5dB\n", CENTER_FREQ/1e6);
|
||||
|
||||
struct iio_channel *tx_i = iio_device_find_channel(tx_dma, "voltage0", true);
|
||||
struct iio_channel *tx_q = iio_device_find_channel(tx_dma, "voltage1", true);
|
||||
iio_channel_enable(tx_i);
|
||||
iio_channel_enable(tx_q);
|
||||
|
||||
// Циклический буфер — заполняется ОДИН раз
|
||||
struct iio_buffer *txbuf = iio_device_create_buffer(tx_dma, BUF_SIZE, true);
|
||||
ptrdiff_t buf_bytes = iio_buffer_end(txbuf) - iio_buffer_start(txbuf);
|
||||
size_t buf_samples = buf_bytes / sizeof(int16_t) / 2;
|
||||
size_t num_symbols = buf_samples / SPS;
|
||||
printf("[OK] Буфер: %zu I/Q пар = %zu символов\n", buf_samples, num_symbols);
|
||||
|
||||
int16_t *buf = (int16_t *)iio_buffer_start(txbuf);
|
||||
|
||||
// Генерация символов (все случайные — непрерывный поток)
|
||||
float sym_i[num_symbols];
|
||||
float sym_q[num_symbols];
|
||||
|
||||
printf("[TX] Генерация %zu символов...\n", num_symbols);
|
||||
for (size_t s = 0; s < num_symbols; s++) {
|
||||
unsigned int bits = lfsr_bits(2);
|
||||
qpsk_map(bits, &sym_i[s], &sym_q[s]);
|
||||
}
|
||||
|
||||
// Применение RRC
|
||||
memset(buf, 0, buf_bytes);
|
||||
for (size_t s = 0; s < num_symbols; s++) {
|
||||
for (int tap = 0; tap < RRC_LEN; tap++) {
|
||||
int sym_idx = (int)s + tap - RRC_LEN/2;
|
||||
while (sym_idx < 0) sym_idx += num_symbols;
|
||||
sym_idx %= num_symbols;
|
||||
if (tap < SPS) {
|
||||
size_t sample_idx = (s * SPS + tap) % buf_samples;
|
||||
buf[sample_idx * 2] += (int16_t)(sym_i[sym_idx] * rrc_pulse[tap] * TX_AMPLITUDE * 0.5);
|
||||
buf[sample_idx * 2 + 1] += (int16_t)(sym_q[sym_idx] * rrc_pulse[tap] * TX_AMPLITUDE * 0.5);
|
||||
}
|
||||
/* ---- парсинг аргументов ---- */
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "f:r:g:a:p:m:M:u: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;
|
||||
}
|
||||
}
|
||||
|
||||
// ОДИН push — циклический DMA сам крутит буфер
|
||||
size_t pushed = iio_buffer_push(txbuf);
|
||||
printf("[OK] Push: %zu. Циклический DMA запущен!\n", pushed);
|
||||
printf("[TX] ПЕРЕДАЧА ИДЁТ. Частота: %.1f МГц\n", CENTER_FREQ/1e6);
|
||||
printf("[TX] Ctrl+C для остановки\n");
|
||||
/* ---- валидация параметров ---- */
|
||||
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;
|
||||
|
||||
// Просто ждём — не делаем новых push!
|
||||
while (!stop) {
|
||||
sleep(1);
|
||||
/* ---- инициализация ---- */
|
||||
signal(SIGINT, sigint_handler); /* Ctrl+C → sigint_handler */
|
||||
srand(time(NULL)); /* случайный seed */
|
||||
|
||||
/* ---- заголовок программы ---- */
|
||||
printf("══════════════════════════════════════════\n");
|
||||
printf(" QPSK TRANSMITTER (framegen64)\n");
|
||||
printf("══════════════════════════════════════════\n");
|
||||
printf(" Frequency: %.3f MHz\n", center_freq / 1e6);
|
||||
printf(" Sample rate: %.3f MSPS\n", sample_rate / 1e6);
|
||||
printf(" TX gain: %.0f dB\n", tx_gain);
|
||||
printf(" Amplitude: %.2f\n", amplitude);
|
||||
printf(" Payload: %d-%d bytes\n", min_payload, max_payload);
|
||||
printf(" Frame pause: %d us\n", frame_pause_us);
|
||||
printf(" URI: %s\n", uri);
|
||||
printf("══════════════════════════════════════════\n");
|
||||
|
||||
/* ============================================================
|
||||
* Инициализация PlutoSDR
|
||||
* ============================================================ */
|
||||
|
||||
/* создаём контекст IIO */
|
||||
struct iio_context *ctx = iio_create_context_from_uri(uri);
|
||||
if (!ctx) {
|
||||
fprintf(stderr, "ERROR: Cannot connect to %s\n", uri);
|
||||
fprintf(stderr, " Check USB cable and try 'iio_info -s'\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("\n[TX] Стоп.\n");
|
||||
iio_buffer_destroy(txbuf);
|
||||
/* находим устройства */
|
||||
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;
|
||||
}
|
||||
|
||||
/* каналы 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);
|
||||
|
||||
/* настройка частоты несущей */
|
||||
if (iio_channel_attr_write_longlong(tx_lo, "frequency", center_freq) < 0) {
|
||||
fprintf(stderr, "WARNING: Cannot set TX LO frequency\n");
|
||||
}
|
||||
|
||||
/* настройка частоты дискретизации */
|
||||
if (iio_channel_attr_write_longlong(tx_phy, "sampling_frequency", sample_rate) < 0) {
|
||||
fprintf(stderr, "WARNING: Cannot set sample rate\n");
|
||||
}
|
||||
|
||||
/* настройка полосы, порта, усиления */
|
||||
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);
|
||||
|
||||
/* включение 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);
|
||||
|
||||
/* создание 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);
|
||||
|
||||
/* ============================================================
|
||||
* Основной цикл передачи
|
||||
* ============================================================ */
|
||||
|
||||
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++;
|
||||
}
|
||||
|
||||
/* отправка буфера в Pluto */
|
||||
iio_buffer_push(buf);
|
||||
|
||||
/* статистика */
|
||||
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;
|
||||
}
|
||||
|
||||
/* пауза между кадрами (чтобы не забивать буфер Pluto) */
|
||||
if (frame_pause_us > 0)
|
||||
usleep(frame_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);
|
||||
iio_buffer_destroy(buf);
|
||||
iio_context_destroy(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user