Files
Pluto-SDR/src/transmitter.c
Maxim 23b60c6fa2 group-FEC 16+2: стирающее восстановление кадров без обратного канала (§10 п.6)
Групповой FEC над GF(256), RAID-6-стиль: 16 кадров данных + P(⊕) + Q(Σgⁱ·dᵢ)
закрывают до 2 стёртых кадров на группу. Потеря (пропуск seq или битый CRC) —
это стирание с известной позицией, поэтому 2 паритета оптимальны (overhead 12.5%,
~99% передач 10 МБ байт-в-байт при PER 0.26%).

- src/group_fec.{c,h}: GF(256) polynom 0x11d, gf_encode/gf_recover + self-test
  (make gftest, нативный cc). Восстановление 1 стирания через P или Q, 2 — P+Q.
- transmitter.c: флаг -G, вынесен tx_frame(); копит группу, шлёт k данных +
  P/Q с флагами в hdr[11]. Мета паритета: k′ + длина хвостового кадра.
- receiver.c: групповой ассемблер в потоке C, автодетект по hdr[11], финализация
  группы на смене базы seq и на выходе потока. Счётчики Rec/GrpF в статистике.
- hdr[11] (был резерв): bit7 режим, bit6 паритет, bit5 Q, bits4-0 idx. RS/CRC
  слой прозрачен — геометрия кадра не тронута, PHY-бенчмарк не требуется.
- test_file_link.sh: GROUPFEC=1 по умолчанию (TX -G, RX автодетект).

README §12.7 + перенумерация роадмапа — отдельным коммитом после верификации.
2026-07-15 09:18:17 +03:00

