feat: Pluto RX hw-gain control + AD9361 cold-init (FIR via libad9361)

- RX hw-gain (Pluto/AD936x): backend SetRxGain(ModeIdx, GainDb) — gain_control_mode
  (manual/fast_attack/slow_attack/hybrid) + hardwaregain. Controller FRxGainMode/
  FRxGainDb, commands SetRxGainMode/SetRxGain/RxGainBy, rfRxGain event, snapshot,
  applied in StartRunning. Persisted per-device (rx_gain_mode/rx_gain_db). HPSDR
  unaffected (backend no-op).
- UI: separate "RF AGC" panel below the (unchanged) WDSP AGC block, shown only for
  Pluto — FAST/SLOW/HYB/MAN mode buttons + manual GAIN slider. LayoutLeftPanel
  reflows lower panels via RelayoutBelowBands; no layout shift on HPSDR.
- Cold-init fix: direct sampling_frequency write doesn't load the AD9361 FIR
  decimation filter, so a cold-booted Pluto showed a wide centre spike that didn't
  track tuning (worked only after SDR Console pre-initialised it). Add optional
  libad9361 binding (dynamic) and call ad9361_set_bb_rate() in ApplySampleRate,
  which configures BBPLL + loads the FIR; falls back to direct write if absent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 12:06:59 +03:00
