fix TX meter readout: правильная SWR-формула + EMA + троттлинг UI

- SWR: было 1+sqrt(Pr/Pf) — теперь (1+ρ)/(1-ρ). Считается только при
  FwdW>=1W и HPS_PTT=1, иначе 1.0. Cap 9.9.
- На RX FwdW=0 (ADC даёт шум).
- EMA-сглаживание (α=0.30/0.20/0.05) в DoUpdateStatus на UI-потоке.
- LblFwdPwr/LblSWRVal и StatusBar Supply/FWD/SWR обновляются из
  MeterTimerTick (10 Гц), а не на rate HP Status (~150/с).
- PbFwdPower/PbSWR теперь Invalidate в таймере — бары двигаются.
This commit is contained in:
2026-05-06 19:34:57 +03:00
parent 006cf772b8
commit 9bf21f6971
+80 -19
View File
@@ -203,6 +203,10 @@ type
FLastDDCIndex: Integer; // последний DDC index
FLastFwdW: Double;
FLastSWR: Double;
// Эти значения приходят с HP Status пакетами (50..200 раз/с). UI обновляется
// из MeterTimerTick (10 Гц), чтобы цифры/полоски не дёргались.
FLastSupplyV: Double;
FLastPLLLock: Boolean;
// ---- Spectrum / Waterfall view ----
FSpecView: TSpectrumView;
@@ -391,6 +395,7 @@ type
// Public UI update (called from sync objects)
procedure DoAddDevice(const Dev: THPSDRDevice; const Entry: string);
procedure DoUpdateStatus(FwdW, SWRV, SupplyV: Double; PLLLock, HWPTT: Boolean);
procedure UpdateTXMeters;
procedure DoUpdateDDCSeq(DDCIdx: Integer; Seq: LongWord);
procedure DoUpdateDDCSeqOrNoDevice(DDCIdx: Integer; Seq: LongWord);
@@ -1106,6 +1111,8 @@ begin
FLastDDCIndex := 0;
FLastFwdW := 0;
FLastSWR := 1;
FLastSupplyV:= -1;
FLastPLLLock:= False;
FDeviceCount := 0;
FDeviceDialog := TDeviceDialog.Create(Self);
FDeviceDialog.OnDiscover := BtnDiscoverFromDialog;
@@ -2584,6 +2591,9 @@ begin
FSpecView.LastSWR := FLastSWR;
if PbSMeterRight <> nil then PbSMeterRight.Invalidate;
// PWR/SWR метки и бары обновляем здесь (10 Гц), а не в DoUpdateStatus —
// HP Status пакеты идут ~150 раз/с, при таком rate цифры дёргаются.
UpdateTXMeters;
// Диагностика TX mic-пути — в отдельной панели [5], чтобы её не перетирали
// другие источники (DDC seq, audio-логи и т.п.).
@@ -2791,9 +2801,15 @@ begin
end;
procedure TMainForm.OnHPStatusCB(const Status: THighPriorityStatus);
const
// Порог fwd-мощности (W), ниже которого SWR не вычисляем — там всё ADC-шум.
SWR_MIN_FWD_W = 1.0;
// Верхняя граница SWR (выше — это всё равно «беда»).
SWR_CAP = 9.9;
var
ExcPwr, FwdPwr, RevPwr: Word;
SupplyV, FwdW, SWRV: Double;
SupplyV, FwdW, SWRV, Rho: Double;
IsTx: Boolean;
Sync: TStatusUISync;
M: TThreadMethod;
begin
@@ -2813,10 +2829,24 @@ begin
// FLastSMeter обновляется только из WDSP (GetSMeterDBm) в SpectrumTimerTick —
// ExciterPwr это мощность TX, не уровень принятого сигнала.
FwdW := ADCToWatts100(FwdPwr);
if FwdW > 0 then
SWRV := 1 + Sqrt(RevPwr / FwdPwr)
else
IsTx := (Status.StatusBits and HPS_PTT) <> 0;
// SWR: правильная формула (1+ρ)/(1-ρ), считаем только когда есть значимая
// прямая мощность — иначе ADC-шум RevPwr даёт случайные «прыжки» SWR.
// Не на передаче — SWR не показываем (фиксируем 1.0), Fwd принудительно 0.
if IsTx and (FwdW >= SWR_MIN_FWD_W) and (FwdPwr > 0) then
begin
Rho := Sqrt(RevPwr / FwdPwr);
if Rho >= 0.99 then
SWRV := SWR_CAP
else
begin
SWRV := (1.0 + Rho) / (1.0 - Rho);
if SWRV > SWR_CAP then SWRV := SWR_CAP;
if SWRV < 1.0 then SWRV := 1.0;
end;
end else
SWRV := 1.0;
if not IsTx then FwdW := 0;
Sync := TStatusUISync.Create(Self, FwdW, SWRV, SupplyV,
(Status.StatusBits and HPS_PLL_LOCKED) <> 0,
(Status.StatusBits and HPS_PTT) <> 0);
@@ -2829,24 +2859,30 @@ begin
end;
procedure TMainForm.DoUpdateStatus(FwdW, SWRV, SupplyV: Double; PLLLock, HWPTT: Boolean);
var
PLLStr: string;
const
// EMA-сглаживание (Thetis/piHPSDR-style): значения с ADC шумят, особенно
// RevPwr на низкой мощности. Эти константы подобраны на rate ~150 пакетов/с.
ALPHA_FWD = 0.30; // быстрый отклик для текущей мощности
ALPHA_SWR = 0.20; // SWR более инерционный
ALPHA_SUPPLY = 0.05; // напряжение питания меняется медленно
begin
FLastFwdW := FwdW;
FLastSWR := SWRV;
LblFwdPwr.Caption := Format('%.0fW', [FwdW]);
LblSWRVal.Caption := Format('SWR:%.1f', [SWRV]);
if PLLLock then PLLStr := 'PLL OK' else PLLStr := 'PLL?';
// Запоминаем значения; вывод в UI (метки, бары, статусбар) делает
// MeterTimerTick на 10 Гц — иначе цифры дёргаются по частоте HP Status (~150/с).
// EMA-сглаживание здесь же (на UI-потоке через Synchronize, безопасно для
// чтения из MeterTimerTick).
FLastFwdW := ALPHA_FWD * FwdW + (1.0 - ALPHA_FWD) * FLastFwdW;
FLastSWR := ALPHA_SWR * SWRV + (1.0 - ALPHA_SWR) * FLastSWR;
if SupplyV >= 0 then
StatusBar1.Panels[2].Text :=
Format('Supply: %.1fV | FWD: %.0fW | SWR: %.1f | %s',
[SupplyV, FwdW, SWRV, PLLStr])
else
StatusBar1.Panels[2].Text :=
Format('FWD: %.0fW | SWR: %.1f | %s',
[FwdW, SWRV, PLLStr]);
begin
if FLastSupplyV < 0 then
FLastSupplyV := SupplyV // первое значение — без сглаживания
else
FLastSupplyV := ALPHA_SUPPLY * SupplyV + (1.0 - ALPHA_SUPPLY) * FLastSupplyV;
end;
FLastPLLLock := PLLLock;
// Hardware PTT (foot switch / mic PTT) — обнаружение фронта
// Hardware PTT (foot switch / mic PTT) — обнаружение фронта (нужно делать
// на rate HP Status, а не таймера, чтобы не пропустить короткое нажатие).
if HWPTT <> FHWPTTActive then
begin
FHWPTTActive := HWPTT;
@@ -2857,6 +2893,31 @@ begin
end;
end;
procedure TMainForm.UpdateTXMeters;
// Вызывается из MeterTimerTick (10 Гц). Использует FLastFwdW/FLastSWR/
// FLastSupplyV/FLastPLLLock — они обновляются на rate HP Status (~150/с)
// в DoUpdateStatus.
var
PLLStr: string;
begin
if LblFwdPwr <> nil then
LblFwdPwr.Caption := Format('%.0fW', [FLastFwdW]);
if LblSWRVal <> nil then
LblSWRVal.Caption := Format('SWR:%.1f', [FLastSWR]);
if PbFwdPower <> nil then PbFwdPower.Invalidate;
if PbSWR <> nil then PbSWR.Invalidate;
if FLastPLLLock then PLLStr := 'PLL OK' else PLLStr := 'PLL?';
if FLastSupplyV >= 0 then
StatusBar1.Panels[2].Text :=
Format('Supply: %.1fV | FWD: %.0fW | SWR: %.1f | %s',
[FLastSupplyV, FLastFwdW, FLastSWR, PLLStr])
else
StatusBar1.Panels[2].Text :=
Format('FWD: %.0fW | SWR: %.1f | %s',
[FLastFwdW, FLastSWR, PLLStr]);
end;
procedure TMainForm.OnDDCIQCB(DDCIndex: Integer; const Data: TDDCIQPacket);
var
SamplesPerFrame: Integer;