mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
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:
+82
-34
@@ -154,12 +154,8 @@ type
|
|||||||
// FXvtrDisablePA — при TX подавляет бит 27 Alex (T/R relay): внутренний
|
// FXvtrDisablePA — при TX подавляет бит 27 Alex (T/R relay): внутренний
|
||||||
// PA остаётся в RX-положении, TX-сигнал идёт через XVTR-IF.
|
// PA остаётся в RX-положении, TX-сигнал идёт через XVTR-IF.
|
||||||
FXvtrDisablePA: Boolean;
|
FXvtrDisablePA: Boolean;
|
||||||
// FXvtrUseRxDDCIn — True если RX тракт XVTR подключён к XVTR DDC IN пину
|
FSendLock: TCriticalSection; // защита concurrent UDP sends (fpSendTo)
|
||||||
// Alex (Alex bit 8 + bit 11). Эквивалент Alex.RxOnly=3, но включается
|
FHPSendLock: TCriticalSection; // защита seq+send в SendFullHP атомарно
|
||||||
// динамически при выборе XVTR-band, не привязан к band-индексу.
|
|
||||||
FXvtrUseRxDDCIn: Boolean;
|
|
||||||
|
|
||||||
FSendLock: TCriticalSection; // защита concurrent UDP sends
|
|
||||||
|
|
||||||
// DUC IQ sender: WDSP отдаёт IQ блоками, а железу нужен равномерный UDP
|
// DUC IQ sender: WDSP отдаёт IQ блоками, а железу нужен равномерный UDP
|
||||||
// поток 240 IQ-пар/пакет @ 192 kS/s, то есть пакет каждые 1.25 ms.
|
// поток 240 IQ-пар/пакет @ 192 kS/s, то есть пакет каждые 1.25 ms.
|
||||||
@@ -213,8 +209,7 @@ type
|
|||||||
// Включение/настройка XVTR-режима. Все параметры применяются мгновенно
|
// Включение/настройка XVTR-режима. Все параметры применяются мгновенно
|
||||||
// (отправляется HP-пакет). Когда AEnable=False, XVTR-bit гасится,
|
// (отправляется HP-пакет). Когда AEnable=False, XVTR-bit гасится,
|
||||||
// антенна и Alex routing возвращаются к per-band дефолту.
|
// антенна и Alex routing возвращаются к per-band дефолту.
|
||||||
procedure SetXvtrMode(AEnable, AUseRxDDCIn, ADisablePA: Boolean;
|
procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte);
|
||||||
ARxAnt: Byte);
|
|
||||||
procedure SendFullHP;
|
procedure SendFullHP;
|
||||||
// Отдельная отправка PTT: переключает реле (bit27) и drive ДО, потом PTT.
|
// Отдельная отправка PTT: переключает реле (bit27) и drive ДО, потом PTT.
|
||||||
// Не меняет FIsTransmitting — вызывать после UpdateState+SendFullHP.
|
// Не меняет FIsTransmitting — вызывать после UpdateState+SendFullHP.
|
||||||
@@ -639,6 +634,7 @@ begin
|
|||||||
FAlexEnabled := True;
|
FAlexEnabled := True;
|
||||||
TSettingsManager.DefaultAlex(FAlexConfig);
|
TSettingsManager.DefaultAlex(FAlexConfig);
|
||||||
FSendLock := TCriticalSection.Create;
|
FSendLock := TCriticalSection.Create;
|
||||||
|
FHPSendLock := TCriticalSection.Create;
|
||||||
FDUCIQLock := TCriticalSection.Create;
|
FDUCIQLock := TCriticalSection.Create;
|
||||||
FDUCIQSem := RTLEventCreate;
|
FDUCIQSem := RTLEventCreate;
|
||||||
ResetDUCIQQueue;
|
ResetDUCIQQueue;
|
||||||
@@ -649,6 +645,7 @@ begin
|
|||||||
Disconnect;
|
Disconnect;
|
||||||
RTLEventDestroy(FDUCIQSem);
|
RTLEventDestroy(FDUCIQSem);
|
||||||
FDUCIQLock.Free;
|
FDUCIQLock.Free;
|
||||||
|
FHPSendLock.Free;
|
||||||
FSendLock.Free;
|
FSendLock.Free;
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
@@ -1118,22 +1115,20 @@ end;
|
|||||||
|
|
||||||
// Строит 32-битное слово Alex0 для HP-пакета (байты 1432-1435).
|
// Строит 32-битное слово Alex0 для HP-пакета (байты 1432-1435).
|
||||||
// Протокол: openHPSDR Ethernet Protocol v4.3, Appendix D.
|
// Протокол: openHPSDR Ethernet Protocol v4.3, Appendix D.
|
||||||
// Все биты active-high. Bit 27 = T/R relay (0=RX, 1=TX).
|
// Bit 27: T/R relay. Биты 24-26: ANT. Биты 20-23,29-31: TX LPF.
|
||||||
// Биты 24-26: ANT1/ANT2/ANT3. Биты 20-23,29-31: LPF. Биты 1-12: HPF/BPF.
|
// Биты 1-12: HPF/BPF (по RX-частоте). Биты 8-14: вторичный RX вход.
|
||||||
// Биты 8-11: вторичный RX вход (XVTR/Ext1/Ext2/Bypass relay).
|
|
||||||
//
|
//
|
||||||
// XvtrRxAnt — override антенны для RX (1..3) если активен трансвертер;
|
// XvtrRxAnt — override антенны для RX (1..3) если активен трансвертер;
|
||||||
// 0 = не переопределять, использовать Alex.RxAnt[band].
|
// 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):
|
// XvtrDisablePA — True: при TX НЕ устанавливать bit 27 (T/R relay):
|
||||||
// внутренний PA не активируется, IF-сигнал идёт по тракту
|
// внутренний PA не активируется, IF-сигнал идёт по тракту
|
||||||
// XVTR без усиления.
|
// XVTR без усиления.
|
||||||
|
// XvtrActive — True когда активен XVTR-диапазон; разрешает биты 8+11
|
||||||
|
// (стандарт) или 8+14 (Orion2) из Alex.RxOnly[IF_band]=3.
|
||||||
function CalcAlex0(RXFreqHz, TXFreqHz: Double;
|
function CalcAlex0(RXFreqHz, TXFreqHz: Double;
|
||||||
Transmitting, IsOrion2: Boolean;
|
Transmitting, IsOrion2: Boolean;
|
||||||
const Alex: TAlexSettings;
|
const Alex: TAlexSettings;
|
||||||
XvtrRxAnt: Byte = 0;
|
XvtrRxAnt: Byte = 0;
|
||||||
XvtrUseRxDDCIn: Boolean = False;
|
|
||||||
XvtrDisablePA: Boolean = False;
|
XvtrDisablePA: Boolean = False;
|
||||||
XvtrActive: Boolean = False): LongWord;
|
XvtrActive: Boolean = False): LongWord;
|
||||||
var
|
var
|
||||||
@@ -1144,17 +1139,12 @@ begin
|
|||||||
Result := 0;
|
Result := 0;
|
||||||
|
|
||||||
// Bit 27 = T/R relay (0=RX, 1=TX).
|
// 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
|
// Если XvtrDisablePA=True — НЕ устанавливаем bit 27 (PA остаётся в RX
|
||||||
// положении). Bit 18 (TxRx status) всё равно устанавливаем — это просто
|
// положении, TX-сигнал идёт через XVTR-IF без усиления).
|
||||||
// индикатор состояния для логики платы, не управление реле.
|
// Bit 18 (PureSignal) не ставим — PureSignal не реализован.
|
||||||
if Transmitting then
|
if Transmitting then
|
||||||
begin
|
|
||||||
if not XvtrDisablePA then
|
if not XvtrDisablePA then
|
||||||
Result := Result or $08000000; // bit 27: T/R relay
|
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-частоте.
|
// HPF (ANAN-100/200) или BPF (ANAN-7000/8000/Saturn/G2) — по RX-частоте.
|
||||||
if IsOrion2 then
|
if IsOrion2 then
|
||||||
@@ -1216,8 +1206,8 @@ begin
|
|||||||
|
|
||||||
// Биты 8-11: вторичный RX вход (Bypass relay, Ext1, Ext2, XvtrDDC).
|
// Биты 8-11: вторичный RX вход (Bypass relay, Ext1, Ext2, XvtrDDC).
|
||||||
// В режиме TX управляется глобальными флагами; в RX — per-band конфигом.
|
// В режиме TX управляется глобальными флагами; в RX — per-band конфигом.
|
||||||
// Когда активен XVTR с XvtrUseRxDDCIn=True — переопределяем на XVTR DDC IN
|
// Alex.RxOnly[IF_band]=3 (галочка XVTR в Antenna/Alex tab для IF-диапазона)
|
||||||
// вне зависимости от Alex.RxOnly[band].
|
// разрешает XVTR DDC In только когда XvtrActive=True — иначе порт молчит.
|
||||||
if Transmitting then
|
if Transmitting then
|
||||||
begin
|
begin
|
||||||
if Alex.RxBypassOnTx then
|
if Alex.RxBypassOnTx then
|
||||||
@@ -1226,11 +1216,6 @@ begin
|
|||||||
Result := Result or $00000200 // bit 9: Ext1
|
Result := Result or $00000200 // bit 9: Ext1
|
||||||
or $00000800; // bit 11: Bypass relay
|
or $00000800; // bit 11: Bypass relay
|
||||||
end
|
end
|
||||||
else if XvtrUseRxDDCIn then
|
|
||||||
begin
|
|
||||||
Result := Result or $00000100 // bit 8: XVTR DDC In
|
|
||||||
or $00000800; // bit 11: Bypass relay
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
case Alex.RxOnly[BandIdx] of
|
case Alex.RxOnly[BandIdx] of
|
||||||
@@ -1238,26 +1223,69 @@ begin
|
|||||||
or $00000800; // bit 11: Bypass relay
|
or $00000800; // bit 11: Bypass relay
|
||||||
2: Result := Result or $00000400 // bit 10: Ext2
|
2: Result := Result or $00000400 // bit 10: Ext2
|
||||||
or $00000800; // bit 11: Bypass relay
|
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
|
3: if XvtrActive then
|
||||||
|
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
|
Result := Result or $00000100 // bit 8: XVTR DDC In
|
||||||
or $00000800; // bit 11: Bypass relay
|
or $00000800; // bit 11: Bypass relay
|
||||||
end;
|
end;
|
||||||
end;
|
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);
|
procedure THPSDRNetwork.SetAlexConfig(const A: TAlexSettings);
|
||||||
begin
|
begin
|
||||||
FAlexConfig := A;
|
FAlexConfig := A;
|
||||||
if FConnected then SendFullHP;
|
if FConnected then SendFullHP;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure THPSDRNetwork.SetXvtrMode(AEnable, AUseRxDDCIn, ADisablePA: Boolean;
|
procedure THPSDRNetwork.SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte);
|
||||||
ARxAnt: Byte);
|
|
||||||
begin
|
begin
|
||||||
FXvtrEnable := AEnable;
|
FXvtrEnable := AEnable;
|
||||||
FXvtrUseRxDDCIn := AEnable and AUseRxDDCIn;
|
|
||||||
FXvtrDisablePA := AEnable and ADisablePA;
|
FXvtrDisablePA := AEnable and ADisablePA;
|
||||||
if AEnable and (ARxAnt >= 1) and (ARxAnt <= 3) then
|
if AEnable and (ARxAnt >= 1) and (ARxAnt <= 3) then
|
||||||
FXvtrRxAntOverride := ARxAnt
|
FXvtrRxAntOverride := ARxAnt
|
||||||
@@ -1363,11 +1391,15 @@ var
|
|||||||
Buf: array[0..1443] of Byte;
|
Buf: array[0..1443] of Byte;
|
||||||
Ph: LongWord;
|
Ph: LongWord;
|
||||||
Alex0: LongWord;
|
Alex0: LongWord;
|
||||||
|
Alex1: LongWord;
|
||||||
IsOrion2: Boolean;
|
IsOrion2: Boolean;
|
||||||
DDCBase: Integer; // 0 для HERMES, 2 для ORION/ORION2/ANGELIA
|
DDCBase: Integer; // 0 для HERMES, 2 для ORION/ORION2/ANGELIA
|
||||||
begin
|
begin
|
||||||
if not FConnected then Exit;
|
if not FConnected then Exit;
|
||||||
|
|
||||||
|
FHPSendLock.Enter;
|
||||||
|
try
|
||||||
|
|
||||||
FillChar(Buf, SizeOf(Buf), 0);
|
FillChar(Buf, SizeOf(Buf), 0);
|
||||||
|
|
||||||
// Sequence — будет упакован в SendHighPriority через PackSeqBytes,
|
// Sequence — будет упакован в SendHighPriority через PackSeqBytes,
|
||||||
@@ -1416,12 +1448,24 @@ begin
|
|||||||
begin
|
begin
|
||||||
Alex0 := CalcAlex0(FCurrentRXFreq, FCurrentTXFreq,
|
Alex0 := CalcAlex0(FCurrentRXFreq, FCurrentTXFreq,
|
||||||
FIsTransmitting, IsOrion2, FAlexConfig,
|
FIsTransmitting, IsOrion2, FAlexConfig,
|
||||||
FXvtrRxAntOverride, FXvtrUseRxDDCIn, FXvtrDisablePA,
|
FXvtrRxAntOverride, FXvtrDisablePA,
|
||||||
FXvtrEnable);
|
FXvtrEnable);
|
||||||
Buf[1432] := (Alex0 shr 24) and $FF;
|
Buf[1432] := (Alex0 shr 24) and $FF;
|
||||||
Buf[1433] := (Alex0 shr 16) and $FF;
|
Buf[1433] := (Alex0 shr 16) and $FF;
|
||||||
Buf[1434] := (Alex0 shr 8) and $FF;
|
Buf[1434] := (Alex0 shr 8) and $FF;
|
||||||
Buf[1435] := Alex0 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;
|
end;
|
||||||
|
|
||||||
// XVTR enable bit (byte 1400 bit 0) — управляет выходом VHF T/R relay платы.
|
// XVTR enable bit (byte 1400 bit 0) — управляет выходом VHF T/R relay платы.
|
||||||
@@ -1445,6 +1489,10 @@ begin
|
|||||||
Buf[1442] := FStepAtten; // ADC1 attenuation
|
Buf[1442] := FStepAtten; // ADC1 attenuation
|
||||||
|
|
||||||
DoSendTo(FSocket, Buf, SizeOf(Buf), FDevice.IPAddress, FPortHPFromPC);
|
DoSendTo(FSocket, Buf, SizeOf(Buf), FDevice.IPAddress, FPortHPFromPC);
|
||||||
|
|
||||||
|
finally
|
||||||
|
FHPSendLock.Leave;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure THPSDRNetwork.SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
procedure THPSDRNetwork.SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
||||||
|
|||||||
+65
-30
@@ -209,6 +209,7 @@ type
|
|||||||
FDriveLevel: Byte;
|
FDriveLevel: Byte;
|
||||||
FPAMaxPower: Double;
|
FPAMaxPower: Double;
|
||||||
FPABandCal: array[0..BAND_COUNT-1] of Double; // калибровка 38.8..100.0 на диапазон
|
FPABandCal: array[0..BAND_COUNT-1] of Double; // калибровка 38.8..100.0 на диапазон
|
||||||
|
FVHFBandCal: array[0..CFG_XVTR_COUNT-1] of Double; // калибровка VHF/XVTR слотов
|
||||||
FRunning: Boolean;
|
FRunning: Boolean;
|
||||||
FTransmitting: Boolean;
|
FTransmitting: Boolean;
|
||||||
FSplitTxB: Boolean;
|
FSplitTxB: Boolean;
|
||||||
@@ -658,6 +659,7 @@ type
|
|||||||
procedure ApplyMOX(Active: Boolean);
|
procedure ApplyMOX(Active: Boolean);
|
||||||
procedure OnTXIQReady(const Buf: array of Double; Count: Integer);
|
procedure OnTXIQReady(const Buf: array of Double; Count: Integer);
|
||||||
procedure OnPASettingsChange(MaxPower: Double; const BandCal: array of Double);
|
procedure OnPASettingsChange(MaxPower: Double; const BandCal: array of Double);
|
||||||
|
procedure OnVHFCalSettingsChange(const VHFCal: array of Double);
|
||||||
procedure OnTXSettingsChange(const T: TTXSettings);
|
procedure OnTXSettingsChange(const T: TTXSettings);
|
||||||
procedure BtnAGCModeClick(Sender: TObject);
|
procedure BtnAGCModeClick(Sender: TObject);
|
||||||
procedure TrkAGCChange(Sender: TObject);
|
procedure TrkAGCChange(Sender: TObject);
|
||||||
@@ -947,6 +949,8 @@ begin
|
|||||||
Result.PAMaxPower := FPAMaxPower;
|
Result.PAMaxPower := FPAMaxPower;
|
||||||
for i := 0 to BAND_COUNT - 1 do
|
for i := 0 to BAND_COUNT - 1 do
|
||||||
Result.PABandCal[i] := FPABandCal[i];
|
Result.PABandCal[i] := FPABandCal[i];
|
||||||
|
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||||
|
Result.VHFBandCal[i] := FVHFBandCal[i];
|
||||||
Result.ActiveVfo := FActiveVfo;
|
Result.ActiveVfo := FActiveVfo;
|
||||||
Result.NRMode := BtnNR.Tag;
|
Result.NRMode := BtnNR.Tag;
|
||||||
Result.NBMode := BtnNB.Tag;
|
Result.NBMode := BtnNB.Tag;
|
||||||
@@ -1118,6 +1122,8 @@ begin
|
|||||||
FVfoB := B.VfoB;
|
FVfoB := B.VfoB;
|
||||||
FreqDispB.Frequency := Round(FVfoB);
|
FreqDispB.Frequency := Round(FVfoB);
|
||||||
|
|
||||||
|
// Пересчитываем drive byte для нового диапазона перед отправкой в сеть
|
||||||
|
FDriveLevel := CalcDriveByte;
|
||||||
// ApplyVfoA обновит сеть, FreqDisp, shift, перерисует
|
// ApplyVfoA обновит сеть, FreqDisp, shift, перерисует
|
||||||
ApplyVfoA(Round(B.VfoA));
|
ApplyVfoA(Round(B.VfoA));
|
||||||
end;
|
end;
|
||||||
@@ -1157,7 +1163,8 @@ begin
|
|||||||
FSpecDrag := False;
|
FSpecDrag := False;
|
||||||
FDriveLevel := 0;
|
FDriveLevel := 0;
|
||||||
FPAMaxPower := 100.0;
|
FPAMaxPower := 100.0;
|
||||||
for i := 0 to BAND_COUNT - 1 do FPABandCal[i] := 100.0;
|
for i := 0 to BAND_COUNT - 1 do FPABandCal[i] := CFG_PA_BAND_CAL_DEFAULT[i];
|
||||||
|
for i := 0 to CFG_XVTR_COUNT - 1 do FVHFBandCal[i] := CFG_VHF_CAL_DEFAULT;
|
||||||
FRunning := False;
|
FRunning := False;
|
||||||
FTransmitting := False;
|
FTransmitting := False;
|
||||||
FSplitTxB := False;
|
FSplitTxB := False;
|
||||||
@@ -3731,10 +3738,12 @@ begin
|
|||||||
FPAMaxPower := G_Settings.PAMaxPower;
|
FPAMaxPower := G_Settings.PAMaxPower;
|
||||||
for i := 0 to BAND_COUNT - 1 do
|
for i := 0 to BAND_COUNT - 1 do
|
||||||
FPABandCal[i] := G_Settings.PABandCal[i];
|
FPABandCal[i] := G_Settings.PABandCal[i];
|
||||||
|
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||||
|
FVHFBandCal[i] := G_Settings.VHFBandCal[i];
|
||||||
// Slider position (0..100) and calibrated drive byte
|
// Slider position (0..100) and calibrated drive byte
|
||||||
TrkDrive.Position := EnsureRange(G_Settings.DriveLevel, 0, 100);
|
TrkDrive.Position := EnsureRange(G_Settings.DriveLevel, 0, 100);
|
||||||
FDriveLevel := CalcDriveByte;
|
|
||||||
FCurrentBand := G_Settings.LastBand;
|
FCurrentBand := G_Settings.LastBand;
|
||||||
|
FDriveLevel := CalcDriveByte;
|
||||||
// SampleRate — глобальный, загружаем до RestoreBand
|
// SampleRate — глобальный, загружаем до RestoreBand
|
||||||
if G_Settings.SampleRate > 0 then
|
if G_Settings.SampleRate > 0 then
|
||||||
begin
|
begin
|
||||||
@@ -4098,6 +4107,7 @@ begin
|
|||||||
StyleButton(BtnBand[FCurrentBand], False);
|
StyleButton(BtnBand[FCurrentBand], False);
|
||||||
FCurrentBand := BandIdx;
|
FCurrentBand := BandIdx;
|
||||||
StyleButton(BtnBand[FCurrentBand], True);
|
StyleButton(BtnBand[FCurrentBand], True);
|
||||||
|
FDriveLevel := CalcDriveByte;
|
||||||
end;
|
end;
|
||||||
FSpecView.InvalidateRulerCache;
|
FSpecView.InvalidateRulerCache;
|
||||||
SyncSpecViewFreq;
|
SyncSpecViewFreq;
|
||||||
@@ -4297,6 +4307,7 @@ begin
|
|||||||
StyleButton(BtnBand[FCurrentBand], False);
|
StyleButton(BtnBand[FCurrentBand], False);
|
||||||
FCurrentBand := BandIdx;
|
FCurrentBand := BandIdx;
|
||||||
StyleButton(BtnBand[FCurrentBand], True);
|
StyleButton(BtnBand[FCurrentBand], True);
|
||||||
|
FDriveLevel := CalcDriveByte;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -5002,6 +5013,7 @@ begin
|
|||||||
Exit;
|
Exit;
|
||||||
WasTransmitting := FTransmitting;
|
WasTransmitting := FTransmitting;
|
||||||
FTransmitting := Active;
|
FTransmitting := Active;
|
||||||
|
if Active then FDriveLevel := CalcDriveByte;
|
||||||
FDUCPendingCount := 0;
|
FDUCPendingCount := 0;
|
||||||
if FTransmitting then
|
if FTransmitting then
|
||||||
begin
|
begin
|
||||||
@@ -5907,31 +5919,33 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
function TMainForm.CalcDriveByte: Byte;
|
function TMainForm.CalcDriveByte: Byte;
|
||||||
|
// Thetis-compatible formula:
|
||||||
|
// drive_byte = int( min(target_volts/0.8, 1.0) * 1.02 * 255 )
|
||||||
|
// where:
|
||||||
|
// target_volts = sqrt( 10^((target_dBm)/10) * 0.05 ) [E=sqrt(P*R), R=50, P in mW]
|
||||||
|
// target_dBm = 10*log10(pwr_W * 1000) - Cal
|
||||||
|
// pwr_W = Pos/100 * FPAMaxPower
|
||||||
|
// Cal = per-band PA gain in dB (HF: ~38.8-41.3, VHF default: ~56.2)
|
||||||
var
|
var
|
||||||
Cal: Double;
|
Cal: Double;
|
||||||
Pos: Integer;
|
Pos: Integer;
|
||||||
XvtrActive: Boolean;
|
XvtrActive: Boolean;
|
||||||
|
pwr_W: Double;
|
||||||
|
target_dBm: Double;
|
||||||
|
target_volts: Double;
|
||||||
|
audio_vol: Double;
|
||||||
begin
|
begin
|
||||||
if (FCurrentBand >= 0) and (FCurrentBand < BAND_COUNT) and (FPABandCal[FCurrentBand] > 0.0) then
|
|
||||||
Cal := FPABandCal[FCurrentBand]
|
|
||||||
else
|
|
||||||
Cal := 100.0;
|
|
||||||
|
|
||||||
XvtrActive := (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
|
XvtrActive := (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
|
||||||
FXvtrSettings.Entries[FCurrentXvtr].Enabled;
|
FXvtrSettings.Entries[FCurrentXvtr].Enabled;
|
||||||
|
|
||||||
// Pos — итоговый процент драйва 0..100 ДО per-band калибровки.
|
if XvtrActive then
|
||||||
//
|
Cal := FVHFBandCal[FCurrentXvtr]
|
||||||
// Логика (как в Thetis):
|
else if (FCurrentBand >= 0) and (FCurrentBand < BAND_COUNT) then
|
||||||
// • Регулярный TX:
|
Cal := FPABandCal[FCurrentBand]
|
||||||
// HF → Pos = TrkDrive.Position
|
else
|
||||||
// XVTR → Pos = TrkDrive.Position × (XVTR.TXPower / 100)
|
Cal := 38.8;
|
||||||
// (TXPower работает как верхний предел драйва на трансверторе)
|
|
||||||
// • TUN (кнопка):
|
// Pos — итоговый процент драйва 0..100
|
||||||
// HF → Pos = TUNLevel (TTXSettings.TUNLevel)
|
|
||||||
// XVTR, UseXVTRTunePower=False → Pos = TUNLevel (поведение как HF)
|
|
||||||
// XVTR, UseXVTRTunePower=True → Pos = XVTR.TXPower
|
|
||||||
// (галка ЗАМЕНЯЕТ TUNLevel на XVTR power — Thetis-семантика)
|
|
||||||
if FTuning then
|
if FTuning then
|
||||||
begin
|
begin
|
||||||
if XvtrActive and FXvtrSettings.UseXVTRTunePower then
|
if XvtrActive and FXvtrSettings.UseXVTRTunePower then
|
||||||
@@ -5942,13 +5956,18 @@ begin
|
|||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
if XvtrActive then
|
if XvtrActive then
|
||||||
Pos := Round(TrkDrive.Position
|
Pos := Round(TrkDrive.Position * FXvtrSettings.Entries[FCurrentXvtr].TXPower / 100.0)
|
||||||
* FXvtrSettings.Entries[FCurrentXvtr].TXPower / 100.0)
|
|
||||||
else
|
else
|
||||||
Pos := TrkDrive.Position;
|
Pos := TrkDrive.Position;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := Round(Pos / 100.0 * Cal / 100.0 * 255.0);
|
if Pos <= 0 then begin Result := 0; Exit; end;
|
||||||
|
|
||||||
|
pwr_W := Pos / 100.0 * FPAMaxPower;
|
||||||
|
target_dBm := 10.0 * Log10(pwr_W * 1000.0) - Cal;
|
||||||
|
target_volts := Sqrt(Power(10.0, target_dBm * 0.1) * 0.05);
|
||||||
|
audio_vol := Min(target_volts / 0.8, 1.0);
|
||||||
|
Result := Min(Round(audio_vol * 1.02 * 255.0), 255);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnTXIQReady(const Buf: array of Double; Count: Integer);
|
procedure TMainForm.OnTXIQReady(const Buf: array of Double; Count: Integer);
|
||||||
@@ -6002,6 +6021,21 @@ begin
|
|||||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.OnVHFCalSettingsChange(const VHFCal: array of Double);
|
||||||
|
var i: Integer;
|
||||||
|
begin
|
||||||
|
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||||
|
begin
|
||||||
|
if i <= High(VHFCal) then
|
||||||
|
FVHFBandCal[i] := VHFCal[i]
|
||||||
|
else
|
||||||
|
FVHFBandCal[i] := CFG_VHF_CAL_DEFAULT;
|
||||||
|
end;
|
||||||
|
FDriveLevel := CalcDriveByte;
|
||||||
|
if FNetwork.Connected and FNetwork.Running then
|
||||||
|
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnTXSettingsChange(const T: TTXSettings);
|
procedure TMainForm.OnTXSettingsChange(const T: TTXSettings);
|
||||||
// Любая правка в Settings→Transmit летит сюда: обновляем FTXSettings,
|
// Любая правка в Settings→Transmit летит сюда: обновляем FTXSettings,
|
||||||
// перешиваем WDSP TX-цепь и (если поменялись mic-биты) перепосылаем DUC Specific.
|
// перешиваем WDSP TX-цепь и (если поменялись mic-биты) перепосылаем DUC Specific.
|
||||||
@@ -6097,11 +6131,12 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Передаёт XVTR-режим в сетевой слой. Вызывается при смене XVTR или его
|
// Передаёт XVTR-режим в сетевой слой. Вызывается при смене XVTR или его
|
||||||
// настроек. Управляет XVTR Enable bit, DDC IN routing, T/R relay suppression.
|
// настроек. Управляет XVTR Enable bit, T/R relay suppression.
|
||||||
|
// XVTR DDC In (Alex bits 8+11) управляется через галочку XVTR в Antenna/Alex
|
||||||
|
// tab для IF-диапазона (Alex.RxOnly[IF_band]=3) — аналогично Thetis.
|
||||||
procedure TMainForm.ApplyXvtrToNetwork;
|
procedure TMainForm.ApplyXvtrToNetwork;
|
||||||
var
|
var
|
||||||
En: Boolean;
|
En: Boolean;
|
||||||
UseDDCIn: Boolean;
|
|
||||||
DisablePA: Boolean;
|
DisablePA: Boolean;
|
||||||
RxAnt: Byte;
|
RxAnt: Byte;
|
||||||
begin
|
begin
|
||||||
@@ -6109,16 +6144,15 @@ begin
|
|||||||
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
|
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
|
||||||
begin
|
begin
|
||||||
En := True;
|
En := True;
|
||||||
UseDDCIn := True;
|
|
||||||
DisablePA := FXvtrSettings.Entries[FCurrentXvtr].DisablePA;
|
DisablePA := FXvtrSettings.Entries[FCurrentXvtr].DisablePA;
|
||||||
RxAnt := FXvtrSettings.Entries[FCurrentXvtr].RXAntenna;
|
RxAnt := FXvtrSettings.Entries[FCurrentXvtr].RXAntenna;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
En := False; UseDDCIn := False; DisablePA := False; RxAnt := 0;
|
En := False; DisablePA := False; RxAnt := 0;
|
||||||
end;
|
end;
|
||||||
if FNetwork.Connected then
|
if FNetwork.Connected then
|
||||||
FNetwork.SetXvtrMode(En, UseDDCIn, DisablePA, RxAnt);
|
FNetwork.SetXvtrMode(En, DisablePA, RxAnt);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Перестраивает динамические XVTR-кнопки в PanelBands. Удаляет старые,
|
// Перестраивает динамические XVTR-кнопки в PanelBands. Удаляет старые,
|
||||||
@@ -6289,9 +6323,8 @@ begin
|
|||||||
FCenterFreq := Vis;
|
FCenterFreq := Vis;
|
||||||
FreqDispA.Frequency := Round(FVfoA);
|
FreqDispA.Frequency := Round(FVfoA);
|
||||||
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
if FWDSPReady then FDSPEngine.SetShift(0.0);
|
||||||
// Применяем драйв (если есть кастомный TX power для этого XVTR)
|
// Пересчитываем drive byte для нового XVTR слота и отправляем в сеть
|
||||||
// Сделано на стороне ActiveTXFreqHz / TX path — здесь только обновляем дисплей.
|
FDriveLevel := CalcDriveByte;
|
||||||
// Отправляем в сеть с трансляцией
|
|
||||||
if FNetwork.Connected and FNetwork.Running then
|
if FNetwork.Connected and FNetwork.Running then
|
||||||
begin
|
begin
|
||||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz),
|
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz),
|
||||||
@@ -6683,6 +6716,7 @@ begin
|
|||||||
SF.OnVisibilityChange := ApplyVisibility;
|
SF.OnVisibilityChange := ApplyVisibility;
|
||||||
SF.OnFPSChange := ApplyFPS;
|
SF.OnFPSChange := ApplyFPS;
|
||||||
SF.OnPAChange := OnPASettingsChange;
|
SF.OnPAChange := OnPASettingsChange;
|
||||||
|
SF.OnVHFCalChange := OnVHFCalSettingsChange;
|
||||||
SF.OnCATChange := OnCATSettingsChange;
|
SF.OnCATChange := OnCATSettingsChange;
|
||||||
SF.OnThemeChange := SetLightTheme;
|
SF.OnThemeChange := SetLightTheme;
|
||||||
SF.OnFreqMhzDigitsChange := ApplyFreqMhzDigits;
|
SF.OnFreqMhzDigitsChange := ApplyFreqMhzDigits;
|
||||||
@@ -6699,6 +6733,7 @@ begin
|
|||||||
IfThen(FNetwork.Connected, FNetwork.Device.BoardType, FPendingBoardType));
|
IfThen(FNetwork.Connected, FNetwork.Device.BoardType, FPendingBoardType));
|
||||||
SF.LoadXvtrSettings(FXvtrSettings);
|
SF.LoadXvtrSettings(FXvtrSettings);
|
||||||
SF.LoadPASettings(FPAMaxPower, FPABandCal);
|
SF.LoadPASettings(FPAMaxPower, FPABandCal);
|
||||||
|
SF.LoadVHFCalSettings(FVHFBandCal);
|
||||||
SF.LoadCATSettings(
|
SF.LoadCATSettings(
|
||||||
FCATLastGlobal.CATSerialEnabled,
|
FCATLastGlobal.CATSerialEnabled,
|
||||||
FCATLastGlobal.CATSerialPort,
|
FCATLastGlobal.CATSerialPort,
|
||||||
|
|||||||
+13
-2
@@ -12,6 +12,11 @@ const
|
|||||||
SETTINGS_FILE = 'hpsdr_settings.json';
|
SETTINGS_FILE = 'hpsdr_settings.json';
|
||||||
CFG_BAND_COUNT = 11;
|
CFG_BAND_COUNT = 11;
|
||||||
CFG_XVTR_COUNT = 8; // количество XVTR-слотов (трансверторов)
|
CFG_XVTR_COUNT = 8; // количество XVTR-слотов (трансверторов)
|
||||||
|
// PA gain defaults (dB) — match Thetis HERMES/HPSDR defaults
|
||||||
|
// order: 160m,80m,60m,40m,30m,20m,17m,15m,12m,10m,6m
|
||||||
|
CFG_PA_BAND_CAL_DEFAULT: array[0..CFG_BAND_COUNT-1] of Double = (
|
||||||
|
41.0, 41.2, 41.3, 41.3, 41.0, 40.5, 39.9, 38.8, 38.8, 38.8, 38.8);
|
||||||
|
CFG_VHF_CAL_DEFAULT = 56.2; // dB, default for all XVTR slots
|
||||||
CFG_BAND_DEFAULT_FREQ: array[0..CFG_BAND_COUNT-1] of Double = (
|
CFG_BAND_DEFAULT_FREQ: array[0..CFG_BAND_COUNT-1] of Double = (
|
||||||
1900000, 3750000, 5357000, 7100000, 10125000,
|
1900000, 3750000, 5357000, 7100000, 10125000,
|
||||||
14200000, 18120000, 21200000, 24940000, 28500000, 50150000);
|
14200000, 18120000, 21200000, 24940000, 28500000, 50150000);
|
||||||
@@ -223,6 +228,7 @@ type
|
|||||||
// --- PA (Power Amplifier) settings ---
|
// --- PA (Power Amplifier) settings ---
|
||||||
PAMaxPower: Double; // максимальная выходная мощность, Вт (5..200)
|
PAMaxPower: Double; // максимальная выходная мощность, Вт (5..200)
|
||||||
PABandCal: array[0..CFG_BAND_COUNT-1] of Double; // калибровка на диапазон 38.8..100.0
|
PABandCal: array[0..CFG_BAND_COUNT-1] of Double; // калибровка на диапазон 38.8..100.0
|
||||||
|
VHFBandCal: array[0..CFG_XVTR_COUNT-1] of Double; // калибровка для XVTR слотов 38.8..100.0
|
||||||
// --- ADC settings ---
|
// --- ADC settings ---
|
||||||
DitherEnabled: Boolean; // DDC Specific byte 5 — уменьшает интермодуляцию
|
DitherEnabled: Boolean; // DDC Specific byte 5 — уменьшает интермодуляцию
|
||||||
RandomEnabled: Boolean; // DDC Specific byte 6 — рандомизация ADC
|
RandomEnabled: Boolean; // DDC Specific byte 6 — рандомизация ADC
|
||||||
@@ -362,7 +368,8 @@ begin
|
|||||||
G.WfNFEnabled := False;
|
G.WfNFEnabled := False;
|
||||||
G.SampleRate := 192000;
|
G.SampleRate := 192000;
|
||||||
G.PAMaxPower := 100.0;
|
G.PAMaxPower := 100.0;
|
||||||
for i := 0 to CFG_BAND_COUNT-1 do G.PABandCal[i] := 100.0;
|
for i := 0 to CFG_BAND_COUNT-1 do G.PABandCal[i] := CFG_PA_BAND_CAL_DEFAULT[i];
|
||||||
|
for i := 0 to CFG_XVTR_COUNT-1 do G.VHFBandCal[i] := CFG_VHF_CAL_DEFAULT;
|
||||||
// Display defaults
|
// Display defaults
|
||||||
G.FFTSize := 131072;
|
G.FFTSize := 131072;
|
||||||
G.WindowType := 2; // Hann
|
G.WindowType := 2; // Hann
|
||||||
@@ -604,7 +611,9 @@ begin
|
|||||||
// PA settings
|
// PA settings
|
||||||
G.PAMaxPower := JD(GObj,'pa_max_power',100.0);
|
G.PAMaxPower := JD(GObj,'pa_max_power',100.0);
|
||||||
for i := 0 to CFG_BAND_COUNT-1 do
|
for i := 0 to CFG_BAND_COUNT-1 do
|
||||||
G.PABandCal[i] := JD(GObj,'pa_band_cal_'+IntToStr(i),100.0);
|
G.PABandCal[i] := JD(GObj,'pa_band_cal_'+IntToStr(i), CFG_PA_BAND_CAL_DEFAULT[i]);
|
||||||
|
for i := 0 to CFG_XVTR_COUNT-1 do
|
||||||
|
G.VHFBandCal[i] := JD(GObj,'vhf_cal_'+IntToStr(i), CFG_VHF_CAL_DEFAULT);
|
||||||
// CAT serial settings
|
// CAT serial settings
|
||||||
for i := 0 to 3 do begin
|
for i := 0 to 3 do begin
|
||||||
G.CATSerialEnabled[i] := JB(GObj,'cat_serial_en_'+IntToStr(i), False);
|
G.CATSerialEnabled[i] := JB(GObj,'cat_serial_en_'+IntToStr(i), False);
|
||||||
@@ -688,6 +697,8 @@ begin
|
|||||||
JW(O,'pa_max_power',G.PAMaxPower);
|
JW(O,'pa_max_power',G.PAMaxPower);
|
||||||
for i := 0 to CFG_BAND_COUNT-1 do
|
for i := 0 to CFG_BAND_COUNT-1 do
|
||||||
JW(O,'pa_band_cal_'+IntToStr(i),G.PABandCal[i]);
|
JW(O,'pa_band_cal_'+IntToStr(i),G.PABandCal[i]);
|
||||||
|
for i := 0 to CFG_XVTR_COUNT-1 do
|
||||||
|
JW(O,'vhf_cal_'+IntToStr(i),G.VHFBandCal[i]);
|
||||||
// CAT serial settings
|
// CAT serial settings
|
||||||
for i := 0 to 3 do begin
|
for i := 0 to 3 do begin
|
||||||
JW(O,'cat_serial_en_'+IntToStr(i), G.CATSerialEnabled[i]);
|
JW(O,'cat_serial_en_'+IntToStr(i), G.CATSerialEnabled[i]);
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ type
|
|||||||
// PA: MaxPower в Вт, BandCal[0..10] — калибровка 38.8..100.0 на диапазон
|
// PA: MaxPower в Вт, BandCal[0..10] — калибровка 38.8..100.0 на диапазон
|
||||||
TOnPASettingsChange = procedure(MaxPower: Double;
|
TOnPASettingsChange = procedure(MaxPower: Double;
|
||||||
const BandCal: array of Double) of object;
|
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 с актуальной структурой)
|
// TX-настройки целиком (любая правка → один callback с актуальной структурой)
|
||||||
TOnTXSettingsChange = procedure(const T: TTXSettings) of object;
|
TOnTXSettingsChange = procedure(const T: TTXSettings) of object;
|
||||||
|
|
||||||
@@ -121,6 +123,8 @@ type
|
|||||||
// ---- PA Settings tab controls ----
|
// ---- PA Settings tab controls ----
|
||||||
FEdMaxPower: TSpinEdit;
|
FEdMaxPower: TSpinEdit;
|
||||||
FEdBandCal: array[0..10] of TFloatSpinEdit;
|
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 ----
|
// ---- Transmit tab controls ----
|
||||||
// Текущее состояние, отдаётся в OnTXChange целиком при любой правке.
|
// Текущее состояние, отдаётся в OnTXChange целиком при любой правке.
|
||||||
@@ -252,6 +256,7 @@ type
|
|||||||
// ---- Callbacks ----
|
// ---- Callbacks ----
|
||||||
FOnCATChange: TOnCATChange;
|
FOnCATChange: TOnCATChange;
|
||||||
FOnPAChange: TOnPASettingsChange;
|
FOnPAChange: TOnPASettingsChange;
|
||||||
|
FOnVHFCalChange: TOnVHFCalChange;
|
||||||
FOnADCChange: TOnADCChange;
|
FOnADCChange: TOnADCChange;
|
||||||
FOnWebSettingsChange: TOnWebSettingsChange;
|
FOnWebSettingsChange: TOnWebSettingsChange;
|
||||||
FOnDisplayChange: TOnDisplayParamChange;
|
FOnDisplayChange: TOnDisplayParamChange;
|
||||||
@@ -325,6 +330,7 @@ type
|
|||||||
procedure OnFreqMhzDigitsCmbChange(Sender: TObject);
|
procedure OnFreqMhzDigitsCmbChange(Sender: TObject);
|
||||||
procedure OnMaxPowerChange(Sender: TObject);
|
procedure OnMaxPowerChange(Sender: TObject);
|
||||||
procedure OnBandCalChange(Sender: TObject);
|
procedure OnBandCalChange(Sender: TObject);
|
||||||
|
procedure OnVHFCalEdit(Sender: TObject);
|
||||||
procedure OnCATSerialChange(Sender: TObject);
|
procedure OnCATSerialChange(Sender: TObject);
|
||||||
procedure OnCATTcpChange(Sender: TObject);
|
procedure OnCATTcpChange(Sender: TObject);
|
||||||
procedure FireCATChange;
|
procedure FireCATChange;
|
||||||
@@ -362,6 +368,7 @@ type
|
|||||||
procedure LoadLightTheme(ALight: Boolean);
|
procedure LoadLightTheme(ALight: Boolean);
|
||||||
procedure LoadFreqMhzDigits(Digits: Integer);
|
procedure LoadFreqMhzDigits(Digits: Integer);
|
||||||
procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double);
|
procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double);
|
||||||
|
procedure LoadVHFCalSettings(const VHFCal: array of Double);
|
||||||
procedure LoadCATSettings(
|
procedure LoadCATSettings(
|
||||||
const SerEnabled: array of Boolean;
|
const SerEnabled: array of Boolean;
|
||||||
const SerPort: array of string;
|
const SerPort: array of string;
|
||||||
@@ -379,6 +386,7 @@ type
|
|||||||
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
|
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
|
||||||
property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange;
|
property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange;
|
||||||
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
|
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
|
||||||
|
property OnVHFCalChange: TOnVHFCalChange read FOnVHFCalChange write FOnVHFCalChange;
|
||||||
property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange;
|
property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange;
|
||||||
property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange;
|
property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange;
|
||||||
property OnGridChange: TOnGridParamChange read FOnGridChange write FOnGridChange;
|
property OnGridChange: TOnGridParamChange read FOnGridChange write FOnGridChange;
|
||||||
@@ -1584,6 +1592,38 @@ begin
|
|||||||
FEdBandCal[i].Tag := i;
|
FEdBandCal[i].Tag := i;
|
||||||
end;
|
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;
|
end;
|
||||||
|
|
||||||
procedure TSettingsForm.BuildAdvancedTab;
|
procedure TSettingsForm.BuildAdvancedTab;
|
||||||
@@ -2299,6 +2339,31 @@ begin
|
|||||||
FOnPAChange(FEdMaxPower.Value, BandCal);
|
FOnPAChange(FEdMaxPower.Value, BandCal);
|
||||||
end;
|
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);
|
procedure TSettingsForm.BtnCloseClick(Sender: TObject);
|
||||||
begin
|
begin
|
||||||
Close;
|
Close;
|
||||||
@@ -3106,6 +3171,14 @@ begin
|
|||||||
if FXvtrRXAnt[i] <> nil then
|
if FXvtrRXAnt[i] <> nil then
|
||||||
FXvtrRXAnt[i].ItemIndex := EnsureRange(X.Entries[i].RXAntenna, 0, 3);
|
FXvtrRXAnt[i].ItemIndex := EnsureRange(X.Entries[i].RXAntenna, 0, 3);
|
||||||
UpdateXvtrRowEnabled(i);
|
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;
|
end;
|
||||||
if FChkXvtrTunPwr <> nil then FChkXvtrTunPwr.Checked := X.UseXVTRTunePower;
|
if FChkXvtrTunPwr <> nil then FChkXvtrTunPwr.Checked := X.UseXVTRTunePower;
|
||||||
finally
|
finally
|
||||||
|
|||||||
Reference in New Issue
Block a user