mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(puresignal): PureSignal-предыскажение для openHPSDR P2 + кнопки PS/2TON
Протокол (по pihpsdr new_protocol.c): на PTT при PS DDC0<-ADC0 (RX-feedback), DDC1<-ADC2 (TX DAC loopback), 192к/24бит, sync DDC1->DDC0 (byte 1363=0x02, interleaved в потоке DDC0), частоты DDC0/1 = TX в HP-пакете, Alex0 bit18 (мост обратной связи). Caps.HasPureSignal для плат с DDCBase=2 (Angelia/Orion/Orion2/Saturn); попутно NumADCs=2 для Angelia. WDSPEngine: PushPSFeedback (деинтерлив -> pscc(TXA, tx=DDC1, rx=DDC0) из сетевого потока), PS-параметры calcc в кэше с переприменением после пересоздания TXA; SetTXTwoTone (PostGen mode 1, 700/1900, 0.49+0.49, инверсия LSB/CWL/DIGL, мьют mic как TUN). RadioController: state machine команд (порт Thetis PSForm timer1) в PureSignalTick @10Гц + auto-attenuate (AttOnTX по уровню feedback, цель ~152, через DUC Specific); SetPSMox и перестройка feedback-DDC на T/R; маршрутизация DDC0 в pscc из OnDDCIQ; SetTwoTone с гейтами safety. UI: кнопки PS (ЛКМ = поповер настроек, ПКМ = быстрый toggle) и 2TON в блоке TX; поповер TPureSignalPopup (дочерний контрол, Wayland-safe): PS ON/OFF, CAL, RESET, 2TON, лампа/уровень feedback, cals, corr, GetPk/SetPk, MOX/CAL wait, PH#, Auto-atten, Relax ptol, Pin/Map/Stbl, Tint, тона двухтональника. Персист ps_* в tx-секции per-device. Не проверено на железе; Save/Restore коррекции не реализованы. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+86
-7
@@ -166,6 +166,13 @@ type
|
||||
FAlexEnabled: Boolean;
|
||||
FAlexConfig: TAlexSettings; // per-band antenna + routing config
|
||||
FStepAtten: Byte; // ADC0 step attenuator 0-31 dB
|
||||
// ---- PureSignal ----
|
||||
// FPSEnabled — фича вооружена (кнопка PS). FPSTXActive — активная PTT-фаза:
|
||||
// RebuildDDCSpecific включает feedback-DDC0 (ADC0) + sync DDC1 (TX DAC),
|
||||
// SendFullHP ставит частоты DDC0/1 = TX и Alex bit18. Пишутся из потока
|
||||
// контроллера (дисциплина как у FCurrentRXFreq).
|
||||
FPSEnabled: Boolean;
|
||||
FPSTXActive: Boolean;
|
||||
// ---- XVTR (Transverter) state ----
|
||||
// FXvtrEnable — True когда активен XVTR-диапазон. Используется как
|
||||
// XvtrActive в CalcAlex0 и для управления byte 1400 bit 0.
|
||||
@@ -243,6 +250,9 @@ type
|
||||
procedure SetPanDDC(DDCIdx: Integer; Enabled: Boolean; FreqHz: Double;
|
||||
RateKHz: Word; ADCSource: Byte); override;
|
||||
procedure SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double); override;
|
||||
// PureSignal: вооружение фичи и PTT-фаза (см. TRadioBackend).
|
||||
procedure SetPureSignal(Enabled: Boolean); override;
|
||||
procedure SetPureSignalTX(Active: Boolean); override;
|
||||
procedure SendDUCSpecific(const Pkt: TDUCSpecificPacket); override;
|
||||
procedure SendHighPriority(const Pkt: THighPriorityPacket); override;
|
||||
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
||||
@@ -693,12 +703,14 @@ begin
|
||||
// см. doc/SLICES_PLAN.md 3.1). Каждый DDC тюнится независимо.
|
||||
Result.MaxPans := 4;
|
||||
Result.IndependentPanFreq := True;
|
||||
// Два АЦП у ANAN-100D/200D (ORION), 7000/8000 (ORION2) и Saturn/G2 —
|
||||
// Два АЦП у ANAN-100D/200D (ANGELIA/ORION), 7000/8000 (ORION2) и Saturn/G2 —
|
||||
// те же платы, что в RebuildDDCSpecific (Pkt.NumADCs).
|
||||
if FDevice.BoardType in [4, 5, 10] then
|
||||
if FDevice.BoardType in [3, 4, 5, 10] then
|
||||
Result.NumADCs := 2
|
||||
else
|
||||
Result.NumADCs := 1;
|
||||
// PureSignal: нужны свободные DDC0/1 под feedback — платы с DDCBase=2.
|
||||
Result.HasPureSignal := FDevice.BoardType in [3, 4, 5, 10];
|
||||
end;
|
||||
|
||||
constructor THPSDRNetwork.Create;
|
||||
@@ -1328,7 +1340,8 @@ function CalcAlex0(RXFreqHz, TXFreqHz: Double;
|
||||
XvtrRxAnt: Byte = 0;
|
||||
XvtrDisablePA: Boolean = False;
|
||||
XvtrActive: Boolean = False;
|
||||
WidebandBypass: Boolean = False): LongWord;
|
||||
WidebandBypass: Boolean = False;
|
||||
PureSignalTX: Boolean = False): LongWord;
|
||||
var
|
||||
txf: Double;
|
||||
BandIdx: Integer;
|
||||
@@ -1339,10 +1352,15 @@ begin
|
||||
// Bit 27 = T/R relay (0=RX, 1=TX).
|
||||
// Если XvtrDisablePA=True — НЕ устанавливаем bit 27 (PA остаётся в RX
|
||||
// положении, TX-сигнал идёт через XVTR-IF без усиления).
|
||||
// Bit 18 (PureSignal) не ставим — PureSignal не реализован.
|
||||
if Transmitting then
|
||||
if not XvtrDisablePA then
|
||||
begin
|
||||
Result := Result or $08000000; // bit 27: T/R relay
|
||||
// Bit 18 = PureSignal (ALEX_PS_BIT в pihpsdr): на TX подключает
|
||||
// внутренний мост обратной связи PA к RX-тракту ADC0.
|
||||
if PureSignalTX then
|
||||
Result := Result or $00040000;
|
||||
end;
|
||||
|
||||
// HPF (ANAN-100/200) или BPF (ANAN-7000/8000/Saturn/G2) — по RX-частоте.
|
||||
if WidebandBypass and (not Transmitting) then
|
||||
@@ -1545,8 +1563,8 @@ var
|
||||
begin
|
||||
FillChar(Pkt, SizeOf(Pkt), 0);
|
||||
|
||||
// ANAN-7000/8000/Saturn имеют 2 ADC
|
||||
if FDevice.BoardType in [4, 5, 10] then // ORION=4, ORION2=5, SATURN=10
|
||||
// ANAN-100D/200D (ANGELIA/ORION), 7000/8000 (ORION2) и Saturn имеют 2 ADC
|
||||
if FDevice.BoardType in [3, 4, 5, 10] then // ANGELIA=3, ORION=4, ORION2=5, SATURN=10
|
||||
Pkt.NumADCs := 2
|
||||
else
|
||||
Pkt.NumADCs := 1;
|
||||
@@ -1568,6 +1586,21 @@ begin
|
||||
if FPanDDCEnabled[ddc] then
|
||||
AddDDC(ddc, FPanDDCRateKHz[ddc], FPanDDCADC[ddc]);
|
||||
|
||||
// PureSignal feedback (как pihpsdr new_protocol_receive_specific): на время
|
||||
// PTT DDC0 ← ADC0 (RX-feedback с моста PA), DDC1 ← «ADC 2» = TX DAC loopback,
|
||||
// оба 192 кС/с 24 бита, DDC1 синхронизирован с DDC0 (byte 1363 bit1) —
|
||||
// его сэмплы приходят interleaved внутри потока DDC0 (порт 1035).
|
||||
// Enable-бит ставится только DDC0. Только платы с DDCBase=2 (DDC0/1 свободны).
|
||||
if FPSTXActive and (DDCBase = 2) then
|
||||
begin
|
||||
AddDDC(0, 192, 0); // DDC0: enable, ADC0, 192 kSPS, 24 bit
|
||||
Pkt.DDCConfig[1 * 6] := 2; // DDC1: источник = TX DAC (Angelia/Orion)
|
||||
Pkt.DDCConfig[1 * 6 + 1] := 0;
|
||||
Pkt.DDCConfig[1 * 6 + 2] := 192;
|
||||
Pkt.DDCConfig[1 * 6 + 5] := 24;
|
||||
Pkt.SyncDDC[0] := $02; // sync DDC1 → DDC0
|
||||
end;
|
||||
|
||||
SendDDCSpecific(Pkt);
|
||||
FCachedDDCSpec := Pkt;
|
||||
FCachedDDCValid := True;
|
||||
@@ -1596,6 +1629,37 @@ begin
|
||||
if FConnected and FPanDDCEnabled[DDCIdx] then SendFullHP;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.SetPureSignal(Enabled: Boolean);
|
||||
begin
|
||||
// Только платы, где DDC0/1 зарезервированы под feedback (Angelia/Orion/
|
||||
// Orion2/Saturn). На прочих (Hermes: DDC0 = главный) PS не включаем.
|
||||
if Enabled and (DDCBaseIndex <> 2) then Exit;
|
||||
if FPSEnabled = Enabled then Exit;
|
||||
FPSEnabled := Enabled;
|
||||
if not Enabled and FPSTXActive then
|
||||
begin
|
||||
// Выключение посреди передачи: убрать feedback-DDC немедленно.
|
||||
FPSTXActive := False;
|
||||
if FConnected and FRunning then
|
||||
begin
|
||||
RebuildDDCSpecific;
|
||||
SendFullHP;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.SetPureSignalTX(Active: Boolean);
|
||||
begin
|
||||
Active := Active and FPSEnabled and (DDCBaseIndex = 2);
|
||||
if FPSTXActive = Active then Exit;
|
||||
FPSTXActive := Active;
|
||||
if FConnected and FRunning then
|
||||
begin
|
||||
RebuildDDCSpecific; // включить/убрать DDC0 + sync DDC1
|
||||
SendFullHP; // частоты DDC0/1 = TX, Alex bit18
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.SendDUCSpecific(const Pkt: TDUCSpecificPacket);
|
||||
var
|
||||
B: TDUCSpecificPacket;
|
||||
@@ -1691,6 +1755,21 @@ begin
|
||||
Buf[12 + ddc*4] := Ph and $FF;
|
||||
end;
|
||||
|
||||
// PureSignal: feedback-DDC0/DDC1 следят за TX-частотой (pihpsdr, bytes 9..16).
|
||||
// DDCBase=2 ⇒ с частотным словом главного DDC (bytes 17..20) не пересекаемся.
|
||||
if FPSTXActive then
|
||||
begin
|
||||
Ph := FreqToPhaseWord(FCurrentTXFreq);
|
||||
Buf[9] := (Ph shr 24) and $FF;
|
||||
Buf[10] := (Ph shr 16) and $FF;
|
||||
Buf[11] := (Ph shr 8) and $FF;
|
||||
Buf[12] := Ph and $FF;
|
||||
Buf[13] := (Ph shr 24) and $FF;
|
||||
Buf[14] := (Ph shr 16) and $FF;
|
||||
Buf[15] := (Ph shr 8) and $FF;
|
||||
Buf[16] := Ph and $FF;
|
||||
end;
|
||||
|
||||
// DUC TX frequency (bytes 329-332)
|
||||
Ph := FreqToPhaseWord(FCurrentTXFreq);
|
||||
Buf[329] := (Ph shr 24) and $FF;
|
||||
@@ -1708,7 +1787,7 @@ begin
|
||||
Alex0 := CalcAlex0(FCurrentRXFreq, FCurrentTXFreq,
|
||||
FIsTransmitting, IsOrion2, FAlexConfig,
|
||||
FXvtrRxAntOverride, FXvtrDisablePA,
|
||||
FXvtrEnable, FWidebandEnabled);
|
||||
FXvtrEnable, FWidebandEnabled, FPSTXActive);
|
||||
Buf[1432] := (Alex0 shr 24) and $FF;
|
||||
Buf[1433] := (Alex0 shr 16) and $FF;
|
||||
Buf[1434] := (Alex0 shr 8) and $FF;
|
||||
|
||||
+89
-1
@@ -33,7 +33,7 @@ uses
|
||||
WebServer, WebAdapter,
|
||||
CATAdapter,
|
||||
SpectrumView, SpectrumViewOpengl,
|
||||
PanZoomBar, PanafallPanel, PanDisplayPopup, FlatPopupMenu,
|
||||
PanZoomBar, PanafallPanel, PanDisplayPopup, PureSignalPopup, FlatPopupMenu,
|
||||
WidebandView,
|
||||
StatusBar,
|
||||
PlatformUtils,
|
||||
@@ -337,6 +337,9 @@ type
|
||||
TrkDrive: TFlatSlider;
|
||||
BtnMOX: TFlatButton;
|
||||
BtnTUN: TFlatButton;
|
||||
BtnPS: TFlatButton; // PureSignal: ЛКМ = вкл/выкл, ПКМ = поповер настроек
|
||||
Btn2Ton: TFlatButton; // двухтональник (IMD/проверка PS)
|
||||
FPSPopup: TObject; // TPureSignalPopup (cast при использовании)
|
||||
|
||||
PanelRXBlock: TPanel;
|
||||
BtnChannels: TFlatButton;
|
||||
@@ -460,6 +463,13 @@ type
|
||||
procedure UpdateBandPlanOverlay; // вкл/выкл полоски бэндплана QO-100 по InQO100
|
||||
procedure BtnTUNClick(Sender: TObject);
|
||||
procedure ApplyTUN(Active: Boolean);
|
||||
// PureSignal: ЛКМ по PS = вкл/выкл, ПКМ = поповер настроек.
|
||||
procedure BtnPSClick(Sender: TObject);
|
||||
procedure BtnPSMouseDown(Sender: TObject; Button: TMouseButton;
|
||||
Shift: TShiftState; X, Y: Integer);
|
||||
procedure Btn2TonClick(Sender: TObject);
|
||||
procedure OnPSPopupChanged(Sender: TObject); // персист PS-параметров
|
||||
procedure UpdatePSButtons; // стиль/доступность PS/2TON
|
||||
procedure UpdateFilterButtons;
|
||||
procedure BtnFMSQClick(Sender: TObject);
|
||||
procedure TrkFMSQChange(Sender: TObject);
|
||||
@@ -1369,6 +1379,8 @@ begin
|
||||
end; // (DUC-очередь сбрасывает SetMOX)
|
||||
rfTuning:
|
||||
StyleButton(BtnTUN, FController.FTuning);
|
||||
rfPureSignal, rfTwoTone:
|
||||
UpdatePSButtons; // подсветка PS/2TON + обновление статусов поповера
|
||||
rfRunning:
|
||||
// Run-состояние: кнопка START/STOP, доступность MOX, запрет сна.
|
||||
// Текст offline-статуса контекстно-зависим (Disconnected / Connection
|
||||
@@ -1952,6 +1964,14 @@ begin
|
||||
BtnTUN := MakeBtn(PanelTX, 'TUN',
|
||||
LeftPanelButtonLeft(LEFT_W, 4, 1), 16,
|
||||
LeftPanelButtonWidth(LEFT_W, 4, 1), BTN_SM, BtnTUNClick);
|
||||
// PureSignal: ЛКМ = вкл/выкл, ПКМ = поповер настроек/управления.
|
||||
BtnPS := MakeBtn(PanelTX, 'PS',
|
||||
LeftPanelButtonLeft(LEFT_W, 4, 2), 16,
|
||||
LeftPanelButtonWidth(LEFT_W, 4, 2), BTN_SM, BtnPSClick);
|
||||
BtnPS.OnMouseDown := BtnPSMouseDown;
|
||||
Btn2Ton := MakeBtn(PanelTX, '2TON',
|
||||
LeftPanelButtonLeft(LEFT_W, 4, 3), 16,
|
||||
LeftPanelButtonWidth(LEFT_W, 4, 3), BTN_SM, Btn2TonClick);
|
||||
UpdateTXControlAvailability;
|
||||
|
||||
BtnDUP := MakeBtn(PanelTX, 'DUP',
|
||||
@@ -3291,6 +3311,9 @@ begin
|
||||
FController.ServiceBeaconLock;
|
||||
// Pluto: опрос температуры/RSSI (троттлинг внутри; no-op для HPSDR).
|
||||
FController.ServicePlutoTelemetry;
|
||||
// PureSignal: state machine команд + auto-attenuate + опрос GetPSInfo
|
||||
// (no-op на бэкендах без PS; рендер кнопок/поповера через rfPureSignal).
|
||||
FController.PureSignalTick;
|
||||
|
||||
// Сглаженное среднее (EMA)
|
||||
FSMeterAvg := FSMeterAvg * (1.0 - AVG_ALPHA) + FController.FLastSMeter * AVG_ALPHA;
|
||||
@@ -3534,6 +3557,25 @@ begin
|
||||
(FController.ActiveTXMode <> MODE_DMR);
|
||||
BtnMOX.Enabled := Allowed;
|
||||
BtnTUN.Enabled := Allowed;
|
||||
UpdatePSButtons;
|
||||
end;
|
||||
|
||||
procedure TMainForm.UpdatePSButtons;
|
||||
// PS: доступен на openHPSDR-платах с feedback-DDC (Caps.HasPureSignal) при
|
||||
// Run. 2TON — обычный TX-тест, доступен как MOX/TUN.
|
||||
var
|
||||
PSAllowed, TXAllowed: Boolean;
|
||||
begin
|
||||
if (BtnPS = nil) or (Btn2Ton = nil) or (FController = nil) then Exit;
|
||||
TXAllowed := FController.FRunning and
|
||||
(FController.ActiveTXMode <> MODE_DMR);
|
||||
PSAllowed := TXAllowed and FController.PSAvailable;
|
||||
BtnPS.Enabled := PSAllowed;
|
||||
Btn2Ton.Enabled := TXAllowed;
|
||||
StyleButton(BtnPS, FController.FPSEnabled or FController.PSSingleCalActive);
|
||||
StyleButton(Btn2Ton, FController.FTwoTone);
|
||||
if (FPSPopup <> nil) and TPureSignalPopup(FPSPopup).Visible then
|
||||
TPureSignalPopup(FPSPopup).RefreshStatus;
|
||||
end;
|
||||
|
||||
procedure TMainForm.UpdateStatusField3;
|
||||
@@ -4481,6 +4523,52 @@ begin
|
||||
ApplyTUN(not FController.FTuning);
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PureSignal: кнопка PS (ЛКМ = вкл/выкл, ПКМ = поповер настроек) + 2TON
|
||||
// ---------------------------------------------------------------------------
|
||||
procedure TMainForm.BtnPSClick(Sender: TObject);
|
||||
begin
|
||||
if not FController.PSAvailable then Exit;
|
||||
FController.SetPureSignal(not FController.FPSEnabled); // рендер через rfPureSignal
|
||||
end;
|
||||
|
||||
procedure TMainForm.BtnPSMouseDown(Sender: TObject; Button: TMouseButton;
|
||||
Shift: TShiftState; X, Y: Integer);
|
||||
var
|
||||
Pop: TPureSignalPopup;
|
||||
begin
|
||||
if Button <> mbRight then Exit;
|
||||
if not FController.PSAvailable then Exit;
|
||||
if FPSPopup = nil then
|
||||
begin
|
||||
Pop := TPureSignalPopup.Create(Self);
|
||||
Pop.OnChanged := OnPSPopupChanged;
|
||||
FPSPopup := Pop;
|
||||
end
|
||||
else
|
||||
Pop := TPureSignalPopup(FPSPopup);
|
||||
if Pop.Visible then
|
||||
Pop.Hide
|
||||
else
|
||||
Pop.PopupBelow(FController, BtnPS);
|
||||
end;
|
||||
|
||||
procedure TMainForm.Btn2TonClick(Sender: TObject);
|
||||
begin
|
||||
FController.SetTwoTone(not FController.FTwoTone); // рендер через rfTwoTone
|
||||
end;
|
||||
|
||||
procedure TMainForm.OnPSPopupChanged(Sender: TObject);
|
||||
// Правка PS-параметров в поповере: применение к WDSP делает сам поповер
|
||||
// (ApplyPSSettingsToDSP), здесь — только персист per-device.
|
||||
begin
|
||||
if FController.FDevConnected then
|
||||
begin
|
||||
FController.FSettings.SaveTX(FController.FDevMAC, FController.FTXSettings);
|
||||
FController.FSettings.Save;
|
||||
end;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Клик/драг по спектру/водопаду
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
unit PureSignalPopup;
|
||||
|
||||
{
|
||||
TPureSignalPopup — поповер управления и настроек PureSignal, открывается
|
||||
ПКМ по кнопке PS на левой панели. Управление: PS (авто-калибровка вкл/выкл),
|
||||
CAL (одиночная калибровка), RESET (сброс коррекции), 2TON. Статус: лампа и
|
||||
уровень feedback (оптимум ~152), счётчик калибровок, лампа коррекции,
|
||||
GetPk/SetPk. Параметры calcc (MOX/CAL wait, PH#, Relax ptol, Auto-atten,
|
||||
Pin/Map/Stbl, Ints, тона 2TON) пишутся в FTXSettings.PS* живьём:
|
||||
ApplyPSSettingsToDSP + OnChanged (персист в MainForm).
|
||||
|
||||
Реализован как ДОЧЕРНИЙ контрол верхнеуровневой формы (не отдельное окно) —
|
||||
как TPanDisplayPopup: Wayland не даёт клиенту позиционировать top-level окна.
|
||||
}
|
||||
|
||||
{$IFDEF FPC}
|
||||
{$MODE Delphi}
|
||||
{$ENDIF}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, Math, Types, Controls, ExtCtrls, StdCtrls, Graphics, Forms,
|
||||
RadioController, Settings,
|
||||
FlatButton, FlatComboBox, FlatCheckBox, FlatSpinEdit, FlatFloatSpinEdit;
|
||||
|
||||
type
|
||||
TPureSignalPopup = class(TCustomControl)
|
||||
private
|
||||
FController: TRadioController; // не владеем
|
||||
FLoading: Boolean; // подавляет Apply при программной заливке
|
||||
FBg: TPanel;
|
||||
FTitle: TLabel;
|
||||
FBtnClose: TFlatButton;
|
||||
// Управление
|
||||
FBtnPS: TFlatButton;
|
||||
FBtnCal: TFlatButton;
|
||||
FBtnReset: TFlatButton;
|
||||
FBtn2Ton: TFlatButton;
|
||||
// Статус
|
||||
FFbLamp: TPanel;
|
||||
FLbFbVal: TLabel;
|
||||
FLbCals: TLabel;
|
||||
FCorrLamp: TPanel;
|
||||
FLbGetPk: TLabel;
|
||||
FLbAtt: TLabel;
|
||||
// Параметры
|
||||
FSePk: TFlatFloatSpinEdit;
|
||||
FBtnDefPk: TFlatButton;
|
||||
FSeMoxWait: TFlatFloatSpinEdit;
|
||||
FSeCalWait: TFlatFloatSpinEdit;
|
||||
FSePhNum: TFlatSpinEdit;
|
||||
FChkRelax: TFlatCheckBox;
|
||||
FChkAutoAtt:TFlatCheckBox;
|
||||
FChkPin: TFlatCheckBox;
|
||||
FChkMap: TFlatCheckBox;
|
||||
FChkStbl: TFlatCheckBox;
|
||||
FCbInts: TFlatComboBox;
|
||||
FSeTT1: TFlatSpinEdit;
|
||||
FSeTT2: TFlatSpinEdit;
|
||||
FOnChanged: TNotifyEvent;
|
||||
|
||||
function MakeLbl(const Cap: string; ALeft, ATop, AW: Integer): TLabel;
|
||||
procedure Apply(Sender: TObject); // контролы → FTXSettings.PS* → WDSP
|
||||
procedure PSClick(Sender: TObject);
|
||||
procedure CalClick(Sender: TObject);
|
||||
procedure ResetClick(Sender: TObject);
|
||||
procedure TwoToneClick(Sender: TObject);
|
||||
procedure DefPkClick(Sender: TObject);
|
||||
procedure CloseClick(Sender: TObject);
|
||||
protected
|
||||
procedure Paint; override;
|
||||
public
|
||||
constructor Create(AOwner: TComponent); override;
|
||||
procedure LoadFrom(C: TRadioController);
|
||||
// Обновление статусных ламп/значений (звать с UI-таймера пока Visible
|
||||
// и по rfPureSignal/rfTwoTone).
|
||||
procedure RefreshStatus;
|
||||
// Показать под контролом-якорем (кнопкой PS) как дочерний контрол формы.
|
||||
procedure PopupBelow(C: TRadioController; Anchor: TControl);
|
||||
property OnChanged: TNotifyEvent read FOnChanged write FOnChanged;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
const
|
||||
{$IFDEF WINDOWS}
|
||||
UI_FONT = 'Segoe UI';
|
||||
{$ELSE}
|
||||
UI_FONT = 'Sans';
|
||||
{$ENDIF}
|
||||
CLR_BG = TColor($00303030); // рамка (1px кайма формы)
|
||||
CLR_PANEL = TColor($001A1A1A);
|
||||
CLR_TEXT = TColor($00E0E0E0);
|
||||
CLR_TEXTDIM = TColor($00888888);
|
||||
CLR_ACCENT = TColor($0040FF80);
|
||||
CLR_INPUT = TColor($00FFFFFF);
|
||||
CLR_INPUT_TEXT = TColor($00202020);
|
||||
|
||||
// Лампа feedback (пороги Thetis: >181 горячо, 129..181 оптимум, 91..128
|
||||
// слабовато, <=90 нет сигнала). TColor = BGR.
|
||||
CLR_FB_HOT = TColor($00FF9030); // голубой: слишком горячо (атт. мал)
|
||||
CLR_FB_OK = TColor($0000FF00); // зелёный: оптимум
|
||||
CLR_FB_LOW = TColor($0000FFFF); // жёлтый: слабовато
|
||||
CLR_FB_NONE = TColor($000000C0); // красный: нет feedback
|
||||
CLR_LAMP_OFF= TColor($00101010);
|
||||
|
||||
POP_W = 272;
|
||||
PAD = 10;
|
||||
LBLW = 92;
|
||||
CTLX = 108;
|
||||
ROWH = 24;
|
||||
|
||||
constructor TPureSignalPopup.Create(AOwner: TComponent);
|
||||
var
|
||||
Y: Integer;
|
||||
|
||||
function NextRow: Integer;
|
||||
begin
|
||||
Result := Y;
|
||||
Inc(Y, ROWH + 4);
|
||||
end;
|
||||
|
||||
function MakeIntSpin(ATop, AMin, AMax, AInc: Integer): TFlatSpinEdit;
|
||||
begin
|
||||
Result := TFlatSpinEdit.Create(Self);
|
||||
Result.Parent := FBg;
|
||||
Result.SetBounds(CTLX, ATop, POP_W - CTLX - PAD, ROWH - 2);
|
||||
Result.MinValue := AMin;
|
||||
Result.MaxValue := AMax;
|
||||
Result.Increment := AInc;
|
||||
Result.Color := CLR_INPUT;
|
||||
Result.Font.Color := CLR_INPUT_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.OnChange := Apply;
|
||||
end;
|
||||
|
||||
function MakeFloatSpin(ATop: Integer; AMin, AMax, AInc: Double;
|
||||
ADec: Integer): TFlatFloatSpinEdit;
|
||||
begin
|
||||
Result := TFlatFloatSpinEdit.Create(Self);
|
||||
Result.Parent := FBg;
|
||||
Result.SetBounds(CTLX, ATop, POP_W - CTLX - PAD, ROWH - 2);
|
||||
Result.MinValue := AMin;
|
||||
Result.MaxValue := AMax;
|
||||
Result.Increment := AInc;
|
||||
Result.DecimalPlaces := ADec;
|
||||
Result.Color := CLR_INPUT;
|
||||
Result.Font.Color := CLR_INPUT_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.OnChange := Apply;
|
||||
end;
|
||||
|
||||
function MakeChk(const Cap: string; ALeft, ATop, AW: Integer): TFlatCheckBox;
|
||||
begin
|
||||
Result := TFlatCheckBox.Create(Self);
|
||||
Result.Parent := FBg;
|
||||
Result.SetBounds(ALeft, ATop, AW, ROWH - 4);
|
||||
Result.Caption := Cap;
|
||||
Result.Font.Color := CLR_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
Result.OnChange := Apply;
|
||||
end;
|
||||
|
||||
function MakeLamp(ALeft, ATop: Integer): TPanel;
|
||||
begin
|
||||
Result := TPanel.Create(Self);
|
||||
Result.Parent := FBg;
|
||||
Result.SetBounds(ALeft, ATop, 16, 16);
|
||||
Result.BevelOuter := bvNone;
|
||||
Result.Color := CLR_LAMP_OFF;
|
||||
Result.Caption := '';
|
||||
end;
|
||||
|
||||
var
|
||||
BW: Integer;
|
||||
begin
|
||||
inherited Create(AOwner);
|
||||
ControlStyle := ControlStyle + [csOpaque];
|
||||
Visible := False;
|
||||
Color := CLR_BG;
|
||||
Font.Name := UI_FONT;
|
||||
Font.Size := 9;
|
||||
Font.Color := CLR_TEXT;
|
||||
FLoading := False;
|
||||
|
||||
FBg := TPanel.Create(Self);
|
||||
FBg.Parent := Self;
|
||||
FBg.BevelOuter := bvNone;
|
||||
FBg.Color := CLR_PANEL;
|
||||
FBg.SetBounds(1, 1, POP_W - 2, 10);
|
||||
|
||||
Y := PAD;
|
||||
|
||||
FTitle := MakeLbl('PureSignal', PAD, NextRow + 2, POP_W - 2 * PAD - 18);
|
||||
FTitle.Font.Color := CLR_ACCENT;
|
||||
FTitle.Font.Style := [fsBold];
|
||||
FBtnClose := MakeFlatBtn(FBg, '×', POP_W - PAD - 16, PAD, 16, 16, CloseClick);
|
||||
FBtnClose.ClrText := CLR_TEXTDIM;
|
||||
FBtnClose.ClrTextAct := CLR_TEXT;
|
||||
|
||||
// ---- Управление: PS / CAL / RESET / 2TON ----
|
||||
BW := (POP_W - 2 * PAD - 2 * 6) div 3;
|
||||
FBtnPS := MakeFlatBtn(FBg, 'PS ON', PAD, Y, BW, ROWH, PSClick);
|
||||
FBtnCal := MakeFlatBtn(FBg, 'CAL', PAD + BW + 6, Y, BW, ROWH, CalClick);
|
||||
FBtnReset := MakeFlatBtn(FBg, 'RESET', PAD + 2 * (BW + 6), Y, BW, ROWH, ResetClick);
|
||||
NextRow;
|
||||
FBtn2Ton := MakeFlatBtn(FBg, '2TON', PAD, Y, BW, ROWH, TwoToneClick);
|
||||
// ---- Статус: лампа + FB + cals + corr в одной строке с 2TON ----
|
||||
FFbLamp := MakeLamp(PAD + BW + 6, Y + 4);
|
||||
FLbFbVal := MakeLbl('FB: —', PAD + BW + 28, Y + 4, 68);
|
||||
FCorrLamp := MakeLamp(POP_W - PAD - 62, Y + 4);
|
||||
MakeLbl('corr', POP_W - PAD - 42, Y + 4, 36).Font.Color := CLR_TEXTDIM;
|
||||
NextRow;
|
||||
FLbCals := MakeLbl('cals: 0', PAD, Y + 2, 90);
|
||||
FLbCals.Font.Color := CLR_TEXTDIM;
|
||||
FLbAtt := MakeLbl('TX ATT: — dB', PAD + 96, Y + 2, POP_W - 2 * PAD - 96);
|
||||
FLbAtt.Font.Color := CLR_TEXTDIM;
|
||||
Inc(Y, 20);
|
||||
|
||||
MakeLbl('— Calibration —', PAD, Y + 3, POP_W - 2 * PAD).Font.Color := CLR_TEXTDIM;
|
||||
NextRow;
|
||||
|
||||
// ---- GetPk / SetPk ----
|
||||
FLbGetPk := MakeLbl('GetPk: —', PAD, Y + 3, LBLW);
|
||||
FSePk := MakeFloatSpin(Y, 0.05, 1.00, 0.01, 4);
|
||||
FSePk.Width := FSePk.Width - 42;
|
||||
FBtnDefPk := MakeFlatBtn(FBg, 'DEF', POP_W - PAD - 38, Y, 38, ROWH - 2, DefPkClick);
|
||||
NextRow;
|
||||
|
||||
MakeLbl('MOX wait, s', PAD, Y + 3, LBLW);
|
||||
FSeMoxWait := MakeFloatSpin(Y, 0.0, 0.9, 0.1, 1);
|
||||
NextRow;
|
||||
MakeLbl('CAL wait, s', PAD, Y + 3, LBLW);
|
||||
FSeCalWait := MakeFloatSpin(Y, 0.0, 10.0, 0.5, 1);
|
||||
NextRow;
|
||||
MakeLbl('PH# delay, ns', PAD, Y + 3, LBLW);
|
||||
FSePhNum := MakeIntSpin(Y, 0, 1000, 10);
|
||||
NextRow;
|
||||
|
||||
FChkAutoAtt := MakeChk('Auto-attenuate', PAD, Y + 2, 130);
|
||||
FChkRelax := MakeChk('Relax tolerance', PAD + 134, Y + 2, POP_W - 2 * PAD - 134);
|
||||
NextRow;
|
||||
FChkPin := MakeChk('Pin', PAD, Y + 2, 56);
|
||||
FChkMap := MakeChk('Map', PAD + 62, Y + 2, 56);
|
||||
FChkStbl := MakeChk('Stbl', PAD + 124, Y + 2, 60);
|
||||
NextRow;
|
||||
|
||||
MakeLbl('Tint (ints/spi)', PAD, Y + 3, LBLW);
|
||||
FCbInts := TFlatComboBox.Create(Self);
|
||||
FCbInts.Parent := FBg;
|
||||
FCbInts.SetBounds(CTLX, Y, POP_W - CTLX - PAD, ROWH - 2);
|
||||
FCbInts.Items.Add('16 / 256');
|
||||
FCbInts.Items.Add('8 / 512');
|
||||
FCbInts.Items.Add('4 / 1024');
|
||||
FCbInts.Color := CLR_INPUT;
|
||||
FCbInts.Font.Color := CLR_INPUT_TEXT;
|
||||
FCbInts.Font.Name := UI_FONT;
|
||||
FCbInts.Font.Size := 9;
|
||||
FCbInts.OnChange := Apply;
|
||||
NextRow;
|
||||
|
||||
MakeLbl('— Two-tone, Hz —', PAD, Y + 3, POP_W - 2 * PAD).Font.Color := CLR_TEXTDIM;
|
||||
NextRow;
|
||||
MakeLbl('F1 / F2', PAD, Y + 3, LBLW);
|
||||
FSeTT1 := MakeIntSpin(Y, 100, 5000, 100);
|
||||
FSeTT1.Width := (POP_W - CTLX - PAD - 6) div 2;
|
||||
FSeTT2 := MakeIntSpin(Y, 100, 5000, 100);
|
||||
FSeTT2.SetBounds(CTLX + FSeTT1.Width + 6, Y, (POP_W - CTLX - PAD - 6) div 2, ROWH - 2);
|
||||
NextRow;
|
||||
|
||||
FBg.Height := Y - 1 + PAD;
|
||||
Width := POP_W;
|
||||
Height := Y + 1 + PAD;
|
||||
end;
|
||||
|
||||
procedure TPureSignalPopup.Paint;
|
||||
begin
|
||||
Canvas.Brush.Style := bsSolid;
|
||||
Canvas.Brush.Color := CLR_BG;
|
||||
Canvas.Pen.Style := psClear;
|
||||
Canvas.FillRect(0, 0, Width, Height);
|
||||
end;
|
||||
|
||||
function TPureSignalPopup.MakeLbl(const Cap: string; ALeft, ATop, AW: Integer): TLabel;
|
||||
begin
|
||||
Result := TLabel.Create(Self);
|
||||
Result.Parent := FBg;
|
||||
Result.Caption := Cap;
|
||||
Result.Left := ALeft;
|
||||
Result.Top := ATop;
|
||||
if AW > 0 then Result.Width := AW;
|
||||
Result.Font.Color := CLR_TEXT;
|
||||
Result.Font.Name := UI_FONT;
|
||||
Result.Font.Size := 9;
|
||||
end;
|
||||
|
||||
procedure TPureSignalPopup.LoadFrom(C: TRadioController);
|
||||
begin
|
||||
FController := C;
|
||||
if C = nil then Exit;
|
||||
FLoading := True;
|
||||
try
|
||||
FSePk.Value := EnsureRange(C.FTXSettings.PSHWPeak, 0.05, 1.00);
|
||||
FSeMoxWait.Value := EnsureRange(C.FTXSettings.PSMoxDelay, 0.0, 0.9);
|
||||
FSeCalWait.Value := EnsureRange(C.FTXSettings.PSLoopDelay, 0.0, 10.0);
|
||||
FSePhNum.Value := EnsureRange(C.FTXSettings.PSTXDelayNs, 0, 1000);
|
||||
FChkRelax.Checked := C.FTXSettings.PSRelaxPtol;
|
||||
FChkAutoAtt.Checked := C.FTXSettings.PSAutoAtten;
|
||||
FChkPin.Checked := C.FTXSettings.PSPin;
|
||||
FChkMap.Checked := C.FTXSettings.PSMap;
|
||||
FChkStbl.Checked := C.FTXSettings.PSStabilize;
|
||||
FCbInts.ItemIndex := EnsureRange(C.FTXSettings.PSIntsIdx, 0, 2);
|
||||
FSeTT1.Value := EnsureRange(C.FTXSettings.PSTTFreq1, 100, 5000);
|
||||
FSeTT2.Value := EnsureRange(C.FTXSettings.PSTTFreq2, 100, 5000);
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
RefreshStatus;
|
||||
end;
|
||||
|
||||
procedure TPureSignalPopup.Apply(Sender: TObject);
|
||||
begin
|
||||
if FLoading or (FController = nil) then Exit;
|
||||
FController.FTXSettings.PSHWPeak := FSePk.Value;
|
||||
FController.FTXSettings.PSMoxDelay := FSeMoxWait.Value;
|
||||
FController.FTXSettings.PSLoopDelay := FSeCalWait.Value;
|
||||
FController.FTXSettings.PSTXDelayNs := FSePhNum.Value;
|
||||
FController.FTXSettings.PSRelaxPtol := FChkRelax.Checked;
|
||||
FController.FTXSettings.PSAutoAtten := FChkAutoAtt.Checked;
|
||||
FController.FTXSettings.PSPin := FChkPin.Checked;
|
||||
FController.FTXSettings.PSMap := FChkMap.Checked;
|
||||
FController.FTXSettings.PSStabilize := FChkStbl.Checked;
|
||||
FController.FTXSettings.PSIntsIdx := EnsureRange(FCbInts.ItemIndex, 0, 2);
|
||||
FController.FTXSettings.PSTTFreq1 := FSeTT1.Value;
|
||||
FController.FTXSettings.PSTTFreq2 := FSeTT2.Value;
|
||||
FController.ApplyPSSettingsToDSP;
|
||||
if Assigned(FOnChanged) then FOnChanged(Self);
|
||||
end;
|
||||
|
||||
procedure TPureSignalPopup.PSClick(Sender: TObject);
|
||||
begin
|
||||
if FController = nil then Exit;
|
||||
FController.SetPureSignal(not FController.FPSEnabled);
|
||||
RefreshStatus;
|
||||
end;
|
||||
|
||||
procedure TPureSignalPopup.CalClick(Sender: TObject);
|
||||
begin
|
||||
if FController = nil then Exit;
|
||||
FController.PSSingleCal;
|
||||
RefreshStatus;
|
||||
end;
|
||||
|
||||
procedure TPureSignalPopup.ResetClick(Sender: TObject);
|
||||
begin
|
||||
if FController = nil then Exit;
|
||||
FController.PSReset;
|
||||
RefreshStatus;
|
||||
end;
|
||||
|
||||
procedure TPureSignalPopup.TwoToneClick(Sender: TObject);
|
||||
begin
|
||||
if FController = nil then Exit;
|
||||
FController.SetTwoTone(not FController.FTwoTone);
|
||||
RefreshStatus;
|
||||
end;
|
||||
|
||||
procedure TPureSignalPopup.DefPkClick(Sender: TObject);
|
||||
// Дефолт SetPk для протокола 2 (Thetis clsHardwareSpecific.PSDefaultPeak).
|
||||
begin
|
||||
FSePk.Value := 0.2899; // OnChange → Apply
|
||||
end;
|
||||
|
||||
procedure TPureSignalPopup.CloseClick(Sender: TObject);
|
||||
begin
|
||||
Hide;
|
||||
end;
|
||||
|
||||
procedure TPureSignalPopup.RefreshStatus;
|
||||
var
|
||||
FB: Integer;
|
||||
LampClr: TColor;
|
||||
begin
|
||||
if (FController = nil) or not Visible then Exit;
|
||||
|
||||
FBtnPS.Active := FController.FPSEnabled;
|
||||
if FController.FPSEnabled then FBtnPS.Caption := 'PS ON'
|
||||
else FBtnPS.Caption := 'PS OFF';
|
||||
FBtnCal.Active := FController.PSSingleCalActive;
|
||||
FBtn2Ton.Active := FController.FTwoTone;
|
||||
|
||||
FB := FController.PSFeedbackLevel;
|
||||
if FController.FPSFeedbackOn then
|
||||
begin
|
||||
if FB > 181 then LampClr := CLR_FB_HOT
|
||||
else if FB > 128 then LampClr := CLR_FB_OK
|
||||
else if FB > 90 then LampClr := CLR_FB_LOW
|
||||
else LampClr := CLR_FB_NONE;
|
||||
end
|
||||
else
|
||||
LampClr := CLR_LAMP_OFF;
|
||||
if FFbLamp.Color <> LampClr then FFbLamp.Color := LampClr;
|
||||
FLbFbVal.Caption := Format('FB: %d', [FB]);
|
||||
|
||||
if FController.PSCorrecting then
|
||||
begin
|
||||
if FCorrLamp.Color <> CLR_FB_OK then FCorrLamp.Color := CLR_FB_OK;
|
||||
end
|
||||
else if FCorrLamp.Color <> CLR_LAMP_OFF then
|
||||
FCorrLamp.Color := CLR_LAMP_OFF;
|
||||
|
||||
FLbCals.Caption := Format('cals: %d', [FController.PSCalCount]);
|
||||
FLbAtt.Caption := Format('TX ATT: %d dB', [FController.FTXSettings.AttOnTX]);
|
||||
FLbGetPk.Caption := Format('GetPk: %.3f', [FController.PSMaxTXVal]);
|
||||
end;
|
||||
|
||||
procedure TPureSignalPopup.PopupBelow(C: TRadioController; Anchor: TControl);
|
||||
var
|
||||
Root: TCustomForm;
|
||||
L, T: Integer;
|
||||
Cl: TPoint;
|
||||
begin
|
||||
if Anchor = nil then Exit;
|
||||
Root := GetParentForm(Anchor);
|
||||
if Root = nil then Exit;
|
||||
LoadFrom(C);
|
||||
// Позиция под якорем в КЛИЕНТСКИХ координатах формы (не top-level окно!).
|
||||
Cl := Root.ScreenToClient(Anchor.ClientToScreen(Point(0, Anchor.Height + 2)));
|
||||
L := Cl.X;
|
||||
T := Cl.Y;
|
||||
if L + Width > Root.ClientWidth then L := Root.ClientWidth - Width;
|
||||
if L < 0 then L := 0;
|
||||
if T + Height > Root.ClientHeight then
|
||||
T := Root.ScreenToClient(Anchor.ClientToScreen(Point(0, 0))).Y - Height; // вверх
|
||||
if T < 0 then T := 0;
|
||||
Parent := Root;
|
||||
SetBounds(L, T, Width, Height);
|
||||
Visible := True;
|
||||
BringToFront;
|
||||
RefreshStatus;
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -56,6 +56,10 @@ type
|
||||
// парами SMA (LibreSDR/AntSDR/Pluto+). Гейтит блок «Antenna» в настройках;
|
||||
// у openHPSDR антеннами рулит Alex, поэтому False. Достоверно после Connect.
|
||||
HasAntSwitch: Boolean;
|
||||
// PureSignal-предыскажение: openHPSDR P2 платы со свободными DDC0/1 под
|
||||
// feedback (Angelia/Orion/Orion2/Saturn). Гейтит кнопки PS/2TON.
|
||||
// Достоверно после Connect.
|
||||
HasPureSignal: Boolean;
|
||||
MinSampleRate: Integer;
|
||||
RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay
|
||||
MinFreqHz: Double;
|
||||
@@ -142,6 +146,14 @@ type
|
||||
// У бэкендов без переключателя — no-op.
|
||||
procedure SetAntPorts(RxPort, TxPort: Byte); virtual;
|
||||
|
||||
// ---- PureSignal (openHPSDR P2, платы с DDC0/1 под feedback) ----
|
||||
// SetPureSignal — фича вооружена (кнопка PS в UI). SetPureSignalTX — фаза
|
||||
// PTT: на True бэкенд включает feedback-DDC0 (+sync DDC1 = TX DAC) на 192к
|
||||
// и ставит их частоты = TX; на False возвращает RX-конфигурацию.
|
||||
// База — no-op (Pluto и др. без PS-тракта).
|
||||
procedure SetPureSignal(Enabled: Boolean); virtual;
|
||||
procedure SetPureSignalTX(Active: Boolean); virtual;
|
||||
|
||||
// ---- Частота / drive / TX-состояние ----
|
||||
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
||||
DriveLevel: Byte = 100); virtual; abstract;
|
||||
@@ -306,4 +318,13 @@ procedure TRadioBackend.SetAntPorts(RxPort, TxPort: Byte);
|
||||
begin
|
||||
end;
|
||||
|
||||
procedure TRadioBackend.SetPureSignal(Enabled: Boolean);
|
||||
// PureSignal есть только у openHPSDR P2 (THPSDRNetwork переопределяет).
|
||||
begin
|
||||
end;
|
||||
|
||||
procedure TRadioBackend.SetPureSignalTX(Active: Boolean);
|
||||
begin
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+377
-2
@@ -74,6 +74,7 @@ type
|
||||
rfCenterFreq, rfSpan, rfSampleRate, rfZoom,
|
||||
rfCTun, rfDuplex, rfRxMuteOnTx, // rfRxMuteOnTx: QO-100 self-monitor toggle
|
||||
rfRunning, rfConnected, rfTransmitting, rfTuning,
|
||||
rfPureSignal, rfTwoTone, // PureSignal вкл/статус; 2TON вкл/выкл
|
||||
rfNR, rfNB, rfSNB, rfANF,
|
||||
rfWfAGC, rfWfNF, rfDither, rfRandom,
|
||||
rfFMDeviation, rfFMCTCSS, rfFMCTCSSTone, rfFMSQ, rfFMSQLevel,
|
||||
@@ -289,6 +290,22 @@ type
|
||||
FRunning: Boolean;
|
||||
FTransmitting: Boolean;
|
||||
FTuning: Boolean;
|
||||
// ---- PureSignal ----
|
||||
FPSEnabled: Boolean; // авто-калибровка PS включена (кнопка PS)
|
||||
FPSSingleCal: Boolean; // запрошена одиночная калибровка
|
||||
FPSResetReq: Boolean; // запрошен сброс коррекции (OFF)
|
||||
FPSCmdState: Integer; // eCMDState Thetis (см. PureSignalTick)
|
||||
FPSAAState: Integer; // auto-attenuate state (0=monitor,1=set,2=restore)
|
||||
FPSAASaveAuto: Integer; // сохранённый automode на время смены атт.
|
||||
FPSAASaveSingle: Integer;
|
||||
FPSAADeltaDB: Integer;
|
||||
FPSInfo: array[0..15] of Integer; // кэш GetPSInfo (UI-поток)
|
||||
FPSInfoPrev: array[0..15] of Integer;
|
||||
FPSMaxTX: Double; // кэш GetPSMaxTX
|
||||
// Feedback-тракт вооружён (сеть DDC0/1 + pscc в движке). Пишет UI-поток
|
||||
// (PureSignalTick/PSSetFeedbackPath), читает сетевой (OnDDCIQ).
|
||||
FPSFeedbackOn: Boolean;
|
||||
FTwoTone: Boolean; // 2TON активен
|
||||
FSplitTxB: Boolean;
|
||||
FTxSliceId: Integer; // 0 = главный VFO (A/B split); >0 = слайс-источник TX
|
||||
FVfoLock: Boolean; // блокировка перестройки VFO (Andromeda Lock)
|
||||
@@ -721,6 +738,31 @@ type
|
||||
// Передача
|
||||
procedure SetMOX(On_: Boolean);
|
||||
procedure SetTune(On_: Boolean); // TUN
|
||||
|
||||
// ---- PureSignal ----
|
||||
// Доступность PS у активного бэкенда/платы (Caps.HasPureSignal).
|
||||
function PSAvailable: Boolean;
|
||||
// Вкл/выкл авто-калибровки PureSignal (кнопка PS). Вооружает feedback-тракт
|
||||
// (DDC0/1 на TX) и WDSP calcc automode; выкл = reset коррекции.
|
||||
procedure SetPureSignal(On_: Boolean);
|
||||
procedure PSSingleCal; // одиночная калибровка
|
||||
procedure PSReset; // выключить и сбросить коррекцию
|
||||
// Тик PS (~10 Гц, из UI-таймера): state machine команд (порт Thetis
|
||||
// PSForm timer1/timer2), опрос GetPSInfo, auto-attenuate через AttOnTX.
|
||||
procedure PureSignalTick;
|
||||
procedure ApplyPSSettingsToDSP; // FTXSettings.PS* → WDSP calcc
|
||||
// Вооружение/снятие feedback-тракта (сеть + движок), идемпотентно.
|
||||
procedure PSSetFeedbackPath(En: Boolean);
|
||||
// Статус для UI (кэш последнего PureSignalTick)
|
||||
function PSFeedbackLevel: Integer; // info[4], оптимум ~140-160
|
||||
function PSCalCount: Integer; // info[5]
|
||||
function PSCorrecting: Boolean; // info[14]=1 — коррекция применяется
|
||||
function PSMaxTXVal: Double; // GetPSMaxTX (пик TX для SetPk)
|
||||
function PSSingleCalActive: Boolean; // идёт одиночная калибровка
|
||||
|
||||
// 2TON: двухтональник (PostGen two-tone) + MOX — тест IMD / проверка PS.
|
||||
procedure SetTwoTone(On_: Boolean);
|
||||
|
||||
procedure SetSplit(TxOnB: Boolean); // split: TX на VFO-B (иначе VFO-A)
|
||||
procedure SetVfoLock(On_: Boolean); // блокировка перестройки VFO
|
||||
|
||||
@@ -815,6 +857,16 @@ const
|
||||
// (TUNLevel), падение уровня IQ ~1.6дБ компенсируется калибровкой TUNLevel.
|
||||
TUN_TONE_MAG = 0.83;
|
||||
|
||||
// Состояния командной state machine PureSignal (порт Thetis eCMDState;
|
||||
// Restore-ветка (7) не реализована — Save/Restore коррекции нет).
|
||||
PS_ST_OFF = 0;
|
||||
PS_ST_TURNON_AUTO = 1;
|
||||
PS_ST_AUTOCAL = 2;
|
||||
PS_ST_TURNON_SINGLE = 3;
|
||||
PS_ST_SINGLECAL = 4;
|
||||
PS_ST_STAYON = 5;
|
||||
PS_ST_TURNOFF = 6;
|
||||
|
||||
constructor TDMRSliceDecoder.Create(AOwner: TRadioController;
|
||||
ASliceId: Integer);
|
||||
begin
|
||||
@@ -1065,13 +1117,20 @@ begin
|
||||
FDDCSeqValid[DDCIndex] := True;
|
||||
end;
|
||||
|
||||
// Подаём IQ в DSP: активный DDC → главный тракт, пан-DDC → его пан (3.2).
|
||||
// Подаём IQ в DSP: активный DDC → главный тракт, пан-DDC → его пан (3.2),
|
||||
// DDC0 при вооружённом PureSignal на TX → feedback в pscc (DDC0 несёт
|
||||
// interleaved пары DDC0+DDC1; на PS-платах главный DDC ≥ 2, конфликта нет).
|
||||
if FWDSPReady then
|
||||
begin
|
||||
SamplesPerFrame := (Integer(Data.SamplesPerFrame[0]) shl 8) or
|
||||
Integer(Data.SamplesPerFrame[1]);
|
||||
if SamplesPerFrame <= 0 then SamplesPerFrame := 238; // fallback
|
||||
if DDCIndex = FActiveDDC then
|
||||
if (DDCIndex = 0) and FPSFeedbackOn and (FActiveDDC <> 0) then
|
||||
begin
|
||||
if FTransmitting then
|
||||
FDSPEngine.PushPSFeedback(Data.IQData, 0, SamplesPerFrame);
|
||||
end
|
||||
else if DDCIndex = FActiveDDC then
|
||||
FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame)
|
||||
else if (DDCIndex >= 0) and (DDCIndex < MAX_DDCS)
|
||||
and (FDDCPanMap[DDCIndex] > 0) then
|
||||
@@ -4063,6 +4122,8 @@ begin
|
||||
FDSPEngine.SetTXWaterfallDisplay(FTXSettings.TXWfDetector,
|
||||
FTXSettings.TXWfAvgMode,
|
||||
FTXSettings.TXWfAvgTimeMS);
|
||||
// PureSignal-параметры calcc (mox/loop/tx delay, ptol, pin/map/stbl, SetPk)
|
||||
ApplyPSSettingsToDSP;
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetMOX(On_: Boolean);
|
||||
@@ -4081,6 +4142,14 @@ begin
|
||||
Exit;
|
||||
WasTransmitting := FTransmitting;
|
||||
FTransmitting := On_;
|
||||
// 2TON: прямое снятие MOX (кнопкой/CAT/web) гасит и двухтональник.
|
||||
if (not On_) and FTwoTone then
|
||||
begin
|
||||
FTwoTone := False;
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
FDSPEngine.SetTXTwoTone(False, 0, 0);
|
||||
Changed(rfTwoTone);
|
||||
end;
|
||||
if On_ then
|
||||
begin
|
||||
FDriveLevel := CalcDriveByte;
|
||||
@@ -4096,6 +4165,9 @@ begin
|
||||
FDriveLevel, True, True, True);
|
||||
FNetwork.SendFullHP; // 1й пакет: Alex bit27=1 (T/R relay), drive — без PTT
|
||||
FNetwork.SendPTT(True); // 2й пакет: PTT=1 после переключения реле
|
||||
// PureSignal: перевести feedback-DDC0/1 в TX-конфигурацию (частоты = TX,
|
||||
// sync, Alex bit18). No-op если PS не вооружён (гейт внутри бэкенда).
|
||||
FNetwork.SetPureSignalTX(True);
|
||||
// Pre-roll: THROTTLE (2000, порог FPGA — DAC не стартует с пустого буфера)
|
||||
// + ДВА WDSP-блока (2×2048) нулевых пар подушкой в очередь. Раньше было 3
|
||||
// блока: обоснование «2 не хватало — щелчок через ~0.5с» теперь под вопросом,
|
||||
@@ -4140,6 +4212,8 @@ begin
|
||||
// Мультислайс-TX: передающий тракт под режим слайса-источника (SetMode мог
|
||||
// оставить главный режим). Ставим перед стартом TX-потока.
|
||||
if FTransmitting then FDSPEngine.SetTXMode(ActiveTXMode);
|
||||
// PureSignal: mox-флаг calcc (гейтит сбор данных калибровки в pscc).
|
||||
FDSPEngine.PSSetMox(FTransmitting);
|
||||
FDSPEngine.SetTXRun(FTransmitting);
|
||||
if WasTransmitting and (not FTransmitting) and (not FDisplayDuplex) then
|
||||
begin
|
||||
@@ -4161,6 +4235,8 @@ begin
|
||||
if (not FTransmitting) and Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then
|
||||
begin
|
||||
FNetwork.SendPTT(False); // 1й пакет: PTT=0 — сначала снимаем PTT
|
||||
// PureSignal: вернуть DDC0/1 в RX-конфигурацию (убрать feedback-DDC).
|
||||
FNetwork.SetPureSignalTX(False);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz),
|
||||
FDriveLevel, False, True, True);
|
||||
FNetwork.ClearDUCIQQueue;
|
||||
@@ -4181,6 +4257,7 @@ begin
|
||||
and FAlexSettings.DoNotTx[EnsureRange(TxBandIdx, 0, 10)] then Exit;
|
||||
if On_ and (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT)
|
||||
and FXvtrSettings.Entries[FCurrentXvtr].RXOnly then Exit;
|
||||
if On_ and FTwoTone then SetTwoTone(False); // TUN поверх 2TON — гасим тона
|
||||
if On_ then
|
||||
begin
|
||||
// Тон включаем ДО запуска TX-аудио, чтобы первые сэмплы уже шли с тоном.
|
||||
@@ -4210,6 +4287,285 @@ begin
|
||||
Changed(rfTuning);
|
||||
end;
|
||||
|
||||
// ===========================================================================
|
||||
// PureSignal — оркестрация (порт state machine Thetis PSForm timer1/timer2)
|
||||
// ===========================================================================
|
||||
|
||||
function TRadioController.PSAvailable: Boolean;
|
||||
begin
|
||||
Result := FDevConnected and BackendCaps.HasPureSignal;
|
||||
end;
|
||||
|
||||
procedure TRadioController.PSSetFeedbackPath(En: Boolean);
|
||||
// Единая точка вкл/выкл feedback-тракта: сеть (DDC0/1 на TX) + движок (pscc).
|
||||
// Намеренно БЕЗ раннего выхода при том же значении: движок сбрасывает armed
|
||||
// при Close (ChangeSampleRate), сеть гейтится внутри — повторный вызов с тем
|
||||
// же значением дёшев и восстанавливает рассинхрон.
|
||||
begin
|
||||
FPSFeedbackOn := En;
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
FDSPEngine.SetPureSignalArmed(En);
|
||||
if Assigned(FNetwork) then
|
||||
begin
|
||||
FNetwork.SetPureSignal(En);
|
||||
// Включение/выключение посреди передачи — сразу перестроить DDC.
|
||||
if FTransmitting then FNetwork.SetPureSignalTX(En);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetPureSignal(On_: Boolean);
|
||||
// Кнопка PS = режим авто-калибровки (Thetis AutoCalEnabled). Выключение —
|
||||
// через запрос сброса: state machine выполнит reset коррекции и погасит тракт.
|
||||
begin
|
||||
if On_ = FPSEnabled then Exit;
|
||||
if On_ and not PSAvailable then Exit;
|
||||
FPSEnabled := On_;
|
||||
if On_ then
|
||||
begin
|
||||
ApplyPSSettingsToDSP; // параметры calcc до старта калибровки
|
||||
FPSResetReq := False;
|
||||
end
|
||||
else
|
||||
FPSResetReq := True;
|
||||
Changed(rfPureSignal);
|
||||
end;
|
||||
|
||||
procedure TRadioController.PSSingleCal;
|
||||
// Одиночная калибровка (Thetis btnPSCalibrate): авто-режим выключается.
|
||||
begin
|
||||
if not PSAvailable then Exit;
|
||||
if FPSSingleCal then begin FPSSingleCal := False; Exit; end; // отмена
|
||||
ApplyPSSettingsToDSP;
|
||||
FPSEnabled := False;
|
||||
FPSSingleCal := True;
|
||||
Changed(rfPureSignal);
|
||||
end;
|
||||
|
||||
procedure TRadioController.PSReset;
|
||||
begin
|
||||
FPSEnabled := False;
|
||||
FPSSingleCal := False;
|
||||
FPSResetReq := True;
|
||||
Changed(rfPureSignal);
|
||||
end;
|
||||
|
||||
procedure TRadioController.ApplyPSSettingsToDSP;
|
||||
var
|
||||
Ints, Spi: Integer;
|
||||
begin
|
||||
if not (FWDSPReady and Assigned(FDSPEngine)) then Exit;
|
||||
case FTXSettings.PSIntsIdx of
|
||||
1: begin Ints := 8; Spi := 512; end;
|
||||
2: begin Ints := 4; Spi := 1024; end;
|
||||
else begin Ints := 16; Spi := 256; end;
|
||||
end;
|
||||
FDSPEngine.PSSetParams(FTXSettings.PSMoxDelay, FTXSettings.PSLoopDelay,
|
||||
FTXSettings.PSTXDelayNs, FTXSettings.PSRelaxPtol,
|
||||
FTXSettings.PSPin, FTXSettings.PSMap,
|
||||
FTXSettings.PSStabilize, Ints, Spi,
|
||||
FTXSettings.PSHWPeak);
|
||||
end;
|
||||
|
||||
function TRadioController.PSFeedbackLevel: Integer;
|
||||
begin
|
||||
Result := FPSInfo[4];
|
||||
end;
|
||||
|
||||
function TRadioController.PSCalCount: Integer;
|
||||
begin
|
||||
Result := FPSInfo[5];
|
||||
end;
|
||||
|
||||
function TRadioController.PSCorrecting: Boolean;
|
||||
begin
|
||||
Result := FPSInfo[14] = 1;
|
||||
end;
|
||||
|
||||
function TRadioController.PSMaxTXVal: Double;
|
||||
begin
|
||||
Result := FPSMaxTX;
|
||||
end;
|
||||
|
||||
function TRadioController.PSSingleCalActive: Boolean;
|
||||
begin
|
||||
Result := FPSSingleCal
|
||||
or (FPSCmdState in [PS_ST_TURNON_SINGLE, PS_ST_SINGLECAL]);
|
||||
end;
|
||||
|
||||
procedure TRadioController.PureSignalTick;
|
||||
// Вызывается из UI-таймера (~10 Гц). Командная state machine — порт Thetis
|
||||
// PSForm timer1code (без Restore-ветки: Save/Restore коррекции не реализованы)
|
||||
// + auto-attenuate (timer2code): подстройка AttOnTX по уровню feedback.
|
||||
var
|
||||
NewAtt: Integer;
|
||||
ddB: Double;
|
||||
CalAttemptsChanged: Boolean;
|
||||
begin
|
||||
if not (FWDSPReady and Assigned(FDSPEngine)) then Exit;
|
||||
if not PSAvailable then
|
||||
begin
|
||||
// Устройство сменилось/отключилось: гарантированно всё погасить.
|
||||
if FPSFeedbackOn then PSSetFeedbackPath(False);
|
||||
FPSCmdState := PS_ST_OFF;
|
||||
Exit;
|
||||
end;
|
||||
// Полный простой (PS ни разу не включался / выключен и сброшен) — не дёргаем
|
||||
// WDSP каждые 100 мс впустую.
|
||||
if (FPSCmdState = PS_ST_OFF) and not (FPSEnabled or FPSSingleCal
|
||||
or FPSResetReq or FPSFeedbackOn) then Exit;
|
||||
|
||||
// Опрос статуса WDSP calcc
|
||||
Move(FPSInfo[0], FPSInfoPrev[0], SizeOf(FPSInfo));
|
||||
FDSPEngine.PSGetInfo(FPSInfo);
|
||||
FPSMaxTX := FDSPEngine.PSGetMaxTX;
|
||||
|
||||
// --- Командная state machine ---
|
||||
case FPSCmdState of
|
||||
PS_ST_OFF:
|
||||
begin
|
||||
FDSPEngine.PSControl(1, 0, 0, 0);
|
||||
PSSetFeedbackPath(False);
|
||||
FPSResetReq := False;
|
||||
if FPSEnabled then
|
||||
FPSCmdState := PS_ST_TURNON_AUTO
|
||||
else if FPSSingleCal then
|
||||
FPSCmdState := PS_ST_TURNON_SINGLE;
|
||||
end;
|
||||
PS_ST_TURNON_AUTO:
|
||||
begin
|
||||
FDSPEngine.PSControl(1, 0, 1, 0);
|
||||
PSSetFeedbackPath(True);
|
||||
FPSCmdState := PS_ST_AUTOCAL;
|
||||
end;
|
||||
PS_ST_AUTOCAL:
|
||||
begin
|
||||
PSSetFeedbackPath(True); // ре-синк (движок мог переоткрыться)
|
||||
if FPSResetReq or (not FPSEnabled) then
|
||||
FPSCmdState := PS_ST_TURNOFF
|
||||
else if FPSSingleCal then
|
||||
FPSCmdState := PS_ST_TURNON_SINGLE;
|
||||
end;
|
||||
PS_ST_TURNON_SINGLE:
|
||||
begin
|
||||
FDSPEngine.PSControl(1, 1, 0, 0);
|
||||
PSSetFeedbackPath(True);
|
||||
FPSCmdState := PS_ST_SINGLECAL;
|
||||
end;
|
||||
PS_ST_SINGLECAL:
|
||||
begin
|
||||
PSSetFeedbackPath(True); // ре-синк (движок мог переоткрыться)
|
||||
FPSSingleCal := False;
|
||||
if FPSResetReq then
|
||||
FPSCmdState := PS_ST_TURNOFF
|
||||
else if FPSEnabled then
|
||||
FPSCmdState := PS_ST_TURNON_AUTO
|
||||
else if PSCorrecting then
|
||||
FPSCmdState := PS_ST_STAYON;
|
||||
end;
|
||||
PS_ST_STAYON:
|
||||
begin
|
||||
// Коррекция применяется, калибровка остановлена, feedback не нужен.
|
||||
PSSetFeedbackPath(False);
|
||||
if FPSResetReq then
|
||||
FPSCmdState := PS_ST_TURNOFF
|
||||
else if FPSEnabled then
|
||||
FPSCmdState := PS_ST_TURNON_AUTO
|
||||
else if FPSSingleCal then
|
||||
FPSCmdState := PS_ST_TURNON_SINGLE;
|
||||
end;
|
||||
PS_ST_TURNOFF:
|
||||
begin
|
||||
// Reset исполняется внутри pscc — держим feedback-тракт, пока calcc
|
||||
// не дойдёт до LRESET (как Thetis: PSEnabled=true в состоянии TurnOFF).
|
||||
FDSPEngine.PSControl(1, 0, 0, 0);
|
||||
PSSetFeedbackPath(True);
|
||||
FPSResetReq := False;
|
||||
if FPSEnabled then
|
||||
FPSCmdState := PS_ST_TURNON_AUTO
|
||||
else if FPSSingleCal then
|
||||
FPSCmdState := PS_ST_TURNON_SINGLE
|
||||
else if (not PSCorrecting) and (FPSInfo[15] = 0) then // 0 = LRESET
|
||||
FPSCmdState := PS_ST_OFF;
|
||||
end;
|
||||
end;
|
||||
|
||||
// --- Auto-attenuate (Thetis timer2code) ---
|
||||
// По завершении очередной попытки калибровки: если feedback слишком горячий
|
||||
// (>181) или слабый при ненулевом атт. (<=128), подстроить AttOnTX так,
|
||||
// чтобы уровень лёг к оптимуму 152.
|
||||
case FPSAAState of
|
||||
0: begin
|
||||
CalAttemptsChanged := FPSInfo[5] <> FPSInfoPrev[5];
|
||||
if FTXSettings.PSAutoAtten and CalAttemptsChanged
|
||||
and ((FPSInfo[4] > 181) or
|
||||
((FPSInfo[4] <= 128) and (FTXSettings.AttOnTX > 0))) then
|
||||
begin
|
||||
FPSAAState := 1;
|
||||
if (FPSInfo[4] > 0) and (FPSInfo[4] <= 256) then
|
||||
begin
|
||||
ddB := 20.0 * Log10(FPSInfo[4] / 152.293);
|
||||
if ddB < -100.0 then ddB := -100.0;
|
||||
if ddB > +100.0 then ddB := +100.0;
|
||||
end
|
||||
else
|
||||
ddB := 31.1;
|
||||
FPSAADeltaDB := Round(ddB);
|
||||
FPSAASaveAuto := Ord(FPSCmdState = PS_ST_AUTOCAL);
|
||||
FPSAASaveSingle := Ord(FPSCmdState = PS_ST_SINGLECAL);
|
||||
FDSPEngine.PSControl(1, 0, 0, 0); // всё стоп + reset на время смены
|
||||
end;
|
||||
end;
|
||||
1: begin
|
||||
FPSAAState := 2;
|
||||
NewAtt := EnsureRange(FTXSettings.AttOnTX + FPSAADeltaDB, 0, 31);
|
||||
if NewAtt <> FTXSettings.AttOnTX then
|
||||
begin
|
||||
FTXSettings.AttOnTX := NewAtt;
|
||||
SendDUCSpecificFromSettings; // применить атт. немедленно
|
||||
Changed(rfPureSignal); // UI: показать новый ATT
|
||||
end;
|
||||
end;
|
||||
2: begin
|
||||
FPSAAState := 0;
|
||||
FDSPEngine.PSControl(0, FPSAASaveSingle, FPSAASaveAuto, 0);
|
||||
end;
|
||||
end;
|
||||
|
||||
// UI-статус (feedback-лампа/счётчик) обновляем при изменении info
|
||||
if not CompareMem(@FPSInfo[0], @FPSInfoPrev[0], SizeOf(FPSInfo)) then
|
||||
Changed(rfPureSignal);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetTwoTone(On_: Boolean);
|
||||
// 2TON: двухтональник PostGen + MOX (как Thetis chkTestIMD). Уровень — обычный
|
||||
// Drive; тона PSTTFreq1/2 (700/1900 по умолчанию).
|
||||
begin
|
||||
if On_ = FTwoTone then Exit;
|
||||
if On_ and (ActiveTXMode = MODE_DMR) then Exit;
|
||||
if On_ and FTuning then Exit; // не поверх TUN-тона
|
||||
// Safety: те же блокировки, что SetMOX/SetTune.
|
||||
if On_ and FDevConnected and (FCurrentXvtr < 0)
|
||||
and FAlexSettings.DoNotTx[EnsureRange(TxBandIdx, 0, 10)] then Exit;
|
||||
if On_ and (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT)
|
||||
and FXvtrSettings.Entries[FCurrentXvtr].RXOnly then Exit;
|
||||
if On_ then
|
||||
begin
|
||||
// Генератор ДО запуска TX-тракта — первые блоки уже с тонами.
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
FDSPEngine.SetTXTwoTone(True, FTXSettings.PSTTFreq1, FTXSettings.PSTTFreq2);
|
||||
FTwoTone := True;
|
||||
if not FTransmitting then SetMOX(True);
|
||||
end
|
||||
else
|
||||
begin
|
||||
FTwoTone := False;
|
||||
if FTransmitting then SetMOX(False);
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
FDSPEngine.SetTXTwoTone(False, 0, 0);
|
||||
end;
|
||||
Changed(rfTwoTone);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetFMStep(Idx: Integer);
|
||||
begin
|
||||
if Idx < 0 then Idx := 0 else if Idx > 3 then Idx := 3; // FM_STEP_COUNT = 4
|
||||
@@ -4441,6 +4797,25 @@ begin
|
||||
if FWDSPReady then FDSPEngine.SetTXTone(False, 0, 0);
|
||||
Changed(rfTuning); // UI: TUN-кнопка off
|
||||
end;
|
||||
if FTwoTone then
|
||||
begin
|
||||
FTwoTone := False;
|
||||
if FWDSPReady then FDSPEngine.SetTXTwoTone(False, 0, 0);
|
||||
Changed(rfTwoTone); // UI: 2TON-кнопка off
|
||||
end;
|
||||
// PureSignal: погасить калибровку и feedback-тракт (без сброса коррекции —
|
||||
// сеть уже останавливается; state machine стартует заново при след. Run).
|
||||
if FPSEnabled or FPSSingleCal or FPSFeedbackOn then
|
||||
begin
|
||||
FPSEnabled := False;
|
||||
FPSSingleCal := False;
|
||||
FPSResetReq := False;
|
||||
FPSCmdState := PS_ST_OFF;
|
||||
FPSAAState := 0;
|
||||
PSSetFeedbackPath(False);
|
||||
if FWDSPReady then FDSPEngine.PSControl(1, 0, 0, 0);
|
||||
Changed(rfPureSignal);
|
||||
end;
|
||||
if FWDSPReady then FDSPEngine.SetTXRun(False);
|
||||
Changed(rfTransmitting); // MOX-кнопка off
|
||||
end;
|
||||
|
||||
@@ -209,6 +209,19 @@ type
|
||||
TXSpecRefLevel: Double; // dBm reference (top of scale), e.g. 0
|
||||
TXSpecRange: Double; // dB range, e.g. 80
|
||||
TXSpecGridStep: Double; // grid step in dB, e.g. 10
|
||||
// PureSignal (WDSP calcc; дефолты = Thetis PSForm, протокол 2)
|
||||
PSMoxDelay: Double; // с — задержка после MOX до сбора feedback (0.2)
|
||||
PSLoopDelay: Double; // с — пауза между итерациями калибровки (0)
|
||||
PSTXDelayNs: Integer; // нс — компенсация задержки TX-тракта (150)
|
||||
PSRelaxPtol: Boolean; // ослабленный допуск фазы (ptol 0.4 вместо 0.8)
|
||||
PSAutoAtten: Boolean; // авто-подстройка AttOnTX по уровню feedback
|
||||
PSPin: Boolean; // pin mode (True)
|
||||
PSMap: Boolean; // map mode (True)
|
||||
PSStabilize: Boolean; // stabilize (False)
|
||||
PSIntsIdx: Integer; // 0=16/256, 1=8/512, 2=4/1024
|
||||
PSHWPeak: Double; // SetPk (0.2899 для P2, 0.6121 Saturn)
|
||||
PSTTFreq1: Integer; // Гц — тон 1 двухтональника (700)
|
||||
PSTTFreq2: Integer; // Гц — тон 2 двухтональника (1900)
|
||||
end;
|
||||
|
||||
// Web-сервер — глобальные настройки (не привязаны к устройству, секция "web").
|
||||
@@ -1170,6 +1183,19 @@ begin
|
||||
T.TXSpecRefLevel := 0.0;
|
||||
T.TXSpecRange := 80.0;
|
||||
T.TXSpecGridStep := 10.0;
|
||||
// PureSignal — дефолты Thetis PSForm (протокол 2)
|
||||
T.PSMoxDelay := 0.2;
|
||||
T.PSLoopDelay := 0.0;
|
||||
T.PSTXDelayNs := 150;
|
||||
T.PSRelaxPtol := False;
|
||||
T.PSAutoAtten := True;
|
||||
T.PSPin := True;
|
||||
T.PSMap := True;
|
||||
T.PSStabilize := False;
|
||||
T.PSIntsIdx := 0; // 16 ints / 256 spi
|
||||
T.PSHWPeak := 0.2899;
|
||||
T.PSTTFreq1 := 700;
|
||||
T.PSTTFreq2 := 1900;
|
||||
end;
|
||||
|
||||
function TSettingsManager.LoadTX(const MAC: array of Byte;
|
||||
@@ -1236,6 +1262,19 @@ begin
|
||||
T.TXSpecRefLevel := JD(O,'tx_spec_ref_level', T.TXSpecRefLevel);
|
||||
T.TXSpecRange := JD(O,'tx_spec_range', T.TXSpecRange);
|
||||
T.TXSpecGridStep := JD(O,'tx_spec_grid_step', T.TXSpecGridStep);
|
||||
// PureSignal
|
||||
T.PSMoxDelay := JD(O,'ps_mox_delay', T.PSMoxDelay);
|
||||
T.PSLoopDelay := JD(O,'ps_loop_delay', T.PSLoopDelay);
|
||||
T.PSTXDelayNs := JI(O,'ps_tx_delay_ns', T.PSTXDelayNs);
|
||||
T.PSRelaxPtol := JB(O,'ps_relax_ptol', T.PSRelaxPtol);
|
||||
T.PSAutoAtten := JB(O,'ps_auto_atten', T.PSAutoAtten);
|
||||
T.PSPin := JB(O,'ps_pin', T.PSPin);
|
||||
T.PSMap := JB(O,'ps_map', T.PSMap);
|
||||
T.PSStabilize := JB(O,'ps_stabilize', T.PSStabilize);
|
||||
T.PSIntsIdx := EnsureRange(JI(O,'ps_ints_idx', T.PSIntsIdx), 0, 2);
|
||||
T.PSHWPeak := JD(O,'ps_hw_peak', T.PSHWPeak);
|
||||
T.PSTTFreq1 := JI(O,'ps_tt_freq1', T.PSTTFreq1);
|
||||
T.PSTTFreq2 := JI(O,'ps_tt_freq2', T.PSTTFreq2);
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveTX(const MAC: array of Byte;
|
||||
@@ -1294,6 +1333,19 @@ begin
|
||||
JW(O,'tx_spec_ref_level', T.TXSpecRefLevel);
|
||||
JW(O,'tx_spec_range', T.TXSpecRange);
|
||||
JW(O,'tx_spec_grid_step', T.TXSpecGridStep);
|
||||
// PureSignal
|
||||
JW(O,'ps_mox_delay', T.PSMoxDelay);
|
||||
JW(O,'ps_loop_delay', T.PSLoopDelay);
|
||||
JW(O,'ps_tx_delay_ns', T.PSTXDelayNs);
|
||||
JW(O,'ps_relax_ptol', T.PSRelaxPtol);
|
||||
JW(O,'ps_auto_atten', T.PSAutoAtten);
|
||||
JW(O,'ps_pin', T.PSPin);
|
||||
JW(O,'ps_map', T.PSMap);
|
||||
JW(O,'ps_stabilize', T.PSStabilize);
|
||||
JW(O,'ps_ints_idx', T.PSIntsIdx);
|
||||
JW(O,'ps_hw_peak', T.PSHWPeak);
|
||||
JW(O,'ps_tt_freq1', T.PSTTFreq1);
|
||||
JW(O,'ps_tt_freq2', T.PSTTFreq2);
|
||||
end;
|
||||
|
||||
class procedure TSettingsManager.DefaultAlex(out A: TAlexSettings);
|
||||
|
||||
+209
-2
@@ -375,6 +375,31 @@ type
|
||||
// PostGen стоит ПОСЛЕ ALC — голос из микрофона складывался бы с тоном
|
||||
// поверх полной шкалы (тон ~0.98) → клип 24-бит → сплэттер на спектре.
|
||||
FTXToneOn: Boolean;
|
||||
// 2TON активен (PostGen two-tone 700/1900 Гц для IMD/PureSignal-теста):
|
||||
// mic-вход глушится так же, как при TUN.
|
||||
FTXTTOn: Boolean;
|
||||
|
||||
// ---- PureSignal ----
|
||||
// FPSArmed: PS включён контроллером — PushPSFeedback подаёт feedback в
|
||||
// pscc (WDSP calcc). Пишется из UI-потока, читается в сетевом.
|
||||
FPSArmed: Boolean;
|
||||
// Деинтерливленные буферы feedback-пакета: interleaved I/Q пары каждого
|
||||
// потока (≤119 пар на поток при 238 парах в пакете; 256 пар с запасом).
|
||||
// Используются только сетевым потоком.
|
||||
FPSTxBuf: array[0..511] of Double; // DDC1 = TX DAC (чистый эталон)
|
||||
FPSRxBuf: array[0..511] of Double; // DDC0 = RX feedback (после PA)
|
||||
// Кэш PS-параметров: применяются в ApplyTXChainSettings (т.е. и после
|
||||
// каждого пересоздания TXA-канала в Open/ChangeSampleRate).
|
||||
FPSMoxDelay: Double;
|
||||
FPSLoopDelay: Double;
|
||||
FPSTXDelayNs: Integer;
|
||||
FPSRelaxPtol: Boolean;
|
||||
FPSPin: Boolean;
|
||||
FPSMap: Boolean;
|
||||
FPSStabilize: Boolean;
|
||||
FPSInts: Integer;
|
||||
FPSSpi: Integer;
|
||||
FPSHWPeak: Double;
|
||||
|
||||
// Кэшированные TX-параметры — применяются после OpenChannel(TXA) и при изменении.
|
||||
// Для Filter/Compressor/Leveler/ALC/PhaseRot/EQ/AM/FM/CTCSS.
|
||||
@@ -501,6 +526,7 @@ type
|
||||
procedure ProcessTXBlock; // вызывается из TTXDSPThread
|
||||
procedure FeedTXDisplaySample(const I, Q: Double);
|
||||
procedure ApplyTXChainSettings; // прокидывает все FTX* поля в WDSP
|
||||
procedure ApplyPSParams; // прокидывает FPS*-параметры в WDSP calcc
|
||||
procedure ApplyRXAnalyzerSettings; // применяет RX FFT/Window/Det/Avg к RX_DISP_ID
|
||||
procedure ApplyTXAnalyzerSettings; // применяет TX FFT/Window/Det/Avg к TX_DISP_ID
|
||||
|
||||
@@ -606,6 +632,29 @@ type
|
||||
// TUN: WDSP TXA PostGen — гонит чистый тон в DSP-цепь после всей
|
||||
// обработки. Mag — амплитуда в [0..1], Freq — Hz (audio).
|
||||
procedure SetTXTone(Enabled: Boolean; FreqHz, Mag: Double);
|
||||
// 2TON: PostGen two-tone (IMD-тест / калибровка PureSignal).
|
||||
// Тона Freq1/Freq2 Гц (для LSB/CWL/DIGL знак инвертируется), амплитуды
|
||||
// по 0.49 (сумма чуть ниже полной шкалы, как Thetis/pihpsdr).
|
||||
procedure SetTXTwoTone(Enabled: Boolean; Freq1, Freq2: Double);
|
||||
|
||||
// ---- PureSignal (WDSP calcc на TXA_CHAN) ----
|
||||
// Армирование подачи feedback в pscc (кнопка PS). Сама калибровка
|
||||
// управляется PSControl (state machine в контроллере).
|
||||
procedure SetPureSignalArmed(Armed: Boolean);
|
||||
// Feedback-пакет синхронизированного DDC0 (interleaved: пара DDC0 =
|
||||
// RX-feedback, пара DDC1 = TX DAC, 24-bit BE). Вызывается из СЕТЕВОГО
|
||||
// потока (как Thetis/pihpsdr — pscc потокобезопасен относительно
|
||||
// fexchange0 TX-потока). TotalPairs — всего IQ-пар в пакете (оба потока).
|
||||
procedure PushPSFeedback(const Buf: array of Byte; DataOffset: Integer;
|
||||
TotalPairs: Integer);
|
||||
// Тонкие врапперы WDSP PS API (все на TXA_CHAN, guard FInitialized):
|
||||
procedure PSControl(Reset, Mancal, Automode, Turnon: Integer);
|
||||
procedure PSSetMox(Mox: Boolean);
|
||||
procedure PSGetInfo(var Info: array of Integer); // 16 int
|
||||
function PSGetMaxTX: Double;
|
||||
procedure PSSetParams(MoxDelay, LoopDelay: Double; TXDelayNs: Integer;
|
||||
RelaxPtol: Boolean; Pin, Map, Stabilize: Boolean;
|
||||
Ints, Spi: Integer; HWPeak: Double);
|
||||
|
||||
// Расширенное TX управление (применяются мгновенно если канал открыт)
|
||||
procedure SetTXFilterFull(Low, High, NC, MP, Window: Integer);
|
||||
@@ -1197,7 +1246,20 @@ begin
|
||||
FTXMicTail := 0;
|
||||
FTXActive := False;
|
||||
FTXToneOn := False;
|
||||
FTXTTOn := False;
|
||||
FKeepRXDuringTX := False;
|
||||
// PureSignal — дефолты как Thetis PSForm / clsHardwareSpecific (P2)
|
||||
FPSArmed := False;
|
||||
FPSMoxDelay := 0.2; // с — задержка после MOX до сбора данных
|
||||
FPSLoopDelay := 0.0; // с — пауза между итерациями калибровки
|
||||
FPSTXDelayNs := 150; // нс — компенсация задержки TX-тракта
|
||||
FPSRelaxPtol := False;
|
||||
FPSPin := True;
|
||||
FPSMap := True;
|
||||
FPSStabilize := False;
|
||||
FPSInts := 16;
|
||||
FPSSpi := 256;
|
||||
FPSHWPeak := 0.2899; // протокол 2 (Saturn: 0.6121)
|
||||
FMonitorRXDuringTX := False;
|
||||
FPostTXMuteUntil := 0;
|
||||
FActiveDisplayID := RX_DISP_ID;
|
||||
@@ -1661,6 +1723,10 @@ var
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
|
||||
// PureSignal: снять подачу feedback ДО закрытия TXA — pscc зовётся из
|
||||
// сетевого потока напрямую и не должен попасть в закрываемый канал.
|
||||
FPSArmed := False;
|
||||
|
||||
// Останавливаем DSP поток перед закрытием WDSP каналов
|
||||
if Assigned(FDSPThread) then
|
||||
begin
|
||||
@@ -3139,6 +3205,35 @@ begin
|
||||
SetTXAPostGenRun(TXA_CHAN, 0);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXTwoTone(Enabled: Boolean; Freq1, Freq2: Double);
|
||||
// 2TON: двухтональник PostGen (mode 1) для IMD-теста и проверки PureSignal.
|
||||
// Амплитуды 0.49+0.49 — сумма чуть ниже полной шкалы (как Thetis/pihpsdr).
|
||||
var
|
||||
F1, F2: Double;
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
FTXTTOn := Enabled; // мьют mic-входа (см. ProcessTXBlock)
|
||||
if Enabled then
|
||||
begin
|
||||
// Для нижней боковой тона должны лежать НИЖЕ несущей.
|
||||
if (FMode = MODE_LSB) or (FMode = MODE_CWL) or (FMode = MODE_DIGL) then
|
||||
begin
|
||||
F1 := -Abs(Freq1);
|
||||
F2 := -Abs(Freq2);
|
||||
end
|
||||
else
|
||||
begin
|
||||
F1 := Abs(Freq1);
|
||||
F2 := Abs(Freq2);
|
||||
end;
|
||||
SetTXAPostGenMode(TXA_CHAN, 1); // 1 = two-tone
|
||||
SetTXAPostGenTTFreq(TXA_CHAN, F1, F2);
|
||||
SetTXAPostGenTTMag(TXA_CHAN, 0.49, 0.49);
|
||||
SetTXAPostGenRun(TXA_CHAN, 1);
|
||||
end else
|
||||
SetTXAPostGenRun(TXA_CHAN, 0);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetDisplaySourceTX(UseTX: Boolean);
|
||||
begin
|
||||
// Тот же сброс позиций, что и в SetTXRun — analyzer-буферы у обоих DispID
|
||||
@@ -3179,9 +3274,9 @@ begin
|
||||
tail := (tail + 1) and (TX_MIC_RING - 1);
|
||||
end;
|
||||
FTXMicTail := tail; // Атомарно обновляем tail (один writer — TX thread)
|
||||
// TUN: ринг потребляем (иначе поток зациклится на «есть полный блок»),
|
||||
// TUN/2TON: ринг потребляем (иначе поток зациклится на «есть полный блок»),
|
||||
// но в TXA подаём тишину — тон PostGen должен идти без голоса поверх.
|
||||
if FTXToneOn then
|
||||
if FTXToneOn or FTXTTOn then
|
||||
FillChar(FTXIn[0], FAudioBufSize * 2 * SizeOf(Double), 0);
|
||||
end else
|
||||
begin
|
||||
@@ -3335,6 +3430,118 @@ begin
|
||||
// CTCSS
|
||||
SetTXACTCSSFreq(TXA_CHAN, FTXCTCSSFreq);
|
||||
SetTXACTCSSRun(TXA_CHAN, Ord(FTXCTCSSOn));
|
||||
|
||||
// PureSignal (calcc живёт внутри TXA-канала — после пересоздания канала
|
||||
// все его параметры надо прокинуть заново)
|
||||
ApplyPSParams;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyPSParams;
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
// Feedback приходит с DDC0/1 @ 192 кС/с (фиксированный PS-rate протокола 2)
|
||||
SetPSFeedbackRate(TXA_CHAN, TX_SAMPLE_RATE);
|
||||
SetPSMoxDelay(TXA_CHAN, FPSMoxDelay);
|
||||
SetPSLoopDelay(TXA_CHAN, FPSLoopDelay);
|
||||
SetPSTXDelay(TXA_CHAN, FPSTXDelayNs * 1.0e-9);
|
||||
if FPSRelaxPtol then
|
||||
SetPSPtol(TXA_CHAN, 0.400)
|
||||
else
|
||||
SetPSPtol(TXA_CHAN, 0.800);
|
||||
SetPSPinMode(TXA_CHAN, Ord(FPSPin));
|
||||
SetPSMapMode(TXA_CHAN, Ord(FPSMap));
|
||||
SetPSStabilize(TXA_CHAN, Ord(FPSStabilize));
|
||||
SetPSIntsAndSpi(TXA_CHAN, FPSInts, FPSSpi);
|
||||
SetPSHWPeak(TXA_CHAN, FPSHWPeak);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.PSSetParams(MoxDelay, LoopDelay: Double; TXDelayNs: Integer;
|
||||
RelaxPtol: Boolean; Pin, Map, Stabilize: Boolean; Ints, Spi: Integer;
|
||||
HWPeak: Double);
|
||||
begin
|
||||
FPSMoxDelay := MoxDelay;
|
||||
FPSLoopDelay := LoopDelay;
|
||||
FPSTXDelayNs := TXDelayNs;
|
||||
FPSRelaxPtol := RelaxPtol;
|
||||
FPSPin := Pin;
|
||||
FPSMap := Map;
|
||||
FPSStabilize := Stabilize;
|
||||
if Ints > 0 then FPSInts := Ints;
|
||||
if Spi > 0 then FPSSpi := Spi;
|
||||
if HWPeak > 0 then FPSHWPeak := HWPeak;
|
||||
ApplyPSParams;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetPureSignalArmed(Armed: Boolean);
|
||||
begin
|
||||
FPSArmed := Armed;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.PushPSFeedback(const Buf: array of Byte;
|
||||
DataOffset: Integer; TotalPairs: Integer);
|
||||
// Сетевой поток: interleaved 24-bit BE пары [DDC0 I,Q][DDC1 I,Q]... →
|
||||
// деинтерливим и кормим WDSP pscc (как pihpsdr process_ps_iq_data):
|
||||
// DDC0 = RX-feedback (искажённый сигнал после PA), DDC1 = TX DAC (эталон).
|
||||
var
|
||||
i, n, Pos: Integer;
|
||||
IR, QR: LongInt;
|
||||
P: PByte;
|
||||
const
|
||||
SCALE = 1.0 / 8388608.0;
|
||||
begin
|
||||
if not (FPSArmed and FInitialized) then Exit;
|
||||
n := TotalPairs div 2; // пар на каждый поток
|
||||
if n > 256 then n := 256; // ёмкость FPSTx/RxBuf
|
||||
if n <= 0 then Exit;
|
||||
if (DataOffset + n * 2 * 6) > Length(Buf) then Exit;
|
||||
|
||||
P := @Buf[DataOffset];
|
||||
for i := 0 to n * 2 - 1 do
|
||||
begin
|
||||
IR := (LongInt(P[0]) shl 16) or (LongInt(P[1]) shl 8) or LongInt(P[2]);
|
||||
QR := (LongInt(P[3]) shl 16) or (LongInt(P[4]) shl 8) or LongInt(P[5]);
|
||||
Inc(P, 6);
|
||||
if (IR and $800000) <> 0 then IR := IR or LongInt($FF000000);
|
||||
if (QR and $800000) <> 0 then QR := QR or LongInt($FF000000);
|
||||
Pos := i shr 1; // индекс пары в потоке
|
||||
if (i and 1) = 0 then
|
||||
begin
|
||||
FPSRxBuf[Pos * 2] := IR * SCALE; // чётные = DDC0 (RX feedback)
|
||||
FPSRxBuf[Pos * 2 + 1] := QR * SCALE;
|
||||
end
|
||||
else
|
||||
begin
|
||||
FPSTxBuf[Pos * 2] := IR * SCALE; // нечётные = DDC1 (TX DAC)
|
||||
FPSTxBuf[Pos * 2 + 1] := QR * SCALE;
|
||||
end;
|
||||
end;
|
||||
|
||||
pscc(TXA_CHAN, n, @FPSTxBuf[0], @FPSRxBuf[0]);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.PSControl(Reset, Mancal, Automode, Turnon: Integer);
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
SetPSControl(TXA_CHAN, Reset, Mancal, Automode, Turnon);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.PSSetMox(Mox: Boolean);
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
SetPSMox(TXA_CHAN, Ord(Mox));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.PSGetInfo(var Info: array of Integer);
|
||||
begin
|
||||
if (not FInitialized) or (Length(Info) < 16) then Exit;
|
||||
GetPSInfo(TXA_CHAN, @Info[0]);
|
||||
end;
|
||||
|
||||
function TWDSPEngine.PSGetMaxTX: Double;
|
||||
begin
|
||||
Result := 0.0;
|
||||
if not FInitialized then Exit;
|
||||
GetPSMaxTX(TXA_CHAN, @Result);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXFilterFull(Low, High, NC, MP, Window: Integer);
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<UseVersionInfo Value="True"/>
|
||||
<AutoIncrementBuild Value="True"/>
|
||||
<MinorVersionNr Value="9"/>
|
||||
<BuildNr Value="275"/>
|
||||
<BuildNr Value="276"/>
|
||||
</VersionInfo>
|
||||
<MacroValues Count="105">
|
||||
<MacroValues Count="106">
|
||||
<Macro1 Name="LCLWidgetType" Value="qt6"/>
|
||||
<Macro2 Name="LCLWidgetType" Value="qt6"/>
|
||||
<Macro3 Name="LCLWidgetType" Value="qt6"/>
|
||||
@@ -125,6 +125,7 @@
|
||||
<Macro103 Name="LCLWidgetType" Value="qt6"/>
|
||||
<Macro104 Name="LCLWidgetType" Value="qt6"/>
|
||||
<Macro105 Name="LCLWidgetType" Value="qt6"/>
|
||||
<Macro106 Name="LCLWidgetType" Value="qt6"/>
|
||||
</MacroValues>
|
||||
<BuildModes>
|
||||
<Item Name="Debug" Default="True"/>
|
||||
|
||||
Reference in New Issue
Block a user