feat(pluto): выбор антенного разъёма RX/TX на диапазон (LibreSDR/AntSDR)

Второй разъём у AD936x-плат — это второй КАНАЛ чипа (не порт B первого), и в
1R1T-образе он выбирается мультиплексором AD9361: debug-параметры
adi,1rx-1tx-mode-use-rx-num/-tx-num + биты RX1/RX2, TX1/TX2 регистров 0x3/0x2
(как switch_rfinput.sh/switch_rfoutput.sh прошивки). rf_port_select тут ни при
чём — прошивка его к тому же блокирует.

- IIOBindings: debug-атрибуты + прямой доступ к регистрам.
- PlutoBackend: детект второго разъёма (2R2T-каналы либо мультиплексор + модель
  платы), SetAntPorts(RxPort,TxPort), ApplyRxMux/ApplyTxMux; Caps.HasAntSwitch.
  ⚠ApplyGain(Force): драйвер пропускает запись gain_control_mode, если считает
  режим неизменным, — а у нового канала AGC-регистры не запрограммированы (в
  железе крутится auto-AGC, hardwaregain отвергается EOPNOTSUPP). Форсируем
  перепрог через другой режим после смены разъёма и после ad9361_set_bb_rate.
- RadioBackend: TBackendCaps.HasAntSwitch + virtual SetAntPorts (HPSDR — no-op,
  там антеннами рулит Alex).
- Settings: секция ant936x (разъём RX/TX на диапазон и на слот трансвертера);
  hw-AGC (режим + manual gain) переехал в TBandSettings/TXvtrEntry — теперь он
  свой на каждом диапазоне и трансвертере.
- RadioController: FAnt936x + ApplyAntPorts (RestoreBand/ActivateXvtr/
  StartRunning); ApplyBandDSP восстанавливает hw-AGC диапазона.
- SettingsForm: вкладка Antenna — Alex-таблица (openHPSDR) либо таблица
  разъёмов AD936x по диапазонам/трансвертерам.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 21:26:18 +03:00
