diff --git a/SDR/reciever.c b/SDR/reciever.c new file mode 100644 index 0000000..8830935 --- /dev/null +++ b/SDR/reciever.c @@ -0,0 +1,215 @@ +// qpsk_rx_constellation.c +#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; } + +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; +} \ No newline at end of file diff --git a/SDR/transmitter.c b/SDR/transmitter.c new file mode 100644 index 0000000..5eb0dd4 --- /dev/null +++ b/SDR/transmitter.c @@ -0,0 +1,122 @@ +// qpsk_tx.c +#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 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 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; +} + +int main() { + signal(SIGINT, sigint_handler); + + 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"); + + 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); + } + } + } + + // ОДИН 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"); + + // Просто ждём — не делаем новых push! + while (!stop) { + sleep(1); + } + + printf("\n[TX] Стоп.\n"); + iio_buffer_destroy(txbuf); + iio_context_destroy(ctx); + return 0; +} \ No newline at end of file