доработка в части приема и передачи

This commit is contained in:
Maxim
2026-07-14 09:14:39 +03:00
parent 005c59fc3a
commit 4cfeb38686
3 changed files with 88 additions and 50 deletions

View File

@@ -14,7 +14,8 @@ SIZE_KB="${3:-1024}"
FREQ=915000000
RATE=1920000 # НЕ поднимать: бюджет CPU, docs/BENCHMARK.md
BW=1500000
TXGAIN=-40 # для кабеля; антенны: 20…0
TXGAIN="${TXGAIN:--30}" # OTA: переопредели, напр. TXGAIN=-20 scripts/test_file_link.sh
FEC="${FEC:-rs8}" # rs8 | none — фаза 3.2 гоняет сначала none, потом rs8
TIMEOUT=$(( SIZE_KB / 100 + 30 )) # ~160 КБ/с + запас
if command -v sshpass >/dev/null; then
@@ -36,14 +37,14 @@ echo "=== [2/5] Заливка файла на Pluto A ($A) ==="
echo "=== [3/5] Старт приёмника на Pluto B ($B), таймаут ${TIMEOUT}с ==="
"${SSH[@]}" "root@$B" \
"rm -f /tmp/out.bin; timeout $TIMEOUT /tmp/receiver -f $FREQ -r $RATE \
-b $BW -c rs8 -u local: > /tmp/out.bin 2>/tmp/rx.log" &
-b $BW -c $FEC -u local: > /tmp/out.bin 2>/tmp/rx.log" &
RX_PID=$!
sleep 3 # дать RX прогреться до старта TX
echo "=== [4/5] Передача с Pluto A ==="
"${SSH[@]}" "root@$A" \
"cat /tmp/link_test.bin | /tmp/transmitter -f $FREQ -r $RATE -b $BW \
-g $TXGAIN -p 0 -c rs8 -u local: 2>/tmp/tx.log"
-g $TXGAIN -p 0 -c $FEC -u local: 2>/tmp/tx.log"
sleep 3 # дохлебать хвост эфира
"${SSH[@]}" "root@$B" "killall receiver 2>/dev/null || true"
wait "$RX_PID" 2>/dev/null || true

View File