co-authored by Claude Opus 4.8
parent a2e403af3d
commit 9dd38e1dd3
8 changed files with 673 additions and 32 deletions
+215 -8
View File
@@ -29,7 +29,7 @@ uses
StdCtrls, ExtCtrls,
FlatButton, FlatCheckBox, FlatComboBox, FlatEdit, FlatSpinEdit, FlatFloatSpinEdit,
FlatRadioButton,
EqualizerControl, AudioOutput, AudioInput, AppTheme, Settings;
EqualizerControl, AudioOutput, AudioInput, AppTheme, Settings, BoardUtils;
type
// PA: MaxPower в Вт, BandCal[0..10] — калибровка 38.8..100.0 на диапазон
@@ -37,6 +37,8 @@ type
const BandCal: array of Double) of object;
// Pluto TX: аттенюация выходного тракта на drive=100% (потолок), дБ<0
TOnPlutoTxChange = procedure(MaxAttDb: Double) of object;
// Антенные разъёмы AD936x (Pluto/LibreSDR): таблица «диапазон → RX/TX разъём»
TOnAnt936xChange = procedure(const A: TAD936xAnt) of object;
// VHF (XVTR) PA calibration: 8 slots, 38.8..100.0
TOnVHFCalChange = procedure(const VHFCal: array of Double) of object;
// TX-настройки целиком (любая правка → один callback с актуальной структурой)
@@ -228,6 +230,21 @@ type
FAlexExt1OnTx: TFlatCheckBox;
FAlexGndBpf2OnTx: TFlatCheckBox;
FAlexEnableXvtrHf: TFlatCheckBox;
// Группы вкладки Antenna: Alex-таблица (openHPSDR) vs выбор приёмного входа
// AD936x (Pluto/LibreSDR) — показывается одна из двух (SetAntennaBackend).
FAlexSubtitle: TLabel;
FAlexGrpAnt: TPanel;
FAlexGrpTX: TPanel;
FPlutoAntGrp: TPanel;
// На диапазон (VU-план, 12 слотов) и на слот трансвертера — по паре радио-
// кнопок RX (RX1/RX2) и TX (TX1/TX2).
FAntRx: array[0..CFG_BAND_COUNT-1, 0..1] of TFlatRadioButton;
FAntTx: array[0..CFG_BAND_COUNT-1, 0..1] of TFlatRadioButton;
FAntXvtrRx: array[0..CFG_XVTR_COUNT-1, 0..1] of TFlatRadioButton;
FAntXvtrTx: array[0..CFG_XVTR_COUNT-1, 0..1] of TFlatRadioButton;
FAntXvtrRow: array[0..CFG_XVTR_COUNT-1] of TPanel; // строка слота (скрыта, если выкл.)
FAntXvtrLbl: array[0..CFG_XVTR_COUNT-1] of TLabel;
FAnt936x: TAD936xAnt;
// Current Alex config state
FAlexConfig: TAlexSettings;
FAlexBoardType: Integer;
@@ -292,6 +309,7 @@ type
FOnCATChange: TOnCATChange;
FOnPAChange: TOnPASettingsChange;
FOnPlutoTxChange: TOnPlutoTxChange;
FOnAnt936xChange: TOnAnt936xChange;
FOnVHFCalChange: TOnVHFCalChange;
FOnADCChange: TOnADCChange;
FOnWebSettingsChange: TOnWebSettingsChange;
@@ -379,6 +397,7 @@ type
procedure OnFreqMhzDigitsCmbChange(Sender: TObject);
procedure OnMaxPowerChange(Sender: TObject);
procedure OnPlutoTxMaxAttChange(Sender: TObject);
procedure Ant936xUIChange(Sender: TObject);
procedure OnBandCalChange(Sender: TObject);
procedure OnVHFCalEdit(Sender: TObject);
procedure OnCATSerialChange(Sender: TObject);
@@ -425,6 +444,10 @@ type
procedure LoadFreqMhzDigits(Digits: Integer);
procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double);
procedure LoadPlutoTxMaxAtt(MaxAttDb: Double);
// Вкладка Antenna: Alex-таблица (openHPSDR) либо таблица разъёмов AD936x.
procedure SetAntennaBackend(IsAD936x, HasAntSwitch: Boolean);
// Таблица разъёмов + имена/видимость слотов трансвертеров.
procedure LoadAnt936x(const A: TAD936xAnt; const X: TXvtrSettings);
procedure LoadVHFCalSettings(const VHFCal: array of Double);
procedure LoadCATSettings(
const SerEnabled: array of Boolean;
@@ -448,6 +471,7 @@ type
property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange;
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
property OnPlutoTxChange: TOnPlutoTxChange read FOnPlutoTxChange write FOnPlutoTxChange;
property OnAnt936xChange: TOnAnt936xChange read FOnAnt936xChange write FOnAnt936xChange;
property OnVHFCalChange: TOnVHFCalChange read FOnVHFCalChange write FOnVHFCalChange;
property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange;
property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange;
@@ -2977,6 +3001,12 @@ const
TX3_X = PAD + 360;
DNTX_X = PAD + 398;
GRP_W = DNTX_X + 80;
// Таблица разъёмов AD936x (Pluto/LibreSDR) — та же сетка, свои колонки.
ANT_RX1_X = PAD + 90;
ANT_RX2_X = PAD + 116;
ANT_TX1_X = PAD + 160;
ANT_TX2_X = PAD + 186;
ANT_GRP_W = ANT_TX2_X + 60;
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;
@@ -3029,6 +3059,44 @@ var
Result.OnChange := OnAlexAnyChange;
end;
// ---- Таблица разъёмов AD936x ----
function MkAntRb(AParent: TWinControl; AX: Integer): TFlatRadioButton;
begin
Result := TFlatRadioButton.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 := Ant936xUIChange;
end;
function MkAntLbl(AParent: TWinControl; const ACap: string;
AX, AY: Integer): TLabel;
begin
Result := TLabel.Create(Self);
Result.Parent := AParent;
Result.Caption := ACap;
Result.SetBounds(AX, AY, 84, 18);
Result.Font.Name := UI_FONT;
Result.Font.Size := 9;
Result.Font.Color := CLR_TEXT;
end;
procedure MkAntHdr(AParent: TWinControl; const ACap: string; AX, AY: Integer);
begin
with TLabel.Create(Self) do
begin
Parent := AParent;
Caption := ACap;
SetBounds(AX, AY, 30, 16);
Font.Name := UI_FONT;
Font.Size := 9;
Font.Color := CLR_TEXTDIM;
end;
end;
begin
FLoading := True;
try
@@ -3040,13 +3108,14 @@ begin
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;
FAlexSubtitle := TLabel.Create(Self);
FAlexSubtitle.Parent := FPageAlex;
FAlexSubtitle.Caption :=
'Per-band RX/TX antenna and secondary path routing for the Alex filter board.';
FAlexSubtitle.SetBounds(MARGIN, 48, 680, 20);
FAlexSubtitle.Font.Name := UI_FONT;
FAlexSubtitle.Font.Size := 9;
FAlexSubtitle.Font.Color := CLR_TEXTDIM;
// ---- Antenna grid group ----
Grp := MakeGroupPanel(FPageAlex, 'Antenna Selection', MARGIN, 86, GRP_W, GRP_H);
@@ -3123,11 +3192,149 @@ begin
PAD, GRP_TITLE_H + 8 + 56, 440);
FAlexEnableXvtrHf := MkCh(GrpTX, 'Enable XVTR HF',
PAD, GRP_TITLE_H + 8 + 84, 440);
FAlexGrpAnt := Grp;
FAlexGrpTX := GrpTX;
// ---- Антенные разъёмы AD936x (Pluto/LibreSDR) ----
// Alex-таблицы у таких плат нет; «антенна» = какой из физических разъёмов
// (RX1/RX2, TX1/TX2) используется на диапазоне. Строки — VU-план + слоты
// трансвертеров (QO-100 и пр.). Группа занимает место Alex-групп и
// показывается вместо них (SetAntennaBackend).
FPlutoAntGrp := MakeGroupPanel(FPageAlex, 'Antenna Ports',
MARGIN, 86, ANT_GRP_W,
GRP_TITLE_H + HDR2_H + (CFG_BAND_COUNT + CFG_XVTR_COUNT) * ROW_H + PAD);
FPlutoAntGrp.Visible := False;
MkAntHdr(FPlutoAntGrp, 'RX', ANT_RX1_X - 4, GRP_TITLE_H - 2);
MkAntHdr(FPlutoAntGrp, 'TX', ANT_TX1_X - 4, GRP_TITLE_H - 2);
MkAntHdr(FPlutoAntGrp, '1', ANT_RX1_X + 4, GRP_TITLE_H + 14);
MkAntHdr(FPlutoAntGrp, '2', ANT_RX2_X + 4, GRP_TITLE_H + 14);
MkAntHdr(FPlutoAntGrp, '1', ANT_TX1_X + 4, GRP_TITLE_H + 14);
MkAntHdr(FPlutoAntGrp, '2', ANT_TX2_X + 4, GRP_TITLE_H + 14);
Y := GRP_TITLE_H + HDR2_H + 4;
for b := 0 to CFG_BAND_COUNT - 1 do
begin
// Пустой слот VU-плана (12-й у HF) не рисуем — строка остаётся пустой.
if BandName(b, True) <> '' then
begin
MkAntLbl(FPlutoAntGrp, BandName(b, True), BAND_X, Y + 2);
RxGrp := MkRbGroup(FPlutoAntGrp, ANT_RX1_X, Y, 60);
FAntRx[b, 0] := MkAntRb(RxGrp, 0);
FAntRx[b, 1] := MkAntRb(RxGrp, ANT_RX2_X - ANT_RX1_X);
TxGrp := MkRbGroup(FPlutoAntGrp, ANT_TX1_X, Y, 60);
FAntTx[b, 0] := MkAntRb(TxGrp, 0);
FAntTx[b, 1] := MkAntRb(TxGrp, ANT_TX2_X - ANT_TX1_X);
end;
Inc(Y, ROW_H);
end;
// Слоты трансвертеров: строка на слот, показываем только включённые
// (LoadAnt936x подставит имя и видимость).
for b := 0 to CFG_XVTR_COUNT - 1 do
begin
FAntXvtrRow[b] := TPanel.Create(Self);
FAntXvtrRow[b].Parent := FPlutoAntGrp;
FAntXvtrRow[b].SetBounds(0, Y, ANT_GRP_W, ROW_H);
FAntXvtrRow[b].BevelOuter := bvNone;
FAntXvtrRow[b].Color := CLR_PANEL;
FAntXvtrRow[b].Caption := '';
FAntXvtrRow[b].Visible := False;
FAntXvtrLbl[b] := MkAntLbl(FAntXvtrRow[b], '', BAND_X, 2);
RxGrp := MkRbGroup(FAntXvtrRow[b], ANT_RX1_X, 0, 60);
FAntXvtrRx[b, 0] := MkAntRb(RxGrp, 0);
FAntXvtrRx[b, 1] := MkAntRb(RxGrp, ANT_RX2_X - ANT_RX1_X);
TxGrp := MkRbGroup(FAntXvtrRow[b], ANT_TX1_X, 0, 60);
FAntXvtrTx[b, 0] := MkAntRb(TxGrp, 0);
FAntXvtrTx[b, 1] := MkAntRb(TxGrp, ANT_TX2_X - ANT_TX1_X);
Inc(Y, ROW_H);
end;
finally
FLoading := False;
end;
end;
procedure TSettingsForm.SetAntennaBackend(IsAD936x, HasAntSwitch: Boolean);
// Переключает вкладку Antenna: Alex-таблица (openHPSDR) либо таблица разъёмов
// AD936x. HasAntSwitch=False (стоковый Pluto, один разъём) — Alex-таблица тоже
// не нужна: показываем пустую вкладку с поясняющим подзаголовком.
begin
FLoading := True;
try
if FAlexGrpAnt <> nil then FAlexGrpAnt.Visible := not IsAD936x;
if FAlexGrpTX <> nil then FAlexGrpTX.Visible := not IsAD936x;
if FPlutoAntGrp <> nil then FPlutoAntGrp.Visible := IsAD936x and HasAntSwitch;
if FAlexSubtitle <> nil then
begin
if IsAD936x and HasAntSwitch then
FAlexSubtitle.Caption :=
'Which physical connector (RX1/RX2, TX1/TX2) is used on each band.'
else if IsAD936x then
FAlexSubtitle.Caption := 'This board has a single antenna connector pair.'
else
FAlexSubtitle.Caption :=
'Per-band RX/TX antenna and secondary path routing for the Alex filter board.';
end;
finally
FLoading := False;
end;
end;
procedure TSettingsForm.LoadAnt936x(const A: TAD936xAnt; const X: TXvtrSettings);
// Таблица разъёмов + строки трансвертеров (видимы только включённые слоты).
var b: Integer;
begin
FLoading := True;
try
FAnt936x := A;
for b := 0 to CFG_BAND_COUNT - 1 do
begin
if FAntRx[b, 0] = nil then Continue;
FAntRx[b, 0].Checked := A.RxAnt[b] = 0;
FAntRx[b, 1].Checked := A.RxAnt[b] = 1;
FAntTx[b, 0].Checked := A.TxAnt[b] = 0;
FAntTx[b, 1].Checked := A.TxAnt[b] = 1;
end;
for b := 0 to CFG_XVTR_COUNT - 1 do
begin
if FAntXvtrRow[b] = nil then Continue;
FAntXvtrRow[b].Visible := X.Entries[b].Enabled;
if X.Entries[b].ButtonText <> '' then
FAntXvtrLbl[b].Caption := string(X.Entries[b].ButtonText)
else
FAntXvtrLbl[b].Caption := Format('XVTR %d', [b + 1]);
FAntXvtrRx[b, 0].Checked := A.XvtrRxAnt[b] = 0;
FAntXvtrRx[b, 1].Checked := A.XvtrRxAnt[b] = 1;
FAntXvtrTx[b, 0].Checked := A.XvtrTxAnt[b] = 0;
FAntXvtrTx[b, 1].Checked := A.XvtrTxAnt[b] = 1;
end;
finally
FLoading := False;
end;
end;
procedure TSettingsForm.Ant936xUIChange(Sender: TObject);
// Любая радиокнопка таблицы разъёмов: собрать состояние и отдать наружу.
var b: Integer;
begin
if FLoading then Exit;
for b := 0 to CFG_BAND_COUNT - 1 do
begin
if FAntRx[b, 0] = nil then Continue;
if FAntRx[b, 1].Checked then FAnt936x.RxAnt[b] := 1 else FAnt936x.RxAnt[b] := 0;
if FAntTx[b, 1].Checked then FAnt936x.TxAnt[b] := 1 else FAnt936x.TxAnt[b] := 0;
end;
for b := 0 to CFG_XVTR_COUNT - 1 do
begin
if FAntXvtrRx[b, 0] = nil then Continue;
if FAntXvtrRx[b, 1].Checked then FAnt936x.XvtrRxAnt[b] := 1
else FAnt936x.XvtrRxAnt[b] := 0;
if FAntXvtrTx[b, 1].Checked then FAnt936x.XvtrTxAnt[b] := 1
else FAnt936x.XvtrTxAnt[b] := 0;
end;
if Assigned(FOnAnt936xChange) then FOnAnt936xChange(FAnt936x);
end;
// ---------------------------------------------------------------------------
// Alex event handlers and data collectors
// ---------------------------------------------------------------------------