206 lines
7.7 KiB
C
206 lines
7.7 KiB
C
/**
|
||
* receiver.c — OFDM-приёмник с RS(255,223) FEC и частотной коррекцией
|
||
*/
|
||
|
||
#include "common.h"
|
||
|
||
#define BUF_SAMPLES 32768
|
||
#define MAX_FRAME_SAMPLES (1 << 16)
|
||
#define RX_GAIN 50.0
|
||
#define CFO_FILTER_ALPHA 0.95f
|
||
#define CFO_MAX_VALID 0.5f
|
||
|
||
extern volatile sig_atomic_t stop;
|
||
|
||
static uint64_t calls = 0, hdr_ok = 0, crc_raw = 0;
|
||
static uint64_t rs_saved = 0, crc_final = 0, total_bytes = 0;
|
||
static float rssi = -100, evm = 0;
|
||
static float cfo_est = 0.0f;
|
||
static nco_crcf nco = NULL;
|
||
static fec decoder = NULL;
|
||
static int fec_on = 0;
|
||
static liquid_float_complex frame[MAX_FRAME_SAMPLES];
|
||
|
||
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++;
|
||
rssi = stats.rssi;
|
||
evm = stats.evm;
|
||
|
||
// Обновляем CFO только если NCO инициализирован
|
||
if (nco && fabsf(stats.cfo) < CFO_MAX_VALID) {
|
||
cfo_est = CFO_FILTER_ALPHA * cfo_est + (1.0f - CFO_FILTER_ALPHA) * stats.cfo;
|
||
nco_crcf_set_frequency(nco, cfo_est);
|
||
}
|
||
|
||
if (!hdr_valid) return 0;
|
||
hdr_ok++;
|
||
if (pay_valid) crc_raw++;
|
||
|
||
uint16_t original_len = (hdr[6] << 8) | hdr[7];
|
||
uint8_t last_block_bytes = hdr[10];
|
||
|
||
if (fec_on && decoder && pay_len >= RS_ENC) {
|
||
size_t nblocks = pay_len / RS_ENC;
|
||
if (nblocks == 0 || nblocks > 100) return 0;
|
||
|
||
uint8_t recovered[4096];
|
||
int rs_ok = 1;
|
||
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) {
|
||
if (!pay_valid) rs_saved++;
|
||
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) {
|
||
fwrite(pay, 1, original_len, stdout);
|
||
fflush(stdout);
|
||
total_bytes += original_len;
|
||
crc_final++;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
int main(int argc, char *argv[]) {
|
||
long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
|
||
const char *uri = RX_URI_DEFAULT;
|
||
const char *fec_opt = "rs8";
|
||
|
||
int opt;
|
||
while ((opt = getopt(argc, argv, "f:r:b:u:c:h")) != -1) {
|
||
switch (opt) {
|
||
case 'f': freq = atoll(optarg); break;
|
||
case 'r': rate = atoll(optarg); break;
|
||
case 'b': bw = atoll(optarg); break;
|
||
case 'u': uri = optarg; break;
|
||
case 'c': fec_opt = optarg; break;
|
||
case 'h':
|
||
print_usage(argv[0], "ПРИЁМНИК");
|
||
fprintf(stderr, "\nСПЕЦИФИЧНЫЕ ОПЦИИ RX:\n");
|
||
fprintf(stderr, " Усиление фиксированное: %.0f дБ\n", RX_GAIN);
|
||
fprintf(stderr, " Частотная коррекция: адаптивная (α=%.2f)\n", CFO_FILTER_ALPHA);
|
||
fprintf(stderr, "\nОПТИМАЛЬНЫЙ ЗАПУСК:\n");
|
||
fprintf(stderr, " %s -f %lld -r %lld -b %lld -c rs8 > output.bin\n",
|
||
argv[0], DEFAULT_FREQ, DEFAULT_RATE, DEFAULT_BW);
|
||
return 0;
|
||
default: return 1;
|
||
}
|
||
}
|
||
|
||
fec_on = !strcmp(fec_opt, "rs8");
|
||
setup_signal_handlers();
|
||
|
||
print_config("ПРИЁМНИК", freq, rate, bw, fec_on);
|
||
fprintf(stderr, "Усиление: %.0f дБ (ручное)\n", RX_GAIN);
|
||
fprintf(stderr, "CFO фильтр: α=%.2f\n", CFO_FILTER_ALPHA);
|
||
fprintf(stderr, "URI: %s\n", uri);
|
||
fprintf(stderr, "═══════════════════════════════════════════════════\n");
|
||
|
||
struct iio_context *ctx = NULL;
|
||
struct iio_device *phy = NULL, *rx = NULL;
|
||
if (pluto_init(uri, &ctx, &phy, &rx, 0) != 0) return 1;
|
||
|
||
pluto_configure(phy, freq, rate, bw, RX_GAIN, 0);
|
||
|
||
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, "Ошибка создания буфера приёма\n");
|
||
iio_context_destroy(ctx);
|
||
return 1;
|
||
}
|
||
|
||
// Инициализация FEC
|
||
if (fec_on) {
|
||
decoder = fec_create(LIQUID_FEC_RS_M8, NULL);
|
||
if (!decoder) {
|
||
fprintf(stderr, "Ошибка создания FEC-декодера\n");
|
||
}
|
||
}
|
||
|
||
// Инициализация NCO для частотной коррекции
|
||
nco = nco_crcf_create(LIQUID_NCO);
|
||
if (!nco) {
|
||
fprintf(stderr, "Ошибка создания NCO\n");
|
||
} else {
|
||
nco_crcf_set_frequency(nco, 0.0f);
|
||
}
|
||
|
||
ofdmflexframesync fs = ofdmflexframesync_create(
|
||
OFDM_M, OFDM_CP, OFDM_TAPER, NULL, callback, NULL);
|
||
if (!fs) {
|
||
fprintf(stderr, "Ошибка создания OFDM-синхронизатора\n");
|
||
if (decoder) fec_destroy(decoder);
|
||
if (nco) nco_crcf_destroy(nco);
|
||
iio_buffer_destroy(buf);
|
||
iio_context_destroy(ctx);
|
||
return 1;
|
||
}
|
||
|
||
fprintf(stderr, "Приёмник готов. Ожидание сигнала...\n\n");
|
||
fprintf(stderr, "Кадры | Загол. | CRC | RS испр | Вывод | RSSI | EVM | CFO\n");
|
||
fprintf(stderr, "--------+--------+------+---------+--------+-------+-------+----------\n");
|
||
|
||
time_t last = time(NULL);
|
||
|
||
while (!stop) {
|
||
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;
|
||
if (ns > MAX_FRAME_SAMPLES) ns = MAX_FRAME_SAMPLES;
|
||
|
||
for (int i = 0; i < ns; i++) {
|
||
frame[i] = ((float)bb[2*i] / 2048.0f) +
|
||
((float)bb[2*i+1] / 2048.0f) * _Complex_I;
|
||
if (nco) {
|
||
nco_crcf_mix_down(nco, frame[i], &frame[i]);
|
||
nco_crcf_step(nco);
|
||
}
|
||
}
|
||
if (nco) nco_crcf_set_phase(nco, 0.0f);
|
||
|
||
ofdmflexframesync_execute(fs, frame, ns);
|
||
|
||
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, (unsigned long long)rs_saved,
|
||
(unsigned long long)crc_final, rssi, evm, cfo_est);
|
||
fflush(stderr);
|
||
last = time(NULL);
|
||
}
|
||
}
|
||
|
||
fprintf(stderr, "\n\nПРИЁМ ЗАВЕРШЁН\n");
|
||
fprintf(stderr, "Кадров: %llu | Заголовков: %llu | CRC: %llu | RS испр: %llu | Вывод: %llu | Байт: %llu\n",
|
||
(unsigned long long)calls, (unsigned long long)hdr_ok,
|
||
(unsigned long long)crc_raw, (unsigned long long)rs_saved,
|
||
(unsigned long long)crc_final, (unsigned long long)total_bytes);
|
||
|
||
if (decoder) fec_destroy(decoder);
|
||
if (nco) nco_crcf_destroy(nco);
|
||
ofdmflexframesync_destroy(fs);
|
||
iio_buffer_destroy(buf);
|
||
iio_context_destroy(ctx);
|
||
return 0;
|
||
} |