mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:27: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:
@@ -72,6 +72,23 @@ var
|
|||||||
iio_channel_attr_write_double: function(chn: Piio_channel; attr: PAnsiChar;
|
iio_channel_attr_write_double: function(chn: Piio_channel; attr: PAnsiChar;
|
||||||
val: cdouble): cint; cdecl;
|
val: cdouble): cint; cdecl;
|
||||||
|
|
||||||
|
// ---- Debug-атрибуты устройства (debugfs) ----
|
||||||
|
// Нужны для выбора антенного разъёма у AD936x в режиме 1R1T:
|
||||||
|
// 'adi,1rx-1tx-mode-use-rx-num'/'-tx-num' (какой физический канал кормит тракт)
|
||||||
|
// + 'initialize' (драйвер заново прогоняет ad9361_setup из pdata).
|
||||||
|
iio_device_debug_attr_read: function(dev: Piio_device; attr: PAnsiChar;
|
||||||
|
dst: PAnsiChar; len: csize_t): ptrint; cdecl;
|
||||||
|
iio_device_debug_attr_write: function(dev: Piio_device; attr: PAnsiChar;
|
||||||
|
src: PAnsiChar): ptrint; cdecl;
|
||||||
|
iio_device_debug_attr_write_longlong: function(dev: Piio_device; attr: PAnsiChar;
|
||||||
|
val: clonglong): cint; cdecl;
|
||||||
|
// Прямой доступ к регистрам (debugfs direct_reg_access) — быстрое переключение
|
||||||
|
// антенного мультиплексора AD9361 (биты RX1/RX2 и TX1/TX2 регистров 0x3/0x2).
|
||||||
|
iio_device_reg_read: function(dev: Piio_device; address: cuint32;
|
||||||
|
out value: cuint32): cint; cdecl;
|
||||||
|
iio_device_reg_write: function(dev: Piio_device; address: cuint32;
|
||||||
|
value: cuint32): cint; cdecl;
|
||||||
|
|
||||||
// ---- Буферы (RX refill / TX push) ----
|
// ---- Буферы (RX refill / TX push) ----
|
||||||
iio_device_create_buffer: function(dev: Piio_device; samples_count: csize_t;
|
iio_device_create_buffer: function(dev: Piio_device; samples_count: csize_t;
|
||||||
cyclic: ByteBool): Piio_buffer; cdecl;
|
cyclic: ByteBool): Piio_buffer; cdecl;
|
||||||
@@ -168,6 +185,11 @@ begin
|
|||||||
iio_channel_attr_write := Sym('iio_channel_attr_write');
|
iio_channel_attr_write := Sym('iio_channel_attr_write');
|
||||||
iio_channel_attr_write_bool := Sym('iio_channel_attr_write_bool');
|
iio_channel_attr_write_bool := Sym('iio_channel_attr_write_bool');
|
||||||
iio_channel_attr_write_double := Sym('iio_channel_attr_write_double');
|
iio_channel_attr_write_double := Sym('iio_channel_attr_write_double');
|
||||||
|
iio_device_debug_attr_read := Sym('iio_device_debug_attr_read');
|
||||||
|
iio_device_debug_attr_write := Sym('iio_device_debug_attr_write');
|
||||||
|
iio_device_debug_attr_write_longlong := Sym('iio_device_debug_attr_write_longlong');
|
||||||
|
iio_device_reg_read := Sym('iio_device_reg_read');
|
||||||
|
iio_device_reg_write := Sym('iio_device_reg_write');
|
||||||
iio_device_create_buffer := Sym('iio_device_create_buffer');
|
iio_device_create_buffer := Sym('iio_device_create_buffer');
|
||||||
iio_buffer_destroy := Sym('iio_buffer_destroy');
|
iio_buffer_destroy := Sym('iio_buffer_destroy');
|
||||||
iio_buffer_cancel := Sym('iio_buffer_cancel');
|
iio_buffer_cancel := Sym('iio_buffer_cancel');
|
||||||
|
|||||||
+30
-4
@@ -663,6 +663,7 @@ type
|
|||||||
Shift: TShiftState; X, Y: Integer);
|
Shift: TShiftState; X, Y: Integer);
|
||||||
procedure LayoutPanStack(AWideH: Integer); // регионы панов + Layout каждого
|
procedure LayoutPanStack(AWideH: Integer); // регионы панов + Layout каждого
|
||||||
function PanMinHeight(PanId: Integer): Integer; // мин. высота региона пана
|
function PanMinHeight(PanId: Integer): Integer; // мин. высота региона пана
|
||||||
|
function Pan0HeaderVisible: Boolean; // шапка пана 0: стек ИЛИ 2 входа
|
||||||
function Pan0HeaderH: Integer; // высота шапки пана 0 (0 без стека)
|
function Pan0HeaderH: Integer; // высота шапки пана 0 (0 без стека)
|
||||||
procedure PbSpectrumDblClick(Sender: TObject);
|
procedure PbSpectrumDblClick(Sender: TObject);
|
||||||
procedure TrkDriveChange(Sender: TObject);
|
procedure TrkDriveChange(Sender: TObject);
|
||||||
@@ -674,6 +675,7 @@ type
|
|||||||
procedure ApplyMOX(Active: Boolean);
|
procedure ApplyMOX(Active: Boolean);
|
||||||
procedure OnPASettingsChange(MaxPower: Double; const BandCal: array of Double);
|
procedure OnPASettingsChange(MaxPower: Double; const BandCal: array of Double);
|
||||||
procedure OnPlutoTxMaxAttChange(MaxAttDb: Double);
|
procedure OnPlutoTxMaxAttChange(MaxAttDb: Double);
|
||||||
|
procedure OnAnt936xChange(const A: TAD936xAnt); // разъёмы AD936x (Antenna)
|
||||||
procedure OnVHFCalSettingsChange(const VHFCal: 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);
|
||||||
@@ -2629,9 +2631,18 @@ begin
|
|||||||
PositionSampleRateOverlay;
|
PositionSampleRateOverlay;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TMainForm.Pan0HeaderVisible: Boolean;
|
||||||
|
// Шапка пана 0 — только при стеке панов (у одиночного пана она лишняя). Единый
|
||||||
|
// источник правды: LayoutPanStack (обе ветки), Pan0HeaderH, UpdatePanHeaders.
|
||||||
|
// Выбор приёмного входа у одноканальных по панам бэкендов (Pluto/LibreSDR) живёт
|
||||||
|
// не здесь, а во вкладке Antenna настроек.
|
||||||
|
begin
|
||||||
|
Result := PanCount > 1;
|
||||||
|
end;
|
||||||
|
|
||||||
function TMainForm.Pan0HeaderH: Integer;
|
function TMainForm.Pan0HeaderH: Integer;
|
||||||
begin
|
begin
|
||||||
if PanCount > 1 then
|
if Pan0HeaderVisible then
|
||||||
Result := MulDiv(20, Screen.PixelsPerInch, 96) // = HEADER_H панели
|
Result := MulDiv(20, Screen.PixelsPerInch, 96) // = HEADER_H панели
|
||||||
else
|
else
|
||||||
Result := 0;
|
Result := 0;
|
||||||
@@ -2711,7 +2722,8 @@ begin
|
|||||||
for k := 0 to N - 1 do
|
for k := 0 to N - 1 do
|
||||||
begin
|
begin
|
||||||
i := ActiveIds[k];
|
i := ActiveIds[k];
|
||||||
FPans[i].HeaderVisible := PanCount > 1;
|
if i = 0 then FPans[0].HeaderVisible := Pan0HeaderVisible
|
||||||
|
else FPans[i].HeaderVisible := PanCount > 1;
|
||||||
MinH[k] := PanMinHeight(i);
|
MinH[k] := PanMinHeight(i);
|
||||||
Inc(MinRest, MinH[k]);
|
Inc(MinRest, MinH[k]);
|
||||||
end;
|
end;
|
||||||
@@ -2758,7 +2770,7 @@ begin
|
|||||||
Row0H := Round((TotalH - PAN_SPLIT_H) * FPanGridRowShare);
|
Row0H := Round((TotalH - PAN_SPLIT_H) * FPanGridRowShare);
|
||||||
// Кламп ряда минимумами содержимого (шапки — до расчёта минимумов):
|
// Кламп ряда минимумами содержимого (шапки — до расчёта минимумов):
|
||||||
// ни пан 0, ни колонки нижнего ряда не смеют стать ниже своих минимумов.
|
// ни пан 0, ни колонки нижнего ряда не смеют стать ниже своих минимумов.
|
||||||
FPans[0].HeaderVisible := PanCount > 1;
|
FPans[0].HeaderVisible := Pan0HeaderVisible;
|
||||||
BotMin := 0;
|
BotMin := 0;
|
||||||
for k := 1 to N - 1 do
|
for k := 1 to N - 1 do
|
||||||
begin
|
begin
|
||||||
@@ -6069,7 +6081,8 @@ begin
|
|||||||
begin
|
begin
|
||||||
P := FPans[i];
|
P := FPans[i];
|
||||||
if P = nil then Continue;
|
if P = nil then Continue;
|
||||||
P.HeaderVisible := (N > 1) or ((i > 0) and PanFloating(i));
|
if i = 0 then P.HeaderVisible := Pan0HeaderVisible
|
||||||
|
else P.HeaderVisible := (N > 1) or PanFloating(i);
|
||||||
if P.BtnClosePan <> nil then P.BtnClosePan.Visible := (N > 1) and (i > 0);
|
if P.BtnClosePan <> nil then P.BtnClosePan.Visible := (N > 1) and (i > 0);
|
||||||
if P.BtnPopPan <> nil then
|
if P.BtnPopPan <> nil then
|
||||||
P.BtnPopPan.Visible := (N > 1) and (i > 0) and not PanFloating(i);
|
P.BtnPopPan.Visible := (N > 1) and (i > 0) and not PanFloating(i);
|
||||||
@@ -7120,6 +7133,13 @@ begin
|
|||||||
UpdateDriveLabel; // изменился потолок → пересчитать дБ-подпись drive
|
UpdateDriveLabel; // изменился потолок → пересчитать дБ-подпись drive
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.OnAnt936xChange(const A: TAD936xAnt);
|
||||||
|
// Вкладка Antenna (AD936x): таблица «диапазон → разъём». Контроллер сохраняет её
|
||||||
|
// и сразу применяет разъёмы текущего диапазона/трансвертера.
|
||||||
|
begin
|
||||||
|
FController.SetAnt936x(A);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnVHFCalSettingsChange(const VHFCal: array of Double);
|
procedure TMainForm.OnVHFCalSettingsChange(const VHFCal: array of Double);
|
||||||
var i: Integer;
|
var i: Integer;
|
||||||
begin
|
begin
|
||||||
@@ -7906,6 +7926,7 @@ begin
|
|||||||
SF.OnFPSChange := ApplyFPS;
|
SF.OnFPSChange := ApplyFPS;
|
||||||
SF.OnPAChange := OnPASettingsChange;
|
SF.OnPAChange := OnPASettingsChange;
|
||||||
SF.OnPlutoTxChange := OnPlutoTxMaxAttChange;
|
SF.OnPlutoTxChange := OnPlutoTxMaxAttChange;
|
||||||
|
SF.OnAnt936xChange := OnAnt936xChange;
|
||||||
SF.OnVHFCalChange := OnVHFCalSettingsChange;
|
SF.OnVHFCalChange := OnVHFCalSettingsChange;
|
||||||
SF.OnCATChange := OnCATSettingsChange;
|
SF.OnCATChange := OnCATSettingsChange;
|
||||||
SF.OnThemeChange := SetLightTheme;
|
SF.OnThemeChange := SetLightTheme;
|
||||||
@@ -7922,6 +7943,11 @@ begin
|
|||||||
IfThen(FController.FNetwork.Connected, FController.FNetwork.Device.BoardType, FPendingBoardType));
|
IfThen(FController.FNetwork.Connected, FController.FNetwork.Device.BoardType, FPendingBoardType));
|
||||||
SF.LoadAlexSettings(FController.FAlexSettings,
|
SF.LoadAlexSettings(FController.FAlexSettings,
|
||||||
IfThen(FController.FNetwork.Connected, FController.FNetwork.Device.BoardType, FPendingBoardType));
|
IfThen(FController.FNetwork.Connected, FController.FNetwork.Device.BoardType, FPendingBoardType));
|
||||||
|
// Вкладка Antenna: у AD936x (Pluto/LibreSDR) вместо Alex-таблицы — таблица
|
||||||
|
// физических разъёмов на диапазон/трансвертер (если разъёмов две пары).
|
||||||
|
SF.SetAntennaBackend(FController.BackendCaps.Kind = bkPluto,
|
||||||
|
FController.BackendCaps.HasAntSwitch);
|
||||||
|
SF.LoadAnt936x(FController.Ant936x, FController.FXvtrSettings);
|
||||||
SF.LoadXvtrSettings(FController.FXvtrSettings);
|
SF.LoadXvtrSettings(FController.FXvtrSettings);
|
||||||
SF.LoadPASettings(FController.FPAMaxPower, FController.FPABandCal);
|
SF.LoadPASettings(FController.FPAMaxPower, FController.FPABandCal);
|
||||||
SF.LoadPlutoTxMaxAtt(FController.FPlutoTxMaxAttDb);
|
SF.LoadPlutoTxMaxAtt(FController.FPlutoTxMaxAttDb);
|
||||||
|
|||||||
+226
-18
@@ -58,6 +58,24 @@ type
|
|||||||
FRxThread: TThread;
|
FRxThread: TThread;
|
||||||
FStreaming: Boolean;
|
FStreaming: Boolean;
|
||||||
FRxSeq: LongWord;
|
FRxSeq: LongWord;
|
||||||
|
// Второй антенный вход (RX2). Два разных механизма — какой доступен, зависит
|
||||||
|
// от прошивки/FPGA-образа платы:
|
||||||
|
// • 2R2T-образ: второй канал инстанцирован, в cf-ad9361-lpc есть voltage2/3 →
|
||||||
|
// выбор = пара стрим-каналов (FRx2Avail). Задел на второй пан (этап 3.6).
|
||||||
|
// • 1R1T-образ (напр. LibreSDR/tezuka): второй канал в FPGA не разведён, но
|
||||||
|
// внутри AD9361 есть мультиплексор — какой физический разъём кормит
|
||||||
|
// единственный тракт. Выбор = debug-атрибуты 'adi,1rx-1tx-mode-use-rx-num'/
|
||||||
|
// '-tx-num' (1|2) + 'initialize'=1 (драйвер заново прогоняет ad9361_setup
|
||||||
|
// из pdata). ⚠Сырые записи битов RX1/RX2 в регистры 0x2/0x3 (как в
|
||||||
|
// switch_rfinput.sh прошивки) НЕ годятся: тракт переключается, а учёт
|
||||||
|
// драйвера (gain_control_mode/agc_mode на канал) остаётся от старого —
|
||||||
|
// hardwaregain потом отвергается («Rx gain can be set in MGC mode only»),
|
||||||
|
// а в железе продолжает крутиться AGC. rf_port_select тут ни при чём:
|
||||||
|
// RX2-разъём разведён на второй КАНАЛ, а не на порт B первого.
|
||||||
|
FRx2Avail: Boolean; // 2R2T: в cf-ad9361-lpc есть voltage2/voltage3
|
||||||
|
FRxMuxAvail: Boolean; // 1R1T: доступен RX/TX-мультиплексор AD9361
|
||||||
|
FRxChanSel: Byte; // 0 = RX1, 1 = RX2
|
||||||
|
FTxChanSel: Byte; // 0 = TX1, 1 = TX2
|
||||||
|
|
||||||
// TX-стрим (cf-ad9361-dds-core-lpc): I/Q output-каналы, буфер, поток push.
|
// TX-стрим (cf-ad9361-dds-core-lpc): I/Q output-каналы, буфер, поток push.
|
||||||
// Full-duplex: стрим живёт всё время Run; на приёме гоним нули (мощности нет
|
// Full-duplex: стрим живёт всё время Run; на приёме гоним нули (мощности нет
|
||||||
@@ -103,8 +121,19 @@ type
|
|||||||
procedure SetTxLOPowerdown(PoweredOn: Boolean); // TX LO вкл/выкл (по PTT)
|
procedure SetTxLOPowerdown(PoweredOn: Boolean); // TX LO вкл/выкл (по PTT)
|
||||||
procedure ApplySampleRate(Hz: Integer);
|
procedure ApplySampleRate(Hz: Integer);
|
||||||
procedure BuildTxResampler; // (пере)строить полифазные коэффициенты под FTxRatio
|
procedure BuildTxResampler; // (пере)строить полифазные коэффициенты под FTxRatio
|
||||||
procedure ApplyGain;
|
// Force=True — перед записью режима пишем ДРУГОЙ режим: драйвер пропускает
|
||||||
|
// перепрог AGC-регистров, если считает режим неизменным, а после смены
|
||||||
|
// антенного мультиплексора настройки AGC нового канала ещё не запрограммированы
|
||||||
|
// (в железе крутится auto-AGC, hardwaregain отвергается с EOPNOTSUPP).
|
||||||
|
procedure ApplyGain(Force: Boolean = False);
|
||||||
procedure ApplyTxAtten;
|
procedure ApplyTxAtten;
|
||||||
|
// Имя phy-канала управления выбранным RX-трактом (гейн/RSSI/rf_bandwidth):
|
||||||
|
// 'voltage0' = RX1, 'voltage1' = RX2 (только 2R2T; в 1R1T канал всегда один).
|
||||||
|
function RxPhyChanName: string;
|
||||||
|
// 1R1T: переключить мультиплексор AD9361 на выбранные разъёмы (см. FRxMuxAvail).
|
||||||
|
// Дёшево: debug-атрибут (маппинг каналов у драйвера) + биты регистра 0x3/0x2.
|
||||||
|
procedure ApplyRxMux;
|
||||||
|
procedure ApplyTxMux;
|
||||||
procedure StartRX;
|
procedure StartRX;
|
||||||
procedure StopRX;
|
procedure StopRX;
|
||||||
procedure StartTX;
|
procedure StartTX;
|
||||||
@@ -139,6 +168,7 @@ type
|
|||||||
Transmitting, PAEnabled, AlexEnabled: Boolean); override;
|
Transmitting, PAEnabled, AlexEnabled: Boolean); override;
|
||||||
procedure SendFullHP; override;
|
procedure SendFullHP; override;
|
||||||
procedure SendPTT(Active: Boolean); override;
|
procedure SendPTT(Active: Boolean); override;
|
||||||
|
procedure SetAntPorts(RxPort, TxPort: Byte); override;
|
||||||
procedure SetRxGain(ModeIdx: Integer; GainDb: Integer); override;
|
procedure SetRxGain(ModeIdx: Integer; GainDb: Integer); override;
|
||||||
procedure SetTxAtten(Db: Double); override;
|
procedure SetTxAtten(Db: Double); override;
|
||||||
procedure SendDUCIQ(const IData, QData: array of Integer); override;
|
procedure SendDUCIQ(const IData, QData: array of Integer); override;
|
||||||
@@ -171,6 +201,10 @@ const
|
|||||||
TX_FIR_CUT = 40000; // частота среза анти-имидж ФНЧ, Гц (покрывает SSB/NFM/AM/data)
|
TX_FIR_CUT = 40000; // частота среза анти-имидж ФНЧ, Гц (покрывает SSB/NFM/AM/data)
|
||||||
PLUTO_TX_OFF = -89.75; // макс. аттенюация AD9361 — TX практически выключен
|
PLUTO_TX_OFF = -89.75; // макс. аттенюация AD9361 — TX практически выключен
|
||||||
|
|
||||||
|
// Детект второго антенного входа (тела ниже — нужны до Connect).
|
||||||
|
function BoardHasSecondRxPort(const Model: string): Boolean; forward;
|
||||||
|
function DebugAttrExists(Dev: Piio_device; const Attr: string): Boolean; forward;
|
||||||
|
|
||||||
type
|
type
|
||||||
// RX-поток: блокирующий refill libiio → конверсия int16→24bit BE → OnDDCIQ.
|
// RX-поток: блокирующий refill libiio → конверсия int16→24bit BE → OnDDCIQ.
|
||||||
// Работает с private-полями TPlutoBackend (одна единица компиляции).
|
// Работает с private-полями TPlutoBackend (одна единица компиляции).
|
||||||
@@ -432,11 +466,14 @@ begin
|
|||||||
Result.RatePresets[7] := 5760000;
|
Result.RatePresets[7] := 5760000;
|
||||||
Result.MinFreqHz := PLUTO_MIN_HZ;
|
Result.MinFreqHz := PLUTO_MIN_HZ;
|
||||||
Result.MaxFreqHz := PLUTO_MAX_HZ;
|
Result.MaxFreqHz := PLUTO_MAX_HZ;
|
||||||
// Один RX-тракт — только главный пан. (Pluto+ 2r2t = MaxPans 2 с общим
|
// Стримим ровно один тракт ⇒ только главный пан. Одновременный приём с двух
|
||||||
// RX LO, IndependentPanFreq=False — этап 3.6.)
|
// антенн (второй пан на RX2, общий RX LO) — этап 3.6, нужен 2R2T-образ.
|
||||||
Result.MaxPans := 1;
|
Result.MaxPans := 1;
|
||||||
Result.IndependentPanFreq := False;
|
Result.IndependentPanFreq := False;
|
||||||
Result.NumADCs := 1;
|
// Пан один ⇒ ADC-селектор панов не при чём: у AD936x «антенна» — это выбор
|
||||||
|
// физических разъёмов (RX1/RX2, TX1/TX2) на диапазон, вкладка Antenna настроек.
|
||||||
|
Result.NumADCs := 1;
|
||||||
|
Result.HasAntSwitch := FRx2Avail or FRxMuxAvail;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TPlutoBackend.ProbeURI(const AURI: string; out Dev: TRadioDevice): Boolean;
|
function TPlutoBackend.ProbeURI(const AURI: string; out Dev: TRadioDevice): Boolean;
|
||||||
@@ -536,6 +573,7 @@ end;
|
|||||||
function TPlutoBackend.Connect(const Dev: TRadioDevice): Boolean;
|
function TPlutoBackend.Connect(const Dev: TRadioDevice): Boolean;
|
||||||
var
|
var
|
||||||
M: TBytes;
|
M: TBytes;
|
||||||
|
PV: PAnsiChar;
|
||||||
begin
|
begin
|
||||||
Result := False;
|
Result := False;
|
||||||
if not IIOLoad then begin FLastError := 'libiio not available'; Exit; end;
|
if not IIOLoad then begin FLastError := 'libiio not available'; Exit; end;
|
||||||
@@ -566,11 +604,33 @@ begin
|
|||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// 2R2T-образ — по НАЛИЧИЮ каналов второго тракта (клоны не отличить по строкам
|
||||||
|
// описания). Управление RX2 (гейн/RSSI) — phy input voltage1, требуем и его.
|
||||||
|
FRx2Avail := (FRxDev <> nil) and
|
||||||
|
(iio_device_find_channel(FRxDev, 'voltage2', 0) <> nil) and
|
||||||
|
(iio_device_find_channel(FRxDev, 'voltage3', 0) <> nil) and
|
||||||
|
(iio_device_find_channel(FPhy, 'voltage1', 0) <> nil);
|
||||||
|
// Модель берём из ЖИВОГО контекста: в записи устройства она могла приехать из
|
||||||
|
// сохранёнки/фолбэка ProbeURI ('AD936x SDR') и не содержать имени платы.
|
||||||
|
PV := iio_context_get_attr_value(FCtx, 'hw_model');
|
||||||
|
if PV <> nil then FDevice.Model := string(PV);
|
||||||
|
// 1R1T-образ: RX-мультиплексор AD9361. Гейт по МОДЕЛИ — у стокового Pluto
|
||||||
|
// второй вход никуда не разведён (кремний двухканальный, а SMA один), так что
|
||||||
|
// селектор там показывать нельзя: он дал бы «мёртвую» антенну.
|
||||||
|
FRxMuxAvail := (not FRx2Avail) and BoardHasSecondRxPort(FDevice.Model) and
|
||||||
|
Assigned(iio_device_debug_attr_write) and
|
||||||
|
DebugAttrExists(FPhy, 'adi,1rx-1tx-mode-use-rx-num') and
|
||||||
|
DebugAttrExists(FPhy, 'adi,1rx-1tx-mode-use-tx-num') and
|
||||||
|
DebugAttrExists(FPhy, 'initialize');
|
||||||
|
if not (FRx2Avail or FRxMuxAvail) then FRxChanSel := 0;
|
||||||
|
|
||||||
FConnected := True;
|
FConnected := True;
|
||||||
// Приводим железо в известное состояние: валидный rate + усиление.
|
// Приводим железо в известное состояние: валидный rate + вход + усиление.
|
||||||
if FSampleRate < PLUTO_MIN_SR then FSampleRate := PLUTO_DEF_SR;
|
if FSampleRate < PLUTO_MIN_SR then FSampleRate := PLUTO_DEF_SR;
|
||||||
ApplySampleRate(FSampleRate);
|
ApplySampleRate(FSampleRate);
|
||||||
ApplyGain;
|
ApplyRxMux; // разъёмы могли остаться от прошлой сессии (состояние в чипе)
|
||||||
|
ApplyTxMux;
|
||||||
|
ApplyGain(True);
|
||||||
ApplyTxAtten; // TX заглушён (макс. аттенюация) до явного PTT
|
ApplyTxAtten; // TX заглушён (макс. аттенюация) до явного PTT
|
||||||
StartTelemetry;
|
StartTelemetry;
|
||||||
Result := True;
|
Result := True;
|
||||||
@@ -651,8 +711,9 @@ begin
|
|||||||
iio_channel_attr_write_longlong(Ch, 'sampling_frequency', Hz);
|
iio_channel_attr_write_longlong(Ch, 'sampling_frequency', Hz);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// RF-полоса отдельно (set_bb_rate её не трогает).
|
// RF-полоса отдельно (set_bb_rate её не трогает) — на phy-канале выбранного
|
||||||
Ch := iio_device_find_channel(FPhy, 'voltage0', 0);
|
// приёмного тракта (у 2R2T они независимы).
|
||||||
|
Ch := iio_device_find_channel(FPhy, PAnsiChar(AnsiString(RxPhyChanName)), 0);
|
||||||
if Ch <> nil then
|
if Ch <> nil then
|
||||||
iio_channel_attr_write_longlong(Ch, 'rf_bandwidth', Hz);
|
iio_channel_attr_write_longlong(Ch, 'rf_bandwidth', Hz);
|
||||||
|
|
||||||
@@ -698,12 +759,129 @@ begin
|
|||||||
for k := 0 to TX_FIR_TAPS - 1 do begin FTxDlI[k] := 0; FTxDlQ[k] := 0; end;
|
for k := 0 to TX_FIR_TAPS - 1 do begin FTxDlI[k] := 0; FTxDlQ[k] := 0; end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPlutoBackend.ApplyGain;
|
function BoardHasSecondRxPort(const Model: string): Boolean;
|
||||||
var Ch: Piio_channel;
|
// Есть ли у платы физический разъём RX2. Кремний AD9361/AD9363 двухканальный на
|
||||||
|
// ЛЮБОЙ плате, включая стоковый Pluto, — но у Pluto второй канал никуда не выведен,
|
||||||
|
// поэтому переключать на него нельзя (антенны там нет). Список плат с двумя RX SMA.
|
||||||
|
const
|
||||||
|
BOARDS: array[0..4] of string = ('libresdr', 'antsdr', 'pluto+', 'plutoplus', 'e200');
|
||||||
|
var
|
||||||
|
M: string;
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
M := LowerCase(StringReplace(Model, ' ', '', [rfReplaceAll]));
|
||||||
|
for i := Low(BOARDS) to High(BOARDS) do
|
||||||
|
if Pos(BOARDS[i], M) > 0 then Exit(True);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function DebugAttrExists(Dev: Piio_device; const Attr: string): Boolean;
|
||||||
|
var Buf: array[0..63] of AnsiChar;
|
||||||
|
begin
|
||||||
|
Result := Assigned(iio_device_debug_attr_read) and (Dev <> nil) and
|
||||||
|
(iio_device_debug_attr_read(Dev, PAnsiChar(AnsiString(Attr)),
|
||||||
|
@Buf[0], SizeOf(Buf)) >= 0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPlutoBackend.RxPhyChanName: string;
|
||||||
|
// Управляющий phy-канал приёмного тракта: RX1 = input voltage0, RX2 = voltage1.
|
||||||
|
// В 1R1T-образе канал ровно один (voltage0) — мультиплексор меняет вход ПОД ним,
|
||||||
|
// поэтому гейн/RSSI остаются на voltage0.
|
||||||
|
begin
|
||||||
|
if (FRxChanSel = 1) and FRx2Avail then Result := 'voltage1'
|
||||||
|
else Result := 'voltage0';
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPlutoBackend.ApplyRxMux;
|
||||||
|
// Приёмный разъём: debug-параметр (по нему драйвер маппит gain-атрибуты на канал)
|
||||||
|
// + биты RX1/RX2 (маска 0xC0) регистра 0x003 — как switch_rfinput.sh прошивки.
|
||||||
|
// ⚠Вызывающий обязан следом сделать ApplyGain(True): AGC-регистры нового канала
|
||||||
|
// ещё не запрограммированы (см. ApplyGain).
|
||||||
|
var
|
||||||
|
Val: cuint32;
|
||||||
|
RxNum: Integer;
|
||||||
|
begin
|
||||||
|
if not (FConnected and FRxMuxAvail and (FPhy <> nil)) then Exit;
|
||||||
|
if FRxChanSel = 1 then RxNum := 2 else RxNum := 1;
|
||||||
|
iio_device_debug_attr_write(FPhy, 'adi,1rx-1tx-mode-use-rx-num',
|
||||||
|
PAnsiChar(AnsiString(IntToStr(RxNum))));
|
||||||
|
Val := 0;
|
||||||
|
if iio_device_reg_read(FPhy, $3, Val) <> 0 then Exit;
|
||||||
|
Val := Val and $3F;
|
||||||
|
if FRxChanSel = 1 then Val := Val or $80 // RX2
|
||||||
|
else Val := Val or $40; // RX1
|
||||||
|
iio_device_reg_write(FPhy, $3, Val);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPlutoBackend.ApplyTxMux;
|
||||||
|
// То же для передачи (switch_rfoutput.sh): '-tx-num' + биты TX1/TX2 регистра 0x002.
|
||||||
|
var
|
||||||
|
Val: cuint32;
|
||||||
|
TxNum: Integer;
|
||||||
|
begin
|
||||||
|
if not (FConnected and FRxMuxAvail and (FPhy <> nil)) then Exit;
|
||||||
|
if FTxChanSel = 1 then TxNum := 2 else TxNum := 1;
|
||||||
|
iio_device_debug_attr_write(FPhy, 'adi,1rx-1tx-mode-use-tx-num',
|
||||||
|
PAnsiChar(AnsiString(IntToStr(TxNum))));
|
||||||
|
Val := 0;
|
||||||
|
if iio_device_reg_read(FPhy, $2, Val) <> 0 then Exit;
|
||||||
|
Val := Val and $3F;
|
||||||
|
if FTxChanSel = 1 then Val := Val or $80 // TX2
|
||||||
|
else Val := Val or $40; // TX1
|
||||||
|
iio_device_reg_write(FPhy, $2, Val);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPlutoBackend.SetAntPorts(RxPort, TxPort: Byte);
|
||||||
|
// Выбор физических разъёмов: 0 = RX1/TX1, 1 = RX2/TX2. Зовётся контроллером при
|
||||||
|
// смене диапазона/трансвертера (антенна — свойство диапазона, см. вкладку Antenna).
|
||||||
|
var
|
||||||
|
RxChanged, TxChanged: Boolean;
|
||||||
|
begin
|
||||||
|
if RxPort > 1 then RxPort := 0;
|
||||||
|
if TxPort > 1 then TxPort := 0;
|
||||||
|
if not (FRx2Avail or FRxMuxAvail) then Exit; // одна пара разъёмов — нечего выбирать
|
||||||
|
RxChanged := RxPort <> FRxChanSel;
|
||||||
|
TxChanged := TxPort <> FTxChanSel;
|
||||||
|
if not (RxChanged or TxChanged) then Exit;
|
||||||
|
FRxChanSel := RxPort;
|
||||||
|
FTxChanSel := TxPort;
|
||||||
|
if not FConnected then Exit;
|
||||||
|
if FRx2Avail then
|
||||||
|
begin
|
||||||
|
// 2R2T-образ: разъём = ДРУГАЯ пара стрим-каналов ⇒ пересоздать буферы, затем
|
||||||
|
// вернуть гейн/аттенюацию (у 2R2T они ещё и живут на phy-канале своего тракта).
|
||||||
|
if RxChanged and FStreaming then begin StopRX; StartRX; end;
|
||||||
|
if TxChanged and FTxStreaming then begin StopTX; StartTX; end;
|
||||||
|
ApplyGain;
|
||||||
|
ApplyTxAtten;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
// 1R1T: мультиплексор внутри чипа — стримы/LO/rate не трогаем (дёшево).
|
||||||
|
// Обе записи ДО восстановления усиления, и гейн — форсированный: у нового
|
||||||
|
// канала AGC-регистры ещё не запрограммированы.
|
||||||
|
if RxChanged then ApplyRxMux;
|
||||||
|
if TxChanged then ApplyTxMux;
|
||||||
|
ApplyGain(True);
|
||||||
|
ApplyTxAtten;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPlutoBackend.ApplyGain(Force: Boolean);
|
||||||
|
var Ch: Piio_channel; Other: string;
|
||||||
begin
|
begin
|
||||||
if FPhy = nil then Exit;
|
if FPhy = nil then Exit;
|
||||||
Ch := iio_device_find_channel(FPhy, 'voltage0', 0); // RX gain — input voltage0
|
// Усиление/режим AGC — поканальные: пишем в тракт выбранной антенны.
|
||||||
|
Ch := iio_device_find_channel(FPhy, PAnsiChar(AnsiString(RxPhyChanName)), 0);
|
||||||
if Ch = nil then Exit;
|
if Ch = nil then Exit;
|
||||||
|
if Force then
|
||||||
|
begin
|
||||||
|
// Прогоняем через другой режим — иначе драйвер сочтёт режим неизменным и не
|
||||||
|
// перепрограммирует AGC-регистры канала (после смены мультиплексора там
|
||||||
|
// остаётся auto-AGC, а hardwaregain отвергается: «Rx gain can be set in MGC only»).
|
||||||
|
if FGainMode = 'manual' then Other := 'slow_attack' else Other := 'manual';
|
||||||
|
iio_channel_attr_write(Ch, 'gain_control_mode', PAnsiChar(AnsiString(Other)));
|
||||||
|
end;
|
||||||
iio_channel_attr_write(Ch, 'gain_control_mode', PAnsiChar(AnsiString(FGainMode)));
|
iio_channel_attr_write(Ch, 'gain_control_mode', PAnsiChar(AnsiString(FGainMode)));
|
||||||
if FGainMode = 'manual' then
|
if FGainMode = 'manual' then
|
||||||
iio_channel_attr_write_longlong(Ch, 'hardwaregain', FGainDb);
|
iio_channel_attr_write_longlong(Ch, 'hardwaregain', FGainDb);
|
||||||
@@ -721,10 +899,17 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPlutoBackend.StartRX;
|
procedure TPlutoBackend.StartRX;
|
||||||
|
var Base: Integer; ChI, ChQ: string;
|
||||||
begin
|
begin
|
||||||
if FStreaming or not FConnected or (FRxDev = nil) then Exit;
|
if FStreaming or not FConnected or (FRxDev = nil) then Exit;
|
||||||
FRxChI := iio_device_find_channel(FRxDev, 'voltage0', 0);
|
// Пара стрим-каналов выбранного тракта: RX1 = voltage0/1, RX2 = voltage2/3.
|
||||||
FRxChQ := iio_device_find_channel(FRxDev, 'voltage1', 0);
|
// Включаем ТОЛЬКО её — второй тракт не стримим (иначе вдвое трафик/CPU).
|
||||||
|
Base := 0;
|
||||||
|
if (FRxChanSel = 1) and FRx2Avail then Base := 2;
|
||||||
|
ChI := 'voltage' + IntToStr(Base);
|
||||||
|
ChQ := 'voltage' + IntToStr(Base + 1);
|
||||||
|
FRxChI := iio_device_find_channel(FRxDev, PAnsiChar(AnsiString(ChI)), 0);
|
||||||
|
FRxChQ := iio_device_find_channel(FRxDev, PAnsiChar(AnsiString(ChQ)), 0);
|
||||||
if (FRxChI = nil) or (FRxChQ = nil) then
|
if (FRxChI = nil) or (FRxChQ = nil) then
|
||||||
begin FLastError := 'RX I/Q channels not found'; Exit; end;
|
begin FLastError := 'RX I/Q channels not found'; Exit; end;
|
||||||
iio_channel_enable(FRxChI);
|
iio_channel_enable(FRxChI);
|
||||||
@@ -753,13 +938,26 @@ begin
|
|||||||
iio_buffer_destroy(FRxBuf);
|
iio_buffer_destroy(FRxBuf);
|
||||||
FRxBuf := nil;
|
FRxBuf := nil;
|
||||||
end;
|
end;
|
||||||
|
// Снять enable с пары каналов: иначе после смены антенного входа (2R2T) в
|
||||||
|
// новом буфере окажутся включёнными оба тракта — чередование I1,Q1,I2,Q2.
|
||||||
|
if FRxChI <> nil then iio_channel_disable(FRxChI);
|
||||||
|
if FRxChQ <> nil then iio_channel_disable(FRxChQ);
|
||||||
|
FRxChI := nil;
|
||||||
|
FRxChQ := nil;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPlutoBackend.StartTX;
|
procedure TPlutoBackend.StartTX;
|
||||||
|
var Base: Integer; ChI, ChQ: string;
|
||||||
begin
|
begin
|
||||||
if FTxStreaming or not FConnected or (FTxDev = nil) then Exit;
|
if FTxStreaming or not FConnected or (FTxDev = nil) then Exit;
|
||||||
FTxChI := iio_device_find_channel(FTxDev, 'voltage0', 1); // output I
|
// 2R2T-образ: пара выходных каналов выбранного разъёма (TX1 = voltage0/1,
|
||||||
FTxChQ := iio_device_find_channel(FTxDev, 'voltage1', 1); // output Q
|
// TX2 = voltage2/3). В 1R1T пара всегда одна — разъём переключает ApplyTxMux.
|
||||||
|
Base := 0;
|
||||||
|
if (FTxChanSel = 1) and FRx2Avail then Base := 2;
|
||||||
|
ChI := 'voltage' + IntToStr(Base);
|
||||||
|
ChQ := 'voltage' + IntToStr(Base + 1);
|
||||||
|
FTxChI := iio_device_find_channel(FTxDev, PAnsiChar(AnsiString(ChI)), 1); // output I
|
||||||
|
FTxChQ := iio_device_find_channel(FTxDev, PAnsiChar(AnsiString(ChQ)), 1); // output Q
|
||||||
if (FTxChI = nil) or (FTxChQ = nil) then
|
if (FTxChI = nil) or (FTxChQ = nil) then
|
||||||
begin FLastError := 'TX I/Q channels not found'; Exit; end;
|
begin FLastError := 'TX I/Q channels not found'; Exit; end;
|
||||||
iio_channel_enable(FTxChI);
|
iio_channel_enable(FTxChI);
|
||||||
@@ -805,11 +1003,20 @@ end;
|
|||||||
procedure TPlutoBackend.ConfigureDDCs(NumDDCs: Byte; SampleRate: Word;
|
procedure TPlutoBackend.ConfigureDDCs(NumDDCs: Byte; SampleRate: Word;
|
||||||
ADCSource: Byte; DitherEnabled, RandomEnabled: Boolean);
|
ADCSource: Byte; DitherEnabled, RandomEnabled: Boolean);
|
||||||
begin
|
begin
|
||||||
// SampleRate приходит в ksps (как у HPSDR ConfigureDDCs). Pluto — в Гц.
|
// ADCSource (АЦП-источник HPSDR) у AD936x смысла не имеет — антенные разъёмы
|
||||||
|
// переключает SetAntPorts. SampleRate приходит в ksps (как у HPSDR), Pluto — в Гц.
|
||||||
// Контроллер может прислать HPSDR-наследие (<520k) — клампим к дефолту.
|
// Контроллер может прислать HPSDR-наследие (<520k) — клампим к дефолту.
|
||||||
FSampleRate := Integer(SampleRate) * 1000;
|
FSampleRate := Integer(SampleRate) * 1000;
|
||||||
if FSampleRate < PLUTO_MIN_SR then FSampleRate := PLUTO_DEF_SR;
|
if FSampleRate < PLUTO_MIN_SR then FSampleRate := PLUTO_DEF_SR;
|
||||||
if FConnected then ApplySampleRate(FSampleRate); // перезапустит RX при смене
|
if not FConnected then Exit;
|
||||||
|
ApplySampleRate(FSampleRate); // перезапустит RX при смене
|
||||||
|
// ad9361_set_bb_rate перепрошивает FIR/BBPLL и сбрасывает усиление/AGC к
|
||||||
|
// дефолтам — возвращаем разъёмы и форсированно гейн + TX-аттенюацию (иначе
|
||||||
|
// в железе остаётся auto-AGC: высокая шумовая полка).
|
||||||
|
ApplyRxMux;
|
||||||
|
ApplyTxMux;
|
||||||
|
ApplyGain(True);
|
||||||
|
ApplyTxAtten;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPlutoBackend.SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
procedure TPlutoBackend.SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
||||||
@@ -990,9 +1197,10 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// RSSI имеет смысл только при активном RX-стриме (иначе чтение даёт ошибку/мусор).
|
// RSSI имеет смысл только при активном RX-стриме (иначе чтение даёт ошибку/мусор).
|
||||||
|
// Читаем с phy-канала выбранного приёмного тракта (RX1/RX2 — независимы).
|
||||||
if FStreaming then
|
if FStreaming then
|
||||||
begin
|
begin
|
||||||
Ch := iio_device_find_channel(FPhy, 'voltage0', 0);
|
Ch := iio_device_find_channel(FPhy, PAnsiChar(AnsiString(RxPhyChanName)), 0);
|
||||||
if (Ch <> nil) and Assigned(iio_channel_attr_read_double)
|
if (Ch <> nil) and Assigned(iio_channel_attr_read_double)
|
||||||
and (iio_channel_attr_read_double(Ch, 'rssi', d) = 0) then
|
and (iio_channel_attr_read_double(Ch, 'rssi', d) = 0) then
|
||||||
begin
|
begin
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ type
|
|||||||
// Число АЦП (SCU): >1 → у панов есть селектор ADC-источника (A1/A2, 3.4).
|
// Число АЦП (SCU): >1 → у панов есть селектор ADC-источника (A1/A2, 3.4).
|
||||||
// Достоверно после Connect (у HPSDR зависит от BoardType).
|
// Достоверно после Connect (у HPSDR зависит от BoardType).
|
||||||
NumADCs: Integer;
|
NumADCs: Integer;
|
||||||
|
// Переключаемые антенные разъёмы AD936x (RX1/RX2, TX1/TX2) — у плат с двумя
|
||||||
|
// парами SMA (LibreSDR/AntSDR/Pluto+). Гейтит блок «Antenna» в настройках;
|
||||||
|
// у openHPSDR антеннами рулит Alex, поэтому False. Достоверно после Connect.
|
||||||
|
HasAntSwitch: Boolean;
|
||||||
MinSampleRate: Integer;
|
MinSampleRate: Integer;
|
||||||
RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay
|
RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay
|
||||||
MinFreqHz: Double;
|
MinFreqHz: Double;
|
||||||
@@ -134,6 +138,9 @@ type
|
|||||||
procedure SetPanDDC(DDCIdx: Integer; Enabled: Boolean; FreqHz: Double;
|
procedure SetPanDDC(DDCIdx: Integer; Enabled: Boolean; FreqHz: Double;
|
||||||
RateKHz: Word; ADCSource: Byte); virtual;
|
RateKHz: Word; ADCSource: Byte); virtual;
|
||||||
procedure SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double); virtual;
|
procedure SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double); virtual;
|
||||||
|
// Антенные разъёмы AD936x: 0 = RX1/TX1, 1 = RX2/TX2 (см. Caps.HasAntSwitch).
|
||||||
|
// У бэкендов без переключателя — no-op.
|
||||||
|
procedure SetAntPorts(RxPort, TxPort: Byte); virtual;
|
||||||
|
|
||||||
// ---- Частота / drive / TX-состояние ----
|
// ---- Частота / drive / TX-состояние ----
|
||||||
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
||||||
@@ -294,4 +301,9 @@ procedure TRadioBackend.SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double);
|
|||||||
begin
|
begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TRadioBackend.SetAntPorts(RxPort, TxPort: Byte);
|
||||||
|
// Бэкенды без переключаемых разъёмов (openHPSDR — там Alex) ничего не делают.
|
||||||
|
begin
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
@@ -324,6 +324,8 @@ type
|
|||||||
// ---- Конфиги / устройство (per-MAC) ----
|
// ---- Конфиги / устройство (per-MAC) ----
|
||||||
FTXSettings: TTXSettings;
|
FTXSettings: TTXSettings;
|
||||||
FAlexSettings: TAlexSettings;
|
FAlexSettings: TAlexSettings;
|
||||||
|
// Антенные разъёмы AD936x (Pluto/LibreSDR) на диапазон/трансвертер.
|
||||||
|
FAnt936x: TAD936xAnt;
|
||||||
FXvtrSettings: TXvtrSettings;
|
FXvtrSettings: TXvtrSettings;
|
||||||
FBandCache: array[0..CFG_BAND_COUNT-1] of TBandSettings;
|
FBandCache: array[0..CFG_BAND_COUNT-1] of TBandSettings;
|
||||||
FPABandCal: array[0..CFG_BAND_COUNT-1] of Double;
|
FPABandCal: array[0..CFG_BAND_COUNT-1] of Double;
|
||||||
@@ -599,6 +601,11 @@ type
|
|||||||
function PanDDCRateKHz(PanId: Integer): Word;
|
function PanDDCRateKHz(PanId: Integer): Word;
|
||||||
// ADC-источник пана (3.4). PanId=0 = главный DDC (реконфиг ConfigureDDCs),
|
// ADC-источник пана (3.4). PanId=0 = главный DDC (реконфиг ConfigureDDCs),
|
||||||
// N>0 = его пан-DDC (SetPanDDC). ADC ∈ [0..Caps.NumADCs-1].
|
// N>0 = его пан-DDC (SetPanDDC). ADC ∈ [0..Caps.NumADCs-1].
|
||||||
|
// Антенные разъёмы AD936x: настройки (вкладка Antenna) + применение текущего
|
||||||
|
// контекста (диапазон | активный трансвертер) к железу.
|
||||||
|
procedure SetAnt936x(const A: TAD936xAnt);
|
||||||
|
procedure ApplyAntPorts;
|
||||||
|
property Ant936x: TAD936xAnt read FAnt936x;
|
||||||
function PanADC(PanId: Integer): Byte;
|
function PanADC(PanId: Integer): Byte;
|
||||||
function SetPanADC(PanId: Integer; ADC: Byte): Boolean;
|
function SetPanADC(PanId: Integer; ADC: Byte): Boolean;
|
||||||
// ---- Зум/пан доп. панов (PanId=0 делегирует главному зуму) ----
|
// ---- Зум/пан доп. панов (PanId=0 делегирует главному зуму) ----
|
||||||
@@ -1410,6 +1417,43 @@ begin
|
|||||||
else Result := 0;
|
else Result := 0;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.SetAnt936x(const A: TAD936xAnt);
|
||||||
|
// Новая таблица «диапазон → разъём» из настроек: сохранить + применить текущий.
|
||||||
|
begin
|
||||||
|
FAnt936x := A;
|
||||||
|
if FDevConnected then
|
||||||
|
begin
|
||||||
|
FSettings.SaveAnt936x(FDevMAC, FAnt936x);
|
||||||
|
FSettings.Save;
|
||||||
|
end;
|
||||||
|
ApplyAntPorts;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.ApplyAntPorts;
|
||||||
|
// Антенна — свойство диапазона (как RxAnt/TxAnt у Alex): при смене диапазона или
|
||||||
|
// входе/выходе из трансвертера включаем разъёмы, выбранные для этого контекста.
|
||||||
|
var
|
||||||
|
Rx, Tx: Byte;
|
||||||
|
Bi: Integer;
|
||||||
|
begin
|
||||||
|
if not (Assigned(FNetwork) and FNetwork.Connected) then Exit;
|
||||||
|
if not FNetwork.Caps.HasAntSwitch then Exit;
|
||||||
|
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
|
||||||
|
begin
|
||||||
|
Rx := FAnt936x.XvtrRxAnt[FCurrentXvtr];
|
||||||
|
Tx := FAnt936x.XvtrTxAnt[FCurrentXvtr];
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
// Диапазон — по ВИДИМОЙ частоте приёма (VU-план AD936x).
|
||||||
|
Bi := FreqToBandIdx(FCenterFreq, IsPluto);
|
||||||
|
if (Bi < 0) or (Bi >= CFG_BAND_COUNT) then Exit; // вне плана — не трогаем
|
||||||
|
Rx := FAnt936x.RxAnt[Bi];
|
||||||
|
Tx := FAnt936x.TxAnt[Bi];
|
||||||
|
end;
|
||||||
|
FNetwork.SetAntPorts(Rx, Tx);
|
||||||
|
end;
|
||||||
|
|
||||||
function TRadioController.PanADC(PanId: Integer): Byte;
|
function TRadioController.PanADC(PanId: Integer): Byte;
|
||||||
begin
|
begin
|
||||||
if PanId = 0 then Result := FMainADCSrc
|
if PanId = 0 then Result := FMainADCSrc
|
||||||
@@ -2231,6 +2275,9 @@ begin
|
|||||||
Result.FMStepIdx := FFMStepIdx;
|
Result.FMStepIdx := FFMStepIdx;
|
||||||
Result.FMRptDir := FFMRptDir;
|
Result.FMRptDir := FFMRptDir;
|
||||||
Result.FMRptOffsetHz := FFMRptOffsetHz;
|
Result.FMRptOffsetHz := FFMRptOffsetHz;
|
||||||
|
// Аппаратный AGC AD936x — на диапазон (усиление тракта разное на 2м и QO-100).
|
||||||
|
Result.RxGainMode := FRxGainMode;
|
||||||
|
Result.RxGainDb := FRxGainDb;
|
||||||
// Waterfall AGC/NF — глобальные (не диапазонные)
|
// Waterfall AGC/NF — глобальные (не диапазонные)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -2267,6 +2314,8 @@ begin
|
|||||||
FXvtrSettings.Entries[i].LastFMRptAutoActive := FFMRptAutoActive;
|
FXvtrSettings.Entries[i].LastFMRptAutoActive := FFMRptAutoActive;
|
||||||
FXvtrSettings.Entries[i].LastZoomFactor := FZoomFactor;
|
FXvtrSettings.Entries[i].LastZoomFactor := FZoomFactor;
|
||||||
FXvtrSettings.Entries[i].LastPanSlider := FPanSlider;
|
FXvtrSettings.Entries[i].LastPanSlider := FPanSlider;
|
||||||
|
FXvtrSettings.Entries[i].LastRxGainMode := FRxGainMode;
|
||||||
|
FXvtrSettings.Entries[i].LastRxGainDb := FRxGainDb;
|
||||||
end;
|
end;
|
||||||
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
|
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
|
||||||
Exit;
|
Exit;
|
||||||
@@ -2386,6 +2435,13 @@ begin
|
|||||||
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetAGCTop(FAGCTop);
|
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetAGCTop(FAGCTop);
|
||||||
ApplyAGCToEngine;
|
ApplyAGCToEngine;
|
||||||
|
|
||||||
|
// --- Аппаратный AGC AD936x (Pluto/LibreSDR): режим + manual-гейн диапазона.
|
||||||
|
// У openHPSDR SetRxGain — no-op, поля просто ездят с диапазоном.
|
||||||
|
FRxGainMode := EnsureRange(B.RxGainMode, 0, 3);
|
||||||
|
FRxGainDb := EnsureRange(B.RxGainDb, 0, 73);
|
||||||
|
ApplyRxGainToBackend;
|
||||||
|
Changed(rfRxGain); // UI: кнопки FAST/SLOW/HYB/MAN + ползунок
|
||||||
|
|
||||||
// --- FM squelch / CTCSS / step / repeater ---
|
// --- FM squelch / CTCSS / step / repeater ---
|
||||||
FFMSQOn := B.FMSQOn;
|
FFMSQOn := B.FMSQOn;
|
||||||
FFMSQLevel := B.FMSQLevel;
|
FFMSQLevel := B.FMSQLevel;
|
||||||
@@ -2471,6 +2527,7 @@ begin
|
|||||||
// Иначе смена диапазона при активном B центрировала бы приёмник на A.
|
// Иначе смена диапазона при активном B центрировала бы приёмник на A.
|
||||||
if FActiveVfo = 0 then SetVfoA(FVfoA)
|
if FActiveVfo = 0 then SetVfoA(FVfoA)
|
||||||
else SetVfoB(FVfoB);
|
else SetVfoB(FVfoB);
|
||||||
|
ApplyAntPorts; // AD936x: разъёмы, выбранные для этого диапазона
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TRadioController.ActiveVfoHz: Double;
|
function TRadioController.ActiveVfoHz: Double;
|
||||||
@@ -3089,6 +3146,8 @@ begin
|
|||||||
B.FMStepIdx := E.LastFMStepIdx;
|
B.FMStepIdx := E.LastFMStepIdx;
|
||||||
B.FMRptDir := E.LastFMRptDir;
|
B.FMRptDir := E.LastFMRptDir;
|
||||||
B.FMRptOffsetHz := E.LastFMRptOffsetHz;
|
B.FMRptOffsetHz := E.LastFMRptOffsetHz;
|
||||||
|
B.RxGainMode := E.LastRxGainMode; // hw-AGC слота (AD936x)
|
||||||
|
B.RxGainDb := E.LastRxGainDb;
|
||||||
B.VfoA := Vis;
|
B.VfoA := Vis;
|
||||||
B.VfoB := FVfoB;
|
B.VfoB := FVfoB;
|
||||||
ApplyBandDSP(B);
|
ApplyBandDSP(B);
|
||||||
@@ -3120,6 +3179,7 @@ begin
|
|||||||
if E.FullDuplexTx and not FDisplayDuplex then SetDuplex(True);
|
if E.FullDuplexTx and not FDisplayDuplex then SetDuplex(True);
|
||||||
PushNetworkState;
|
PushNetworkState;
|
||||||
RefreshPanDDCs; // живые паны — в диапазон XVTR, DDC — на IF
|
RefreshPanDDCs; // живые паны — в диапазон XVTR, DDC — на IF
|
||||||
|
ApplyAntPorts; // AD936x: разъёмы, выбранные для этого трансвертера
|
||||||
|
|
||||||
Changed(rfXvtr);
|
Changed(rfXvtr);
|
||||||
Changed(rfActiveVfo);
|
Changed(rfActiveVfo);
|
||||||
@@ -3947,6 +4007,8 @@ begin
|
|||||||
FNetwork.SetSpeakerAudio(FSendAudioToRadio);
|
FNetwork.SetSpeakerAudio(FSendAudioToRadio);
|
||||||
// RX hw-gain в бэкенд (Pluto). HPSDR — no-op.
|
// RX hw-gain в бэкенд (Pluto). HPSDR — no-op.
|
||||||
ApplyRxGainToBackend;
|
ApplyRxGainToBackend;
|
||||||
|
// Антенные разъёмы AD936x для текущего диапазона/трансвертера. HPSDR — no-op.
|
||||||
|
ApplyAntPorts;
|
||||||
// QO-100 self-monitor: RX-аудио на TX (full-duplex). По умолчанию заглушено.
|
// QO-100 self-monitor: RX-аудио на TX (full-duplex). По умолчанию заглушено.
|
||||||
ApplyRxMuteToEngine;
|
ApplyRxMuteToEngine;
|
||||||
SendDUCSpecificFromSettings;
|
SendDUCSpecificFromSettings;
|
||||||
@@ -4076,6 +4138,7 @@ begin
|
|||||||
// TX/Alex/XVTR per-device
|
// TX/Alex/XVTR per-device
|
||||||
FSettings.LoadTX(FDevMAC, FTXSettings);
|
FSettings.LoadTX(FDevMAC, FTXSettings);
|
||||||
FSettings.LoadAlex(FDevMAC, FAlexSettings);
|
FSettings.LoadAlex(FDevMAC, FAlexSettings);
|
||||||
|
FSettings.LoadAnt936x(FDevMAC, FAnt936x);
|
||||||
FNetwork.SetAlexConfig(FAlexSettings);
|
FNetwork.SetAlexConfig(FAlexSettings);
|
||||||
FSettings.LoadXvtr(FDevMAC, FXvtrSettings);
|
FSettings.LoadXvtr(FDevMAC, FXvtrSettings);
|
||||||
FCurrentXvtr := -1;
|
FCurrentXvtr := -1;
|
||||||
@@ -4247,6 +4310,7 @@ begin
|
|||||||
SaveCurrentBand;
|
SaveCurrentBand;
|
||||||
SaveGlobalSettings;
|
SaveGlobalSettings;
|
||||||
FSettings.SaveAlex(FDevMAC, FAlexSettings);
|
FSettings.SaveAlex(FDevMAC, FAlexSettings);
|
||||||
|
FSettings.SaveAnt936x(FDevMAC, FAnt936x);
|
||||||
// XVTR LastFreq → актуальная видимая частота в момент остановки.
|
// XVTR LastFreq → актуальная видимая частота в момент остановки.
|
||||||
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
|
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
|
||||||
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
|
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ type
|
|||||||
FMRptOffsetHz: Double; // Hz, 0 = use band default
|
FMRptOffsetHz: Double; // Hz, 0 = use band default
|
||||||
ZoomFactor: Double; // зум спектра/водопада 0.0..1.0 (0.0 = без зума)
|
ZoomFactor: Double; // зум спектра/водопада 0.0..1.0 (0.0 = без зума)
|
||||||
PanSlider: Double; // положение окна зума 0..1 (0.5 = центр)
|
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;
|
end;
|
||||||
|
|
||||||
// Персист панадаптеров и слайсов (секция "pans" под MAC). Восстанавливается
|
// Персист панадаптеров и слайсов (секция "pans" под MAC). Восстанавливается
|
||||||
@@ -251,6 +255,9 @@ type
|
|||||||
LastFMRptAutoActive: Boolean; // RPT был включён каналом (авто)
|
LastFMRptAutoActive: Boolean; // RPT был включён каналом (авто)
|
||||||
LastZoomFactor: Double; // зум спектра/водопада 0.0..1.0 (0.0 = без зума)
|
LastZoomFactor: Double; // зум спектра/водопада 0.0..1.0 (0.0 = без зума)
|
||||||
LastPanSlider: Double; // положение окна зума 0..1 (0.5 = центр)
|
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;
|
end;
|
||||||
|
|
||||||
TXvtrSettings = record
|
TXvtrSettings = record
|
||||||
@@ -262,6 +269,16 @@ type
|
|||||||
// Alex (Antenna + Filter board) settings per-device.
|
// Alex (Antenna + Filter board) settings per-device.
|
||||||
// Хранятся в JSON-секции "alex" под MAC.
|
// Хранятся в JSON-секции "alex" под MAC.
|
||||||
// Безопасные дефолты: ANT1 для всех диапазонов, вторичный вход выключен.
|
// Безопасные дефолты: 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
|
TAlexSettings = record
|
||||||
// Per-band (11 bands: 0=160m..10=6m)
|
// Per-band (11 bands: 0=160m..10=6m)
|
||||||
RxAnt: array[0..CFG_BAND_COUNT-1] of Byte; // 1..3 (ANT1/2/3)
|
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 SaveCATSettings(const G: TGlobalSettings);
|
||||||
procedure LoadCATSettings(var G: TGlobalSettings);
|
procedure LoadCATSettings(var G: TGlobalSettings);
|
||||||
// Alex (antenna) settings per-device (JSON-секция "alex" под MAC).
|
// 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);
|
class procedure DefaultAlex(out A: TAlexSettings);
|
||||||
function LoadAlex(const MAC: array of Byte; out A: TAlexSettings): Boolean;
|
function LoadAlex(const MAC: array of Byte; out A: TAlexSettings): Boolean;
|
||||||
procedure SaveAlex(const MAC: array of Byte; const A: TAlexSettings);
|
procedure SaveAlex(const MAC: array of Byte; const A: TAlexSettings);
|
||||||
@@ -473,6 +493,8 @@ begin
|
|||||||
B.CTun := False;
|
B.CTun := False;
|
||||||
B.CenterHz := 0; // 0 = нет сохранённого центра → центрировать на VFO
|
B.CenterHz := 0; // 0 = нет сохранённого центра → центрировать на VFO
|
||||||
B.SpanHz := 192000;
|
B.SpanHz := 192000;
|
||||||
|
B.RxGainMode := 2; // slow_attack (как глобальный дефолт Pluto)
|
||||||
|
B.RxGainDb := 40;
|
||||||
B.FMSQOn := False;
|
B.FMSQOn := False;
|
||||||
B.FMSQLevel := 30;
|
B.FMSQLevel := 30;
|
||||||
B.CTCSSOn := False;
|
B.CTCSSOn := False;
|
||||||
@@ -817,6 +839,8 @@ begin
|
|||||||
Bands[i].FMStepIdx := EnsureRange(JI(BObj,'fmstep_idx', Bands[i].FMStepIdx), 0, 3);
|
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].FMRptDir := EnsureRange(JI(BObj,'fmrpt_dir', Bands[i].FMRptDir), 0, 2);
|
||||||
Bands[i].FMRptOffsetHz := JD(BObj,'fmrpt_offset', Bands[i].FMRptOffsetHz);
|
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;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -916,6 +940,7 @@ begin
|
|||||||
JW(O,'fmstep_on',B.FMStepOn); JW(O,'fmstep_idx',B.FMStepIdx);
|
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,'fmrpt_dir',B.FMRptDir); JW(O,'fmrpt_offset',B.FMRptOffsetHz);
|
||||||
JW(O,'zoom_factor',B.ZoomFactor); JW(O,'pan_slider',B.PanSlider);
|
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;
|
end;
|
||||||
|
|
||||||
function TSettingsManager.LoadBand(const MAC: array of Byte; BandIdx: Integer;
|
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.FMStepIdx := EnsureRange(JI(O,'fmstep_idx', B.FMStepIdx), 0, 3);
|
||||||
B.FMRptDir := EnsureRange(JI(O,'fmrpt_dir', B.FMRptDir), 0, 2);
|
B.FMRptDir := EnsureRange(JI(O,'fmrpt_dir', B.FMRptDir), 0, 2);
|
||||||
B.FMRptOffsetHz := JD(O,'fmrpt_offset', B.FMRptOffsetHz);
|
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.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);
|
B.PanSlider := EnsureRange(JD(O,'pan_slider', B.PanSlider), 0.0, 1.0);
|
||||||
end;
|
end;
|
||||||
@@ -1304,6 +1331,61 @@ begin
|
|||||||
A.EnableXvtrHf := JB(O, 'enable_xvtr_hf', False);
|
A.EnableXvtrHf := JB(O, 'enable_xvtr_hf', False);
|
||||||
end;
|
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;
|
procedure TSettingsManager.SaveAlex(const MAC: array of Byte;
|
||||||
const A: TAlexSettings);
|
const A: TAlexSettings);
|
||||||
var O: TJSONObject; i: Integer;
|
var O: TJSONObject; i: Integer;
|
||||||
@@ -1369,6 +1451,8 @@ begin
|
|||||||
X.Entries[i].LastFMRptAutoActive := False;
|
X.Entries[i].LastFMRptAutoActive := False;
|
||||||
X.Entries[i].LastZoomFactor := 0.0;
|
X.Entries[i].LastZoomFactor := 0.0;
|
||||||
X.Entries[i].LastPanSlider := 0.5;
|
X.Entries[i].LastPanSlider := 0.5;
|
||||||
|
X.Entries[i].LastRxGainMode := 2; // slow_attack
|
||||||
|
X.Entries[i].LastRxGainDb := 40;
|
||||||
end;
|
end;
|
||||||
// Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились)
|
// Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились)
|
||||||
X.Entries[0].ButtonText := '2m';
|
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].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].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].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]
|
// Валидация: Last freqs должны быть в [FreqBegin..FreqEnd]
|
||||||
if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or
|
if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or
|
||||||
(X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then
|
(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_fmrpt_auto', X.Entries[i].LastFMRptAutoActive);
|
||||||
JW(EObj, 'last_zoom_factor', X.Entries[i].LastZoomFactor);
|
JW(EObj, 'last_zoom_factor', X.Entries[i].LastZoomFactor);
|
||||||
JW(EObj, 'last_pan_slider', X.Entries[i].LastPanSlider);
|
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;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
+215
-8
@@ -29,7 +29,7 @@ uses
|
|||||||
StdCtrls, ExtCtrls,
|
StdCtrls, ExtCtrls,
|
||||||
FlatButton, FlatCheckBox, FlatComboBox, FlatEdit, FlatSpinEdit, FlatFloatSpinEdit,
|
FlatButton, FlatCheckBox, FlatComboBox, FlatEdit, FlatSpinEdit, FlatFloatSpinEdit,
|
||||||
FlatRadioButton,
|
FlatRadioButton,
|
||||||
EqualizerControl, AudioOutput, AudioInput, AppTheme, Settings;
|
EqualizerControl, AudioOutput, AudioInput, AppTheme, Settings, BoardUtils;
|
||||||
|
|
||||||
type
|
type
|
||||||
// PA: MaxPower в Вт, BandCal[0..10] — калибровка 38.8..100.0 на диапазон
|
// PA: MaxPower в Вт, BandCal[0..10] — калибровка 38.8..100.0 на диапазон
|
||||||
@@ -37,6 +37,8 @@ type
|
|||||||
const BandCal: array of Double) of object;
|
const BandCal: array of Double) of object;
|
||||||
// Pluto TX: аттенюация выходного тракта на drive=100% (потолок), дБ<0
|
// Pluto TX: аттенюация выходного тракта на drive=100% (потолок), дБ<0
|
||||||
TOnPlutoTxChange = procedure(MaxAttDb: Double) of object;
|
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
|
// VHF (XVTR) PA calibration: 8 slots, 38.8..100.0
|
||||||
TOnVHFCalChange = procedure(const VHFCal: array of Double) of object;
|
TOnVHFCalChange = procedure(const VHFCal: array of Double) of object;
|
||||||
// TX-настройки целиком (любая правка → один callback с актуальной структурой)
|
// TX-настройки целиком (любая правка → один callback с актуальной структурой)
|
||||||
@@ -228,6 +230,21 @@ type
|
|||||||
FAlexExt1OnTx: TFlatCheckBox;
|
FAlexExt1OnTx: TFlatCheckBox;
|
||||||
FAlexGndBpf2OnTx: TFlatCheckBox;
|
FAlexGndBpf2OnTx: TFlatCheckBox;
|
||||||
FAlexEnableXvtrHf: 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
|
// Current Alex config state
|
||||||
FAlexConfig: TAlexSettings;
|
FAlexConfig: TAlexSettings;
|
||||||
FAlexBoardType: Integer;
|
FAlexBoardType: Integer;
|
||||||
@@ -292,6 +309,7 @@ type
|
|||||||
FOnCATChange: TOnCATChange;
|
FOnCATChange: TOnCATChange;
|
||||||
FOnPAChange: TOnPASettingsChange;
|
FOnPAChange: TOnPASettingsChange;
|
||||||
FOnPlutoTxChange: TOnPlutoTxChange;
|
FOnPlutoTxChange: TOnPlutoTxChange;
|
||||||
|
FOnAnt936xChange: TOnAnt936xChange;
|
||||||
FOnVHFCalChange: TOnVHFCalChange;
|
FOnVHFCalChange: TOnVHFCalChange;
|
||||||
FOnADCChange: TOnADCChange;
|
FOnADCChange: TOnADCChange;
|
||||||
FOnWebSettingsChange: TOnWebSettingsChange;
|
FOnWebSettingsChange: TOnWebSettingsChange;
|
||||||
@@ -379,6 +397,7 @@ type
|
|||||||
procedure OnFreqMhzDigitsCmbChange(Sender: TObject);
|
procedure OnFreqMhzDigitsCmbChange(Sender: TObject);
|
||||||
procedure OnMaxPowerChange(Sender: TObject);
|
procedure OnMaxPowerChange(Sender: TObject);
|
||||||
procedure OnPlutoTxMaxAttChange(Sender: TObject);
|
procedure OnPlutoTxMaxAttChange(Sender: TObject);
|
||||||
|
procedure Ant936xUIChange(Sender: TObject);
|
||||||
procedure OnBandCalChange(Sender: TObject);
|
procedure OnBandCalChange(Sender: TObject);
|
||||||
procedure OnVHFCalEdit(Sender: TObject);
|
procedure OnVHFCalEdit(Sender: TObject);
|
||||||
procedure OnCATSerialChange(Sender: TObject);
|
procedure OnCATSerialChange(Sender: TObject);
|
||||||
@@ -425,6 +444,10 @@ type
|
|||||||
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 LoadPlutoTxMaxAtt(MaxAttDb: 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 LoadVHFCalSettings(const VHFCal: array of Double);
|
||||||
procedure LoadCATSettings(
|
procedure LoadCATSettings(
|
||||||
const SerEnabled: array of Boolean;
|
const SerEnabled: array of Boolean;
|
||||||
@@ -448,6 +471,7 @@ type
|
|||||||
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 OnPlutoTxChange: TOnPlutoTxChange read FOnPlutoTxChange write FOnPlutoTxChange;
|
property OnPlutoTxChange: TOnPlutoTxChange read FOnPlutoTxChange write FOnPlutoTxChange;
|
||||||
|
property OnAnt936xChange: TOnAnt936xChange read FOnAnt936xChange write FOnAnt936xChange;
|
||||||
property OnVHFCalChange: TOnVHFCalChange read FOnVHFCalChange write FOnVHFCalChange;
|
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;
|
||||||
@@ -2977,6 +3001,12 @@ const
|
|||||||
TX3_X = PAD + 360;
|
TX3_X = PAD + 360;
|
||||||
DNTX_X = PAD + 398;
|
DNTX_X = PAD + 398;
|
||||||
GRP_W = DNTX_X + 80;
|
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;
|
GRP_H = GRP_TITLE_H + HDR1_H + HDR2_H + BANDS * ROW_H + PAD;
|
||||||
DATA_Y0 = GRP_TITLE_H + HDR1_H + HDR2_H + 4;
|
DATA_Y0 = GRP_TITLE_H + HDR1_H + HDR2_H + 4;
|
||||||
TXGRP_H = GRP_TITLE_H + 4 * 28 + PAD;
|
TXGRP_H = GRP_TITLE_H + 4 * 28 + PAD;
|
||||||
@@ -3029,6 +3059,44 @@ var
|
|||||||
Result.OnChange := OnAlexAnyChange;
|
Result.OnChange := OnAlexAnyChange;
|
||||||
end;
|
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
|
begin
|
||||||
FLoading := True;
|
FLoading := True;
|
||||||
try
|
try
|
||||||
@@ -3040,13 +3108,14 @@ begin
|
|||||||
Font.Name := UI_FONT; Font.Size := 16;
|
Font.Name := UI_FONT; Font.Size := 16;
|
||||||
Font.Style := [fsBold]; Font.Color := CLR_TEXT;
|
Font.Style := [fsBold]; Font.Color := CLR_TEXT;
|
||||||
end;
|
end;
|
||||||
with TLabel.Create(Self) do
|
FAlexSubtitle := TLabel.Create(Self);
|
||||||
begin
|
FAlexSubtitle.Parent := FPageAlex;
|
||||||
Parent := FPageAlex;
|
FAlexSubtitle.Caption :=
|
||||||
Caption := 'Per-band RX/TX antenna and secondary path routing for the Alex filter board.';
|
'Per-band RX/TX antenna and secondary path routing for the Alex filter board.';
|
||||||
SetBounds(MARGIN, 48, 680, 20);
|
FAlexSubtitle.SetBounds(MARGIN, 48, 680, 20);
|
||||||
Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM;
|
FAlexSubtitle.Font.Name := UI_FONT;
|
||||||
end;
|
FAlexSubtitle.Font.Size := 9;
|
||||||
|
FAlexSubtitle.Font.Color := CLR_TEXTDIM;
|
||||||
|
|
||||||
// ---- Antenna grid group ----
|
// ---- Antenna grid group ----
|
||||||
Grp := MakeGroupPanel(FPageAlex, 'Antenna Selection', MARGIN, 86, GRP_W, GRP_H);
|
Grp := MakeGroupPanel(FPageAlex, 'Antenna Selection', MARGIN, 86, GRP_W, GRP_H);
|
||||||
@@ -3123,11 +3192,149 @@ begin
|
|||||||
PAD, GRP_TITLE_H + 8 + 56, 440);
|
PAD, GRP_TITLE_H + 8 + 56, 440);
|
||||||
FAlexEnableXvtrHf := MkCh(GrpTX, 'Enable XVTR HF',
|
FAlexEnableXvtrHf := MkCh(GrpTX, 'Enable XVTR HF',
|
||||||
PAD, GRP_TITLE_H + 8 + 84, 440);
|
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
|
finally
|
||||||
FLoading := False;
|
FLoading := False;
|
||||||
end;
|
end;
|
||||||
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
|
// Alex event handlers and data collectors
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
<UseVersionInfo Value="True"/>
|
<UseVersionInfo Value="True"/>
|
||||||
<AutoIncrementBuild Value="True"/>
|
<AutoIncrementBuild Value="True"/>
|
||||||
<MinorVersionNr Value="9"/>
|
<MinorVersionNr Value="9"/>
|
||||||
<BuildNr Value="258"/>
|
<BuildNr Value="272"/>
|
||||||
</VersionInfo>
|
</VersionInfo>
|
||||||
<MacroValues Count="77">
|
<MacroValues Count="91">
|
||||||
<Macro1 Name="LCLWidgetType" Value="qt6"/>
|
<Macro1 Name="LCLWidgetType" Value="qt6"/>
|
||||||
<Macro2 Name="LCLWidgetType" Value="qt6"/>
|
<Macro2 Name="LCLWidgetType" Value="qt6"/>
|
||||||
<Macro3 Name="LCLWidgetType" Value="qt6"/>
|
<Macro3 Name="LCLWidgetType" Value="qt6"/>
|
||||||
@@ -97,6 +97,20 @@
|
|||||||
<Macro75 Name="LCLWidgetType" Value="qt6"/>
|
<Macro75 Name="LCLWidgetType" Value="qt6"/>
|
||||||
<Macro76 Name="LCLWidgetType" Value="qt6"/>
|
<Macro76 Name="LCLWidgetType" Value="qt6"/>
|
||||||
<Macro77 Name="LCLWidgetType" Value="qt6"/>
|
<Macro77 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro78 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro79 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro80 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro81 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro82 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro83 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro84 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro85 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro86 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro87 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro88 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro89 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro90 Name="LCLWidgetType" Value="qt6"/>
|
||||||
|
<Macro91 Name="LCLWidgetType" Value="qt6"/>
|
||||||
</MacroValues>
|
</MacroValues>
|
||||||
<BuildModes>
|
<BuildModes>
|
||||||
<Item Name="Debug" Default="True"/>
|
<Item Name="Debug" Default="True"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user