mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
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:
@@ -82,6 +82,10 @@ type
|
||||
FMRptOffsetHz: Double; // Hz, 0 = use band default
|
||||
ZoomFactor: Double; // зум спектра/водопада 0.0..1.0 (0.0 = без зума)
|
||||
PanSlider: Double; // положение окна зума 0..1 (0.5 = центр)
|
||||
// Аппаратный AGC AD936x (Pluto/LibreSDR) — на диапазон: усиление тракта
|
||||
// сильно разное на 2м и на QO-100, поэтому режим и manual-гейн per-band.
|
||||
RxGainMode: Integer; // 0=manual,1=fast,2=slow,3=hybrid
|
||||
RxGainDb: Integer; // manual gain, дБ (0..73)
|
||||
end;
|
||||
|
||||
// Персист панадаптеров и слайсов (секция "pans" под MAC). Восстанавливается
|
||||
@@ -251,6 +255,9 @@ type
|
||||
LastFMRptAutoActive: Boolean; // RPT был включён каналом (авто)
|
||||
LastZoomFactor: Double; // зум спектра/водопада 0.0..1.0 (0.0 = без зума)
|
||||
LastPanSlider: Double; // положение окна зума 0..1 (0.5 = центр)
|
||||
// Аппаратный AGC AD936x на слот трансвертера (у QO-100 своё усиление тракта).
|
||||
LastRxGainMode: Integer; // 0=manual,1=fast,2=slow,3=hybrid
|
||||
LastRxGainDb: Integer; // manual gain, дБ
|
||||
end;
|
||||
|
||||
TXvtrSettings = record
|
||||
@@ -262,6 +269,16 @@ type
|
||||
// Alex (Antenna + Filter board) settings per-device.
|
||||
// Хранятся в JSON-секции "alex" под MAC.
|
||||
// Безопасные дефолты: ANT1 для всех диапазонов, вторичный вход выключен.
|
||||
// Антенные разъёмы AD936x (Pluto/LibreSDR): какой физический вход/выход
|
||||
// используется на каждом диапазоне. 0 = RX1/TX1, 1 = RX2/TX2.
|
||||
// Диапазоны — VU-план BoardUtils (12 слотов), плюс слоты трансвертеров.
|
||||
TAD936xAnt = record
|
||||
RxAnt: array[0..CFG_BAND_COUNT-1] of Byte;
|
||||
TxAnt: array[0..CFG_BAND_COUNT-1] of Byte;
|
||||
XvtrRxAnt: array[0..CFG_XVTR_COUNT-1] of Byte;
|
||||
XvtrTxAnt: array[0..CFG_XVTR_COUNT-1] of Byte;
|
||||
end;
|
||||
|
||||
TAlexSettings = record
|
||||
// Per-band (11 bands: 0=160m..10=6m)
|
||||
RxAnt: array[0..CFG_BAND_COUNT-1] of Byte; // 1..3 (ANT1/2/3)
|
||||
@@ -411,6 +428,9 @@ type
|
||||
procedure SaveCATSettings(const G: TGlobalSettings);
|
||||
procedure LoadCATSettings(var G: TGlobalSettings);
|
||||
// Alex (antenna) settings per-device (JSON-секция "alex" под MAC).
|
||||
class procedure DefaultAnt936x(out A: TAD936xAnt);
|
||||
function LoadAnt936x(const MAC: array of Byte; out A: TAD936xAnt): Boolean;
|
||||
procedure SaveAnt936x(const MAC: array of Byte; const A: TAD936xAnt);
|
||||
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);
|
||||
@@ -473,6 +493,8 @@ begin
|
||||
B.CTun := False;
|
||||
B.CenterHz := 0; // 0 = нет сохранённого центра → центрировать на VFO
|
||||
B.SpanHz := 192000;
|
||||
B.RxGainMode := 2; // slow_attack (как глобальный дефолт Pluto)
|
||||
B.RxGainDb := 40;
|
||||
B.FMSQOn := False;
|
||||
B.FMSQLevel := 30;
|
||||
B.CTCSSOn := False;
|
||||
@@ -817,6 +839,8 @@ begin
|
||||
Bands[i].FMStepIdx := EnsureRange(JI(BObj,'fmstep_idx', Bands[i].FMStepIdx), 0, 3);
|
||||
Bands[i].FMRptDir := EnsureRange(JI(BObj,'fmrpt_dir', Bands[i].FMRptDir), 0, 2);
|
||||
Bands[i].FMRptOffsetHz := JD(BObj,'fmrpt_offset', Bands[i].FMRptOffsetHz);
|
||||
Bands[i].RxGainMode := EnsureRange(JI(BObj,'rx_gain_mode', Bands[i].RxGainMode), 0, 3);
|
||||
Bands[i].RxGainDb := EnsureRange(JI(BObj,'rx_gain_db', Bands[i].RxGainDb), 0, 73);
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -916,6 +940,7 @@ begin
|
||||
JW(O,'fmstep_on',B.FMStepOn); JW(O,'fmstep_idx',B.FMStepIdx);
|
||||
JW(O,'fmrpt_dir',B.FMRptDir); JW(O,'fmrpt_offset',B.FMRptOffsetHz);
|
||||
JW(O,'zoom_factor',B.ZoomFactor); JW(O,'pan_slider',B.PanSlider);
|
||||
JW(O,'rx_gain_mode',B.RxGainMode); JW(O,'rx_gain_db',B.RxGainDb);
|
||||
end;
|
||||
|
||||
function TSettingsManager.LoadBand(const MAC: array of Byte; BandIdx: Integer;
|
||||
@@ -945,6 +970,8 @@ begin
|
||||
B.FMStepIdx := EnsureRange(JI(O,'fmstep_idx', B.FMStepIdx), 0, 3);
|
||||
B.FMRptDir := EnsureRange(JI(O,'fmrpt_dir', B.FMRptDir), 0, 2);
|
||||
B.FMRptOffsetHz := JD(O,'fmrpt_offset', B.FMRptOffsetHz);
|
||||
B.RxGainMode := EnsureRange(JI(O,'rx_gain_mode', B.RxGainMode), 0, 3);
|
||||
B.RxGainDb := EnsureRange(JI(O,'rx_gain_db', B.RxGainDb), 0, 73);
|
||||
B.ZoomFactor := EnsureRange(JD(O,'zoom_factor', B.ZoomFactor), 0.0, 1.0);
|
||||
B.PanSlider := EnsureRange(JD(O,'pan_slider', B.PanSlider), 0.0, 1.0);
|
||||
end;
|
||||
@@ -1304,6 +1331,61 @@ begin
|
||||
A.EnableXvtrHf := JB(O, 'enable_xvtr_hf', False);
|
||||
end;
|
||||
|
||||
class procedure TSettingsManager.DefaultAnt936x(out A: TAD936xAnt);
|
||||
var i: Integer;
|
||||
begin
|
||||
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||
begin
|
||||
A.RxAnt[i] := 0; // RX1
|
||||
A.TxAnt[i] := 0; // TX1
|
||||
end;
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
A.XvtrRxAnt[i] := 0;
|
||||
A.XvtrTxAnt[i] := 0;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TSettingsManager.LoadAnt936x(const MAC: array of Byte;
|
||||
out A: TAD936xAnt): Boolean;
|
||||
var MacStr: string; DevObj, O: TJSONObject; i: Integer;
|
||||
begin
|
||||
DefaultAnt936x(A);
|
||||
MacStr := MacToStr(MAC);
|
||||
Result := FRoot.Find(MacStr) <> nil;
|
||||
if not Result then Exit;
|
||||
DevObj := GetDevObj(MacStr);
|
||||
if DevObj.Find('ant936x') = nil then begin Result := False; Exit; end;
|
||||
O := EnsureObj(DevObj, 'ant936x');
|
||||
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||
begin
|
||||
A.RxAnt[i] := EnsureRange(JI(O, 'rx_' + IntToStr(i), 0), 0, 1);
|
||||
A.TxAnt[i] := EnsureRange(JI(O, 'tx_' + IntToStr(i), 0), 0, 1);
|
||||
end;
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
A.XvtrRxAnt[i] := EnsureRange(JI(O, 'xvtr_rx_' + IntToStr(i), 0), 0, 1);
|
||||
A.XvtrTxAnt[i] := EnsureRange(JI(O, 'xvtr_tx_' + IntToStr(i), 0), 0, 1);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveAnt936x(const MAC: array of Byte;
|
||||
const A: TAD936xAnt);
|
||||
var O: TJSONObject; i: Integer;
|
||||
begin
|
||||
O := EnsureObj(GetDevObj(MacToStr(MAC)), 'ant936x');
|
||||
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||
begin
|
||||
JW(O, 'rx_' + IntToStr(i), Integer(A.RxAnt[i]));
|
||||
JW(O, 'tx_' + IntToStr(i), Integer(A.TxAnt[i]));
|
||||
end;
|
||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||
begin
|
||||
JW(O, 'xvtr_rx_' + IntToStr(i), Integer(A.XvtrRxAnt[i]));
|
||||
JW(O, 'xvtr_tx_' + IntToStr(i), Integer(A.XvtrTxAnt[i]));
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveAlex(const MAC: array of Byte;
|
||||
const A: TAlexSettings);
|
||||
var O: TJSONObject; i: Integer;
|
||||
@@ -1369,6 +1451,8 @@ begin
|
||||
X.Entries[i].LastFMRptAutoActive := False;
|
||||
X.Entries[i].LastZoomFactor := 0.0;
|
||||
X.Entries[i].LastPanSlider := 0.5;
|
||||
X.Entries[i].LastRxGainMode := 2; // slow_attack
|
||||
X.Entries[i].LastRxGainDb := 40;
|
||||
end;
|
||||
// Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились)
|
||||
X.Entries[0].ButtonText := '2m';
|
||||
@@ -1448,6 +1532,8 @@ begin
|
||||
X.Entries[i].LastFMRptAutoActive := JB(EObj, 'last_fmrpt_auto', X.Entries[i].LastFMRptAutoActive);
|
||||
X.Entries[i].LastZoomFactor := EnsureRange(JD(EObj, 'last_zoom_factor', X.Entries[i].LastZoomFactor), 0.0, 1.0);
|
||||
X.Entries[i].LastPanSlider := EnsureRange(JD(EObj, 'last_pan_slider', X.Entries[i].LastPanSlider), 0.0, 1.0);
|
||||
X.Entries[i].LastRxGainMode := EnsureRange(JI(EObj, 'last_rx_gain_mode', X.Entries[i].LastRxGainMode), 0, 3);
|
||||
X.Entries[i].LastRxGainDb := EnsureRange(JI(EObj, 'last_rx_gain_db', X.Entries[i].LastRxGainDb), 0, 73);
|
||||
// Валидация: Last freqs должны быть в [FreqBegin..FreqEnd]
|
||||
if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or
|
||||
(X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then
|
||||
@@ -1546,6 +1632,8 @@ begin
|
||||
JW(EObj, 'last_fmrpt_auto', X.Entries[i].LastFMRptAutoActive);
|
||||
JW(EObj, 'last_zoom_factor', X.Entries[i].LastZoomFactor);
|
||||
JW(EObj, 'last_pan_slider', X.Entries[i].LastPanSlider);
|
||||
JW(EObj, 'last_rx_gain_mode', X.Entries[i].LastRxGainMode);
|
||||
JW(EObj, 'last_rx_gain_db', X.Entries[i].LastRxGainDb);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user