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
+84 -36
View File
@@ -154,12 +154,8 @@ type
// FXvtrDisablePA — при TX подавляет бит 27 Alex (T/R relay): внутренний
// PA остаётся в RX-положении, TX-сигнал идёт через XVTR-IF.
FXvtrDisablePA: Boolean;
// FXvtrUseRxDDCIn — True если RX тракт XVTR подключён к XVTR DDC IN пину
// Alex (Alex bit 8 + bit 11). Эквивалент Alex.RxOnly=3, но включается
// динамически при выборе XVTR-band, не привязан к band-индексу.
FXvtrUseRxDDCIn: Boolean;
FSendLock: TCriticalSection; // защита concurrent UDP sends
FSendLock: TCriticalSection; // защита concurrent UDP sends (fpSendTo)
FHPSendLock: TCriticalSection; // защита seq+send в SendFullHP атомарно
// DUC IQ sender: WDSP отдаёт IQ блоками, а железу нужен равномерный UDP
// поток 240 IQ-пар/пакет @ 192 kS/s, то есть пакет каждые 1.25 ms.
@@ -213,8 +209,7 @@ type
// Включение/настройка XVTR-режима. Все параметры применяются мгновенно
// (отправляется HP-пакет). Когда AEnable=False, XVTR-bit гасится,
// антенна и Alex routing возвращаются к per-band дефолту.
procedure SetXvtrMode(AEnable, AUseRxDDCIn, ADisablePA: Boolean;
ARxAnt: Byte);
procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte);
procedure SendFullHP;
// Отдельная отправка PTT: переключает реле (bit27) и drive ДО, потом PTT.
// Не меняет FIsTransmitting — вызывать после UpdateState+SendFullHP.
@@ -639,6 +634,7 @@ begin
FAlexEnabled := True;
TSettingsManager.DefaultAlex(FAlexConfig);
FSendLock := TCriticalSection.Create;
FHPSendLock := TCriticalSection.Create;
FDUCIQLock := TCriticalSection.Create;
FDUCIQSem := RTLEventCreate;
ResetDUCIQQueue;
@@ -649,6 +645,7 @@ begin
Disconnect;
RTLEventDestroy(FDUCIQSem);
FDUCIQLock.Free;
FHPSendLock.Free;
FSendLock.Free;
inherited;
end;
@@ -1118,22 +1115,20 @@ end;
// Строит 32-битное слово Alex0 для HP-пакета (байты 1432-1435).
// Протокол: openHPSDR Ethernet Protocol v4.3, Appendix D.
// Все биты active-high. Bit 27 = T/R relay (0=RX, 1=TX).
// Биты 24-26: ANT1/ANT2/ANT3. Биты 20-23,29-31: LPF. Биты 1-12: HPF/BPF.
// Биты 8-11: вторичный RX вход (XVTR/Ext1/Ext2/Bypass relay).
// Bit 27: T/R relay. Биты 24-26: ANT. Биты 20-23,29-31: TX LPF.
// Биты 1-12: HPF/BPF (по RX-частоте). Биты 8-14: вторичный RX вход.
//
// XvtrRxAnt — override антенны для RX (1..3) если активен трансвертер;
// 0 = не переопределять, использовать Alex.RxAnt[band].
// XvtrUseRxDDCIn — True: при RX подключить XVTR DDC IN (Alex bit 8 + bit 11).
// Применяется только в RX и только когда XvtrRxAnt активен.
// XvtrDisablePA — True: при TX НЕ устанавливать bit 27 (T/R relay):
// внутренний PA не активируется, IF-сигнал идёт по тракту
// XVTR без усиления.
// XvtrActive — True когда активен XVTR-диапазон; разрешает биты 8+11
// (стандарт) или 8+14 (Orion2) из Alex.RxOnly[IF_band]=3.
function CalcAlex0(RXFreqHz, TXFreqHz: Double;
Transmitting, IsOrion2: Boolean;
const Alex: TAlexSettings;
XvtrRxAnt: Byte = 0;
XvtrUseRxDDCIn: Boolean = False;
XvtrDisablePA: Boolean = False;
XvtrActive: Boolean = False): LongWord;
var
@@ -1144,17 +1139,12 @@ begin
Result := 0;
// Bit 27 = T/R relay (0=RX, 1=TX).
// Bit 18 = TxRx Status для Orion MkII 5.2 / 7000DLE / Saturn / G2.
// Если XvtrDisablePA=True — НЕ устанавливаем bit 27 (PA остаётся в RX
// положении). Bit 18 (TxRx status) всё равно устанавливаем — это просто
// индикатор состояния для логики платы, не управление реле.
// положении, TX-сигнал идёт через XVTR-IF без усиления).
// Bit 18 (PureSignal) не ставим — PureSignal не реализован.
if Transmitting then
begin
if not XvtrDisablePA then
Result := Result or $08000000; // bit 27: T/R relay
if IsOrion2 then
Result := Result or $00040000; // bit 18: TxRx Status
end;
// HPF (ANAN-100/200) или BPF (ANAN-7000/8000/Saturn/G2) — по RX-частоте.
if IsOrion2 then
@@ -1216,8 +1206,8 @@ begin
// Биты 8-11: вторичный RX вход (Bypass relay, Ext1, Ext2, XvtrDDC).
// В режиме TX управляется глобальными флагами; в RX — per-band конфигом.
// Когда активен XVTR с XvtrUseRxDDCIn=True — переопределяем на XVTR DDC IN
// вне зависимости от Alex.RxOnly[band].
// Alex.RxOnly[IF_band]=3 (галочка XVTR в Antenna/Alex tab для IF-диапазона)
// разрешает XVTR DDC In только когда XvtrActive=True — иначе порт молчит.
if Transmitting then
begin
if Alex.RxBypassOnTx then
@@ -1226,11 +1216,6 @@ begin
Result := Result or $00000200 // bit 9: Ext1
or $00000800; // bit 11: Bypass relay
end
else if XvtrUseRxDDCIn then
begin
Result := Result or $00000100 // bit 8: XVTR DDC In
or $00000800; // bit 11: Bypass relay
end
else
begin
case Alex.RxOnly[BandIdx] of
@@ -1238,26 +1223,69 @@ begin
or $00000800; // bit 11: Bypass relay
2: Result := Result or $00000400 // bit 10: Ext2
or $00000800; // bit 11: Bypass relay
// XVTR DDC In только когда трансвертер активен — иначе порт молчит
// (аналог Thetis Alex.cs: "do not use XVTR ant port if not using transverter")
3: if XvtrActive then
Result := Result or $00000100 // bit 8: XVTR DDC In
or $00000800; // bit 11: Bypass relay
if IsOrion2 then
Result := Result or $00000100 // bit 8: XVTR DDC In
or $00004000 // bit 14: Orion2 Master RX select
else
Result := Result or $00000100 // bit 8: XVTR DDC In
or $00000800; // bit 11: Bypass relay
end;
end;
end;
// Строит 32-битное слово Alex1 для HP-пакета (байты 1428-1431).
// Только для Orion MkII 5.2 / ANAN-7000/8000 / Saturn / ANAN-G2.
// Bit 8 = ALEX1_ANAN7000_RX_GNDonTX: заземляет ADC1-вход во время TX
// чтобы предотвратить перегрузку второго приёмника.
// Остальные биты — те же LPF/BPF/ANT что и у ALEX0.
function CalcAlex1(RXFreqHz, TXFreqHz: Double;
Transmitting: Boolean;
const Alex: TAlexSettings;
XvtrDisablePA: Boolean = False): LongWord;
var
txf: Double;
begin
Result := 0;
if Transmitting and not XvtrDisablePA then
Result := Result or $08000000; // bit 27: T/R relay
// BPF для ADC1 — те же пороги что ALEX0 (Orion2-only, используем частоту ADC0)
if RXFreqHz < 1500000 then Result := Result or $00001000 // bit 12: HF Bypass
else if RXFreqHz < 2100000 then Result := Result or $00000040 // bit 6: 160m BPF
else if RXFreqHz < 5500000 then Result := Result or $00000020 // bit 5: 80/60m BPF
else if RXFreqHz < 11000000 then Result := Result or $00000010 // bit 4: 40/30m BPF
else if RXFreqHz < 22000000 then Result := Result or $00000002 // bit 1: 20/15m BPF
else if RXFreqHz < 35600000 then Result := Result or $00000004 // bit 2: 12/10m BPF
else Result := Result or $00000008; // bit 3: 6m + preamp
// TX LPF — Orion2 RX не проходит через TX LPF, поэтому всегда TX-частота
txf := TXFreqHz;
if txf > 35600000 then Result := Result or $20000000 // bit 29: 6m bypass LPF
else if txf > 24000000 then Result := Result or $40000000 // bit 30: 12/10m LPF
else if txf > 16500000 then Result := Result or $80000000 // bit 31: 17/15m LPF
else if txf > 8000000 then Result := Result or $00100000 // bit 20: 30/20m LPF
else if txf > 5000000 then Result := Result or $00200000 // bit 21: 60/40m LPF
else if txf > 2500000 then Result := Result or $00400000 // bit 22: 80m LPF
else Result := Result or $00800000; // bit 23: 160m LPF
Result := Result or $01000000; // bit 24: ANT1 (ADC1 дефолт)
// Bit 8: заземление ADC1-входа во время TX (галочка "Ground BPF2 on TX")
if Transmitting and Alex.GndBpf2OnTx then
Result := Result or $00000100;
end;
procedure THPSDRNetwork.SetAlexConfig(const A: TAlexSettings);
begin
FAlexConfig := A;
if FConnected then SendFullHP;
end;
procedure THPSDRNetwork.SetXvtrMode(AEnable, AUseRxDDCIn, ADisablePA: Boolean;
ARxAnt: Byte);
procedure THPSDRNetwork.SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte);
begin
FXvtrEnable := AEnable;
FXvtrUseRxDDCIn := AEnable and AUseRxDDCIn;
FXvtrDisablePA := AEnable and ADisablePA;
if AEnable and (ARxAnt >= 1) and (ARxAnt <= 3) then
FXvtrRxAntOverride := ARxAnt
@@ -1363,11 +1391,15 @@ var
Buf: array[0..1443] of Byte;
Ph: LongWord;
Alex0: LongWord;
Alex1: LongWord;
IsOrion2: Boolean;
DDCBase: Integer; // 0 для HERMES, 2 для ORION/ORION2/ANGELIA
begin
if not FConnected then Exit;
FHPSendLock.Enter;
try
FillChar(Buf, SizeOf(Buf), 0);
// Sequence — будет упакован в SendHighPriority через PackSeqBytes,
@@ -1416,12 +1448,24 @@ begin
begin
Alex0 := CalcAlex0(FCurrentRXFreq, FCurrentTXFreq,
FIsTransmitting, IsOrion2, FAlexConfig,
FXvtrRxAntOverride, FXvtrUseRxDDCIn, FXvtrDisablePA,
FXvtrRxAntOverride, FXvtrDisablePA,
FXvtrEnable);
Buf[1432] := (Alex0 shr 24) and $FF;
Buf[1433] := (Alex0 shr 16) and $FF;
Buf[1434] := (Alex0 shr 8) and $FF;
Buf[1435] := Alex0 and $FF;
// ALEX1 filter bits (bytes 1428-1431) — только Orion2/Saturn
if IsOrion2 then
begin
Alex1 := CalcAlex1(FCurrentRXFreq, FCurrentTXFreq,
FIsTransmitting, FAlexConfig,
FXvtrDisablePA);
Buf[1428] := (Alex1 shr 24) and $FF;
Buf[1429] := (Alex1 shr 16) and $FF;
Buf[1430] := (Alex1 shr 8) and $FF;
Buf[1431] := Alex1 and $FF;
end;
end;
// XVTR enable bit (byte 1400 bit 0) — управляет выходом VHF T/R relay платы.
@@ -1445,6 +1489,10 @@ begin
Buf[1442] := FStepAtten; // ADC1 attenuation
DoSendTo(FSocket, Buf, SizeOf(Buf), FDevice.IPAddress, FPortHPFromPC);
finally
FHPSendLock.Leave;
end;
end;
procedure THPSDRNetwork.SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;