diff --git a/src/common.c b/src/common.c index 54954a7..e91722a 100644 --- a/src/common.c +++ b/src/common.c @@ -109,4 +109,90 @@ int pluto_configure(struct iio_device *phy, long long freq, long long rate, iio_channel_attr_write(ch, "rf_port_select", "A_BALANCED"); } return 0; +} + +int pluto_init_fdd(const char *uri, struct iio_context **ctx, + struct iio_device **phy, struct iio_device **txdev, + struct iio_device **rxdev) { + *ctx = iio_create_context_from_uri(uri); + if (!*ctx) { + LOG_ERROR("Не удалось подключиться к PlutoSDR по адресу %s", uri); + return -1; + } + + *phy = iio_context_find_device(*ctx, "ad9361-phy"); + *txdev = iio_context_find_device(*ctx, "cf-ad9361-dds-core-lpc"); + *rxdev = iio_context_find_device(*ctx, "cf-ad9361-lpc"); + + if (!*phy || !*txdev || !*rxdev) { + LOG_ERROR("Радиоустройства (phy/tx/rx) для FDD не найдены"); + iio_context_destroy(*ctx); + return -1; + } + + return 0; +} + +int pluto_configure_fdd(struct iio_device *phy, long long tx_freq, + long long rx_freq, long long rate, long long bw, + double tx_gain, double rx_gain) { + // Скорость дискретизации — общий bb_rate на весь чип, ставим ОДИН раз + // (грузит FIR, см. pluto_configure). Оба тракта работают на этом rate. + if (ad9361_set_bb_rate(phy, (unsigned long)rate) != 0) { + LOG_ERROR("ad9361_set_bb_rate(%lld) не удалась (нужен FIR/libad9361)", rate); + return -1; + } + + // Два независимых LO: altvoltage1=TX, altvoltage0=RX (оба output=true). + struct iio_channel *lo_tx = iio_device_find_channel(phy, "altvoltage1", true); + struct iio_channel *lo_rx = iio_device_find_channel(phy, "altvoltage0", true); + // Два тракта данных на одном voltage0: направление задаёт признак output. + struct iio_channel *ch_tx = iio_device_find_channel(phy, "voltage0", true); + struct iio_channel *ch_rx = iio_device_find_channel(phy, "voltage0", false); + if (!lo_tx || !lo_rx || !ch_tx || !ch_rx) { + LOG_ERROR("Каналы ad9361-phy для FDD не найдены"); + return -1; + } + + // Обе несущие — критично, при отказе выходим. + if (iio_channel_attr_write_longlong(lo_tx, "frequency", tx_freq) < 0) { + LOG_ERROR("Установка TX LO %lld Гц не удалась", tx_freq); + return -1; + } + if (iio_channel_attr_write_longlong(lo_rx, "frequency", rx_freq) < 0) { + LOG_ERROR("Установка RX LO %lld Гц не удалась", rx_freq); + return -1; + } + + // Полосы обоих трактов — при отказе предупреждаем, но продолжаем. + if (iio_channel_attr_write_longlong(ch_tx, "rf_bandwidth", bw) < 0) + LOG_ERROR("TX rf_bandwidth %lld не принят (продолжаю)", bw); + if (iio_channel_attr_write_longlong(ch_rx, "rf_bandwidth", bw) < 0) + LOG_ERROR("RX rf_bandwidth %lld не принят (продолжаю)", bw); + + // TX-тракт данных + iio_channel_attr_write_double(ch_tx, "hardwaregain", tx_gain); + iio_channel_attr_write(ch_tx, "rf_port_select", "A"); + // RX-тракт данных (ручное усиление — как в симплексе) + iio_channel_attr_write(ch_rx, "gain_control_mode", "manual"); + iio_channel_attr_write_double(ch_rx, "hardwaregain", rx_gain); + iio_channel_attr_write(ch_rx, "rf_port_select", "A_BALANCED"); + + // РИДБЭК = верификация FDD (README §12.2): читаем обе несущие и режим ENSM + // прямо с чипа. Расхождение LO с заданием → LOG_ERROR (но не фатал: цифровой + // BIST/эфир покажут правду). ENSM ≠ fdd → одновременный TX+RX не заработает. + long long tx_rb = -1, rx_rb = -1; + char ensm[32] = {0}; + iio_channel_attr_read_longlong(lo_tx, "frequency", &tx_rb); + iio_channel_attr_read_longlong(lo_rx, "frequency", &rx_rb); + iio_device_attr_read(phy, "ensm_mode", ensm, sizeof(ensm)); + fprintf(stderr, "FDD ридбэк: TX LO %.3f МГц | RX LO %.3f МГц | ENSM=%s\n", + tx_rb / 1e6, rx_rb / 1e6, ensm[0] ? ensm : "?"); + if (tx_rb != tx_freq || rx_rb != rx_freq) + LOG_ERROR("ридбэк LO не совпал: TX %lld≠%lld или RX %lld≠%lld", + tx_rb, tx_freq, rx_rb, rx_freq); + if (ensm[0] && strncmp(ensm, "fdd", 3) != 0) + LOG_ERROR("ENSM=%s (не fdd) — одновременный TX+RX не гарантирован", ensm); + + return 0; } \ No newline at end of file diff --git a/src/common.h b/src/common.h index 4281888..50f033e 100644 --- a/src/common.h +++ b/src/common.h @@ -68,6 +68,20 @@ int pluto_init(const char *uri, struct iio_context **ctx, int pluto_configure(struct iio_device *phy, long long freq, long long rate, long long bw, double gain, int is_tx); +// FDD-варианты (roadmap §10 п.7): открывают ОБА cf-девайса и настраивают ОБА +// LO на одном ad9361-phy. Старые pluto_init/pluto_configure не трогаются — +// симплекс без -F идёт прежним однодевайсным путём. AD9361 аппаратно FDD умеет +// (altvoltage1=TX, altvoltage0=RX; bb_rate общий на чип; ENSM Pluto по умолч. fdd). +int pluto_init_fdd(const char *uri, struct iio_context **ctx, + struct iio_device **phy, struct iio_device **txdev, + struct iio_device **rxdev); +// tx_freq→altvoltage1, rx_freq→altvoltage0; ридбэк обоих LO + ENSM в stderr — +// это и есть верификация (README §12.2). Возврат 0 даже при расхождении ридбэка +// (только LOG_ERROR): фатально лишь недоступность каналов/несущей. +int pluto_configure_fdd(struct iio_device *phy, long long tx_freq, + long long rx_freq, long long rate, long long bw, + double tx_gain, double rx_gain); + // Привязка вызывающего потока к ядру CPU (доктрина README §2: ядро 0 — только // sync, ядро 1 — всё остальное). Ошибка не фатальна — планировщик разведёт сам. void pin_to_cpu(int cpu); diff --git a/src/receiver.c b/src/receiver.c index b8d3cea..c10886f 100644 --- a/src/receiver.c +++ b/src/receiver.c @@ -15,6 +15,7 @@ #define FQ_SLOTS 32 // очередь кадров B→C, ~147 КБ .bss #define FQ_PAY_MAX (18 * RS_ENC) // 4590: ≤18 блоков RS (техдолг #2) #define RX_GAIN 50.0 +#define FB_TX_GAIN -20.0 // Усиление feedback-TX по умолч. (§10 п.7) #define CFO_FILTER_ALPHA 0.95f #define CFO_MAX_VALID 0.5f @@ -420,19 +421,25 @@ 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"; + long long fb_freq = 0; // 0 = симплекс; иначе FDD, LO обратного TX + double fb_gain = FB_TX_GAIN; // усиление feedback-TX (-g) int opt; - while ((opt = getopt(argc, argv, "f:r:b:u:c:h")) != -1) { + while ((opt = getopt(argc, argv, "f:r:b:u:c:F:g: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 'F': fb_freq = atoll(optarg); break; + case 'g': fb_gain = atof(optarg); break; case 'h': print_usage(argv[0], "ПРИЁМНИК"); fprintf(stderr, "\nСПЕЦИФИЧНЫЕ ОПЦИИ RX:\n"); fprintf(stderr, " Усиление фиксированное: %.0f дБ\n", RX_GAIN); + fprintf(stderr, " -F ЧАСТОТА FDD: частота обратного TX в Гц (§10 п.7). Без -F — симплекс\n"); + fprintf(stderr, " -g УСИЛЕНИЕ Усиление feedback-TX в дБ (по умолч. %.0f)\n", FB_TX_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", @@ -449,18 +456,32 @@ int main(int argc, char *argv[]) { print_config("ПРИЁМНИК", freq, rate, bw, fec_on); fprintf(stderr, "Усиление: %.0f дБ (ручное)\n", RX_GAIN); fprintf(stderr, "CFO фильтр: α=%.2f\n", CFO_FILTER_ALPHA); + fprintf(stderr, "Дуплекс: %s\n", fb_freq + ? "FDD (обратный TX настроен, трафик — этап 2)" : "симплекс"); + if (fb_freq) fprintf(stderr, "Обр. TX LO: %.1f МГц, усил. %.0f дБ\n", fb_freq / 1e6, fb_gain); fprintf(stderr, "URI: %s\n", uri); fprintf(stderr, "Конвейер: 3 стадии (A захват / B sync / C FEC+IO)\n"); fprintf(stderr, "═══════════════════════════════════════════════════\n"); + // С -F открываем оба cf-девайса и настраиваем оба LO (обратный TX пока только + // сконфигурирован; передача STATUS/END по нему — этап 2). struct iio_context *ctx = NULL; - struct iio_device *phy = NULL, *rx = NULL; - if (pluto_init(uri, &ctx, &phy, &rx, 0) != 0) return 1; - - if (pluto_configure(phy, freq, rate, bw, RX_GAIN, 0) != 0) { - LOG_ERROR("Настройка радиотракта не удалась"); - iio_context_destroy(ctx); - return 1; + struct iio_device *phy = NULL, *rx = NULL, *txfb = NULL; + if (fb_freq) { + if (pluto_init_fdd(uri, &ctx, &phy, &txfb, &rx) != 0) return 1; + // данные RX на freq (915), обратный TX на fb_freq (868) + if (pluto_configure_fdd(phy, fb_freq, freq, rate, bw, fb_gain, RX_GAIN) != 0) { + LOG_ERROR("Настройка FDD-радиотракта не удалась"); + iio_context_destroy(ctx); + return 1; + } + } else { + if (pluto_init(uri, &ctx, &phy, &rx, 0) != 0) return 1; + if (pluto_configure(phy, freq, rate, bw, RX_GAIN, 0) != 0) { + LOG_ERROR("Настройка радиотракта не удалась"); + iio_context_destroy(ctx); + return 1; + } } iio_channel_enable(iio_device_find_channel(rx, "voltage0", false)); diff --git a/src/transmitter.c b/src/transmitter.c index 4549c98..6b01b0e 100644 --- a/src/transmitter.c +++ b/src/transmitter.c @@ -15,6 +15,7 @@ #define DEFAULT_PAUSE 2000 // Пауза между кадрами: 2000 мкс #define BUF_SAMPLES 16384 // Размер буфера в отсчётах #define MAX_PAYLOAD GF_CHUNK // 1024: размер чанка данных (== GF_CHUNK) +#define FB_RX_GAIN 50.0 // Усиление RX обратного канала (ручное), §10 п.7 // Внешняя переменная флага завершения (определена в common.c) extern volatile sig_atomic_t stop; @@ -30,10 +31,11 @@ int main(int argc, char *argv[]) { float amp = DEFAULT_AMP; const char *uri = TX_URI_DEFAULT; int pause_us = DEFAULT_PAUSE, use_fec = 1, group_fec = 0; + long long fb_freq = 0; // 0 = симплекс; иначе FDD, LO обратного RX // Обработка аргументов командной строки int opt; - while ((opt = getopt(argc, argv, "f:r:b:g:a:p:u:c:Gh")) != -1) { + while ((opt = getopt(argc, argv, "f:r:b:g:a:p:u:c:F:Gh")) != -1) { switch (opt) { case 'f': freq = atoll(optarg); break; case 'r': rate = atoll(optarg); break; @@ -43,6 +45,7 @@ int main(int argc, char *argv[]) { case 'p': pause_us = atoi(optarg); break; case 'u': uri = optarg; break; case 'c': use_fec = strcmp(optarg, "none") ? 1 : 0; break; + case 'F': fb_freq = atoll(optarg); break; case 'G': group_fec = 1; break; case 'h': print_usage(argv[0], "ПЕРЕДАТЧИК"); @@ -51,6 +54,7 @@ int main(int argc, char *argv[]) { fprintf(stderr, " -a АМПЛИТ. Амплитуда сигнала (по умолчанию: %.2f)\n", DEFAULT_AMP); fprintf(stderr, " -p ПАУЗА Пауза между кадрами в мкс (по умолчанию: %d)\n", DEFAULT_PAUSE); fprintf(stderr, " -G Групповой стирающий FEC 16+2 (§10 п.6)\n"); + fprintf(stderr, " -F ЧАСТОТА FDD: частота обратного RX в Гц (§10 п.7). Без -F — симплекс\n"); fprintf(stderr, "\nОПТИМАЛЬНЫЙ ЗАПУСК:\n"); fprintf(stderr, " cat test.bin | %s -f %lld -r %lld -b %lld -c rs8 > /dev/null\n", argv[0], DEFAULT_FREQ, DEFAULT_RATE, DEFAULT_BW); @@ -69,19 +73,30 @@ int main(int argc, char *argv[]) { fprintf(stderr, "Амплитуда: %.2f\n", amp); fprintf(stderr, "Пауза: %d мкс\n", pause_us); fprintf(stderr, "Group-FEC: %s\n", group_fec ? "16+2 (§10 п.6)" : "выкл"); + fprintf(stderr, "Дуплекс: %s\n", fb_freq + ? "FDD (обратный RX настроен, трафик — этап 2)" : "симплекс"); + if (fb_freq) fprintf(stderr, "Обр. RX LO: %.1f МГц\n", fb_freq / 1e6); fprintf(stderr, "URI: %s\n", uri); fprintf(stderr, "═══════════════════════════════════════════════════\n"); - // Инициализация PlutoSDR + // Инициализация PlutoSDR. С -F открываем оба cf-девайса и настраиваем оба + // LO (обратный RX пока только сконфигурирован; приём по нему — этап 2). struct iio_context *ctx = NULL; - struct iio_device *phy = NULL, *tx = NULL; - if (pluto_init(uri, &ctx, &phy, &tx, 1) != 0) return 1; - - // Настройка радиотракта - if (pluto_configure(phy, freq, rate, bw, gain, 1) != 0) { - LOG_ERROR("Настройка радиотракта не удалась"); - iio_context_destroy(ctx); - return 1; + struct iio_device *phy = NULL, *tx = NULL, *rxfb = NULL; + if (fb_freq) { + if (pluto_init_fdd(uri, &ctx, &phy, &tx, &rxfb) != 0) return 1; + if (pluto_configure_fdd(phy, freq, fb_freq, rate, bw, gain, FB_RX_GAIN) != 0) { + LOG_ERROR("Настройка FDD-радиотракта не удалась"); + iio_context_destroy(ctx); + return 1; + } + } else { + if (pluto_init(uri, &ctx, &phy, &tx, 1) != 0) return 1; + if (pluto_configure(phy, freq, rate, bw, gain, 1) != 0) { + LOG_ERROR("Настройка радиотракта не удалась"); + iio_context_destroy(ctx); + return 1; + } } // Включение каналов передатчика