mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
fix(pluto): телеметрия больше не блокирует UI-поток
ReadTelemetry делал 5-6 синхронных iio_channel_attr_read_* — каждое это USB round-trip, вместе ~30 мс. Звался из MeterTimerTick, то есть прямо в главном потоке, дважды в секунду (POLL_MS=500). Замер зонным профайлером: тик метра занимал 5633 мкс настенного времени при пиках до 51 мс, тогда как CPU-времени в нём было всего 140 мкс — чистое ожидание USB, ронявшее кадры. Опрос переехал в TPlutoTeleThread: он раз в 500 мс читает те же атрибуты и публикует снимок под FTeleLock. ReadTelemetry теперь только отдаёт снимок и никогда не блокирует, поэтому RadioController.ServicePlutoTelemetry и MainForm.MeterTimerTick не менялись — вся фильтрация температуры на месте. Прежнее блокирующее тело стало PollTelemetryBlocking (только из потока). Конкуренция за контекст libiio не выросла: UI-поток и раньше читал атрибуты параллельно с iio_buffer_refill в RX-потоке. Сменился лишь тот, кто ждёт. StopTelemetry идёт первой строкой Disconnect — до iio_context_destroy, иначе поток продолжил бы читать атрибуты разрушенного контекста. Результат: ui.meter_tick 5633 -> 110 мкс. Значение телеметрии теперь может отставать до секунды (троттл контроллера + период потока) — на 10-герцовом индикаторе температуры это незаметно. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+119
-3
@@ -82,6 +82,21 @@ type
|
||||
FTxFifoCount: Integer; // занято элементов
|
||||
FTxLock: TRTLCriticalSection;
|
||||
|
||||
// Телеметрия. Каждый iio_channel_attr_read_* — USB round-trip (~5 мс), а их
|
||||
// 5-6 за опрос: ~30 мс. Раньше это делал UI-поток (MeterTimerTick, 10 Гц) и
|
||||
// замирал на весь опрос. Теперь опрашивает свой поток, а ReadTelemetry лишь
|
||||
// отдаёт последний снимок и никогда не блокирует.
|
||||
FTeleThread: TThread;
|
||||
FTeleLock: TRTLCriticalSection;
|
||||
FTeleTemp: Double; // TELEMETRY_NONE, пока не прочитано
|
||||
FTeleSoc: Double;
|
||||
FTeleRssi: Double;
|
||||
FTeleValid: Boolean; // был ли хоть один успешный опрос
|
||||
procedure StartTelemetry;
|
||||
procedure StopTelemetry;
|
||||
// Блокирующий опрос железа: только из FTeleThread.
|
||||
function PollTelemetryBlocking(out TempC, SocTempC, RSSIdB: Double): Boolean;
|
||||
|
||||
// Низкоуровневые помощники применения атрибутов к ad9361-phy
|
||||
procedure ApplyRxLO(Hz: Double);
|
||||
procedure ApplyTxLO(Hz: Double);
|
||||
@@ -168,6 +183,19 @@ type
|
||||
constructor Create(ABk: TPlutoBackend);
|
||||
end;
|
||||
|
||||
// Телеметрия: опрашивает железо в своём темпе и публикует снимок. Держит UI
|
||||
// подальше от блокирующих USB-транзакций. Доступ к контексту libiio здесь не
|
||||
// более конкурентен, чем раньше: UI-поток дёргал те же атрибуты параллельно с
|
||||
// refill в RX-потоке — сменился только поток, который ждёт.
|
||||
TPlutoTeleThread = class(TThread)
|
||||
private
|
||||
FBk: TPlutoBackend;
|
||||
protected
|
||||
procedure Execute; override;
|
||||
public
|
||||
constructor Create(ABk: TPlutoBackend);
|
||||
end;
|
||||
|
||||
// TX-поток: тянет int16-пары из FIFO бэкенда (или нули при underrun), пишет в
|
||||
// iio-буфер и push'ит. iio_buffer_push блокирует до приёма DMA → пейсинг ровно
|
||||
// на device-rate. Работает с private-полями TPlutoBackend (одна единица компиляции).
|
||||
@@ -350,6 +378,9 @@ begin
|
||||
FStreaming := False; FRxSeq := 0;
|
||||
FTxBuf := nil; FTxChI := nil; FTxChQ := nil; FTxThread := nil;
|
||||
FTxStreaming := False; FTxRatio := 8;
|
||||
FTeleThread := nil; FTeleValid := False;
|
||||
FTeleTemp := TELEMETRY_NONE; FTeleSoc := TELEMETRY_NONE; FTeleRssi := TELEMETRY_NONE;
|
||||
InitCriticalSection(FTeleLock);
|
||||
FTxAttDb := PLUTO_TX_OFF; // на старте TX выключен (макс. аттенюация)
|
||||
// FIFO на ~4 TX-буфера запаса под джиттер продюсера/консьюмера. Окончательный
|
||||
// размер выставляется в StartTX по фактическому device-rate (TxBufPairsForRate).
|
||||
@@ -363,7 +394,8 @@ end;
|
||||
|
||||
destructor TPlutoBackend.Destroy;
|
||||
begin
|
||||
Disconnect;
|
||||
Disconnect; // остановит и телеметрию
|
||||
DoneCriticalSection(FTeleLock);
|
||||
DoneCriticalSection(FTxLock);
|
||||
inherited Destroy;
|
||||
end;
|
||||
@@ -535,11 +567,13 @@ begin
|
||||
ApplySampleRate(FSampleRate);
|
||||
ApplyGain;
|
||||
ApplyTxAtten; // TX заглушён (макс. аттенюация) до явного PTT
|
||||
StartTelemetry;
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
procedure TPlutoBackend.Disconnect;
|
||||
begin
|
||||
StopTelemetry; // до iio_context_destroy: поток ещё читает атрибуты
|
||||
StopTX;
|
||||
StopRX;
|
||||
if FCtx <> nil then
|
||||
@@ -904,8 +938,8 @@ begin
|
||||
LeaveCriticalSection(FTxLock);
|
||||
end;
|
||||
|
||||
function TPlutoBackend.ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean;
|
||||
// Опрос аппаратной телеметрии (вызывается контроллером, не из RX-потока):
|
||||
function TPlutoBackend.PollTelemetryBlocking(out TempC, SocTempC, RSSIdB: Double): Boolean;
|
||||
// БЛОКИРУЮЩИЙ опрос железа — только из FTeleThread (каждое чтение = USB round-trip):
|
||||
// ad9361-phy temp0/input — температура трансивера в миллиградусах C (longlong);
|
||||
// xadc temp0 — температура Zynq SoC/FPGA; сырой raw + offset (longlong)
|
||||
// + scale (double), C = (raw + offset) * scale / 1000;
|
||||
@@ -963,4 +997,86 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function TPlutoBackend.ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean;
|
||||
// Неблокирующий: отдаёт последний снимок, снятый FTeleThread. Безопасно звать из UI.
|
||||
begin
|
||||
EnterCriticalSection(FTeleLock);
|
||||
try
|
||||
TempC := FTeleTemp;
|
||||
SocTempC := FTeleSoc;
|
||||
RSSIdB := FTeleRssi;
|
||||
Result := FTeleValid;
|
||||
finally
|
||||
LeaveCriticalSection(FTeleLock);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TPlutoBackend.StartTelemetry;
|
||||
begin
|
||||
if FTeleThread = nil then
|
||||
FTeleThread := TPlutoTeleThread.Create(Self);
|
||||
end;
|
||||
|
||||
procedure TPlutoBackend.StopTelemetry;
|
||||
begin
|
||||
if FTeleThread = nil then Exit;
|
||||
FTeleThread.Terminate;
|
||||
FTeleThread.WaitFor; // обязателен до разрушения контекста libiio
|
||||
FreeAndNil(FTeleThread);
|
||||
EnterCriticalSection(FTeleLock);
|
||||
try
|
||||
FTeleValid := False;
|
||||
FTeleTemp := TELEMETRY_NONE;
|
||||
FTeleSoc := TELEMETRY_NONE;
|
||||
FTeleRssi := TELEMETRY_NONE;
|
||||
finally
|
||||
LeaveCriticalSection(FTeleLock);
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TPlutoTeleThread }
|
||||
|
||||
constructor TPlutoTeleThread.Create(ABk: TPlutoBackend);
|
||||
begin
|
||||
FBk := ABk;
|
||||
FreeOnTerminate := False;
|
||||
inherited Create(False);
|
||||
end;
|
||||
|
||||
procedure TPlutoTeleThread.Execute;
|
||||
const
|
||||
POLL_MS = 500; // ~2 Гц: чаще незачем, каждый опрос ~30 мс USB-транзакций
|
||||
STEP_MS = 25; // мелкий шаг сна — чтобы Terminate отрабатывал быстро
|
||||
var
|
||||
t, s, r: Double;
|
||||
ok: Boolean;
|
||||
Waited: Integer;
|
||||
begin
|
||||
while not Terminated do
|
||||
begin
|
||||
if FBk.FConnected then
|
||||
begin
|
||||
ok := FBk.PollTelemetryBlocking(t, s, r);
|
||||
if ok then
|
||||
begin
|
||||
EnterCriticalSection(FBk.FTeleLock);
|
||||
try
|
||||
FBk.FTeleTemp := t;
|
||||
FBk.FTeleSoc := s;
|
||||
FBk.FTeleRssi := r;
|
||||
FBk.FTeleValid := True;
|
||||
finally
|
||||
LeaveCriticalSection(FBk.FTeleLock);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
Waited := 0;
|
||||
while (Waited < POLL_MS) and not Terminated do
|
||||
begin
|
||||
Sleep(STEP_MS);
|
||||
Inc(Waited, STEP_MS);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user