add Alex setup

This commit is contained in:
2026-05-14 18:52:50 +03:00
parent ec596bb0c8
commit aea9f02df8
4 changed files with 559 additions and 52 deletions
+102 -38
View File
@@ -19,7 +19,7 @@ uses
{$ELSE}
Sockets, BaseUnix,
{$ENDIF}
HPSDRProtocol, SyncObjs;
HPSDRProtocol, SyncObjs, Settings;
const
DUC_TX_QUEUE_SIZE = 256; // power of two, bounded latency on TX underrun/overrun
@@ -141,6 +141,7 @@ type
FIsTransmitting: Boolean;
FPAEnabled: Boolean;
FAlexEnabled: Boolean;
FAlexConfig: TAlexSettings; // per-band antenna + routing config
FStepAtten: Byte; // ADC0 step attenuator 0-31 dB
FSendLock: TCriticalSection; // защита concurrent UDP sends
@@ -192,6 +193,7 @@ type
procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
Transmitting, PAEnabled, AlexEnabled: Boolean);
procedure SetStepAtten(dB: Byte);
procedure SetAlexConfig(const A: TAlexSettings);
procedure SendFullHP;
procedure SendDDCAudio(const LeftRight: array of SmallInt);
procedure SendDUCIQ(const IData, QData: array of Integer);
@@ -611,6 +613,7 @@ begin
FIsTransmitting := False;
FPAEnabled := True;
FAlexEnabled := True;
TSettingsManager.DefaultAlex(FAlexConfig);
FSendLock := TCriticalSection.Create;
FDUCIQLock := TCriticalSection.Create;
FDUCIQSem := RTLEventCreate;
@@ -1071,65 +1074,126 @@ end;
// Transmitting — режим передачи
// IsOrion2 — ANAN-7000/8000 (другие BPF)
// ---------------------------------------------------------------------------
// Преобразует частоту в индекс диапазона 0=160m..10=6m (Thetis-compatible).
function FreqToBandIndex(FreqHz: Double): Integer;
begin
if FreqHz >= 39850000 then Result := 10 // 6m
else if FreqHz >= 26465000 then Result := 9 // 10m
else if FreqHz >= 23170000 then Result := 8 // 12m
else if FreqHz >= 19584000 then Result := 7 // 15m
else if FreqHz >= 16209000 then Result := 6 // 17m
else if FreqHz >= 12075000 then Result := 5 // 20m
else if FreqHz >= 8700000 then Result := 4 // 30m
else if FreqHz >= 6202000 then Result := 3 // 40m
else if FreqHz >= 4665000 then Result := 2 // 60m
else if FreqHz >= 2750000 then Result := 1 // 80m
else Result := 0; // 160m
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).
function CalcAlex0(RXFreqHz, TXFreqHz: Double;
Transmitting, IsOrion2: Boolean): LongWord;
Transmitting, IsOrion2: Boolean;
const Alex: TAlexSettings): LongWord;
var
txf: Double;
BandIdx: Integer;
AntVal: Byte;
begin
Result := 0;
// TX relay при передаче
// Bit 27 ($08000000) = T/R relay для всех плат
// Bit 18 ($00040000) = TxRx Status для Orion MkII / Saturn / G2
// Bit 27 = T/R relay (0=RX, 1=TX).
// Bit 18 = TxRx Status для Orion MkII 5.2 / 7000DLE / Saturn / G2.
if Transmitting then
begin
Result := Result or $08000000; // ALEX_TX_RELAY bit27
Result := Result or $08000000; // bit 27: T/R relay
if IsOrion2 then
Result := Result or $00040000; // TxRx Status bit18 (ANAN-7/8000, Saturn)
Result := Result or $00040000; // bit 18: TxRx Status
end;
// RX HPF (для ANAN-100/200) или BPF (для ANAN-7000)
// HPF (ANAN-100/200) или BPF (ANAN-7000/8000/Saturn/G2) — по RX-частоте.
if IsOrion2 then
begin
// Band-pass filters ANAN-7000/8000
if RXFreqHz < 1500000 then Result := Result or $00001000 // BYPASS_BPF
else if RXFreqHz < 2100000 then Result := Result or $00000040 // 160 BPF
else if RXFreqHz < 5500000 then Result := Result or $00000020 // 80/60 BPF
else if RXFreqHz < 11000000 then Result := Result or $00000010 // 40/30 BPF
else if RXFreqHz < 22000000 then Result := Result or $00000002 // 20/15 BPF
else if RXFreqHz < 35600000 then Result := Result or $00000004 // 12/10 BPF
else Result := Result or $00000008; // 6m+preamp
// Band-pass filters — Orion MkII 5.2, ANAN-7000DLE, Saturn, ANAN-G2
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
end
else
begin
// High-pass filters ANAN-100/200
if RXFreqHz < 1800000 then Result := Result or $00001000 // BYPASS_HPF
else if RXFreqHz < 6500000 then Result := Result or $00000040 // 1.5 MHz HPF
else if RXFreqHz < 9500000 then Result := Result or $00000020 // 6.5 MHz HPF
else if RXFreqHz < 13000000 then Result := Result or $00000010 // 9.5 MHz HPF
else if RXFreqHz < 20000000 then Result := Result or $00000002 // 13 MHz HPF
else if RXFreqHz < 50000000 then Result := Result or $00000004 // 20 MHz HPF
else Result := Result or $00000008; // 6m preamp
// High-pass filters — Atlas, Hermes, ANAN-10/10E/100/100B/100D/200D
if RXFreqHz < 1800000 then Result := Result or $00001000 // bit 12: HF Bypass
else if RXFreqHz < 6500000 then Result := Result or $00000040 // bit 6: 1.5 MHz HPF
else if RXFreqHz < 9500000 then Result := Result or $00000020 // bit 5: 6.5 MHz HPF
else if RXFreqHz < 13000000 then Result := Result or $00000010 // bit 4: 9.5 MHz HPF
else if RXFreqHz < 20000000 then Result := Result or $00000002 // bit 1: 13 MHz HPF
else if RXFreqHz < 50000000 then Result := Result or $00000004 // bit 2: 20 MHz HPF
else Result := Result or $00000008; // bit 3: 6m preamp
end;
// TX LPF — при RX через Ant1/2/3 сигнал идёт через TX LPF тоже
// (для pre-Orion2 без внешней антенны)
// TX LPF — по TX-частоте. В RX-режиме для не-Orion2 плат RX-сигнал идёт
// через TX LPF (общий сигнальный путь), поэтому используем RX-частоту.
if not Transmitting and not IsOrion2 then
txf := RXFreqHz // RX через Ant1: используем RX частоту для LPF
txf := RXFreqHz
else
txf := TXFreqHz;
if txf > 35600000 then Result := Result or $20000000 // 6m bypass LPF
else if txf > 24000000 then Result := Result or $40000000 // 12/10m LPF
else if txf > 16500000 then Result := Result or $80000000 // 17/15m LPF
else if txf > 8000000 then Result := Result or $00100000 // 30/20m LPF
else if txf > 5000000 then Result := Result or $00200000 // 60/40m LPF
else if txf > 2500000 then Result := Result or $00400000 // 80m LPF
else Result := Result or $00800000; // 160m LPF
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
// TX antenna — ANT1 по умолчанию
if not Transmitting then
Result := Result or $01000000; // ALEX_TX_ANTENNA_1
// Биты 24-26: ANT 1/2/3. Выбор зависит от диапазона и режима TX/RX.
// Дефолт ANT1 — безопасен при любом состоянии.
BandIdx := FreqToBandIndex(RXFreqHz);
if Transmitting then
AntVal := Alex.TxAnt[BandIdx]
else
AntVal := Alex.RxAnt[BandIdx];
if AntVal < 1 then AntVal := 1;
case AntVal of
2: Result := Result or $02000000; // bit 25: ANT 2
3: Result := Result or $04000000; // bit 26: ANT 3
else Result := Result or $01000000; // bit 24: ANT 1 (дефолт)
end;
// Биты 8-11: вторичный RX вход (Bypass relay, Ext1, Ext2, XvtrDDC).
// В режиме TX управляется глобальными флагами; в RX — per-band конфигом.
if Transmitting then
begin
if Alex.RxBypassOnTx then
Result := Result or $00000800; // bit 11: Bypass relay
if Alex.Ext1OnTx then
Result := Result or $00000200 // bit 9: Ext1
or $00000800; // bit 11: Bypass relay
end
else
begin
case Alex.RxOnly[BandIdx] of
1: Result := Result or $00000200 // bit 9: Ext1
or $00000800; // bit 11: Bypass relay
2: Result := Result or $00000400 // bit 10: Ext2
or $00000800; // bit 11: Bypass relay
3: Result := Result or $00000100 // bit 8: XVTR DDC In
or $00000800; // bit 11: Bypass relay
end;
end;
end;
procedure THPSDRNetwork.SetAlexConfig(const A: TAlexSettings);
begin
FAlexConfig := A;
if FConnected then SendFullHP;
end;
procedure THPSDRNetwork.ConfigureDDCs(NumDDCs: Byte; SampleRate: Word;
@@ -1274,7 +1338,7 @@ begin
if FAlexEnabled then
begin
Alex0 := CalcAlex0(FCurrentRXFreq, FCurrentTXFreq,
FIsTransmitting, IsOrion2);
FIsTransmitting, IsOrion2, FAlexConfig);
Buf[1432] := (Alex0 shr 24) and $FF;
Buf[1433] := (Alex0 shr 16) and $FF;
Buf[1434] := (Alex0 shr 8) and $FF;
+27 -1
View File
@@ -159,6 +159,7 @@ type
FAudioOutDevName: string; // текущее имя выходного устройства
FAudioInDevName: string; // текущее имя входного устройства (TX mic)
FTXSettings: TTXSettings; // полный TX-конфиг (загружается per-MAC)
FAlexSettings: TAlexSettings; // per-band antenna/routing config (Alex board)
FWDSPReady: Boolean; // True когда WDSP открыт и работает
// ---- State ----
@@ -560,6 +561,7 @@ type
const SerPort: array of string;
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
TcpEnabled: Boolean; TcpPort: Integer);
procedure OnAlexSettingsChange(const A: TAlexSettings);
// -------------------------------------------------------------------------
procedure BtnWfAGCClick(Sender: TObject);
procedure BtnWfNFClick(Sender: TObject);
@@ -1173,8 +1175,9 @@ begin
FNetwork.OnHPStatus := OnHPStatusCB;
FNetwork.OnDDCIQ := OnDDCIQCB;
FNetwork.OnMicPacket := OnMicPacketCB;
// Дефолты TX (на случай если устройство ещё не выбрано).
// Дефолты TX и Alex (на случай если устройство ещё не выбрано).
TSettingsManager.DefaultTX(FTXSettings);
TSettingsManager.DefaultAlex(FAlexSettings);
BuildUI;
StyleSpanButton(BtnSpan48k, FSampleRate = 48000);
@@ -3333,6 +3336,7 @@ begin
begin
SaveCurrentBand;
FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings);
FSettings.SaveAlex(FDevMAC, FAlexSettings);
end;
FSettings.SaveStartupPreview(FVfoA, FVfoB, FSampleRate);
FSettings.Save;
@@ -3467,6 +3471,9 @@ begin
CATApplySettings(G_Settings);
// TX-настройки per-device — отдельная JSON-секция "tx"
FSettings.LoadTX(FDevMAC, FTXSettings);
// Alex-настройки per-device (антенны/маршрутизация)
FSettings.LoadAlex(FDevMAC, FAlexSettings);
FNetwork.SetAlexConfig(FAlexSettings);
FTXSpecRefLevel := FTXSettings.TXSpecRefLevel;
FTXSpecRange := FTXSettings.TXSpecRange;
if FTXSettings.TXSpecGridStep > 0 then
@@ -4482,6 +4489,10 @@ procedure TMainForm.ApplyMOX(Active: Boolean);
var
WasTransmitting: Boolean;
begin
// Safety: block TX on bands marked DoNotTx in Alex config
if Active and FDevConnected
and FAlexSettings.DoNotTx[EnsureRange(FCurrentBand, 0, 10)] then
Exit;
WasTransmitting := FTransmitting;
FTransmitting := Active;
FDUCPendingCount := 0;
@@ -5437,6 +5448,18 @@ begin
end;
end;
procedure TMainForm.OnAlexSettingsChange(const A: TAlexSettings);
begin
FAlexSettings := A;
if FNetwork.Connected then
FNetwork.SetAlexConfig(A);
if FDevConnected then
begin
FSettings.SaveAlex(FDevMAC, A);
FSettings.Save;
end;
end;
procedure TMainForm.TrkVolumeChange(Sender: TObject);
begin
FVolume := TrkVolume.Position;
@@ -5721,9 +5744,12 @@ begin
SF.OnCATChange := OnCATSettingsChange;
SF.OnThemeChange := SetLightTheme;
SF.OnTXChange := OnTXSettingsChange;
SF.OnAlexChange := OnAlexSettingsChange;
end;
SF := TSettingsForm(FSettingsForm);
SF.LoadTXSettings(FTXSettings);
SF.LoadAlexSettings(FAlexSettings,
IfThen(FNetwork.Connected, FNetwork.Device.BoardType, FPendingBoardType));
SF.LoadPASettings(FPAMaxPower, FPABandCal);
SF.LoadCATSettings(
FCATLastGlobal.CATSerialEnabled,
+80
View File
@@ -103,6 +103,23 @@ type
TXSpecGridStep: Double; // grid step in dB, e.g. 10
end;
// Alex (Antenna + Filter board) settings per-device.
// Хранятся в JSON-секции "alex" под MAC.
// Безопасные дефолты: ANT1 для всех диапазонов, вторичный вход выключен.
TAlexSettings = record
// Per-band (11 bands: 0=160m..10=6m)
RxAnt: array[0..CFG_BAND_COUNT-1] of Byte; // 1..3 (ANT1/2/3)
TxAnt: array[0..CFG_BAND_COUNT-1] of Byte; // 1..3 (ANT1/2/3)
// Вторичный RX вход: 0=выкл, 1=BYPS(Ext1+Bypass), 2=EXT1(Ext2+Bypass), 3=XVTR(XvtrDDC+Bypass)
RxOnly: array[0..CFG_BAND_COUNT-1] of Byte;
DoNotTx: array[0..CFG_BAND_COUNT-1] of Boolean; // программный запрет TX на диапазоне
// Глобальные флаги в режиме TX
RxBypassOnTx: Boolean; // бит 11 (Bypass relay) во время TX
Ext1OnTx: Boolean; // бит 9 (Ext1) + бит 11 во время TX
GndBpf2OnTx: Boolean; // заземление BPF2/RX2 во время TX (Alex1 бит 8, ANAN-8000DLE)
EnableXvtrHf: Boolean; // режим XVTR HF
end;
TGlobalSettings = record
Volume: Integer;
DriveLevel: Integer;
@@ -210,6 +227,10 @@ type
// CAT settings — не привязаны к MAC, хранятся в корне JSON (доступны до подключения)
procedure SaveCATSettings(const G: TGlobalSettings);
procedure LoadCATSettings(var G: TGlobalSettings);
// Alex (antenna) settings per-device (JSON-секция "alex" под MAC).
class procedure DefaultAlex(out A: TAlexSettings);
function LoadAlex(const MAC: array of Byte; out A: TAlexSettings): Boolean;
procedure SaveAlex(const MAC: array of Byte; const A: TAlexSettings);
// Тема — глобальная настройка, не привязана к устройству
procedure SaveTheme(ALightTheme: Boolean);
function LoadTheme: Boolean;
@@ -914,6 +935,65 @@ begin
JW(O,'tx_spec_grid_step', T.TXSpecGridStep);
end;
class procedure TSettingsManager.DefaultAlex(out A: TAlexSettings);
var i: Integer;
begin
FillChar(A, SizeOf(A), 0);
for i := 0 to CFG_BAND_COUNT - 1 do
begin
A.RxAnt[i] := 1; // ANT1 — безопасный дефолт
A.TxAnt[i] := 1; // ANT1
A.RxOnly[i] := 0; // вторичный вход выключен
A.DoNotTx[i] := False;
end;
A.RxBypassOnTx := False;
A.Ext1OnTx := False;
A.GndBpf2OnTx := False;
A.EnableXvtrHf := False;
end;
function TSettingsManager.LoadAlex(const MAC: array of Byte;
out A: TAlexSettings): Boolean;
var MacStr: string; DevObj, O: TJSONObject; i: Integer;
begin
DefaultAlex(A);
MacStr := MacToStr(MAC);
Result := FRoot.Find(MacStr) <> nil;
if not Result then Exit;
DevObj := GetDevObj(MacStr);
if DevObj.Find('alex') = nil then Exit;
O := EnsureObj(DevObj, 'alex');
for i := 0 to CFG_BAND_COUNT - 1 do
begin
A.RxAnt[i] := EnsureRange(JI(O, 'rx_ant_' + IntToStr(i), 1), 1, 3);
A.TxAnt[i] := EnsureRange(JI(O, 'tx_ant_' + IntToStr(i), 1), 1, 3);
A.RxOnly[i] := EnsureRange(JI(O, 'rx_only_' + IntToStr(i), 0), 0, 3);
A.DoNotTx[i] := JB(O, 'do_not_tx_' + IntToStr(i), False);
end;
A.RxBypassOnTx := JB(O, 'rx_bypass_on_tx', False);
A.Ext1OnTx := JB(O, 'ext1_on_tx', False);
A.GndBpf2OnTx := JB(O, 'gnd_bpf2_on_tx', False);
A.EnableXvtrHf := JB(O, 'enable_xvtr_hf', False);
end;
procedure TSettingsManager.SaveAlex(const MAC: array of Byte;
const A: TAlexSettings);
var O: TJSONObject; i: Integer;
begin
O := EnsureObj(GetDevObj(MacToStr(MAC)), 'alex');
for i := 0 to CFG_BAND_COUNT - 1 do
begin
JW(O, 'rx_ant_' + IntToStr(i), Integer(A.RxAnt[i]));
JW(O, 'tx_ant_' + IntToStr(i), Integer(A.TxAnt[i]));
JW(O, 'rx_only_' + IntToStr(i), Integer(A.RxOnly[i]));
JW(O, 'do_not_tx_' + IntToStr(i), A.DoNotTx[i]);
end;
JW(O, 'rx_bypass_on_tx', A.RxBypassOnTx);
JW(O, 'ext1_on_tx', A.Ext1OnTx);
JW(O, 'gnd_bpf2_on_tx', A.GndBpf2OnTx);
JW(O, 'enable_xvtr_hf', A.EnableXvtrHf);
end;
procedure TSettingsManager.SaveTheme(ALightTheme: Boolean);
var O: TJSONObject;
begin
+340 -3
View File
@@ -54,6 +54,7 @@ type
const SerStopBits: array of Integer;
const SerParity: array of Integer;
TcpEnabled: Boolean; TcpPort: Integer) of object;
TOnAlexChange = procedure(const A: TAlexSettings) of object;
{ TSettingsForm }
TSettingsForm = class(TForm)
@@ -176,6 +177,29 @@ type
FEdTXRange: TEdit;
FCmbTXGridStep: TComboBox;
// ---- Alex / Antenna tab controls ----
FPageAlex: TScrollBox;
FNavAlex: TFlatButton;
// Per-band antenna radio buttons (bands 0..10 = 160m..6m)
FAlexRxAnt: array[0..10, 1..3] of TRadioButton;
FAlexTxAnt: array[0..10, 1..3] of TRadioButton;
// Per-band secondary RX (взаимно исключающие checkboxes)
FAlexRxByps: array[0..10] of TCheckBox; // BYPS: Ext1+Bypass
FAlexRxExt1: array[0..10] of TCheckBox; // EXT1: Ext2+Bypass
FAlexRxXvtr: array[0..10] of TCheckBox; // XVTR: XvtrDDC+Bypass
// Per-band Do Not TX
FAlexDoNotTx: array[0..10] of TCheckBox;
// Global TX routing flags
FAlexRxBypassOnTx: TCheckBox;
FAlexExt1OnTx: TCheckBox;
FAlexGndBpf2OnTx: TCheckBox;
FAlexEnableXvtrHf: TCheckBox;
// Current Alex config state
FAlexConfig: TAlexSettings;
FAlexBoardType: Integer;
// Callback
FOnAlexChange: TOnAlexChange;
// ---- CAT tab controls ----
FCATSerialEn: array[0..3] of TCheckBox;
FCATSerialPort: array[0..3] of TEdit;
@@ -211,6 +235,11 @@ type
procedure BuildPATab;
procedure BuildCATTab;
procedure BuildTransmitTab;
procedure BuildAlexTab;
procedure CollectAlexFromUI;
procedure FireAlexChange;
procedure OnAlexAnyChange(Sender: TObject);
procedure OnAlexRxOnlyChange(Sender: TObject);
procedure CollectTXFromUI;
procedure FireTXChange;
procedure OnTXAnyChange(Sender: TObject);
@@ -288,8 +317,10 @@ type
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
TcpEnabled: Boolean; TcpPort: Integer);
procedure LoadTXSettings(const T: TTXSettings);
procedure LoadAlexSettings(const A: TAlexSettings; BoardType: Integer);
property OnCATChange: TOnCATChange read FOnCATChange write FOnCATChange;
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange;
property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange;
@@ -508,6 +539,7 @@ begin
FNavPA := MakeNavButton('Power Amplifier', 254);
FNavAdvanced := MakeNavButton('Advanced', 290);
FNavCAT := MakeNavButton('CAT', 326);
FNavAlex := MakeNavButton('Antenna / Alex', 362);
FContentPanel := TPanel.Create(Self);
FContentPanel.Parent := Self;
@@ -523,6 +555,9 @@ begin
FPagePA := MakeScrollPage;
FPageAdvanced := MakeScrollPage;
FPageCAT := MakeScrollPage;
FPageAlex := MakeScrollPage;
FAlexBoardType := 0;
TSettingsManager.DefaultAlex(FAlexConfig);
BuildAudioTab;
BuildDisplayTab;
@@ -530,6 +565,7 @@ begin
BuildTransmitTab;
BuildPATab;
BuildCATTab;
BuildAlexTab;
FBtnClose := TFlatButton.Create(Self);
FBtnClose.Parent := Self;
@@ -558,7 +594,8 @@ begin
else if Sender = FNavTransmit then SelectPage(FPageTransmit, FNavTransmit)
else if Sender = FNavPA then SelectPage(FPagePA, FNavPA)
else if Sender = FNavAdvanced then SelectPage(FPageAdvanced, FNavAdvanced)
else if Sender = FNavCAT then SelectPage(FPageCAT, FNavCAT);
else if Sender = FNavCAT then SelectPage(FPageCAT, FNavCAT)
else if Sender = FNavAlex then SelectPage(FPageAlex, FNavAlex);
end;
procedure TSettingsForm.SelectPage(APage: TWinControl; ANav: TFlatButton);
@@ -566,20 +603,22 @@ begin
FPageAudio.Visible := APage = FPageAudio;
FPageDisplay.Visible := APage = FPageDisplay;
FPageSpectrum.Visible := APage = FPageSpectrum;
FPageWaterfall.Visible := APage = FPageWaterfall;
FPageWaterfall.Visible:= APage = FPageWaterfall;
FPageTransmit.Visible := APage = FPageTransmit;
FPagePA.Visible := APage = FPagePA;
FPageAdvanced.Visible := APage = FPageAdvanced;
FPageCAT.Visible := APage = FPageCAT;
FPageAlex.Visible := APage = FPageAlex;
FNavAudio.Active := ANav = FNavAudio;
FNavDisplay.Active := ANav = FNavDisplay;
FNavSpectrum.Active := ANav = FNavSpectrum;
FNavWaterfall.Active := ANav = FNavWaterfall;
FNavWaterfall.Active:= ANav = FNavWaterfall;
FNavTransmit.Active := ANav = FNavTransmit;
FNavPA.Active := ANav = FNavPA;
FNavAdvanced.Active := ANav = FNavAdvanced;
FNavCAT.Active := ANav = FNavCAT;
FNavAlex.Active := ANav = FNavAlex;
end;
// ---------------------------------------------------------------------------
@@ -2194,4 +2233,302 @@ begin
end;
end;
// ---------------------------------------------------------------------------
// BuildAlexTab
// ---------------------------------------------------------------------------
procedure TSettingsForm.BuildAlexTab;
const
MARGIN = 22;
PAD = 12;
HDR1_H = 22; // height of "RX Ant / TX Ant" header row
HDR2_H = 20; // height of "1 2 3 / BYPS EXT1 XVTR" sub-header row
ROW_H = 24;
BANDS = 11;
BAND_X = PAD;
RX1_X = PAD + 50;
RX2_X = PAD + 76;
RX3_X = PAD + 102;
BYPS_X = PAD + 136;
EXT1_X = PAD + 192;
XVTR_X = PAD + 248;
TX1_X = PAD + 308;
TX2_X = PAD + 334;
TX3_X = PAD + 360;
DNTX_X = PAD + 398;
GRP_W = DNTX_X + 80;
GRP_H = GRP_TITLE_H + HDR1_H + HDR2_H + BANDS * ROW_H + PAD;
DATA_Y0 = GRP_TITLE_H + HDR1_H + HDR2_H + 4;
TXGRP_H = GRP_TITLE_H + 4 * 28 + PAD;
BAND_NAMES: array[0..10] of string =
('160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
var
Grp, GrpTX: TPanel;
b: Integer;
Y: Integer;
Chk: TCheckBox;
RxGrp, TxGrp: TPanel;
// In Lazarus, TRadioButton has no GroupIndex; buttons are grouped by parent.
// We use small invisible panels as parents so each band's RX and TX groups
// are independent of each other.
function MkRbGroup(AParent: TWinControl; AX, AY, AW: Integer): TPanel;
begin
Result := TPanel.Create(Self);
Result.Parent := AParent;
Result.SetBounds(AX, AY, AW, 20);
Result.BevelOuter := bvNone;
Result.Color := CLR_PANEL;
Result.Caption := '';
end;
function MkRb(AParent: TWinControl; AX: Integer): TRadioButton;
begin
Result := TRadioButton.Create(Self);
Result.Parent := AParent;
Result.Caption := '';
Result.SetBounds(AX, 0, 22, 20);
Result.Font.Color := CLR_TEXT;
Result.Font.Name := UI_FONT;
Result.Font.Size := 9;
Result.OnChange := OnAlexAnyChange;
end;
function MkCh(AParent: TWinControl; const ACap: string;
AX, AY, AW: Integer): TCheckBox;
begin
Result := TCheckBox.Create(Self);
Result.Parent := AParent;
Result.Caption := ACap;
Result.SetBounds(AX, AY, AW, 20);
Result.Font.Color := CLR_TEXT;
Result.Font.Name := UI_FONT;
Result.Font.Size := 9;
Result.OnChange := OnAlexAnyChange;
end;
begin
FLoading := True;
try
with TLabel.Create(Self) do
begin
Parent := FPageAlex;
Caption := 'Antenna / Alex';
SetBounds(MARGIN, 18, 500, 28);
Font.Name := UI_FONT; Font.Size := 16;
Font.Style := [fsBold]; Font.Color := CLR_TEXT;
end;
with TLabel.Create(Self) do
begin
Parent := FPageAlex;
Caption := 'Per-band RX/TX antenna and secondary path routing for the Alex filter board.';
SetBounds(MARGIN, 48, 680, 20);
Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM;
end;
// ---- Antenna grid group ----
Grp := MakeGroupPanel(FPageAlex, 'Antenna Selection', MARGIN, 86, GRP_W, GRP_H);
// Header row 1 — group labels
MakeLbl(Grp, 'Band', BAND_X, GRP_TITLE_H + 2, 44);
MakeLbl(Grp, 'RX Antenna', RX1_X, GRP_TITLE_H + 2, 80);
MakeLbl(Grp, 'Secondary RX', BYPS_X, GRP_TITLE_H + 2, 170);
MakeLbl(Grp, 'TX Antenna', TX1_X, GRP_TITLE_H + 2, 80);
MakeLbl(Grp, 'No TX', DNTX_X, GRP_TITLE_H + 2, 56);
// Header row 2 — sub-column labels
MakeLbl(Grp, '1', RX1_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
MakeLbl(Grp, '2', RX2_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
MakeLbl(Grp, '3', RX3_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
MakeLbl(Grp, 'BYPS', BYPS_X, GRP_TITLE_H + HDR1_H + 2, 52);
MakeLbl(Grp, 'EXT1', EXT1_X, GRP_TITLE_H + HDR1_H + 2, 52);
MakeLbl(Grp, 'XVTR', XVTR_X, GRP_TITLE_H + HDR1_H + 2, 52);
MakeLbl(Grp, '1', TX1_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
MakeLbl(Grp, '2', TX2_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
MakeLbl(Grp, '3', TX3_X + 3, GRP_TITLE_H + HDR1_H + 2, 20);
// Data rows (bands 0..10 = 160m..6m)
for b := 0 to BANDS - 1 do
begin
Y := DATA_Y0 + b * ROW_H;
MakeLbl(Grp, BAND_NAMES[b], BAND_X, Y + 2, 44);
// RX antenna — 3 radio buttons in a dedicated panel so they are mutually
// exclusive only within that band row (Lazarus groups by parent, not GroupIndex)
RxGrp := MkRbGroup(Grp, RX1_X, Y, RX3_X - RX1_X + 26);
FAlexRxAnt[b, 1] := MkRb(RxGrp, 0);
FAlexRxAnt[b, 2] := MkRb(RxGrp, RX2_X - RX1_X);
FAlexRxAnt[b, 3] := MkRb(RxGrp, RX3_X - RX1_X);
FAlexRxAnt[b, 1].Checked := True;
// Secondary RX checkboxes — mutually exclusive, handled in OnAlexRxOnlyChange
// Tag encodes: b=BYPS, b+100=EXT1, b+200=XVTR
Chk := MkCh(Grp, '', BYPS_X, Y, 22);
Chk.Tag := b;
Chk.OnChange := OnAlexRxOnlyChange;
FAlexRxByps[b] := Chk;
Chk := MkCh(Grp, '', EXT1_X, Y, 22);
Chk.Tag := b + 100;
Chk.OnChange := OnAlexRxOnlyChange;
FAlexRxExt1[b] := Chk;
Chk := MkCh(Grp, '', XVTR_X, Y, 22);
Chk.Tag := b + 200;
Chk.OnChange := OnAlexRxOnlyChange;
FAlexRxXvtr[b] := Chk;
// TX antenna — same pattern: dedicated panel per band row
TxGrp := MkRbGroup(Grp, TX1_X, Y, TX3_X - TX1_X + 26);
FAlexTxAnt[b, 1] := MkRb(TxGrp, 0);
FAlexTxAnt[b, 2] := MkRb(TxGrp, TX2_X - TX1_X);
FAlexTxAnt[b, 3] := MkRb(TxGrp, TX3_X - TX1_X);
FAlexTxAnt[b, 1].Checked := True;
// Do Not TX checkbox
FAlexDoNotTx[b] := MkCh(Grp, '', DNTX_X, Y, 22);
end;
// ---- TX Routing flags group ----
GrpTX := MakeGroupPanel(FPageAlex, 'TX Routing',
MARGIN, 86 + GRP_H + 12, 480, TXGRP_H);
FAlexRxBypassOnTx := MkCh(GrpTX, 'Rx BYPASS on TX',
PAD, GRP_TITLE_H + 8, 440);
FAlexExt1OnTx := MkCh(GrpTX, 'Ext 1 on TX',
PAD, GRP_TITLE_H + 8 + 28, 440);
FAlexGndBpf2OnTx := MkCh(GrpTX, 'Ground BPF2 on TX (ANAN-8000DLE)',
PAD, GRP_TITLE_H + 8 + 56, 440);
FAlexEnableXvtrHf := MkCh(GrpTX, 'Enable XVTR HF',
PAD, GRP_TITLE_H + 8 + 84, 440);
finally
FLoading := False;
end;
end;
// ---------------------------------------------------------------------------
// Alex event handlers and data collectors
// ---------------------------------------------------------------------------
procedure TSettingsForm.CollectAlexFromUI;
var b, a: Integer;
begin
for b := 0 to 10 do
begin
FAlexConfig.RxAnt[b] := 1;
for a := 1 to 3 do
if (FAlexRxAnt[b, a] <> nil) and FAlexRxAnt[b, a].Checked then
begin
FAlexConfig.RxAnt[b] := a;
Break;
end;
FAlexConfig.TxAnt[b] := 1;
for a := 1 to 3 do
if (FAlexTxAnt[b, a] <> nil) and FAlexTxAnt[b, a].Checked then
begin
FAlexConfig.TxAnt[b] := a;
Break;
end;
if (FAlexRxXvtr[b] <> nil) and FAlexRxXvtr[b].Checked then
FAlexConfig.RxOnly[b] := 3
else if (FAlexRxExt1[b] <> nil) and FAlexRxExt1[b].Checked then
FAlexConfig.RxOnly[b] := 2
else if (FAlexRxByps[b] <> nil) and FAlexRxByps[b].Checked then
FAlexConfig.RxOnly[b] := 1
else
FAlexConfig.RxOnly[b] := 0;
FAlexConfig.DoNotTx[b] := (FAlexDoNotTx[b] <> nil) and FAlexDoNotTx[b].Checked;
end;
FAlexConfig.RxBypassOnTx := (FAlexRxBypassOnTx <> nil) and FAlexRxBypassOnTx.Checked;
FAlexConfig.Ext1OnTx := (FAlexExt1OnTx <> nil) and FAlexExt1OnTx.Checked;
FAlexConfig.GndBpf2OnTx := (FAlexGndBpf2OnTx <> nil) and FAlexGndBpf2OnTx.Checked;
FAlexConfig.EnableXvtrHf := (FAlexEnableXvtrHf <> nil) and FAlexEnableXvtrHf.Checked;
end;
procedure TSettingsForm.FireAlexChange;
begin
if FLoading then Exit;
CollectAlexFromUI;
if Assigned(FOnAlexChange) then
FOnAlexChange(FAlexConfig);
end;
procedure TSettingsForm.OnAlexAnyChange(Sender: TObject);
begin
FireAlexChange;
end;
procedure TSettingsForm.OnAlexRxOnlyChange(Sender: TObject);
var
Tag, b: Integer;
begin
if FLoading then Exit;
Tag := TCheckBox(Sender).Tag;
if Tag >= 200 then
begin
b := Tag - 200; // XVTR
if TCheckBox(Sender).Checked then
begin
FLoading := True;
FAlexRxByps[b].Checked := False;
FAlexRxExt1[b].Checked := False;
FLoading := False;
end;
end
else if Tag >= 100 then
begin
b := Tag - 100; // EXT1
if TCheckBox(Sender).Checked then
begin
FLoading := True;
FAlexRxByps[b].Checked := False;
FAlexRxXvtr[b].Checked := False;
FLoading := False;
end;
end
else
begin
b := Tag; // BYPS
if TCheckBox(Sender).Checked then
begin
FLoading := True;
FAlexRxExt1[b].Checked := False;
FAlexRxXvtr[b].Checked := False;
FLoading := False;
end;
end;
FireAlexChange;
end;
procedure TSettingsForm.LoadAlexSettings(const A: TAlexSettings; BoardType: Integer);
var b, ant: Integer;
begin
FLoading := True;
try
FAlexConfig := A;
FAlexBoardType := BoardType;
for b := 0 to 10 do
begin
for ant := 1 to 3 do
if FAlexRxAnt[b, ant] <> nil then
FAlexRxAnt[b, ant].Checked := (A.RxAnt[b] = ant);
for ant := 1 to 3 do
if FAlexTxAnt[b, ant] <> nil then
FAlexTxAnt[b, ant].Checked := (A.TxAnt[b] = ant);
if FAlexRxByps[b] <> nil then FAlexRxByps[b].Checked := (A.RxOnly[b] = 1);
if FAlexRxExt1[b] <> nil then FAlexRxExt1[b].Checked := (A.RxOnly[b] = 2);
if FAlexRxXvtr[b] <> nil then FAlexRxXvtr[b].Checked := (A.RxOnly[b] = 3);
if FAlexDoNotTx[b] <> nil then FAlexDoNotTx[b].Checked := A.DoNotTx[b];
end;
if FAlexRxBypassOnTx <> nil then FAlexRxBypassOnTx.Checked := A.RxBypassOnTx;
if FAlexExt1OnTx <> nil then FAlexExt1OnTx.Checked := A.Ext1OnTx;
if FAlexGndBpf2OnTx <> nil then FAlexGndBpf2OnTx.Checked := A.GndBpf2OnTx;
if FAlexEnableXvtrHf <> nil then FAlexEnableXvtrHf.Checked := A.EnableXvtrHf;
finally
FLoading := False;
end;
end;
end.