262 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* transmitter.c — OFDM-передатчик с RS(255,223) FEC для PlutoSDR
*
* ОПТИМАЛЬНАЯ КОМАНДА ЗАПУСКА (кабель + аттенюатор):
* cat test.bin | ./transmitter -f 915000000 -r 1920000 -b 1500000 -g -40 -a 0.2 -p 0 -c rs8 -u local:
*/
#include "common.h"
#include "group_fec.h"
// Параметры передатчика по умолчанию
#define DEFAULT_GAIN -30.0 // Усиление передатчика: -30 дБ
#define DEFAULT_AMP 0.2f // Амплитуда сигнала
#define DEFAULT_PAUSE 2000 // Пауза между кадрами: 2000 мкс
#define BUF_SAMPLES 16384 // Размер буфера в отсчётах
#define MAX_PAYLOAD GF_CHUNK // 1024: размер чанка данных (== GF_CHUNK)
// Внешняя переменная флага завершения (определена в common.c)
extern volatile sig_atomic_t stop;
// Кодирование и отправка одного кадра: CRC-поверх-RS (техдолг #3) → заголовок
// 12 Б → OFDM-сборка → push_partial → пауза. hdr11 несёт флаги group-FEC
// (0 для legacy). pl/n — полезная нагрузка (данные ≤1024 или паритет 1026).
static void tx_frame(ofdmflexframegen fg, struct iio_buffer *buf, fec enc,
uint32_t seq, uint8_t hdr11,
const uint8_t *pl, size_t n, float amp, int pause_us) {
uint8_t *tx_payload = NULL;
size_t tx_len = 0;
uint8_t hdr[HDR_SIZE] = {0};
if (enc) {
// CRC32 поверх исходных данных ДО RS: fec_decode() liquid не сообщает о
// неисправимых блоках — единственный надёжный критерий на RX (техдолг #3).
uint8_t framed[GF_PAR_PAY + 4]; // ≥ данные+4 и ≥ паритет 1026+4
memcpy(framed, pl, n);
unsigned int crc = crc_generate_key(LIQUID_CRC_32, pl, n);
framed[n] = (crc >> 24) & 0xFF;
framed[n + 1] = (crc >> 16) & 0xFF;
framed[n + 2] = (crc >> 8) & 0xFF;
framed[n + 3] = crc & 0xFF;
size_t framed_n = n + 4;
size_t total_blocks = (framed_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) ? framed_n - b * RS_DATA : RS_DATA;
memcpy(blk, framed + b * RS_DATA, bytes);
fec_encode(enc, RS_DATA, blk, tx_payload + b * RS_ENC);
}
hdr[8] = (total_blocks >> 8) & 0xFF; hdr[9] = total_blocks & 0xFF;
hdr[10] = framed_n % RS_DATA;
} else {
tx_len = n;
tx_payload = malloc(tx_len);
memcpy(tx_payload, pl, 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;
hdr[11] = hdr11; // group-FEC флаги (0 = legacy)
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++;
}
}
// Только фактически заполненные idx сэмплов (техдолг #5): иначе мёртвый эфир
// и сбитый межкадровый тайминг. push_partial даёт ту же длину, что write.
if (idx > 0) iio_buffer_push_partial(buf, idx);
if (pause_us > 0) usleep(pause_us);
}
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, group_fec = 0;
// Обработка аргументов командной строки
int opt;
while ((opt = getopt(argc, argv, "f:r:b:g:a:p:u:c:Gh")) != -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 'G': group_fec = 1; 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, " -G Групповой стирающий FEC 16+2 (§10 п.6)\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);
return 0;
default: return 1;
}
}
// Настройка обработчиков сигналов
setup_signal_handlers();
if (group_fec) gf_init();
// Вывод конфигурации
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, "Group-FEC: %s\n", group_fec ? "16+2 (§10 п.6)" : "выкл");
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;
// Настройка радиотракта
if (pluto_configure(phy, freq, rate, bw, gain, 1) != 0) {
LOG_ERROR("Настройка радиотракта не удалась");
iio_context_destroy(ctx);
return 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);
// Наш заголовок 12 Б; по умолчанию liquid шлёт 8 — байты 8..11 (nblocks,
// last_block_bytes) без этого в эфир не уходят. RX выставляет то же.
ofdmflexframegen_set_header_len(fg, HDR_SIZE);
// Счётчики статистики
uint64_t frames = 0, bytes_tx = 0;
uint32_t seq = 0;
time_t last = time(NULL);
fprintf(stderr, " Передатчик готов. Ожидание данных...\n\n");
// Основной цикл передачи. Legacy: кадр = чанк ≤1024 Б. Group-FEC: копим до
// 16 чанков, затем шлём k данных + 2 паритета (P, Q) с флагами в hdr[11].
if (!group_fec) {
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;
}
tx_frame(fg, buf, enc, seq++, 0, raw, n, amp, pause_us);
frames++; bytes_tx += n;
if (time(NULL) > last) {
fprintf(stderr, "\r📤 Передано: %llu кадров, %llu байт",
(unsigned long long)frames, (unsigned long long)bytes_tx);
fflush(stderr);
last = time(NULL);
}
}
} else {
static uint8_t gdata[GF_K][GF_CHUNK]; // накопитель группы
const uint8_t *gptr[GF_K];
uint16_t glen[GF_K];
int k = 0, done_reading = 0;
while (!stop && !done_reading) {
size_t r = fread(gdata[k], 1, GF_CHUNK, stdin);
if (r > 0) {
if (r < GF_CHUNK) memset(gdata[k] + r, 0, GF_CHUNK - r); // паддинг под GF
glen[k] = (uint16_t)r;
gptr[k] = gdata[k];
k++;
}
if (r < GF_CHUNK) done_reading = 1; // fread добирает до конца → короткий = EOF
if (k == GF_K || (done_reading && k > 0)) {
// k кадров данных: idx=0..k-1
for (int i = 0; i < k; i++) {
tx_frame(fg, buf, enc, seq++, GF_FLAG_MODE | i,
gdata[i], glen[i], amp, pause_us);
frames++; bytes_tx += glen[i];
}
// паритеты P (idx=k) и Q (idx=k+1); мета = k + длина хвоста
uint8_t P[GF_CHUNK], Q[GF_CHUNK], par[GF_PAR_PAY];
gf_encode(gptr, k, P, Q);
uint16_t meta = GF_META_PACK(k, glen[k - 1]);
par[0] = meta >> 8; par[1] = meta & 0xFF;
memcpy(par + GF_META, P, GF_CHUNK);
tx_frame(fg, buf, enc, seq++, GF_FLAG_MODE | GF_FLAG_PAR | k,
par, GF_PAR_PAY, amp, pause_us);
memcpy(par + GF_META, Q, GF_CHUNK);
tx_frame(fg, buf, enc, seq++, GF_FLAG_MODE | GF_FLAG_PAR | GF_FLAG_Q | (k + 1),
par, GF_PAR_PAY, amp, pause_us);
frames += 2;
k = 0;
if (time(NULL) > last) {
fprintf(stderr, "\r📤 Передано: %llu кадров, %llu байт (group-FEC)",
(unsigned long long)frames, (unsigned long long)bytes_tx);
fflush(stderr);
last = time(NULL);
}
}
}
}
fprintf(stderr, "\nПередача завершена: %llu кадров, %llu байт\n",
(unsigned long long)frames, (unsigned long long)bytes_tx);
// Дренаж: выпихнуть последний кадр из kernel-буферов DMA перед закрытием.
// Без паузы iio_buffer_destroy() срезает хвост файла в эфире (техдолг #5).
{
int16_t *out = (int16_t *)iio_buffer_start(buf);
memset(out, 0, BUF_SAMPLES * 2 * sizeof(int16_t));
iio_buffer_push(buf);
usleep(100000); // ~4 kernel-буфера при 1.92 MSPS ≈ 35 мс, с запасом
}
// Освобождение ресурсов
if (enc) fec_destroy(enc);
ofdmflexframegen_destroy(fg);
iio_buffer_destroy(buf);
iio_context_destroy(ctx);
return 0;
}