feat: Pluto TX path — attenuation-based power, full-duplex IQ stream

TX path for Pluto/AD936x (RX was already working):
- PlutoBackend: continuous full-duplex TX stream (cf-ad9361-dds-core-lpc)
  with FIFO + push thread; SendDUCIQ upsamples WDSP 192k -> device rate
  (integer, linear interp), 24->16 bit, Q inverted; zeros on underrun.
- Power via output attenuator (ADI-recommended): SetTxAtten writes
  hardwaregain on voltage0(out). Controller maps drive% -> dB
  (PlutoTxAttForDrive/ApplyPlutoTxAtten); -89.75 dB on RX (~off),
  ceiling PlutoTxMaxAttDb on 100% drive.
- Configurable max-att (default -10 dB, ADI-linear) for external amp:
  persisted in global settings, UI spin on PA page, routed through
  controller SetPlutoTxMaxAtt (UI stays decoupled from logic).
- RadioBackend: virtual SetTxAtten (no-op for HPSDR).
- Update doc/PLUTO_INTEGRATION_PLAN.md: phases 3/4/5 + risks.

Builds clean (headless + qt6 GUI). Not yet verified on hardware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 12:27:49 +03:00
co-authored by Claude Opus 4.8
parent 1321e415f7
commit d08fc17056
7 changed files with 376 additions and 19 deletions
+46
View File
@@ -35,6 +35,8 @@ type
// PA: MaxPower в Вт, BandCal[0..10] — калибровка 38.8..100.0 на диапазон
TOnPASettingsChange = procedure(MaxPower: Double;
const BandCal: array of Double) of object;
// Pluto TX: аттенюация выходного тракта на drive=100% (потолок), дБ<0
TOnPlutoTxChange = procedure(MaxAttDb: Double) of object;
// VHF (XVTR) PA calibration: 8 slots, 38.8..100.0
TOnVHFCalChange = procedure(const VHFCal: array of Double) of object;
// TX-настройки целиком (любая правка → один callback с актуальной структурой)
@@ -132,6 +134,7 @@ type
// ---- PA Settings tab controls ----
FEdMaxPower: TFlatSpinEdit;
FEdPlutoTxMaxAtt: TFlatFloatSpinEdit; // Pluto TX att на drive=100%
FEdBandCal: array[0..10] of TFlatFloatSpinEdit;
FEdVHFCal: array[0..CFG_XVTR_COUNT-1] of TFlatFloatSpinEdit;
FVHFCalLabels: array[0..CFG_XVTR_COUNT-1] of TLabel;
@@ -265,6 +268,7 @@ type
// ---- Callbacks ----
FOnCATChange: TOnCATChange;
FOnPAChange: TOnPASettingsChange;
FOnPlutoTxChange: TOnPlutoTxChange;
FOnVHFCalChange: TOnVHFCalChange;
FOnADCChange: TOnADCChange;
FOnWebSettingsChange: TOnWebSettingsChange;
@@ -344,6 +348,7 @@ type
procedure OnLightThemeChkChange(Sender: TObject);
procedure OnFreqMhzDigitsCmbChange(Sender: TObject);
procedure OnMaxPowerChange(Sender: TObject);
procedure OnPlutoTxMaxAttChange(Sender: TObject);
procedure OnBandCalChange(Sender: TObject);
procedure OnVHFCalEdit(Sender: TObject);
procedure OnCATSerialChange(Sender: TObject);
@@ -386,6 +391,7 @@ type
procedure LoadLightTheme(ALight: Boolean);
procedure LoadFreqMhzDigits(Digits: Integer);
procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double);
procedure LoadPlutoTxMaxAtt(MaxAttDb: Double);
procedure LoadVHFCalSettings(const VHFCal: array of Double);
procedure LoadCATSettings(
const SerEnabled: array of Boolean;
@@ -404,6 +410,7 @@ type
property OnAlexChange: TOnAlexChange read FOnAlexChange write FOnAlexChange;
property OnXvtrChange: TOnXvtrChange read FOnXvtrChange write FOnXvtrChange;
property OnPAChange: TOnPASettingsChange read FOnPAChange write FOnPAChange;
property OnPlutoTxChange: TOnPlutoTxChange read FOnPlutoTxChange write FOnPlutoTxChange;
property OnVHFCalChange: TOnVHFCalChange read FOnVHFCalChange write FOnVHFCalChange;
property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange;
property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange;
@@ -1662,6 +1669,29 @@ begin
Spin.OnChange := OnMaxPowerChange;
FEdMaxPower := Spin;
// Pluto TX max attenuation @ drive 100% (потолок под внешний усилитель), дБ<0
Lbl := TLabel.Create(Self);
Lbl.Parent := Grp;
Lbl.Caption := 'Pluto TX att @100% (dB)';
Lbl.SetBounds(GRP_PAD + 320, R1 + 6, 180, 18);
Lbl.Font.Color := CLR_TEXTDIM;
Lbl.Font.Name := UI_FONT;
Lbl.Font.Size := 9;
FEdPlutoTxMaxAtt := TFlatFloatSpinEdit.Create(Self);
FEdPlutoTxMaxAtt.Parent := Grp;
FEdPlutoTxMaxAtt.SetBounds(GRP_PAD + 510, R1, ED_W, BTN_H + 2);
FEdPlutoTxMaxAtt.Color := CLR_INPUT;
FEdPlutoTxMaxAtt.Font.Color := CLR_INPUT_TEXT;
FEdPlutoTxMaxAtt.Font.Name := UI_FONT;
FEdPlutoTxMaxAtt.Font.Size := 9;
FEdPlutoTxMaxAtt.DecimalPlaces := 2;
FEdPlutoTxMaxAtt.MinValue := -50;
FEdPlutoTxMaxAtt.MaxValue := 0;
FEdPlutoTxMaxAtt.Increment := 0.25;
FEdPlutoTxMaxAtt.Value := -10;
FEdPlutoTxMaxAtt.OnChange := OnPlutoTxMaxAttChange;
// Calibration label
Lbl := TLabel.Create(Self);
Lbl.Parent := Grp;
@@ -2486,6 +2516,16 @@ begin
end;
end;
procedure TSettingsForm.LoadPlutoTxMaxAtt(MaxAttDb: Double);
begin
FLoading := True;
try
FEdPlutoTxMaxAtt.Value := EnsureRange(MaxAttDb, -50.0, 0.0);
finally
FLoading := False;
end;
end;
procedure TSettingsForm.OnMaxPowerChange(Sender: TObject);
var
BandCal: array[0..10] of Double;
@@ -2498,6 +2538,12 @@ begin
FOnPAChange(FEdMaxPower.Value, BandCal);
end;
procedure TSettingsForm.OnPlutoTxMaxAttChange(Sender: TObject);
begin
if FLoading then Exit;
if Assigned(FOnPlutoTxChange) then FOnPlutoTxChange(FEdPlutoTxMaxAtt.Value);
end;
procedure TSettingsForm.OnBandCalChange(Sender: TObject);
var
BandCal: array[0..10] of Double;