mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +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;
|
||||
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) ----
|
||||
iio_device_create_buffer: function(dev: Piio_device; samples_count: csize_t;
|
||||
cyclic: ByteBool): Piio_buffer; cdecl;
|
||||
@@ -168,6 +185,11 @@ begin
|
||||
iio_channel_attr_write := Sym('iio_channel_attr_write');
|
||||
iio_channel_attr_write_bool := Sym('iio_channel_attr_write_bool');
|
||||
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_buffer_destroy := Sym('iio_buffer_destroy');
|
||||
iio_buffer_cancel := Sym('iio_buffer_cancel');
|
||||
|
||||
+30
-4
@@ -663,6 +663,7 @@ type
|
||||
Shift: TShiftState; X, Y: Integer);
|
||||
procedure LayoutPanStack(AWideH: Integer); // регионы панов + Layout каждого
|
||||
function PanMinHeight(PanId: Integer): Integer; // мин. высота региона пана
|
||||
function Pan0HeaderVisible: Boolean; // шапка пана 0: стек ИЛИ 2 входа
|
||||
function Pan0HeaderH: Integer; // высота шапки пана 0 (0 без стека)
|
||||
procedure PbSpectrumDblClick(Sender: TObject);
|
||||
procedure TrkDriveChange(Sender: TObject);
|
||||
@@ -674,6 +675,7 @@ type
|
||||
procedure ApplyMOX(Active: Boolean);
|
||||
procedure OnPASettingsChange(MaxPower: Double; const BandCal: array of Double);
|
||||
procedure OnPlutoTxMaxAttChange(MaxAttDb: Double);
|
||||
procedure OnAnt936xChange(const A: TAD936xAnt); // разъёмы AD936x (Antenna)
|
||||
procedure OnVHFCalSettingsChange(const VHFCal: array of Double);
|
||||
procedure OnTXSettingsChange(const T: TTXSettings);
|
||||
procedure BtnAGCModeClick(Sender: TObject);
|
||||
@@ -2629,9 +2631,18 @@ begin
|
||||
PositionSampleRateOverlay;
|
||||
end;
|
||||
|
||||
function TMainForm.Pan0HeaderVisible: Boolean;
|
||||
// Шапка пана 0 — только при стеке панов (у одиночного пана она лишняя). Единый
|
||||
// источник правды: LayoutPanStack (обе ветки), Pan0HeaderH, UpdatePanHeaders.
|
||||
// Выбор приёмного входа у одноканальных по панам бэкендов (Pluto/LibreSDR) живёт
|
||||
// не здесь, а во вкладке Antenna настроек.
|
||||
begin
|
||||
Result := PanCount > 1;
|
||||
end;
|
||||
|
||||
function TMainForm.Pan0HeaderH: Integer;
|
||||
begin
|
||||
if PanCount > 1 then
|
||||
if Pan0HeaderVisible then
|
||||
Result := MulDiv(20, Screen.PixelsPerInch, 96) // = HEADER_H панели
|
||||
else
|
||||
Result := 0;
|
||||
@@ -2711,7 +2722,8 @@ begin
|
||||
for k := 0 to N - 1 do
|
||||
begin
|
||||
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);
|
||||
Inc(MinRest, MinH[k]);
|
||||
end;
|
||||
@@ -2758,7 +2770,7 @@ begin
|
||||
Row0H := Round((TotalH - PAN_SPLIT_H) * FPanGridRowShare);
|
||||
// Кламп ряда минимумами содержимого (шапки — до расчёта минимумов):
|
||||
// ни пан 0, ни колонки нижнего ряда не смеют стать ниже своих минимумов.
|
||||
FPans[0].HeaderVisible := PanCount > 1;
|
||||
FPans[0].HeaderVisible := Pan0HeaderVisible;
|
||||
BotMin := 0;
|
||||
for k := 1 to N - 1 do
|
||||
begin
|
||||
@@ -6069,7 +6081,8 @@ begin
|
||||
begin
|
||||
P := FPans[i];
|
||||
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.BtnPopPan <> nil then
|
||||
P.BtnPopPan.Visible := (N > 1) and (i > 0) and not PanFloating(i);
|
||||
@@ -7120,6 +7133,13 @@ begin
|
||||
UpdateDriveLabel; // изменился потолок → пересчитать дБ-подпись drive
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnAnt936xChange(const A: TAD936xAnt);
|
||||
// Вкладка Antenna (AD936x): таблица «диапазон → разъём». Контроллер сохраняет её
|
||||
// и сразу применяет разъёмы текущего диапазона/трансвертера.
|
||||
begin
|
||||
FController.SetAnt936x(A);
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnVHFCalSettingsChange(const VHFCal: array of Double);
|
||||
var i: Integer;
|
||||
begin
|
||||
@@ -7906,6 +7926,7 @@ begin
|
||||
SF.OnFPSChange := ApplyFPS;
|
||||
SF.OnPAChange := OnPASettingsChange;
|
||||
SF.OnPlutoTxChange := OnPlutoTxMaxAttChange;
|
||||
SF.OnAnt936xChange := OnAnt936xChange;
|
||||
SF.OnVHFCalChange := OnVHFCalSettingsChange;
|
||||
SF.OnCATChange := OnCATSettingsChange;
|
||||
SF.OnThemeChange := SetLightTheme;
|
||||
@@ -7922,6 +7943,11 @@ begin
|
||||
IfThen(FController.FNetwork.Connected, FController.FNetwork.Device.BoardType, FPendingBoardType));
|
||||
SF.LoadAlexSettings(FController.FAlexSettings,
|
||||
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.LoadPASettings(FController.FPAMaxPower, FController.FPABandCal);
|
||||
SF.LoadPlutoTxMaxAtt(FController.FPlutoTxMaxAttDb);
|
||||
|
||||
+225
-17
@@ -58,6 +58,24 @@ type
|
||||
FRxThread: TThread;
|
||||
FStreaming: Boolean;
|
||||
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.
|
||||
// Full-duplex: стрим живёт всё время Run; на приёме гоним нули (мощности нет
|
||||
@@ -103,8 +121,19 @@ type
|
||||
procedure SetTxLOPowerdown(PoweredOn: Boolean); // TX LO вкл/выкл (по PTT)
|
||||
procedure ApplySampleRate(Hz: Integer);
|
||||
procedure BuildTxResampler; // (пере)строить полифазные коэффициенты под FTxRatio
|
||||
procedure ApplyGain;
|
||||
// Force=True — перед записью режима пишем ДРУГОЙ режим: драйвер пропускает
|
||||
// перепрог AGC-регистров, если считает режим неизменным, а после смены
|
||||
// антенного мультиплексора настройки AGC нового канала ещё не запрограммированы
|
||||
// (в железе крутится auto-AGC, hardwaregain отвергается с EOPNOTSUPP).
|
||||
procedure ApplyGain(Force: Boolean = False);
|
||||
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 StopRX;
|
||||
procedure StartTX;
|
||||
@@ -139,6 +168,7 @@ type
|
||||
Transmitting, PAEnabled, AlexEnabled: Boolean); override;
|
||||
procedure SendFullHP; override;
|
||||
procedure SendPTT(Active: Boolean); override;
|
||||
procedure SetAntPorts(RxPort, TxPort: Byte); override;
|
||||
procedure SetRxGain(ModeIdx: Integer; GainDb: Integer); override;
|
||||
procedure SetTxAtten(Db: Double); override;
|
||||
procedure SendDUCIQ(const IData, QData: array of Integer); override;
|
||||
@@ -171,6 +201,10 @@ const
|
||||
TX_FIR_CUT = 40000; // частота среза анти-имидж ФНЧ, Гц (покрывает SSB/NFM/AM/data)
|
||||
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
|
||||
// RX-поток: блокирующий refill libiio → конверсия int16→24bit BE → OnDDCIQ.
|
||||
// Работает с private-полями TPlutoBackend (одна единица компиляции).
|
||||
@@ -432,11 +466,14 @@ begin
|
||||
Result.RatePresets[7] := 5760000;
|
||||
Result.MinFreqHz := PLUTO_MIN_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.IndependentPanFreq := False;
|
||||
// Пан один ⇒ ADC-селектор панов не при чём: у AD936x «антенна» — это выбор
|
||||
// физических разъёмов (RX1/RX2, TX1/TX2) на диапазон, вкладка Antenna настроек.
|
||||
Result.NumADCs := 1;
|
||||
Result.HasAntSwitch := FRx2Avail or FRxMuxAvail;
|
||||
end;
|
||||
|
||||
function TPlutoBackend.ProbeURI(const AURI: string; out Dev: TRadioDevice): Boolean;
|
||||
@@ -536,6 +573,7 @@ end;
|
||||
function TPlutoBackend.Connect(const Dev: TRadioDevice): Boolean;
|
||||
var
|
||||
M: TBytes;
|
||||
PV: PAnsiChar;
|
||||
begin
|
||||
Result := False;
|
||||
if not IIOLoad then begin FLastError := 'libiio not available'; Exit; end;
|
||||
@@ -566,11 +604,33 @@ begin
|
||||
Exit;
|
||||
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;
|
||||
// Приводим железо в известное состояние: валидный rate + усиление.
|
||||
// Приводим железо в известное состояние: валидный rate + вход + усиление.
|
||||
if FSampleRate < PLUTO_MIN_SR then FSampleRate := PLUTO_DEF_SR;
|
||||
ApplySampleRate(FSampleRate);
|
||||
ApplyGain;
|
||||
ApplyRxMux; // разъёмы могли остаться от прошлой сессии (состояние в чипе)
|
||||
ApplyTxMux;
|
||||
ApplyGain(True);
|
||||
ApplyTxAtten; // TX заглушён (макс. аттенюация) до явного PTT
|
||||
StartTelemetry;
|
||||
Result := True;
|
||||
@@ -651,8 +711,9 @@ begin
|
||||
iio_channel_attr_write_longlong(Ch, 'sampling_frequency', Hz);
|
||||
end;
|
||||
|
||||
// RF-полоса отдельно (set_bb_rate её не трогает).
|
||||
Ch := iio_device_find_channel(FPhy, 'voltage0', 0);
|
||||
// RF-полоса отдельно (set_bb_rate её не трогает) — на phy-канале выбранного
|
||||
// приёмного тракта (у 2R2T они независимы).
|
||||
Ch := iio_device_find_channel(FPhy, PAnsiChar(AnsiString(RxPhyChanName)), 0);
|
||||
if Ch <> nil then
|
||||
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;
|
||||
end;
|
||||
|
||||
procedure TPlutoBackend.ApplyGain;
|
||||
var Ch: Piio_channel;
|
||||
function BoardHasSecondRxPort(const Model: string): Boolean;
|
||||
// Есть ли у платы физический разъём 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
|
||||
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 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)));
|
||||
if FGainMode = 'manual' then
|
||||
iio_channel_attr_write_longlong(Ch, 'hardwaregain', FGainDb);
|
||||
@@ -721,10 +899,17 @@ begin
|
||||
end;
|
||||
|
||||
procedure TPlutoBackend.StartRX;
|
||||
var Base: Integer; ChI, ChQ: string;
|
||||
begin
|
||||
if FStreaming or not FConnected or (FRxDev = nil) then Exit;
|
||||
FRxChI := iio_device_find_channel(FRxDev, 'voltage0', 0);
|
||||
FRxChQ := iio_device_find_channel(FRxDev, 'voltage1', 0);
|
||||
// Пара стрим-каналов выбранного тракта: RX1 = voltage0/1, RX2 = voltage2/3.
|
||||
// Включаем ТОЛЬКО её — второй тракт не стримим (иначе вдвое трафик/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
|
||||
begin FLastError := 'RX I/Q channels not found'; Exit; end;
|
||||
iio_channel_enable(FRxChI);
|
||||
@@ -753,13 +938,26 @@ begin
|
||||
iio_buffer_destroy(FRxBuf);
|
||||
FRxBuf := nil;
|
||||
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;
|
||||
|
||||
procedure TPlutoBackend.StartTX;
|
||||
var Base: Integer; ChI, ChQ: string;
|
||||
begin
|
||||
if FTxStreaming or not FConnected or (FTxDev = nil) then Exit;
|
||||
FTxChI := iio_device_find_channel(FTxDev, 'voltage0', 1); // output I
|
||||
FTxChQ := iio_device_find_channel(FTxDev, 'voltage1', 1); // output Q
|
||||
// 2R2T-образ: пара выходных каналов выбранного разъёма (TX1 = voltage0/1,
|
||||
// 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
|
||||
begin FLastError := 'TX I/Q channels not found'; Exit; end;
|
||||
iio_channel_enable(FTxChI);
|
||||
@@ -805,11 +1003,20 @@ end;
|
||||
procedure TPlutoBackend.ConfigureDDCs(NumDDCs: Byte; SampleRate: Word;
|
||||
ADCSource: Byte; DitherEnabled, RandomEnabled: Boolean);
|
||||
begin
|
||||
// SampleRate приходит в ksps (как у HPSDR ConfigureDDCs). Pluto — в Гц.
|
||||
// ADCSource (АЦП-источник HPSDR) у AD936x смысла не имеет — антенные разъёмы
|
||||
// переключает SetAntPorts. SampleRate приходит в ksps (как у HPSDR), Pluto — в Гц.
|
||||
// Контроллер может прислать HPSDR-наследие (<520k) — клампим к дефолту.
|
||||
FSampleRate := Integer(SampleRate) * 1000;
|
||||
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;
|
||||
|
||||
procedure TPlutoBackend.SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
||||
@@ -990,9 +1197,10 @@ begin
|
||||
end;
|
||||
|
||||
// RSSI имеет смысл только при активном RX-стриме (иначе чтение даёт ошибку/мусор).
|
||||
// Читаем с phy-канала выбранного приёмного тракта (RX1/RX2 — независимы).
|
||||
if FStreaming then
|
||||
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)
|
||||
and (iio_channel_attr_read_double(Ch, 'rssi', d) = 0) then
|
||||
begin
|
||||
|
||||
@@ -52,6 +52,10 @@ type
|
||||
// Число АЦП (SCU): >1 → у панов есть селектор ADC-источника (A1/A2, 3.4).
|
||||
// Достоверно после Connect (у HPSDR зависит от BoardType).
|
||||
NumADCs: Integer;
|
||||
// Переключаемые антенные разъёмы AD936x (RX1/RX2, TX1/TX2) — у плат с двумя
|
||||
// парами SMA (LibreSDR/AntSDR/Pluto+). Гейтит блок «Antenna» в настройках;
|
||||
// у openHPSDR антеннами рулит Alex, поэтому False. Достоверно после Connect.
|
||||
HasAntSwitch: Boolean;
|
||||
MinSampleRate: Integer;
|
||||
RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay
|
||||
MinFreqHz: Double;
|
||||
@@ -134,6 +138,9 @@ type
|
||||
procedure SetPanDDC(DDCIdx: Integer; Enabled: Boolean; FreqHz: Double;
|
||||
RateKHz: Word; ADCSource: Byte); 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-состояние ----
|
||||
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
||||
@@ -294,4 +301,9 @@ procedure TRadioBackend.SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double);
|
||||
begin
|
||||
end;
|
||||
|
||||
procedure TRadioBackend.SetAntPorts(RxPort, TxPort: Byte);
|
||||
// Бэкенды без переключаемых разъёмов (openHPSDR — там Alex) ничего не делают.
|
||||
begin
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
@@ -324,6 +324,8 @@ type
|
||||
// ---- Конфиги / устройство (per-MAC) ----
|
||||
FTXSettings: TTXSettings;
|
||||
FAlexSettings: TAlexSettings;
|
||||
// Антенные разъёмы AD936x (Pluto/LibreSDR) на диапазон/трансвертер.
|
||||
FAnt936x: TAD936xAnt;
|
||||
FXvtrSettings: TXvtrSettings;
|
||||
FBandCache: array[0..CFG_BAND_COUNT-1] of TBandSettings;
|
||||
FPABandCal: array[0..CFG_BAND_COUNT-1] of Double;
|
||||
@@ -599,6 +601,11 @@ type
|
||||
function PanDDCRateKHz(PanId: Integer): Word;
|
||||
// ADC-источник пана (3.4). PanId=0 = главный DDC (реконфиг ConfigureDDCs),
|
||||
// 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 SetPanADC(PanId: Integer; ADC: Byte): Boolean;
|
||||
// ---- Зум/пан доп. панов (PanId=0 делегирует главному зуму) ----
|
||||
@@ -1410,6 +1417,43 @@ begin
|
||||
else Result := 0;
|
||||
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;
|
||||
begin
|
||||
if PanId = 0 then Result := FMainADCSrc
|
||||
@@ -2231,6 +2275,9 @@ begin
|
||||
Result.FMStepIdx := FFMStepIdx;
|
||||
Result.FMRptDir := FFMRptDir;
|
||||
Result.FMRptOffsetHz := FFMRptOffsetHz;
|
||||
// Аппаратный AGC AD936x — на диапазон (усиление тракта разное на 2м и QO-100).
|
||||
Result.RxGainMode := FRxGainMode;
|
||||
Result.RxGainDb := FRxGainDb;
|
||||
// Waterfall AGC/NF — глобальные (не диапазонные)
|
||||
end;
|
||||
|
||||
@@ -2267,6 +2314,8 @@ begin
|
||||
FXvtrSettings.Entries[i].LastFMRptAutoActive := FFMRptAutoActive;
|
||||
FXvtrSettings.Entries[i].LastZoomFactor := FZoomFactor;
|
||||
FXvtrSettings.Entries[i].LastPanSlider := FPanSlider;
|
||||
FXvtrSettings.Entries[i].LastRxGainMode := FRxGainMode;
|
||||
FXvtrSettings.Entries[i].LastRxGainDb := FRxGainDb;
|
||||
end;
|
||||
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
|
||||
Exit;
|
||||
@@ -2386,6 +2435,13 @@ begin
|
||||
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetAGCTop(FAGCTop);
|
||||
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 ---
|
||||
FFMSQOn := B.FMSQOn;
|
||||
FFMSQLevel := B.FMSQLevel;
|
||||
@@ -2471,6 +2527,7 @@ begin
|
||||
// Иначе смена диапазона при активном B центрировала бы приёмник на A.
|
||||
if FActiveVfo = 0 then SetVfoA(FVfoA)
|
||||
else SetVfoB(FVfoB);
|
||||
ApplyAntPorts; // AD936x: разъёмы, выбранные для этого диапазона
|
||||
end;
|
||||
|
||||
function TRadioController.ActiveVfoHz: Double;
|
||||
@@ -3089,6 +3146,8 @@ begin
|
||||
B.FMStepIdx := E.LastFMStepIdx;
|
||||
B.FMRptDir := E.LastFMRptDir;
|
||||
B.FMRptOffsetHz := E.LastFMRptOffsetHz;
|
||||
B.RxGainMode := E.LastRxGainMode; // hw-AGC слота (AD936x)
|
||||
B.RxGainDb := E.LastRxGainDb;
|
||||
B.VfoA := Vis;
|
||||
B.VfoB := FVfoB;
|
||||
ApplyBandDSP(B);
|
||||
@@ -3120,6 +3179,7 @@ begin
|
||||
if E.FullDuplexTx and not FDisplayDuplex then SetDuplex(True);
|
||||
PushNetworkState;
|
||||
RefreshPanDDCs; // живые паны — в диапазон XVTR, DDC — на IF
|
||||
ApplyAntPorts; // AD936x: разъёмы, выбранные для этого трансвертера
|
||||
|
||||
Changed(rfXvtr);
|
||||
Changed(rfActiveVfo);
|
||||
@@ -3947,6 +4007,8 @@ begin
|
||||
FNetwork.SetSpeakerAudio(FSendAudioToRadio);
|
||||
// RX hw-gain в бэкенд (Pluto). HPSDR — no-op.
|
||||
ApplyRxGainToBackend;
|
||||
// Антенные разъёмы AD936x для текущего диапазона/трансвертера. HPSDR — no-op.
|
||||
ApplyAntPorts;
|
||||
// QO-100 self-monitor: RX-аудио на TX (full-duplex). По умолчанию заглушено.
|
||||
ApplyRxMuteToEngine;
|
||||
SendDUCSpecificFromSettings;
|
||||
@@ -4076,6 +4138,7 @@ begin
|
||||
// TX/Alex/XVTR per-device
|
||||
FSettings.LoadTX(FDevMAC, FTXSettings);
|
||||
FSettings.LoadAlex(FDevMAC, FAlexSettings);
|
||||
FSettings.LoadAnt936x(FDevMAC, FAnt936x);
|
||||
FNetwork.SetAlexConfig(FAlexSettings);
|
||||
FSettings.LoadXvtr(FDevMAC, FXvtrSettings);
|
||||
FCurrentXvtr := -1;
|
||||
@@ -4247,6 +4310,7 @@ begin
|
||||
SaveCurrentBand;
|
||||
SaveGlobalSettings;
|
||||
FSettings.SaveAlex(FDevMAC, FAlexSettings);
|
||||
FSettings.SaveAnt936x(FDevMAC, FAnt936x);
|
||||
// XVTR LastFreq → актуальная видимая частота в момент остановки.
|
||||
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
|
||||
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+215
-8
@@ -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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<UseVersionInfo Value="True"/>
|
||||
<AutoIncrementBuild Value="True"/>
|
||||
<MinorVersionNr Value="9"/>
|
||||
<BuildNr Value="258"/>
|
||||
<BuildNr Value="272"/>
|
||||
</VersionInfo>
|
||||
<MacroValues Count="77">
|
||||
<MacroValues Count="91">
|
||||
<Macro1 Name="LCLWidgetType" Value="qt6"/>
|
||||
<Macro2 Name="LCLWidgetType" Value="qt6"/>
|
||||
<Macro3 Name="LCLWidgetType" Value="qt6"/>
|
||||
@@ -97,6 +97,20 @@
|
||||
<Macro75 Name="LCLWidgetType" Value="qt6"/>
|
||||
<Macro76 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>
|
||||
<BuildModes>
|
||||
<Item Name="Debug" Default="True"/>
|
||||
|
||||
Reference in New Issue
Block a user