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