diff --git a/SDR/reciever.c b/SDR/reciever.c index 8830935..a11b07a 100644 --- a/SDR/reciever.c +++ b/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 #include #include -#include -#include #include +#include #include +#include +#include +#include #include +#include -#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; } \ No newline at end of file diff --git a/SDR/transmitter.c b/SDR/transmitter.c index 5eb0dd4..e407d78 100644 --- a/SDR/transmitter.c +++ b/SDR/transmitter.c @@ -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 #include #include -#include -#include #include +#include #include +#include +#include +#include #include +#include -#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; } \ No newline at end of file