Этап 2. Новый модуль обратного канала
This commit is contained in:
@@ -46,6 +46,32 @@ RX платы «глохнет», как только её TX активен. Э
|
|||||||
Поэтому порядок работ: **сначала симплекс** (обе платы 915 МГц, полудуплекс
|
Поэтому порядок работ: **сначала симплекс** (обе платы 915 МГц, полудуплекс
|
||||||
по очереди), FDD — отдельным этапом с замерами деградации RX при активном TX.
|
по очереди), FDD — отдельным этапом с замерами деградации RX при активном TX.
|
||||||
|
|
||||||
|
### 2.1. Предварительные замеры (этап 1, §10 п.7 — FDD-водопровод)
|
||||||
|
|
||||||
|
Числа сняты на **сконфигурированном**, но молчащем обратном тракте (LO 868
|
||||||
|
поднят, `hardwaregain` выставлен, но DMA обратного TX ничего не пушит).
|
||||||
|
Полноценный gate с реальным STATUS-трафиком — этап 2, числа уйдут в §2.2.
|
||||||
|
|
||||||
|
- **Самоглушения от «просто включённого» обратного тракта не видно.** Форвард
|
||||||
|
A→B под FDD (1 МБ, `-p 4000`, group-FEC) байт-в-байт и при fb-TX `-g -89`
|
||||||
|
(≈выкл), и при `-g -20` (штатное). Активный обратный LO 868 в 47 МГц от
|
||||||
|
RX 915 приёмник данных не глушит — на кабеле+аттенюаторе. Это ответ на
|
||||||
|
вопрос PER₁ (десенс от включённого тракта), но НЕ на PER₂ (десенс от
|
||||||
|
реального излучения STATUS) — тот снимается в этапе 2.
|
||||||
|
- **Диагностика: пустой файл на выходе RX ≠ самоглушение.** Симптом «0 кадров,
|
||||||
|
RSSI −100» — это НЕ глушение, а мёртвый вход: остаточный процесс держит
|
||||||
|
RX-DMA (`cf-ad9361-lpc`) от неубитого прошлого запуска. Глушение выглядит
|
||||||
|
ИНАЧЕ — сильный RSSI (−48) при разрушенном EVM (−2.5). **Различать по RSSI.**
|
||||||
|
Лечение: `killall -9 receiver transmitter` перед каждым стартом (уже в
|
||||||
|
`scripts/test_fdd_fwd.sh`).
|
||||||
|
- **Ридбэк LO округляется PLL.** AD9361 — дробный-N синтезатор с шагом ~2 Гц:
|
||||||
|
`868000000` читается как `867999998`. Это норма, не ошибка; сверка ридбэка
|
||||||
|
идёт с допуском `FDD_LO_TOL_HZ`=100 Гц (common.c).
|
||||||
|
- **Канал деградировал против этапа 0.** Эталон регрессии сдвинут `-p 3000` →
|
||||||
|
`-p 4000` (на 3000/3500 теперь Overrun+потери). Направление **B→A** флапает
|
||||||
|
даже на `-p 4000` при СИЛЬНОМ RSSI (−33…−35) — вероятна компрессия входа RX
|
||||||
|
(усиление 50 дБ избыточно). Проверить снижением RX/TX gain до свипа частот.
|
||||||
|
|
||||||
## 3. Регуляторика (868/915 МГц в регионе ETSI)
|
## 3. Регуляторика (868/915 МГц в регионе ETSI)
|
||||||
|
|
||||||
- **868 МГц** — SRD-диапазон: ограничения на полосу канала (сотни кГц),
|
- **868 МГц** — SRD-диапазон: ограничения на полосу канала (сотни кГц),
|
||||||
|
|||||||
8
makefile
8
makefile
@@ -25,11 +25,11 @@ all: $(BIN)/transmitter $(BIN)/receiver $(BIN)/ofdm_bench
|
|||||||
$(BIN):
|
$(BIN):
|
||||||
mkdir -p $(BIN)
|
mkdir -p $(BIN)
|
||||||
|
|
||||||
$(BIN)/transmitter: $(SRC)/transmitter.c $(SRC)/common.c $(SRC)/group_fec.c $(SRC)/frame_tx.c $(SRC)/common.h $(SRC)/group_fec.h $(SRC)/frame_tx.h $(DEPS_LIBS) | $(BIN)
|
$(BIN)/transmitter: $(SRC)/transmitter.c $(SRC)/common.c $(SRC)/group_fec.c $(SRC)/frame_tx.c $(SRC)/feedback.c $(SRC)/common.h $(SRC)/group_fec.h $(SRC)/frame_tx.h $(SRC)/feedback.h $(DEPS_LIBS) | $(BIN)
|
||||||
$(CC) $(CFLAGS) $(SRC)/transmitter.c $(SRC)/common.c $(SRC)/group_fec.c $(SRC)/frame_tx.c -o $@ $(LDFLAGS) -Wl,--start-group -lad9361 -liio $(LDLIBS) -Wl,--end-group
|
$(CC) $(CFLAGS) $(SRC)/transmitter.c $(SRC)/common.c $(SRC)/group_fec.c $(SRC)/frame_tx.c $(SRC)/feedback.c -o $@ $(LDFLAGS) -Wl,--start-group -lad9361 -liio $(LDLIBS) -Wl,--end-group
|
||||||
|
|
||||||
$(BIN)/receiver: $(SRC)/receiver.c $(SRC)/common.c $(SRC)/group_fec.c $(SRC)/common.h $(SRC)/group_fec.h $(DEPS_LIBS) | $(BIN)
|
$(BIN)/receiver: $(SRC)/receiver.c $(SRC)/common.c $(SRC)/group_fec.c $(SRC)/frame_tx.c $(SRC)/feedback.c $(SRC)/common.h $(SRC)/group_fec.h $(SRC)/frame_tx.h $(SRC)/feedback.h $(DEPS_LIBS) | $(BIN)
|
||||||
$(CC) $(CFLAGS) $(SRC)/receiver.c $(SRC)/common.c $(SRC)/group_fec.c -o $@ $(LDFLAGS) -Wl,--start-group -lad9361 -liio $(LDLIBS) -Wl,--end-group
|
$(CC) $(CFLAGS) $(SRC)/receiver.c $(SRC)/common.c $(SRC)/group_fec.c $(SRC)/frame_tx.c $(SRC)/feedback.c -o $@ $(LDFLAGS) -Wl,--start-group -lad9361 -liio $(LDLIBS) -Wl,--end-group
|
||||||
|
|
||||||
$(BIN)/ofdm_bench: $(SRC)/ofdm_bench.c | $(BIN)
|
$(BIN)/ofdm_bench: $(SRC)/ofdm_bench.c | $(BIN)
|
||||||
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) $(LDLIBS)
|
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) $(LDLIBS)
|
||||||
|
|||||||
@@ -83,6 +83,23 @@ echo "--- FDD-ридбэк ---"
|
|||||||
"${SSH[@]}" "root@$A" "grep 'FDD ридбэк' /tmp/tx_fdd.log 2>/dev/null | sed 's/^/A: /'" || true
|
"${SSH[@]}" "root@$A" "grep 'FDD ридбэк' /tmp/tx_fdd.log 2>/dev/null | sed 's/^/A: /'" || true
|
||||||
"${SSH[@]}" "root@$B" "grep 'FDD ридбэк' /tmp/rx_fdd.log 2>/dev/null | sed 's/^/B: /'" || true
|
"${SSH[@]}" "root@$B" "grep 'FDD ридбэк' /tmp/rx_fdd.log 2>/dev/null | sed 's/^/B: /'" || true
|
||||||
|
|
||||||
|
# Обратный канал (этап 2): сколько STATUS B отправил и сколько A декодировал.
|
||||||
|
# Доля доставки — метрика gate самоглушения (§10 п.7).
|
||||||
|
echo "--- Обратный канал STATUS (B→A) ---"
|
||||||
|
SENT=$("${SSH[@]}" "root@$B" "grep -o 'отправлено STATUS: [0-9]*' /tmp/rx_fdd.log 2>/dev/null | grep -o '[0-9]*'" || echo "")
|
||||||
|
RECV=$("${SSH[@]}" "root@$A" "grep -o 'принято STATUS: [0-9]*' /tmp/tx_fdd.log 2>/dev/null | grep -o '[0-9]*'" || echo "")
|
||||||
|
SENT=${SENT:-0}; RECV=${RECV:-0}
|
||||||
|
if [ "$SENT" -gt 0 ]; then
|
||||||
|
echo "B отправил: $SENT | A принял: $RECV | доставка ≈ $(( RECV * 100 / SENT ))%"
|
||||||
|
else
|
||||||
|
echo "B отправил: $SENT | A принял: $RECV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Итоговая RX-статистика — источник PER (Потери seq / Байт), нужна каждый прогон,
|
||||||
|
# особенно при GROUPFEC=0 (сырой PER без маскировки групповым FEC).
|
||||||
|
echo "--- RX-статистика (PER) ---"
|
||||||
|
"${SSH[@]}" "root@$B" "grep '^Кадров:' /tmp/rx_fdd.log 2>/dev/null | tail -1" || true
|
||||||
|
|
||||||
if [ "$MD5_SRC" = "$MD5_DST" ]; then
|
if [ "$MD5_SRC" = "$MD5_DST" ]; then
|
||||||
echo "РЕЗУЛЬТАТ: OK — форвард-линк под FDD цел (FBGAIN=$FBGAIN)"
|
echo "РЕЗУЛЬТАТ: OK — форвард-линк под FDD цел (FBGAIN=$FBGAIN)"
|
||||||
else
|
else
|
||||||
|
|||||||
48
src/feedback.c
Normal file
48
src/feedback.c
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// feedback.c — упаковка/распаковка STATUS обратного канала (этап 2, §10 п.7).
|
||||||
|
// Чистая логика без зависимостей от liquid/iio → пригодна к офлайн-юнит-тесту.
|
||||||
|
|
||||||
|
#include "feedback.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static void put_u32(uint8_t *p, uint32_t v) {
|
||||||
|
p[0] = (v >> 24) & 0xFF; p[1] = (v >> 16) & 0xFF;
|
||||||
|
p[2] = (v >> 8) & 0xFF; p[3] = v & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t get_u32(const uint8_t *p) {
|
||||||
|
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
|
||||||
|
((uint32_t)p[2] << 8) | (uint32_t)p[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t fb_status_pack(const fb_status_t *s, uint8_t *out) {
|
||||||
|
uint8_t cnt = s->nack_cnt > FB_MAX_NACK ? FB_MAX_NACK : s->nack_cnt;
|
||||||
|
out[0] = FB_TYPE_STATUS;
|
||||||
|
out[1] = s->flags;
|
||||||
|
out[2] = cnt;
|
||||||
|
out[3] = 0; // резерв
|
||||||
|
put_u32(out + 4, s->highest_seq);
|
||||||
|
put_u32(out + 8, s->rx_frames);
|
||||||
|
put_u32(out + 12, s->overruns);
|
||||||
|
for (uint8_t i = 0; i < cnt; i++)
|
||||||
|
put_u32(out + FB_STATUS_HDR + (size_t)i * 4, s->nack[i]);
|
||||||
|
return (size_t)FB_STATUS_HDR + (size_t)cnt * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fb_status_unpack(const uint8_t *in, size_t len, fb_status_t *s) {
|
||||||
|
if (len < FB_STATUS_HDR) return -1;
|
||||||
|
if (in[0] != FB_TYPE_STATUS) return -1;
|
||||||
|
uint8_t cnt = in[2];
|
||||||
|
if (cnt > FB_MAX_NACK) return -1;
|
||||||
|
if (len < (size_t)FB_STATUS_HDR + (size_t)cnt * 4) return -1;
|
||||||
|
|
||||||
|
memset(s, 0, sizeof(*s));
|
||||||
|
s->type = in[0];
|
||||||
|
s->flags = in[1];
|
||||||
|
s->nack_cnt = cnt;
|
||||||
|
s->highest_seq = get_u32(in + 4);
|
||||||
|
s->rx_frames = get_u32(in + 8);
|
||||||
|
s->overruns = get_u32(in + 12);
|
||||||
|
for (uint8_t i = 0; i < cnt; i++)
|
||||||
|
s->nack[i] = get_u32(in + FB_STATUS_HDR + (size_t)i * 4);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
45
src/feedback.h
Normal file
45
src/feedback.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// feedback.h — обратный канал STATUS (roadmap §10 п.7, этап 2).
|
||||||
|
// STATUS — кумулятивный СНИМОК состояния RX, а не поток событий: потеря одного
|
||||||
|
// STATUS безвредна, следующий несёт то же состояние (ARQ-над-ARQ не нужен).
|
||||||
|
// Идёт B→A на частоте обратного канала (868); сигнатура кадра F0 55 в hdr[0..1]
|
||||||
|
// (данные — F0 AA, END — F0 E7). Payload RS(255,223)+CRC32, как у данных.
|
||||||
|
|
||||||
|
#ifndef FEEDBACK_H
|
||||||
|
#define FEEDBACK_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#define FB_TYPE_STATUS 0x01
|
||||||
|
#define FB_MAX_NACK 50 // потолок NACK в одном STATUS (влезает в 1 RS-блок)
|
||||||
|
#define FB_FLAG_END_SEEN 0x01 // RX видел END-кадр (этап 3)
|
||||||
|
#define FB_FLAG_COMPLETE 0x02 // все кадры собраны, файл цел (этап 3)
|
||||||
|
|
||||||
|
// Сигнатура STATUS-кадра в hdr[0..1].
|
||||||
|
#define FB_SIG_0 0xF0
|
||||||
|
#define FB_SIG_1 0x55
|
||||||
|
|
||||||
|
// Снимок состояния приёмника. nack[] — отсортированные по возрастанию seq
|
||||||
|
// отсутствующих кадров данных (старейшие первыми), не более FB_MAX_NACK.
|
||||||
|
typedef struct {
|
||||||
|
uint8_t type; // FB_TYPE_STATUS
|
||||||
|
uint8_t flags; // FB_FLAG_END_SEEN | FB_FLAG_COMPLETE
|
||||||
|
uint8_t nack_cnt; // число валидных элементов nack[] (≤ FB_MAX_NACK)
|
||||||
|
uint32_t highest_seq; // макс. принятый seq (0xFFFFFFFF = ничего)
|
||||||
|
uint32_t rx_frames; // кадров данных записано в stdout
|
||||||
|
uint32_t overruns; // Overrun ядра захвата (диагностика линии)
|
||||||
|
uint32_t nack[FB_MAX_NACK];
|
||||||
|
} fb_status_t;
|
||||||
|
|
||||||
|
// Раскладка payload (сетевой порядок, BE):
|
||||||
|
// 0 type | 1 flags | 2 nack_cnt | 3 rsv | 4..7 highest | 8..11 rx_frames
|
||||||
|
// 12..15 overruns | 16.. nack[i] u32
|
||||||
|
#define FB_STATUS_HDR 16 // фикс. часть
|
||||||
|
#define FB_STATUS_MAXLEN (FB_STATUS_HDR + FB_MAX_NACK * 4) // 216 ≤ 219 → 1 RS-блок
|
||||||
|
|
||||||
|
// Упаковка → out (≥ FB_STATUS_MAXLEN). Возврат — длина payload в байтах.
|
||||||
|
size_t fb_status_pack(const fb_status_t *s, uint8_t *out);
|
||||||
|
// Распаковка. Возврат 0 при успехе, -1 при кривой длине/типе.
|
||||||
|
int fb_status_unpack(const uint8_t *in, size_t len, fb_status_t *s);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -68,3 +68,33 @@ void phy_tx_frame(ofdmflexframegen fg, struct iio_buffer *buf, int buf_samples,
|
|||||||
if (idx > 0) iio_buffer_push_partial(buf, idx);
|
if (idx > 0) iio_buffer_push_partial(buf, idx);
|
||||||
if (pause_us > 0) usleep(pause_us);
|
if (pause_us > 0) usleep(pause_us);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int phy_rx_decode(const uint8_t *hdr, const uint8_t *pay, unsigned pay_len,
|
||||||
|
fec dec, uint8_t *out, size_t out_cap, size_t *out_len) {
|
||||||
|
if (!dec || pay_len < RS_ENC) return 0;
|
||||||
|
size_t nblocks = pay_len / RS_ENC;
|
||||||
|
if (nblocks == 0 || nblocks * RS_DATA > out_cap) return 0;
|
||||||
|
|
||||||
|
for (size_t b = 0; b < nblocks; b++)
|
||||||
|
// fec_decode() liquid не const-correct и «всегда успешен» — код возврата
|
||||||
|
// бесполезен, целостность проверяем CRC ниже (техдолг #3).
|
||||||
|
fec_decode(dec, RS_DATA, (unsigned char *)pay + b * RS_ENC,
|
||||||
|
out + b * RS_DATA);
|
||||||
|
|
||||||
|
uint16_t original_len = (uint16_t)((hdr[6] << 8) | hdr[7]);
|
||||||
|
uint8_t last_block_bytes = hdr[10];
|
||||||
|
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) return 0;
|
||||||
|
|
||||||
|
unsigned int rx_crc = crc_generate_key(LIQUID_CRC_32, out, original_len);
|
||||||
|
unsigned int tx_crc = ((uint32_t)out[original_len] << 24) |
|
||||||
|
((uint32_t)out[original_len + 1] << 16) |
|
||||||
|
((uint32_t)out[original_len + 2] << 8) |
|
||||||
|
(uint32_t)out[original_len + 3];
|
||||||
|
if (rx_crc != tx_crc) return 0;
|
||||||
|
|
||||||
|
*out_len = original_len;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,4 +17,14 @@ void phy_tx_frame(ofdmflexframegen fg, struct iio_buffer *buf, int buf_samples,
|
|||||||
fec enc, const uint8_t sig[2], uint32_t seq, uint8_t hdr11,
|
fec enc, const uint8_t sig[2], uint32_t seq, uint8_t hdr11,
|
||||||
const uint8_t *pl, size_t n, float amp, int pause_us);
|
const uint8_t *pl, size_t n, float amp, int pause_us);
|
||||||
|
|
||||||
|
// Обратная операция к phy_tx_frame для кадра, собранного с enc: RS-декод всех
|
||||||
|
// блоков payload + проверка CRC32-поверх-RS (единственный надёжный критерий,
|
||||||
|
// техдолг #3). hdr — 12 Б заголовка (нужны байты 6-7 original_len и 10
|
||||||
|
// last_block_bytes); pay/pay_len — сырой RS-payload из ofdmflexframesync; dec —
|
||||||
|
// декодер RS(255,223); out (≥ out_cap) принимает декодированные данные, out_len
|
||||||
|
// — исходная длина. Возврат 1 при пройденном CRC, 0 иначе. Используется обратным
|
||||||
|
// каналом (fb_rx) на TX-стороне; RX-данные пока декодит свой инлайн в receiver.c.
|
||||||
|
int phy_rx_decode(const uint8_t *hdr, const uint8_t *pay, unsigned pay_len,
|
||||||
|
fec dec, uint8_t *out, size_t out_cap, size_t *out_len);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -165,6 +165,35 @@ static void bench_rs(int nerr, double frame_us)
|
|||||||
fec_destroy(enc); fec_destroy(dec);
|
fec_destroy(enc); fec_destroy(dec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Стоимость генерации короткого кадра обратного канала (STATUS: RS-payload ≈ 1
|
||||||
|
* блок = 255 Б). Этап 2 §10 п.7: модуляция фидбэка идёт на ядре 1 приёмника
|
||||||
|
* (поток C) — убедиться, что assemble+write дёшевы и 10 Гц STATUS не грузят ядро.
|
||||||
|
* Меряем полную генерацию кадра (assemble + write всех символов). */
|
||||||
|
static void bench_framegen(ofdmflexframegen fg, unsigned pay_bytes)
|
||||||
|
{
|
||||||
|
const unsigned sym = OFDM_M + OFDM_CP;
|
||||||
|
unsigned char hdr[12] = {0xF0, 0x55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
|
unsigned char pay[512];
|
||||||
|
if (pay_bytes > sizeof(pay)) pay_bytes = sizeof(pay);
|
||||||
|
for (unsigned i = 0; i < pay_bytes; i++) pay[i] = (unsigned char)(i & 0xFF);
|
||||||
|
float complex buf[OFDM_M + OFDM_CP];
|
||||||
|
|
||||||
|
unsigned long long frames = 0;
|
||||||
|
double t0 = tnow(), t1;
|
||||||
|
do {
|
||||||
|
ofdmflexframegen_assemble(fg, hdr, pay, pay_bytes);
|
||||||
|
int done = 0;
|
||||||
|
while (!done) done = ofdmflexframegen_write(fg, buf, sym);
|
||||||
|
frames++;
|
||||||
|
t1 = tnow();
|
||||||
|
} while (t1 - t0 < 2.0);
|
||||||
|
|
||||||
|
double us = (t1 - t0) * 1e6 / (double)frames;
|
||||||
|
/* STATUS 10 Гц → доля ядра = 10·us/1e6, в процентах = us·1e-3 */
|
||||||
|
printf("framegen кадр %uБ %7.1f мкс/кадр %8.0f кадров/с (STATUS 10 Гц ≈ %.3f%% ядра)\n",
|
||||||
|
pay_bytes, us, (double)frames / (t1 - t0), us * 10.0 / 1e6 * 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int main_cpu = 0, use_hog = 0, opt;
|
int main_cpu = 0, use_hog = 0, opt;
|
||||||
@@ -271,6 +300,10 @@ int main(int argc, char **argv)
|
|||||||
bench_rs(4, frame_us);
|
bench_rs(4, frame_us);
|
||||||
bench_rs(16, frame_us);
|
bench_rs(16, frame_us);
|
||||||
|
|
||||||
|
/* обратный канал: генерация короткого STATUS-кадра (§10 п.7, этап 2) */
|
||||||
|
printf("\n");
|
||||||
|
bench_framegen(fg, 255); /* RS-payload STATUS ≈ 1 блок */
|
||||||
|
|
||||||
/* p_min: минимальная TX-пауза для realtime @ 1.92 при чистой sync-only цепочке
|
/* p_min: минимальная TX-пауза для realtime @ 1.92 при чистой sync-only цепочке
|
||||||
* (без RS, без контеншна если без -H). d/S + (1-d)/N = 1/1.92; d = frm/(frm+p) */
|
* (без RS, без контеншна если без -H). d/S + (1-d)/N = 1/1.92; d = frm/(frm+p) */
|
||||||
printf("\nМодель realtime @ %.2f Msps (sync-only%s):\n",
|
printf("\nМодель realtime @ %.2f Msps (sync-only%s):\n",
|
||||||
|
|||||||
125
src/receiver.c
125
src/receiver.c
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
#include "common.h" // _GNU_SOURCE/pthread/sched — уже подключены здесь
|
#include "common.h" // _GNU_SOURCE/pthread/sched — уже подключены здесь
|
||||||
#include "group_fec.h"
|
#include "group_fec.h"
|
||||||
|
#include "frame_tx.h" // phy_tx_frame — отправка STATUS обратного канала
|
||||||
|
#include "feedback.h" // fb_status_t / pack — телеметрия B→A (этап 2, §10 п.7)
|
||||||
|
|
||||||
#define BUF_SAMPLES 32768
|
#define BUF_SAMPLES 32768
|
||||||
#define RING_SLOTS 16 // 16 × 17 мс = 273 мс поглощения всплесков
|
#define RING_SLOTS 16 // 16 × 17 мс = 273 мс поглощения всплесков
|
||||||
@@ -80,6 +82,75 @@ static int seq_init = 0;
|
|||||||
static fec decoder = NULL;
|
static fec decoder = NULL;
|
||||||
static int fec_on = 0;
|
static int fec_on = 0;
|
||||||
|
|
||||||
|
// --- Missing-tracker обратного канала (этап 2, §10 п.7) -----------------
|
||||||
|
// Отсортированный по возрастанию набор seq отсутствующих кадров данных. Пишется
|
||||||
|
// ТОЛЬКО потоком C (в process_frame и maybe_send_status) — синхронизации нет,
|
||||||
|
// как у прочего C-состояния. Дыры seq → miss_add (по возрастанию), приход кадра
|
||||||
|
// (в т.ч. будущий ретрансмит) → miss_ack. В STATUS уходят FB_MAX_NACK старейших.
|
||||||
|
#define MISS_CAP 512
|
||||||
|
static uint32_t miss[MISS_CAP];
|
||||||
|
static int miss_cnt = 0;
|
||||||
|
|
||||||
|
static void miss_add(uint32_t seq) {
|
||||||
|
if (miss_cnt >= MISS_CAP) return; // переполнение: старейшие дыры важнее
|
||||||
|
miss[miss_cnt++] = seq; // highest_seq только растёт → массив сортирован
|
||||||
|
}
|
||||||
|
|
||||||
|
static void miss_ack(uint32_t seq) {
|
||||||
|
for (int i = 0; i < miss_cnt; i++) {
|
||||||
|
if (miss[i] == seq) {
|
||||||
|
memmove(&miss[i], &miss[i + 1], (size_t)(miss_cnt - i - 1) * sizeof(miss[0]));
|
||||||
|
miss_cnt--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Отправитель STATUS на feedback-TX (только при -F, поток C) ----------
|
||||||
|
// Свой framegen + буфер на txfb (868), свой RS-энкодер. Кадр STATUS шлётся не
|
||||||
|
// чаще FB_PERIOD_MS. Данные RX и обратный TX — независимые cf-девайсы (FDD),
|
||||||
|
// поэтому push сюда не конфликтует с приёмом.
|
||||||
|
#define FB_BUF_SAMPLES 16384
|
||||||
|
#define FB_PERIOD_MS 100
|
||||||
|
#define FB_AMP 0.2f
|
||||||
|
|
||||||
|
static ofdmflexframegen g_fb_fg = NULL;
|
||||||
|
static struct iio_buffer *g_fb_buf = NULL;
|
||||||
|
static fec g_fb_enc = NULL;
|
||||||
|
static uint32_t g_fb_seq = 0;
|
||||||
|
static int64_t g_fb_last_ms = 0;
|
||||||
|
|
||||||
|
static const uint8_t SIG_STATUS[2] = { FB_SIG_0, FB_SIG_1 };
|
||||||
|
|
||||||
|
static int64_t now_ms(void) {
|
||||||
|
struct timespec t;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t);
|
||||||
|
return (int64_t)t.tv_sec * 1000 + t.tv_nsec / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void maybe_send_status(void) {
|
||||||
|
if (!g_fb_fg) return; // симплекс — обратного канала нет
|
||||||
|
int64_t t = now_ms();
|
||||||
|
if (g_fb_last_ms && t - g_fb_last_ms < FB_PERIOD_MS) return;
|
||||||
|
g_fb_last_ms = t;
|
||||||
|
|
||||||
|
fb_status_t st;
|
||||||
|
memset(&st, 0, sizeof(st));
|
||||||
|
st.type = FB_TYPE_STATUS;
|
||||||
|
st.flags = 0; // END_SEEN/COMPLETE — этап 3
|
||||||
|
st.highest_seq = seq_init ? highest_seq : 0xFFFFFFFFu;
|
||||||
|
st.rx_frames = (uint32_t)crc_final;
|
||||||
|
st.overruns = (uint32_t)ring.overruns; // телеметрия, гонка косметическая
|
||||||
|
int cnt = miss_cnt < FB_MAX_NACK ? miss_cnt : FB_MAX_NACK;
|
||||||
|
st.nack_cnt = (uint8_t)cnt;
|
||||||
|
for (int i = 0; i < cnt; i++) st.nack[i] = miss[i];
|
||||||
|
|
||||||
|
uint8_t pay[FB_STATUS_MAXLEN];
|
||||||
|
size_t n = fb_status_pack(&st, pay);
|
||||||
|
phy_tx_frame(g_fb_fg, g_fb_buf, FB_BUF_SAMPLES, g_fb_enc,
|
||||||
|
SIG_STATUS, g_fb_seq++, 0, pay, n, FB_AMP, 0);
|
||||||
|
}
|
||||||
|
|
||||||
// callback потока B — лёгкий: телеметрия + копия сырого кадра в очередь C.
|
// callback потока B — лёгкий: телеметрия + копия сырого кадра в очередь C.
|
||||||
static int callback(unsigned char *hdr, int hdr_valid,
|
static int callback(unsigned char *hdr, int hdr_valid,
|
||||||
unsigned char *pay, unsigned int pay_len,
|
unsigned char *pay, unsigned int pay_len,
|
||||||
@@ -242,6 +313,8 @@ static void process_frame(fq_slot_t *fs) {
|
|||||||
seq_init = 1;
|
seq_init = 1;
|
||||||
highest_seq = seq;
|
highest_seq = seq;
|
||||||
} else if (seq > highest_seq + 1) {
|
} else if (seq > highest_seq + 1) {
|
||||||
|
// Пропавшие seq → NACK-набор обратного канала (этап 2) + счётчик потерь.
|
||||||
|
for (uint32_t m = highest_seq + 1; m < seq; m++) miss_add(m);
|
||||||
lost += seq - highest_seq - 1;
|
lost += seq - highest_seq - 1;
|
||||||
fprintf(stderr, "\n[ПОТЕРЯ] seq %u→%u, дыра %u кадров\n",
|
fprintf(stderr, "\n[ПОТЕРЯ] seq %u→%u, дыра %u кадров\n",
|
||||||
highest_seq, seq, seq - highest_seq - 1);
|
highest_seq, seq, seq - highest_seq - 1);
|
||||||
@@ -250,6 +323,7 @@ static void process_frame(fq_slot_t *fs) {
|
|||||||
highest_seq = seq;
|
highest_seq = seq;
|
||||||
}
|
}
|
||||||
// seq ≤ highest_seq: старый/повторный кадр — highest_seq не трогаем.
|
// seq ≤ highest_seq: старый/повторный кадр — highest_seq не трогаем.
|
||||||
|
miss_ack(seq); // кадр пришёл (в т.ч. ретрансмит старого seq) — снять из NACK
|
||||||
|
|
||||||
uint16_t original_len = (hdr[6] << 8) | hdr[7];
|
uint16_t original_len = (hdr[6] << 8) | hdr[7];
|
||||||
uint8_t last_block_bytes = hdr[10];
|
uint8_t last_block_bytes = hdr[10];
|
||||||
@@ -396,13 +470,28 @@ static void *dsp_thread(void *arg) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Поток C (ядро 1): RS-декод + CRC + запись из очереди кадров.
|
// Поток C (ядро 1): RS-декод + CRC + запись из очереди кадров + периодический
|
||||||
|
// STATUS обратного канала. Ждём кадр таймингованно (50 мс), чтобы STATUS уходил
|
||||||
|
// и в простое (именно тогда TX и нужно знать про дыры). maybe_send_status —
|
||||||
|
// no-op в симплексе (g_fb_fg==NULL), поэтому поведение без -F не меняется.
|
||||||
static void *fec_thread(void *arg) {
|
static void *fec_thread(void *arg) {
|
||||||
(void)arg;
|
(void)arg;
|
||||||
pin_to_cpu(1);
|
pin_to_cpu(1);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (!wait_slot(&fq.mtx, &fq.not_empty, &fq.count, &fq.eof))
|
pthread_mutex_lock(&fq.mtx);
|
||||||
break;
|
while (fq.count == 0 && !fq.eof && !stop) {
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
|
ts.tv_nsec += 50000000L; // 50 мс
|
||||||
|
if (ts.tv_nsec >= 1000000000L) { ts.tv_sec++; ts.tv_nsec -= 1000000000L; }
|
||||||
|
pthread_cond_timedwait(&fq.not_empty, &fq.mtx, &ts);
|
||||||
|
if (fq.count == 0 && !fq.eof && !stop) { // всё ещё пусто → STATUS без кадра
|
||||||
|
pthread_mutex_unlock(&fq.mtx);
|
||||||
|
maybe_send_status();
|
||||||
|
pthread_mutex_lock(&fq.mtx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fq.count == 0) { pthread_mutex_unlock(&fq.mtx); break; } // eof/stop
|
||||||
fq_slot_t *fs = &fq_slots[fq.head];
|
fq_slot_t *fs = &fq_slots[fq.head];
|
||||||
pthread_mutex_unlock(&fq.mtx);
|
pthread_mutex_unlock(&fq.mtx);
|
||||||
|
|
||||||
@@ -412,6 +501,8 @@ static void *fec_thread(void *arg) {
|
|||||||
fq.head = (fq.head + 1) % FQ_SLOTS;
|
fq.head = (fq.head + 1) % FQ_SLOTS;
|
||||||
fq.count--;
|
fq.count--;
|
||||||
pthread_mutex_unlock(&fq.mtx);
|
pthread_mutex_unlock(&fq.mtx);
|
||||||
|
|
||||||
|
maybe_send_status(); // под нагрузкой — период гейтит частоту
|
||||||
}
|
}
|
||||||
group_flush(); // дописать последнюю группу файла (нет кадра-триггера)
|
group_flush(); // дописать последнюю группу файла (нет кадра-триггера)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -457,7 +548,7 @@ int main(int argc, char *argv[]) {
|
|||||||
fprintf(stderr, "Усиление: %.0f дБ (ручное)\n", RX_GAIN);
|
fprintf(stderr, "Усиление: %.0f дБ (ручное)\n", RX_GAIN);
|
||||||
fprintf(stderr, "CFO фильтр: α=%.2f\n", CFO_FILTER_ALPHA);
|
fprintf(stderr, "CFO фильтр: α=%.2f\n", CFO_FILTER_ALPHA);
|
||||||
fprintf(stderr, "Дуплекс: %s\n", fb_freq
|
fprintf(stderr, "Дуплекс: %s\n", fb_freq
|
||||||
? "FDD (обратный TX настроен, трафик — этап 2)" : "симплекс");
|
? "FDD (обратный канал: передача STATUS на 868)" : "симплекс");
|
||||||
if (fb_freq) fprintf(stderr, "Обр. TX LO: %.1f МГц, усил. %.0f дБ\n", fb_freq / 1e6, fb_gain);
|
if (fb_freq) fprintf(stderr, "Обр. TX LO: %.1f МГц, усил. %.0f дБ\n", fb_freq / 1e6, fb_gain);
|
||||||
fprintf(stderr, "URI: %s\n", uri);
|
fprintf(stderr, "URI: %s\n", uri);
|
||||||
fprintf(stderr, "Конвейер: 3 стадии (A захват / B sync / C FEC+IO)\n");
|
fprintf(stderr, "Конвейер: 3 стадии (A захват / B sync / C FEC+IO)\n");
|
||||||
@@ -519,6 +610,27 @@ int main(int argc, char *argv[]) {
|
|||||||
// Тот же 12-байтовый заголовок, что и на TX (иначе байты 8..11 — мусор)
|
// Тот же 12-байтовый заголовок, что и на TX (иначе байты 8..11 — мусор)
|
||||||
ofdmflexframesync_set_header_len(fs, HDR_SIZE);
|
ofdmflexframesync_set_header_len(fs, HDR_SIZE);
|
||||||
|
|
||||||
|
// Обратный канал (этап 2, §10 п.7): framegen + буфер на feedback-TX (868).
|
||||||
|
// Кадры STATUS (F0 55) шлёт поток C через maybe_send_status. set_header_len
|
||||||
|
// 12 обязателен и здесь — иначе TX-сторона прочтёт заголовок как мусор.
|
||||||
|
if (fb_freq && txfb) {
|
||||||
|
iio_channel_enable(iio_device_find_channel(txfb, "voltage0", true));
|
||||||
|
iio_channel_enable(iio_device_find_channel(txfb, "voltage1", true));
|
||||||
|
g_fb_buf = iio_device_create_buffer(txfb, FB_BUF_SAMPLES, false);
|
||||||
|
if (!g_fb_buf) {
|
||||||
|
fprintf(stderr, "[warn] буфер обратного канала не создан — STATUS отключён\n");
|
||||||
|
} else {
|
||||||
|
ofdmflexframegenprops_s fp;
|
||||||
|
ofdmflexframegenprops_init_default(&fp);
|
||||||
|
fp.check = LIQUID_CRC_32;
|
||||||
|
g_fb_fg = ofdmflexframegen_create(OFDM_M, OFDM_CP, OFDM_TAPER, NULL, &fp);
|
||||||
|
ofdmflexframegen_set_header_len(g_fb_fg, HDR_SIZE);
|
||||||
|
g_fb_enc = fec_create(LIQUID_FEC_RS_M8, NULL);
|
||||||
|
fprintf(stderr, "Обратный канал: STATUS каждые %d мс (F0 55, RS+CRC32)\n",
|
||||||
|
FB_PERIOD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fprintf(stderr, "Приёмник готов. Ожидание сигнала...\n\n");
|
fprintf(stderr, "Приёмник готов. Ожидание сигнала...\n\n");
|
||||||
fprintf(stderr, "Кадры | Загол. | CRC | RS испр | RS бит | SigBd | Потери | Overr | FQdrp | Rec | GrpF | Вывод | RSSI | EVM | CFO\n");
|
fprintf(stderr, "Кадры | Загол. | CRC | RS испр | RS бит | SigBd | Потери | Overr | FQdrp | Rec | GrpF | Вывод | RSSI | EVM | CFO\n");
|
||||||
fprintf(stderr, "--------+--------+------+---------+--------+-------+--------+--------+--------+--------+--------+--------+-------+-------+----------\n");
|
fprintf(stderr, "--------+--------+------+---------+--------+-------+--------+--------+--------+--------+--------+--------+-------+-------+----------\n");
|
||||||
@@ -565,10 +677,15 @@ int main(int argc, char *argv[]) {
|
|||||||
(unsigned long long)ring.overruns, (unsigned long long)fq.drops,
|
(unsigned long long)ring.overruns, (unsigned long long)fq.drops,
|
||||||
(unsigned long long)grp_recovered, (unsigned long long)grp_failed,
|
(unsigned long long)grp_recovered, (unsigned long long)grp_failed,
|
||||||
(unsigned long long)crc_final, (unsigned long long)total_bytes);
|
(unsigned long long)crc_final, (unsigned long long)total_bytes);
|
||||||
|
if (fb_freq)
|
||||||
|
fprintf(stderr, "Обратный канал: отправлено STATUS: %u\n", g_fb_seq);
|
||||||
|
|
||||||
if (decoder) fec_destroy(decoder);
|
if (decoder) fec_destroy(decoder);
|
||||||
ofdmflexframesync_destroy(fs);
|
ofdmflexframesync_destroy(fs);
|
||||||
iio_buffer_destroy(buf);
|
iio_buffer_destroy(buf);
|
||||||
|
if (g_fb_fg) ofdmflexframegen_destroy(g_fb_fg);
|
||||||
|
if (g_fb_enc) fec_destroy(g_fb_enc);
|
||||||
|
if (g_fb_buf) iio_buffer_destroy(g_fb_buf);
|
||||||
iio_context_destroy(ctx);
|
iio_context_destroy(ctx);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "group_fec.h"
|
#include "group_fec.h"
|
||||||
#include "frame_tx.h"
|
#include "frame_tx.h"
|
||||||
|
#include "feedback.h" // приём STATUS обратного канала (этап 2, §10 п.7)
|
||||||
|
|
||||||
// Параметры передатчика по умолчанию
|
// Параметры передатчика по умолчанию
|
||||||
#define DEFAULT_GAIN -30.0 // Усиление передатчика: -30 дБ
|
#define DEFAULT_GAIN -30.0 // Усиление передатчика: -30 дБ
|
||||||
@@ -24,6 +25,63 @@ extern volatile sig_atomic_t stop;
|
|||||||
// hdr[0..1]; END/STATUS обратного канала используют другие сигнатуры, §10 п.7).
|
// hdr[0..1]; END/STATUS обратного канала используют другие сигнатуры, §10 п.7).
|
||||||
static const uint8_t SIG_DATA[2] = { HDR_SIGNATURE_0, HDR_SIGNATURE_1 };
|
static const uint8_t SIG_DATA[2] = { HDR_SIGNATURE_0, HDR_SIGNATURE_1 };
|
||||||
|
|
||||||
|
// --- Обратный канал: приём STATUS на 868 (этап 2, §10 п.7) --------------
|
||||||
|
// Отдельный поток на ядре 1 (данные TX — ядро 0). Свой синхронизатор с
|
||||||
|
// header_len 12 и свой RS-декодер. Пока ТОЛЬКО логирует; ARQ-реакция (ретрансмит
|
||||||
|
// по NACK) — этап 3. Слушает rxfb — второй cf-девайс, открытый pluto_init_fdd.
|
||||||
|
#define FB_RX_BUF_SAMPLES 16384
|
||||||
|
|
||||||
|
static struct iio_buffer *g_fbrx_buf = NULL;
|
||||||
|
static ofdmflexframesync g_fbrx_fs = NULL;
|
||||||
|
static fec g_fbrx_dec = NULL;
|
||||||
|
static volatile sig_atomic_t fb_stop = 0;
|
||||||
|
static uint64_t g_fb_status_rx = 0; // сколько STATUS декодировано (для итога)
|
||||||
|
|
||||||
|
// callback синхронизатора обратного канала: отсев по сигнатуре F0 55, RS+CRC
|
||||||
|
// декод, распаковка и лог. Этап 2 — без действий над состоянием передачи.
|
||||||
|
static int fb_callback(unsigned char *hdr, int hdr_valid,
|
||||||
|
unsigned char *pay, unsigned int pay_len,
|
||||||
|
int pay_valid, framesyncstats_s stats, void *user) {
|
||||||
|
(void)pay_valid; (void)stats; (void)user;
|
||||||
|
if (!hdr_valid) return 0;
|
||||||
|
if (hdr[0] != FB_SIG_0 || hdr[1] != FB_SIG_1) return 0; // не STATUS
|
||||||
|
|
||||||
|
static uint8_t dec[RS_ENC * 2]; // STATUS ≤ 1 RS-блок, запас на будущее
|
||||||
|
size_t dlen = 0;
|
||||||
|
if (!phy_rx_decode(hdr, pay, pay_len, g_fbrx_dec, dec, sizeof(dec), &dlen))
|
||||||
|
return 0; // RS/CRC не прошли — молча дропаем
|
||||||
|
fb_status_t st;
|
||||||
|
if (fb_status_unpack(dec, dlen, &st) != 0) return 0;
|
||||||
|
|
||||||
|
g_fb_status_rx++;
|
||||||
|
fprintf(stderr, "\n[FB] highest=%u rx_frames=%u overruns=%u nack=%u{",
|
||||||
|
st.highest_seq, st.rx_frames, st.overruns, st.nack_cnt);
|
||||||
|
for (int i = 0; i < st.nack_cnt && i < 8; i++)
|
||||||
|
fprintf(stderr, "%s%u", i ? "," : "", st.nack[i]);
|
||||||
|
if (st.nack_cnt > 8) fprintf(stderr, ",…");
|
||||||
|
fprintf(stderr, "}\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *fb_rx_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
|
pin_to_cpu(1);
|
||||||
|
static liquid_float_complex conv[FB_RX_BUF_SAMPLES];
|
||||||
|
while (!fb_stop && !stop) {
|
||||||
|
ssize_t nb = iio_buffer_refill(g_fbrx_buf);
|
||||||
|
if (nb < 0) { if (fb_stop || stop) break; usleep(500); continue; }
|
||||||
|
int16_t *bb = (int16_t *)iio_buffer_start(g_fbrx_buf);
|
||||||
|
int ns = (iio_buffer_end(g_fbrx_buf) - iio_buffer_start(g_fbrx_buf))
|
||||||
|
/ (int)sizeof(int16_t) / 2;
|
||||||
|
if (ns > FB_RX_BUF_SAMPLES) ns = FB_RX_BUF_SAMPLES;
|
||||||
|
for (int i = 0; i < ns; i++)
|
||||||
|
conv[i] = ((float)bb[2*i] / 2048.0f) +
|
||||||
|
((float)bb[2*i+1] / 2048.0f) * _Complex_I;
|
||||||
|
ofdmflexframesync_execute(g_fbrx_fs, conv, ns);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
// Инициализация параметров
|
// Инициализация параметров
|
||||||
long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
|
long long freq = DEFAULT_FREQ, rate = DEFAULT_RATE, bw = DEFAULT_BW;
|
||||||
@@ -74,7 +132,7 @@ int main(int argc, char *argv[]) {
|
|||||||
fprintf(stderr, "Пауза: %d мкс\n", pause_us);
|
fprintf(stderr, "Пауза: %d мкс\n", pause_us);
|
||||||
fprintf(stderr, "Group-FEC: %s\n", group_fec ? "16+2 (§10 п.6)" : "выкл");
|
fprintf(stderr, "Group-FEC: %s\n", group_fec ? "16+2 (§10 п.6)" : "выкл");
|
||||||
fprintf(stderr, "Дуплекс: %s\n", fb_freq
|
fprintf(stderr, "Дуплекс: %s\n", fb_freq
|
||||||
? "FDD (обратный RX настроен, трафик — этап 2)" : "симплекс");
|
? "FDD (обратный канал: приём STATUS на 868)" : "симплекс");
|
||||||
if (fb_freq) fprintf(stderr, "Обр. RX LO: %.1f МГц\n", fb_freq / 1e6);
|
if (fb_freq) fprintf(stderr, "Обр. RX LO: %.1f МГц\n", fb_freq / 1e6);
|
||||||
fprintf(stderr, "URI: %s\n", uri);
|
fprintf(stderr, "URI: %s\n", uri);
|
||||||
fprintf(stderr, "═══════════════════════════════════════════════════\n");
|
fprintf(stderr, "═══════════════════════════════════════════════════\n");
|
||||||
@@ -122,6 +180,29 @@ int main(int argc, char *argv[]) {
|
|||||||
// last_block_bytes) без этого в эфир не уходят. RX выставляет то же.
|
// last_block_bytes) без этого в эфир не уходят. RX выставляет то же.
|
||||||
ofdmflexframegen_set_header_len(fg, HDR_SIZE);
|
ofdmflexframegen_set_header_len(fg, HDR_SIZE);
|
||||||
|
|
||||||
|
// Обратный канал (этап 2): поток fb_rx слушает STATUS на 868 (rxfb). Пока
|
||||||
|
// только логирует. Данные TX уводим на ядро 0, fb_rx — на ядро 1 (§affinity).
|
||||||
|
pthread_t fb_tid;
|
||||||
|
int fb_started = 0;
|
||||||
|
if (fb_freq) {
|
||||||
|
iio_channel_enable(iio_device_find_channel(rxfb, "voltage0", false));
|
||||||
|
iio_channel_enable(iio_device_find_channel(rxfb, "voltage1", false));
|
||||||
|
g_fbrx_buf = iio_device_create_buffer(rxfb, FB_RX_BUF_SAMPLES, false);
|
||||||
|
if (!g_fbrx_buf) {
|
||||||
|
LOG_ERROR("Буфер обратного канала не создан — STATUS не принимаются");
|
||||||
|
} else {
|
||||||
|
g_fbrx_fs = ofdmflexframesync_create(OFDM_M, OFDM_CP, OFDM_TAPER,
|
||||||
|
NULL, fb_callback, NULL);
|
||||||
|
ofdmflexframesync_set_header_len(g_fbrx_fs, HDR_SIZE);
|
||||||
|
g_fbrx_dec = fec_create(LIQUID_FEC_RS_M8, NULL);
|
||||||
|
pin_to_cpu(0); // TX-данные — ядро 0
|
||||||
|
pthread_create(&fb_tid, NULL, fb_rx_thread, NULL);
|
||||||
|
fb_started = 1;
|
||||||
|
fprintf(stderr, "Обратный канал: слушаю STATUS на %.1f МГц (ядро 1)\n",
|
||||||
|
fb_freq / 1e6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Счётчики статистики
|
// Счётчики статистики
|
||||||
uint64_t frames = 0, bytes_tx = 0;
|
uint64_t frames = 0, bytes_tx = 0;
|
||||||
uint32_t seq = 0;
|
uint32_t seq = 0;
|
||||||
@@ -207,10 +288,22 @@ int main(int argc, char *argv[]) {
|
|||||||
usleep(100000); // ~4 kernel-буфера при 1.92 MSPS ≈ 35 мс, с запасом
|
usleep(100000); // ~4 kernel-буфера при 1.92 MSPS ≈ 35 мс, с запасом
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Остановить обратный канал: fb_rx выходит по fb_stop в течение одного
|
||||||
|
// refill (~8.5 мс на 16384 сэмпл @ 1.92 Msps), затем join.
|
||||||
|
if (fb_started) {
|
||||||
|
fb_stop = 1;
|
||||||
|
pthread_join(fb_tid, NULL);
|
||||||
|
fprintf(stderr, "Обратный канал: принято STATUS: %llu\n",
|
||||||
|
(unsigned long long)g_fb_status_rx);
|
||||||
|
}
|
||||||
|
|
||||||
// Освобождение ресурсов
|
// Освобождение ресурсов
|
||||||
if (enc) fec_destroy(enc);
|
if (enc) fec_destroy(enc);
|
||||||
ofdmflexframegen_destroy(fg);
|
ofdmflexframegen_destroy(fg);
|
||||||
iio_buffer_destroy(buf);
|
iio_buffer_destroy(buf);
|
||||||
|
if (g_fbrx_fs) ofdmflexframesync_destroy(g_fbrx_fs);
|
||||||
|
if (g_fbrx_dec) fec_destroy(g_fbrx_dec);
|
||||||
|
if (g_fbrx_buf) iio_buffer_destroy(g_fbrx_buf);
|
||||||
iio_context_destroy(ctx);
|
iio_context_destroy(ctx);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user