@@ -13,9 +13,11 @@ 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 uint64_t rs_fail = 0, lost = 0;
static uint32_t last_seq = 0;
static int seq_init = 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];
@@ -29,16 +31,28 @@ static int callback(unsigned char *hdr, int hdr_valid,
rssi = stats.rssi;
evm = stats.evm;
// Обновляем CFO только если NCO инициализирован
if (nco && fabsf(stats.cfo) < CFO_MAX_VALID) {
// CFO только телеметрия: ofdmflexframesync сам компенсирует смещение
// по преамбуле. Внешний NCO-контур убран (техдолг #4): с set_phase(0)
// на границах буферов он рвал фазу посреди кадра.
if (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++;
// Детектор пропусков seq (техдолг #6, минимум): потерю кадра в эфире
// надо отличать от порчи payload. ARQ нет — только лог дыры.
uint32_t seq = ((uint32_t)hdr[2] << 24) | ((uint32_t)hdr[3] << 16) |
((uint32_t)hdr[4] << 8) | hdr[5];
if (seq_init && seq > last_seq + 1) {
lost += seq - last_seq - 1;
fprintf(stderr, "\n[ПОТЕРЯ] seq %u→%u, дыра %u кадров\n",
last_seq, seq, seq - last_seq - 1);
}
last_seq = seq;
seq_init = 1;
uint16_t original_len = (hdr[6] << 8) | hdr[7];
uint8_t last_block_bytes = hdr[10];
@@ -49,26 +63,38 @@ static int callback(unsigned char *hdr, int hdr_valid,
if (nblocks == 0 || nblocks > 4096 / RS_DATA) 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;
}
// fec_decode() liquid всегда "успешен" — код возврата бесполезен.
fec_decode(decoder, RS_DATA, pay + b * RS_ENC,
recovered + b * RS_DATA);
}
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++;
}
// Декодировано framed_len = original_len + 4 (CRC32) байт. Настоящий
// критерий целостности — CRC поверх декода (техдолг #3): fec_decode
// отдаёт мусор молча, поэтому проверяем сами и мусор в файл не пишем.
size_t framed_len = (nblocks - 1) * RS_DATA +
(last_block_bytes > 0 ? last_block_bytes : RS_DATA);
if (framed_len > nblocks * RS_DATA) framed_len = nblocks * RS_DATA;
if (original_len == 0 || (size_t)original_len + 4 > framed_len) {
rs_fail++;
return 0;
}
unsigned int rx_crc = crc_generate_key(LIQUID_CRC_32, recovered, original_len);
unsigned int tx_crc = ((uint32_t)recovered[original_len] << 24) |
((uint32_t)recovered[original_len + 1] << 16) |
((uint32_t)recovered[original_len + 2] << 8) |
recovered[original_len + 3];
if (rx_crc != tx_crc) {
rs_fail++; // RS не справился, кадр битый — не пишем
return 0;
}
if (!pay_valid) rs_saved++; // liquid CRC по payload не прошёл, а наш
// CRC поверх RS — да: значит RS реально спас
fwrite(recovered, 1, original_len, stdout);
fflush(stdout);
total_bytes += original_len;
crc_final++;
} else if (pay_valid && original_len > 0 && original_len <= pay_len) {
fwrite(pay, 1, original_len, stdout);
fflush(stdout);
@@ -141,20 +167,11 @@ int main(int argc, char *argv[]) {
}
}
// Инициализация 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;
@@ -163,8 +180,8 @@ int main(int argc, char *argv[]) {
ofdmflexframesync_set_header_len(fs, HDR_SIZE);
fprintf(stderr, "Приёмник готов. Ожидание сигнала...\n\n");
fprintf(stderr, "Кадры | Загол. | CRC | RS испр | Вывод | RSSI | EVM | CFO\n");
fprintf(stderr, "--------+--------+------+---------+--------+-------+-------+----------\n");
fprintf(stderr, "Кадры | Загол. | CRC | RS испр | RS бит | Потери | Вывод | RSSI | EVM | CFO\n");
fprintf(stderr, "--------+--------+------+---------+--------+--------+--------+-------+-------+----------\n");
time_t last = time(NULL);
@@ -179,19 +196,15 @@ int main(int argc, char *argv[]) {
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",
fprintf(stderr, "\r%6llu | %5llu | %4llu | %6llu | %5llu | %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)rs_fail, (unsigned long long)lost,
(unsigned long long)crc_final, rssi, evm, cfo_est);
fflush(stderr);
last = time(NULL);
@@ -199,13 +212,13 @@ int main(int argc, char *argv[]) {
}
fprintf(stderr, "\n\nПРИЁМ ЗАВЕРШЁН\n");
fprintf(stderr, "Кадров: %llu | Заголовков: %llu | CRC: %llu | RS испр: %llu | Вывод: %llu | Байт: %llu\n",
fprintf(stderr, "Кадров: %llu | Заголовков: %llu | CRC: %llu | RS испр: %llu | RS битых: %llu | Потери seq: %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)rs_fail, (unsigned long long)lost,
(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);

View File

@@ -120,24 +120,36 @@ int main(int argc, char *argv[]) {
// Кодирование данных
if (enc) {
size_t total_blocks = (n + RS_DATA - 1) / RS_DATA;
// CRC32 поверх исходных данных ДО RS: fec_decode() liquid не
// сообщает о неисправимых блоках, поэтому единственный надёжный
// критерий целостности на RX — этот CRC поверх декода (техдолг #3).
uint8_t framed[MAX_PAYLOAD + 4];
memcpy(framed, raw, n);
unsigned int crc = crc_generate_key(LIQUID_CRC_32, raw, 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) ? n - b * RS_DATA : RS_DATA;
memcpy(blk, raw + b * RS_DATA, bytes);
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);
}
// Формирование заголовка
// Заголовок: len = исходные n (без 4 байт CRC), nblocks/last по framed
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;
hdr[10] = framed_n % RS_DATA; hdr[11] = 0;
} else {
tx_len = n;
tx_payload = malloc(tx_len);
@@ -167,7 +179,10 @@ int main(int argc, char *argv[]) {
idx++;
}
}
if (idx > 0) iio_buffer_push(buf);
// Пушим только фактически заполненные idx сэмплов, а не весь буфер
// 16384 (техдолг #5): иначе ~35% мёртвого эфира на кадр и сбитый
// межкадровый тайминг. push_partial даёт ту же длину, что write.
if (idx > 0) iio_buffer_push_partial(buf, idx);
// Обновление статистики
frames++; bytes_tx += n; seq++;
@@ -183,6 +198,15 @@ int main(int argc, char *argv[]) {
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);