fix: Pluto TX buffer scaled by sample-rate (SoapyPlutoSDR model)

Fixed 4096-pair TX buffer was too small for full-duplex above ~1 Msps
("not enough ... samples getting dropped" per SoapyPlutoSDR), causing
clicks/dropouts. Now sized as round(rate/60) rounded up to a power of 2,
clamped [4096..65536] (576k→16384, 1536k→32768). FIFO cushion (~4 buffers)
resizes to match in StartTX.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 20:20:41 +03:00
co-authored by Claude Opus 4.8
parent 3fe5cf3fad
commit 071ffa476d
+30 -4
View File
@@ -68,6 +68,7 @@ type
FTxThread: TThread; FTxThread: TThread;
FTxStreaming: Boolean; FTxStreaming: Boolean;
FTxRatio: Integer; // FSampleRate div 192000 (целочисленный апсемпл) FTxRatio: Integer; // FSampleRate div 192000 (целочисленный апсемпл)
FTxBufPairs: Integer; // размер TX-буфера в IQ-парах (по sample-rate, см. TxBufPairsForRate)
FTxPrevI: LongInt; // последний 24-бит сэмпл (линейная интерполяция между вызовами) FTxPrevI: LongInt; // последний 24-бит сэмпл (линейная интерполяция между вызовами)
FTxPrevQ: LongInt; FTxPrevQ: LongInt;
// FIFO int16-пар @ device-rate (кольцо): продюсер — SendDUCIQ, консьюмер — push-поток. // FIFO int16-пар @ device-rate (кольцо): продюсер — SendDUCIQ, консьюмер — push-поток.
@@ -140,7 +141,11 @@ const
RX_BUF_SAMPLES = 8192; // IQ-пар за один refill (~5.3 мс @ 1.536 MS/s) RX_BUF_SAMPLES = 8192; // IQ-пар за один refill (~5.3 мс @ 1.536 MS/s)
PAIRS_PER_PKT = 238; // как DDC IQ HPSDR (238*6 = 1428 байт IQData) PAIRS_PER_PKT = 238; // как DDC IQ HPSDR (238*6 = 1428 байт IQData)
TX_SRC_RATE = 192000; // фиксированный TX-рейт WDSP (TX_SAMPLE_RATE) TX_SRC_RATE = 192000; // фиксированный TX-рейт WDSP (TX_SAMPLE_RATE)
TX_BUF_PAIRS = 4096; // IQ-пар в одном TX-буфере push (~2.7 мс @ 1.536 MS/s) // Размер TX-буфера масштабируется от sample-rate (как SoapyPlutoSDR):
// round(rate/60) → следующая степень 2. Фикс 4096 «недостаточно для full-duplex
// выше 1 Msps — сэмплы дропаются» (комментарий драйвера). Минимум — TX_BUF_MIN.
TX_BUF_MIN = 4096; // нижняя граница (~7 мс @ 576k)
TX_BUF_MAX = 65536; // верхняя граница (защита от гигантских буферов)
PLUTO_TX_OFF = -89.75; // макс. аттенюация AD9361 — TX практически выключен PLUTO_TX_OFF = -89.75; // макс. аттенюация AD9361 — TX практически выключен
type type
@@ -285,6 +290,21 @@ begin
end; end;
end; end;
function TxBufPairsForRate(Rate: Integer): Integer;
// Размер TX-буфера в IQ-парах под device-rate (как SoapyPlutoSDR): round(rate/60)
// округлённое вверх до степени 2, с клампом [TX_BUF_MIN..TX_BUF_MAX].
// 576k→16384, 1536k→32768, 3072k→65536.
var
Target, P: Integer;
begin
Target := (Rate + 30) div 60; // round(rate/60)
P := TX_BUF_MIN;
while P < Target do P := P shl 1; // следующая степень 2 ≥ Target
if P < TX_BUF_MIN then P := TX_BUF_MIN;
if P > TX_BUF_MAX then P := TX_BUF_MAX;
Result := P;
end;
function PlutoSyntheticMAC(const S: string): TBytes; function PlutoSyntheticMAC(const S: string): TBytes;
var var
H: QWord; H: QWord;
@@ -323,8 +343,10 @@ begin
FTxBuf := nil; FTxChI := nil; FTxChQ := nil; FTxThread := nil; FTxBuf := nil; FTxChI := nil; FTxChQ := nil; FTxThread := nil;
FTxStreaming := False; FTxRatio := 8; FTxPrevI := 0; FTxPrevQ := 0; FTxStreaming := False; FTxRatio := 8; FTxPrevI := 0; FTxPrevQ := 0;
FTxAttDb := PLUTO_TX_OFF; // на старте TX выключен (макс. аттенюация) FTxAttDb := PLUTO_TX_OFF; // на старте TX выключен (макс. аттенюация)
// FIFO на ~4 TX-буфера запаса под джиттер продюсера/консьюмера. // FIFO на ~4 TX-буфера запаса под джиттер продюсера/консьюмера. Окончательный
FTxFifoCap := TX_BUF_PAIRS * 2 * 4; // размер выставляется в StartTX по фактическому device-rate (TxBufPairsForRate).
FTxBufPairs := TxBufPairsForRate(FSampleRate);
FTxFifoCap := FTxBufPairs * 2 * 4;
SetLength(FTxFifo, FTxFifoCap); SetLength(FTxFifo, FTxFifoCap);
FTxFifoHead := 0; FTxFifoTail := 0; FTxFifoCount := 0; FTxFifoHead := 0; FTxFifoTail := 0; FTxFifoCount := 0;
InitCriticalSection(FTxLock); InitCriticalSection(FTxLock);
@@ -645,10 +667,14 @@ begin
FTxRatio := FSampleRate div TX_SRC_RATE; FTxRatio := FSampleRate div TX_SRC_RATE;
if FTxRatio < 1 then FTxRatio := 1; if FTxRatio < 1 then FTxRatio := 1;
FTxPrevI := 0; FTxPrevQ := 0; FTxPrevI := 0; FTxPrevQ := 0;
// Размер push-буфера под текущий rate + FIFO на ~4 таких буфера запаса.
FTxBufPairs := TxBufPairsForRate(FSampleRate);
EnterCriticalSection(FTxLock); EnterCriticalSection(FTxLock);
FTxFifoCap := FTxBufPairs * 2 * 4;
if Length(FTxFifo) <> FTxFifoCap then SetLength(FTxFifo, FTxFifoCap);
FTxFifoHead := 0; FTxFifoTail := 0; FTxFifoCount := 0; FTxFifoHead := 0; FTxFifoTail := 0; FTxFifoCount := 0;
LeaveCriticalSection(FTxLock); LeaveCriticalSection(FTxLock);
FTxBuf := iio_device_create_buffer(FTxDev, TX_BUF_PAIRS, False); FTxBuf := iio_device_create_buffer(FTxDev, FTxBufPairs, False);
if FTxBuf = nil then begin FLastError := 'TX create_buffer failed'; Exit; end; if FTxBuf = nil then begin FLastError := 'TX create_buffer failed'; Exit; end;
ApplyTxAtten; // стартуем с текущей аттенюации (по умолчанию OFF до PTT) ApplyTxAtten; // стартуем с текущей аттенюации (по умолчанию OFF до PTT)
FTxStreaming := True; FTxStreaming := True;