Initial commit: OFDM-радиолиния на двух Pluto+ (PHY/link, код на ARM)
- src: transmitter, receiver, ofdm_bench, common (libiio local: + liquid-dsp) - scripts: кросс-сборка зависимостей, деплой scp -O, e2e-тест с md5 - docs: бенчмарк CPU (выбор rate 1.92 MSPS), грабли FDD/регуляторика - все текстовые файлы нормализованы в LF (.gitattributes принудительно): CRLF в toolchain.env/makefile ломал source и make на Linux - .vscode: IntelliSense в режиме linux-gcc-arm, заголовки из .xarm/include Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
185
src/transmitter.c
Normal file
185
src/transmitter.c
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* transmitter.c — OFDM-передатчик с RS(255,223) FEC для PlutoSDR
|
||||
*
|
||||
* ОПТИМАЛЬНАЯ КОМАНДА ЗАПУСКА:
|
||||
* cat test.bin | ./transmitter -f 1265000000 -r 3840000 -b 2000000 -g -20 -a 0.3 -p 0 -c rs8
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
// Параметры передатчика по умолчанию
|
||||
#define DEFAULT_GAIN -30.0 // Усиление передатчика: -30 дБ
|
||||
#define DEFAULT_AMP 0.2f // Амплитуда сигнала
|
||||
#define DEFAULT_PAUSE 2000 // Пауза между кадрами: 2000 мкс
|
||||
#define BUF_SAMPLES 16384 // Размер буфера в отсчётах
|
||||
#define MAX_PAYLOAD 1024 // Максимальный размер полезной нагрузки
|
||||
|
||||
// Внешняя переменная флага завершения (определена в common.c)
|
||||
extern volatile sig_atomic_t stop;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// Инициализация параметров
|
||||
long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
|
||||
double gain = DEFAULT_GAIN;
|
||||
float amp = DEFAULT_AMP;
|
||||
const char *uri = TX_URI_DEFAULT;
|
||||
int pause_us = DEFAULT_PAUSE, use_fec = 1;
|
||||
|
||||
// Обработка аргументов командной строки
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "f:r:b:g:a:p: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 'g': gain = atof(optarg); break;
|
||||
case 'a': amp = atof(optarg); break;
|
||||
case 'p': pause_us = atoi(optarg); break;
|
||||
case 'u': uri = optarg; break;
|
||||
case 'c': use_fec = strcmp(optarg, "none") ? 1 : 0; break;
|
||||
case 'h':
|
||||
print_usage(argv[0], "ПЕРЕДАТЧИК");
|
||||
fprintf(stderr, "\nСПЕЦИФИЧНЫЕ ОПЦИИ TX:\n");
|
||||
fprintf(stderr, " -g УСИЛЕНИЕ Усиление в дБ (по умолчанию: %.0f)\n", DEFAULT_GAIN);
|
||||
fprintf(stderr, " -a АМПЛИТ. Амплитуда сигнала (по умолчанию: %.2f)\n", DEFAULT_AMP);
|
||||
fprintf(stderr, " -p ПАУЗА Пауза между кадрами в мкс (по умолчанию: %d)\n", DEFAULT_PAUSE);
|
||||
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);
|
||||
return 0;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Настройка обработчиков сигналов
|
||||
setup_signal_handlers();
|
||||
|
||||
// Вывод конфигурации
|
||||
print_config("ПЕРЕДАТЧИК", freq, rate, bw, use_fec);
|
||||
fprintf(stderr, "Усиление: %.0f дБ\n", gain);
|
||||
fprintf(stderr, "Амплитуда: %.2f\n", amp);
|
||||
fprintf(stderr, "Пауза: %d мкс\n", pause_us);
|
||||
fprintf(stderr, "URI: %s\n", uri);
|
||||
fprintf(stderr, "═══════════════════════════════════════════════════\n");
|
||||
|
||||
// Инициализация PlutoSDR
|
||||
struct iio_context *ctx = NULL;
|
||||
struct iio_device *phy = NULL, *tx = NULL;
|
||||
if (pluto_init(uri, &ctx, &phy, &tx, 1) != 0) return 1;
|
||||
|
||||
// Настройка радиотракта
|
||||
pluto_configure(phy, freq, rate, bw, gain, 1);
|
||||
|
||||
// Включение каналов передатчика
|
||||
iio_channel_enable(iio_device_find_channel(tx, "voltage0", true));
|
||||
iio_channel_enable(iio_device_find_channel(tx, "voltage1", true));
|
||||
|
||||
// Создание буфера передачи
|
||||
struct iio_buffer *buf = iio_device_create_buffer(tx, BUF_SAMPLES, false);
|
||||
if (!buf) {
|
||||
LOG_ERROR("Ошибка создания буфера передачи");
|
||||
iio_context_destroy(ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Инициализация FEC и OFDM
|
||||
fec enc = use_fec ? fec_create(LIQUID_FEC_RS_M8, NULL) : NULL;
|
||||
|
||||
ofdmflexframegenprops_s fgp;
|
||||
ofdmflexframegenprops_init_default(&fgp);
|
||||
fgp.check = LIQUID_CRC_32;
|
||||
ofdmflexframegen fg = ofdmflexframegen_create(OFDM_M, OFDM_CP, OFDM_TAPER, NULL, &fgp);
|
||||
|
||||
// Счётчики статистики
|
||||
uint64_t frames = 0, bytes_tx = 0;
|
||||
uint32_t seq = 0;
|
||||
time_t last = time(NULL);
|
||||
|
||||
fprintf(stderr, " Передатчик готов. Ожидание данных...\n\n");
|
||||
|
||||
// Основной цикл передачи
|
||||
while (!stop) {
|
||||
uint8_t raw[MAX_PAYLOAD];
|
||||
size_t n = fread(raw, 1, MAX_PAYLOAD, stdin);
|
||||
if (!n) {
|
||||
if (feof(stdin) || stop) break;
|
||||
usleep(10000);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t *tx_payload = NULL;
|
||||
size_t tx_len = 0;
|
||||
uint8_t hdr[HDR_SIZE] = {0};
|
||||
|
||||
// Кодирование данных
|
||||
if (enc) {
|
||||
size_t total_blocks = (n + RS_DATA - 1) / RS_DATA;
|
||||
tx_len = total_blocks * RS_ENC;
|
||||
tx_payload = malloc(tx_len);
|
||||
|
||||
for (size_t b = 0; b < total_blocks; b++) {
|
||||
uint8_t blk[RS_DATA] = {0};
|
||||
size_t bytes = (b == total_blocks - 1) ? n - b * RS_DATA : RS_DATA;
|
||||
memcpy(blk, raw + b * RS_DATA, bytes);
|
||||
fec_encode(enc, RS_DATA, blk, tx_payload + b * RS_ENC);
|
||||
}
|
||||
|
||||
// Формирование заголовка
|
||||
hdr[0] = HDR_SIGNATURE_0; hdr[1] = HDR_SIGNATURE_1;
|
||||
hdr[2] = (seq >> 24) & 0xFF; hdr[3] = (seq >> 16) & 0xFF;
|
||||
hdr[4] = (seq >> 8) & 0xFF; hdr[5] = seq & 0xFF;
|
||||
hdr[6] = (n >> 8) & 0xFF; hdr[7] = n & 0xFF;
|
||||
hdr[8] = (total_blocks >> 8) & 0xFF; hdr[9] = total_blocks & 0xFF;
|
||||
hdr[10] = n % RS_DATA; hdr[11] = 0;
|
||||
} else {
|
||||
tx_len = n;
|
||||
tx_payload = malloc(tx_len);
|
||||
memcpy(tx_payload, raw, n);
|
||||
|
||||
hdr[0] = HDR_SIGNATURE_0; hdr[1] = HDR_SIGNATURE_1;
|
||||
hdr[2] = (seq >> 24) & 0xFF; hdr[3] = (seq >> 16) & 0xFF;
|
||||
hdr[4] = (seq >> 8) & 0xFF; hdr[5] = seq & 0xFF;
|
||||
hdr[6] = (n >> 8) & 0xFF; hdr[7] = n & 0xFF;
|
||||
}
|
||||
|
||||
// Сборка OFDM-кадра
|
||||
ofdmflexframegen_assemble(fg, hdr, tx_payload, tx_len);
|
||||
free(tx_payload);
|
||||
|
||||
// Формирование выходного сигнала
|
||||
int16_t *out = (int16_t *)iio_buffer_start(buf);
|
||||
memset(out, 0, BUF_SAMPLES * 2 * sizeof(int16_t));
|
||||
int idx = 0, done = 0;
|
||||
float complex symb[OFDM_M + OFDM_CP];
|
||||
|
||||
while (!done && (idx + OFDM_M + OFDM_CP) <= BUF_SAMPLES) {
|
||||
done = ofdmflexframegen_write(fg, symb, OFDM_M + OFDM_CP);
|
||||
for (int i = 0; i < OFDM_M + OFDM_CP; i++) {
|
||||
out[2 * idx] = (int16_t)(crealf(symb[i]) * 32767 * amp);
|
||||
out[2 * idx + 1] = (int16_t)(cimagf(symb[i]) * 32767 * amp);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
if (idx > 0) iio_buffer_push(buf);
|
||||
|
||||
// Обновление статистики
|
||||
frames++; bytes_tx += n; seq++;
|
||||
if (time(NULL) > last) {
|
||||
fprintf(stderr, "\r📤 Передано: %llu кадров, %llu байт",
|
||||
(unsigned long long)frames, (unsigned long long)bytes_tx);
|
||||
fflush(stderr);
|
||||
last = time(NULL);
|
||||
}
|
||||
if (pause_us > 0) usleep(pause_us);
|
||||
}
|
||||
|
||||
fprintf(stderr, "\nПередача завершена: %llu кадров, %llu байт\n",
|
||||
(unsigned long long)frames, (unsigned long long)bytes_tx);
|
||||
|
||||
// Освобождение ресурсов
|
||||
if (enc) fec_destroy(enc);
|
||||
ofdmflexframegen_destroy(fg);
|
||||
iio_buffer_destroy(buf);
|
||||
iio_context_destroy(ctx);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user