215 lines
7.9 KiB
C
215 lines
7.9 KiB
C
// qpsk_rx_constellation.c
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <stdint.h>
|
||
#include <unistd.h>
|
||
#include <math.h>
|
||
#include <string.h>
|
||
#include <signal.h>
|
||
#include <iio.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; }
|
||
|
||
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;
|
||
}
|
||
|
||
int main() {
|
||
signal(SIGINT, sigint_handler);
|
||
|
||
printf("══════════════════════════════════════════\n");
|
||
printf(" QPSK RX + Costas loop\n");
|
||
printf("══════════════════════════════════════════\n");
|
||
|
||
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 *rx_dma = iio_context_find_device(ctx, "cf-ad9361-lpc");
|
||
|
||
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));
|
||
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);
|
||
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);
|
||
iio_context_destroy(ctx);
|
||
return 0;
|
||
}
|
||
|
||
// Созвездие
|
||
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];
|
||
}
|
||
|
||
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;
|
||
|
||
printf(" I: [%.2f, %.2f] Q: [%.2f, %.2f]\n", min_i, max_i, min_q, max_q);
|
||
|
||
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]++;
|
||
}
|
||
|
||
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]);
|
||
}
|
||
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[RX] Готово.\n");
|
||
free(i_acc); free(q_acc);
|
||
iio_buffer_destroy(rxbuf);
|
||
iio_context_destroy(ctx);
|
||
return 0;
|
||
} |