co-authored by Claude Opus 4.8
parent a988849d3e
commit 24f74357cd
6 changed files with 228 additions and 3 deletions
+107
View File
@@ -259,6 +259,11 @@ type
BtnAGCMode: array[0..4] of TFlatButton; // FAST MED SLOW LONG OFF
LblAGCTop: TLabel; // показывает значение уровня
TrkAGC: TFlatSlider;
// RX hw-gain (Pluto) — ОТДЕЛЬНЫЙ блок под AGC (WDSP). Виден только для Pluto.
PanelPlutoAGC: TPanel;
BtnPlutoGain: array[0..3] of TFlatButton; // FAST/SLOW/HYB/MAN (Tag=режим)
TrkRxGain: TFlatSlider; // manual gain 0..73 dB
LblRxGain: TLabel; // значение усиления
TrkVolume: TFlatSlider;
BtnNR: TFlatButton;
BtnNB: TFlatButton;
@@ -328,6 +333,9 @@ type
function ResolveDevice(const IP: string): THPSDRDevice;
procedure RelabelBands; // подписи band-кнопок под план бэкенда
procedure ApplyBackendCapsToUI; // sample-rate пресеты + гейтинг полей по Caps
procedure UpdateRxGainUI; // рендер RX hw-gain (Pluto)
procedure LayoutLeftPanel(Pluto: Boolean); // показать/скрыть Pluto-блок + reflow
procedure BtnPlutoGainClick(Sender: TObject);
procedure DoConnectDevice(const Dev: THPSDRDevice);
// Старт с уже выбранным устройством (preload rate + WDSP open + connect).
// Переиспользуется десктоп-диалогом и web-оверлеем — без модального диалога.
@@ -487,6 +495,8 @@ type
procedure OnTXSettingsChange(const T: TTXSettings);
procedure BtnAGCModeClick(Sender: TObject);
procedure TrkAGCChange(Sender: TObject);
procedure TrkRxGainChange(Sender: TObject);
// (BtnPlutoGainClick объявлен выше)
procedure MeterTimerTick(Sender: TObject);
procedure SpectrumTimerTick(Sender: TObject);
procedure RightPanelResize(Sender: TObject);
@@ -1225,6 +1235,8 @@ begin
for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FController.FAGCMode);
rfVolume:
TrkVolume.Position := FController.FVolume;
rfRxGain:
UpdateRxGainUI;
rfDrive:
begin
TrkDrive.Position := FController.FDrivePercent; // слайдер ← drive%
@@ -1397,6 +1409,7 @@ const
BTN_H = 26;
BTN_SM = 24;
LEFT_W = 232;
PLUTO_AGC_H = 64; // высота отдельного Pluto RF-AGC блока (под AGC)
TOP_VFO_W = 244;
TOP_VFO_FREQ_H = 47;
var
@@ -1759,6 +1772,47 @@ begin
Inc(Y, 74);
// --- Pluto RF AGC/gain — отдельный блок ПОД блоком AGC (WDSP). Виден только
// когда подключён Pluto-бэкенд; раскладку панелей ниже сдвигает LayoutLeftPanel.
PanelPlutoAGC := TPanel.Create(Self);
PanelPlutoAGC.Parent := PanelLeft;
PanelPlutoAGC.SetBounds(0, Y, LEFT_W, PLUTO_AGC_H);
PanelPlutoAGC.BevelOuter := bvNone;
PanelPlutoAGC.Visible := False;
MakeLbl(PanelPlutoAGC, 'RF AGC', 4, 2);
// Режимы AD936x: FAST/SLOW/HYB/MAN. Tag = индекс режима (1/2/3/0).
BtnPlutoGain[0] := MakeBtn(PanelPlutoAGC, 'FAST',
LeftPanelButtonLeft(LEFT_W, 4, 0), 16, LeftPanelButtonWidth(LEFT_W, 4, 0),
BTN_SM, BtnPlutoGainClick);
BtnPlutoGain[0].Tag := 1;
BtnPlutoGain[1] := MakeBtn(PanelPlutoAGC, 'SLOW',
LeftPanelButtonLeft(LEFT_W, 4, 1), 16, LeftPanelButtonWidth(LEFT_W, 4, 1),
BTN_SM, BtnPlutoGainClick);
BtnPlutoGain[1].Tag := 2;
BtnPlutoGain[2] := MakeBtn(PanelPlutoAGC, 'HYB',
LeftPanelButtonLeft(LEFT_W, 4, 2), 16, LeftPanelButtonWidth(LEFT_W, 4, 2),
BTN_SM, BtnPlutoGainClick);
BtnPlutoGain[2].Tag := 3;
BtnPlutoGain[3] := MakeBtn(PanelPlutoAGC, 'MAN',
LeftPanelButtonLeft(LEFT_W, 4, 3), 16, LeftPanelButtonWidth(LEFT_W, 4, 3),
BTN_SM, BtnPlutoGainClick);
BtnPlutoGain[3].Tag := 0;
// Manual gain slider.
MakeLbl(PanelPlutoAGC, 'GAIN', 4, 44);
TrkRxGain := TFlatSlider.Create(Self);
TrkRxGain.Parent := PanelPlutoAGC; TrkRxGain.Left := 56; TrkRxGain.Top := 40;
TrkRxGain.Width := LEFT_W - 96; TrkRxGain.Height := 16;
TrkRxGain.Min := 0; TrkRxGain.Max := 73; // AD936x hardwaregain dB
TrkRxGain.Position := FController.FRxGainDb;
TrkRxGain.OnChange := TrkRxGainChange;
LblRxGain := TLabel.Create(Self);
LblRxGain.Parent := PanelPlutoAGC;
LblRxGain.Left := LEFT_W - 38; LblRxGain.Top := 44;
LblRxGain.Caption := Format('%ddB', [FController.FRxGainDb]);
LblRxGain.Font.Color := CLR_TEXT; LblRxGain.Font.Name := 'Courier New';
LblRxGain.Font.Size := 7;
// НЕ Inc(Y): панель скрыта и наложена на позицию Bands; LayoutLeftPanel сдвинет.
// Band selector. Высота резервирует 2 дополнительных ряда под XVTR-кнопки
// (создаются динамически в RebuildXvtrButtons). В каждом ряду до 6 кнопок,
// итого до 12 XVTR-кнопок (наш CFG_XVTR_COUNT = 8 умещается с запасом).
@@ -3540,6 +3594,10 @@ begin
FSampleRateOverlay.SetRatePresetsDefault; // HPSDR 48k..1536k
FSampleRateOverlay.SetCurrentRate(FController.FSampleRate);
end;
// Отдельный Pluto RF-AGC блок под AGC: показываем и сдвигаем панели ниже.
LayoutLeftPanel(Caps.HasHWGain);
if Caps.HasHWGain then UpdateRxGainUI;
end;
procedure TMainForm.DoConnectDevice(const Dev: THPSDRDevice);
@@ -5252,6 +5310,55 @@ begin
FController.SetAGCTop(TrkAGC.Position);
end;
procedure TMainForm.TrkRxGainChange(Sender: TObject);
begin
if FSyncingFromController then Exit;
FController.SetRxGain(TrkRxGain.Position); // переводит в manual
end;
procedure TMainForm.BtnPlutoGainClick(Sender: TObject);
begin
if FSyncingFromController then Exit;
FController.SetRxGainMode((Sender as TFlatButton).Tag); // Tag = индекс режима
end;
procedure TMainForm.UpdateRxGainUI;
// Рендер Pluto RF-AGC блока: подсветка режима + слайдер/значение manual gain.
var i: Integer;
begin
if PanelPlutoAGC = nil then Exit;
FSyncingFromController := True;
try
for i := 0 to 3 do
if BtnPlutoGain[i] <> nil then
StyleButton(BtnPlutoGain[i], BtnPlutoGain[i].Tag = FController.FRxGainMode);
TrkRxGain.Position := FController.FRxGainDb;
TrkRxGain.Enabled := (FController.FRxGainMode = 0); // активен только в manual
if FController.FRxGainMode = 0 then
LblRxGain.Caption := Format('%ddB', [FController.FRxGainDb])
else
LblRxGain.Caption := 'AGC';
finally
FSyncingFromController := False;
end;
end;
procedure TMainForm.LayoutLeftPanel(Pluto: Boolean);
// Показывает/скрывает Pluto RF-AGC блок под AGC и сдвигает панели ниже
// (Bands и далее через RelayoutBelowBands).
var baseTop: Integer;
begin
if (PanelPlutoAGC = nil) or (PanelAGC = nil) or (PanelBands = nil) then Exit;
baseTop := PanelAGC.Top + PanelAGC.Height + 4;
PanelPlutoAGC.Top := baseTop;
PanelPlutoAGC.Visible := Pluto;
if Pluto then
PanelBands.Top := baseTop + PanelPlutoAGC.Height + 2
else
PanelBands.Top := baseTop;
RelayoutBelowBands;
end;
// ===========================================================================
// DSP/Audio callbacks — вызываются из рабочих потоков
// ===========================================================================