Add VHF XVTR PA calibration; fix TX drive formula and stale FDriveLevel

PA calibration per band (HF and VHF):
- Add VHFBandCal[0..7] in TGlobalSettings (saved as vhf_cal_0..7 in JSON)
- Default HF cal updated to Thetis HERMES defaults (38.8-41.3 dB per band)
- Default VHF cal = 56.2 dB (Thetis HERMES XVTR default)
- SettingsForm: second group in PA tab "VHF Calibration (Transverters)" with
  8 spinboxes VHF0..VHF7; labels dynamically show XVTR slot ButtonText
  when LoadXvtrSettings is called (e.g. "VHF0 (2m):")

TX drive formula (CalcDriveByte) rewritten to match Thetis exactly:
  drive_byte = min(sqrt(10^((10*log10(pwr_W*1000) - Cal)/10) * 0.05) / 0.8, 1.0) * 1.02 * 255
  Cal = PA gain in dB (not a percentage); matches Thetis verified output
  (Cal=63.1 + slider=100% + PAMax=100W → drive_byte=16, verified against Thetis)
  Old formula Pos/100*Cal/100*255 was wrong: treated dB gain as linear percent

Fix stale FDriveLevel not updated on band/XVTR switch:
- Startup: FCurrentBand must be set before CalcDriveByte (was reversed)
- RestoreBand: recalculate FDriveLevel before ApplyVfoA network update
- ActivateXvtrBand: recalculate after FCurrentXvtr changes
- ApplyVfoA: recalculate when VFO crosses HF band boundary
- FreqDispBChanged: same for VFO-B band changes
- ApplyMOX(True): safety recalculate at PTT engage time

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 11:49:12 +03:00
co-authored by Claude Sonnet 4.6
parent 203d9d0f05
commit d823106366
4 changed files with 238 additions and 71 deletions
+73
View File
@@ -33,6 +33,8 @@ type
// PA: MaxPower в Вт, BandCal[0..10] — калибровка 38.8..100.0 на диапазон
TOnPASettingsChange = procedure(MaxPower: Double;
const BandCal: array of Double) of object;
// VHF (XVTR) PA calibration: 8 slots, 38.8..100.0
TOnVHFCalChange = procedure(const VHFCal: array of Double) of object;
// TX-настройки целиком (любая правка → один callback с актуальной структурой)
TOnTXSettingsChange = procedure(const T: TTXSettings) of object;
@@ -121,6 +123,8 @@ type
// ---- PA Settings tab controls ----
FEdMaxPower: TSpinEdit;
FEdBandCal: array[0..10] of TFloatSpinEdit;
FEdVHFCal: array[0..CFG_XVTR_COUNT-1] of TFloatSpinEdit;
FVHFCalLabels: array[0..CFG_XVTR_COUNT-1] of TLabel;
// ---- Transmit tab controls ----
// Текущее состояние, отдаётся в OnTXChange целиком при любой правке.
@@ -252,6 +256,7 @@ type
// ---- Callbacks ----
FOnCATChange: TOnCATChange;
FOnPAChange: TOnPASettingsChange;
FOnVHFCalChange: TOnVHFCalChange;
FOnADCChange: TOnADCChange;
FOnWebSettingsChange: TOnWebSettingsChange;
FOnDisplayChange: TOnDisplayParamChange;
@@ -325,6 +330,7 @@ type
procedure OnFreqMhzDigitsCmbChange(Sender: TObject);
procedure OnMaxPowerChange(Sender: TObject);
procedure OnBandCalChange(Sender: TObject);
procedure OnVHFCalEdit(Sender: TObject);
procedure OnCATSerialChange(Sender: TObject);
procedure OnCATTcpChange(Sender: TObject);
procedure FireCATChange;
@@ -362,6 +368,7 @@ type
procedure LoadLightTheme(ALight: Boolean);
procedure LoadFreqMhzDigits(Digits: Integer);
procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double);
procedure LoadVHFCalSettings(const VHFCal: array of Double);
procedure LoadCATSettings(
const SerEnabled: array of Boolean;
const SerPort: array of string;
@@ -379,6 +386,7 @@ type
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange;
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
property OnVHFCalChange: TOnVHFCalChange read FOnVHFCalChange write FOnVHFCalChange;
property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange;
property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange;
property OnGridChange: TOnGridParamChange read FOnGridChange write FOnGridChange;
@@ -1584,6 +1592,38 @@ begin
FEdBandCal[i].Tag := i;
end;
// ── VHF Calibration (Transverters) ──────────────────────────────────────
Grp := MakeGroupPanel(FPagePA, 'VHF Calibration (Transverters)',
MARGIN, 86 + GRP_H + 14, 650, 2 * ROW_H + 62);
Lbl := TLabel.Create(Self);
Lbl.Parent := Grp;
Lbl.Caption := 'Calibration per transverter slot, 38.8 ... 100.0';
Lbl.SetBounds(GRP_PAD, 16, 400, 18);
Lbl.Font.Color := CLR_TEXTDIM;
Lbl.Font.Name := UI_FONT;
Lbl.Font.Size := 9;
for i := 0 to CFG_XVTR_COUNT - 1 do
begin
r := i div COLS;
c := i mod COLS;
X := GRP_PAD + c * (COL_W + 4);
Y := 38 + r * ROW_H;
Lbl := TLabel.Create(Self);
Lbl.Parent := Grp;
Lbl.Caption := 'VHF' + IntToStr(i) + ':';
Lbl.SetBounds(X, Y + 5, LBL_W, 18);
Lbl.Font.Color := CLR_TEXTDIM;
Lbl.Font.Name := UI_FONT;
Lbl.Font.Size := 9;
FVHFCalLabels[i] := Lbl;
FEdVHFCal[i] := MakeFloatSpin(Grp, X + LBL_W, Y, OnVHFCalEdit);
FEdVHFCal[i].Tag := i;
end;
end;
procedure TSettingsForm.BuildAdvancedTab;
@@ -2299,6 +2339,31 @@ begin
FOnPAChange(FEdMaxPower.Value, BandCal);
end;
procedure TSettingsForm.LoadVHFCalSettings(const VHFCal: array of Double);
var i, n: Integer;
begin
FLoading := True;
try
n := Min(CFG_XVTR_COUNT - 1, High(VHFCal));
for i := 0 to n do
FEdVHFCal[i].Value := EnsureRange(VHFCal[i], 38.8, 100.0);
finally
FLoading := False;
end;
end;
procedure TSettingsForm.OnVHFCalEdit(Sender: TObject);
var
VHFCal: array[0..CFG_XVTR_COUNT-1] of Double;
i: Integer;
begin
if FLoading then Exit;
if not Assigned(FOnVHFCalChange) then Exit;
for i := 0 to CFG_XVTR_COUNT - 1 do
VHFCal[i] := FEdVHFCal[i].Value;
FOnVHFCalChange(VHFCal);
end;
procedure TSettingsForm.BtnCloseClick(Sender: TObject);
begin
Close;
@@ -3106,6 +3171,14 @@ begin
if FXvtrRXAnt[i] <> nil then
FXvtrRXAnt[i].ItemIndex := EnsureRange(X.Entries[i].RXAntenna, 0, 3);
UpdateXvtrRowEnabled(i);
// Sync VHF calibration label in PA tab
if FVHFCalLabels[i] <> nil then
begin
if X.Entries[i].ButtonText <> '' then
FVHFCalLabels[i].Caption := 'VHF' + IntToStr(i) + ' (' + X.Entries[i].ButtonText + '):'
else
FVHFCalLabels[i].Caption := 'VHF' + IntToStr(i) + ':';
end;
end;
if FChkXvtrTunPwr <> nil then FChkXvtrTunPwr.Checked := X.UseXVTRTunePower;
finally