mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(slices): этап 3.1 — per-DDC бэкенд HPSDR (пан-DDC) + фикс Saturn в StartRunning
- TBackendCaps: MaxPans/IndependentPanFreq (HPSDR 4/True, Pluto 1/False); SetPanDDC/SetPanDDCFreq — виртуальные no-op в TRadioBackend - HPSDRNetwork: ConfigureDDCs → кэш главного DDC + RebuildDDCSpecific (главный + активные пан-DDC, каждый со своим rate/ADC); SendFullHP пишет частотные слова всех активных пан-DDC; хелпер DDCBaseIndex; сброс пан-состояния в Connect - RadioController: MAX_PANS=4, PAN_DDC_RATES 192/384/768; AddPanDDC/ RemovePanDDC/SetPanDDCFreq/PanDDC* (аллокатор hw-индексов от FActiveDDC+1, обратный маппинг FDDCPanMap); OnDDCIQ роутит пан-DDC → PushPanIQ; паны гасятся в StopRunning, пул сбрасывается в StartRunning - WDSPEngine: PushPanIQ — заглушка-дроп (аккумулятор/анализатор — этап 3.2) - фикс: StartRunning не включал Saturn (BoardType 10) в список плат с DDCBase=2 — на Saturn гейт FActiveDDC ждал бы IQ с DDC0 - doc/SLICES_PLAN.md: статус 3.1 + B1 проверена на экране Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+117
-29
@@ -140,6 +140,22 @@ type
|
||||
FCachedDUCValid: Boolean;
|
||||
FResendCount: Integer;
|
||||
|
||||
// Панадаптеры на доп. аппаратных DDC (этап 3): состояние per-DDC.
|
||||
// Пишется из потока контроллера (SetPanDDC*), читается в SendFullHP /
|
||||
// RebuildDDCSpecific — та же дисциплина, что у FCurrentRXFreq (torn read
|
||||
// одного HP-кадра чинится следующим же кадром).
|
||||
FPanDDCEnabled: array[0..MAX_DDCS-1] of Boolean;
|
||||
FPanDDCFreqHz: array[0..MAX_DDCS-1] of Double;
|
||||
FPanDDCRateKHz: array[0..MAX_DDCS-1] of Word;
|
||||
FPanDDCADC: array[0..MAX_DDCS-1] of Byte;
|
||||
// Кэш параметров главного DDC для RebuildDDCSpecific (пере-сборка DDC
|
||||
// Specific при добавлении/удалении пан-DDC без повторного знания rate).
|
||||
FMainNumDDCs: Byte;
|
||||
FMainRateKHz: Word;
|
||||
FMainADC: Byte;
|
||||
FMainDither: Boolean;
|
||||
FMainRandom: Boolean;
|
||||
|
||||
// Текущее состояние для построения HP пакетов
|
||||
FCurrentRXFreq: Double;
|
||||
FCurrentTXFreq: Double;
|
||||
@@ -192,6 +208,11 @@ type
|
||||
function DequeueDUCIQ(var Pkt: TDUCIQPacket): Boolean;
|
||||
function NextSeq(var S: LongWord): LongWord;
|
||||
procedure PackSeqBytes(var B: array of Byte; Seq: LongWord);
|
||||
// Первый пользовательский DDC платы: 2 на ANGELIA/ORION/ORION2/SATURN
|
||||
// (DDC0/1 зарезервированы PureSignal), иначе 0.
|
||||
function DDCBaseIndex: Integer;
|
||||
// Сборка+отправка DDC Specific из кэша главного DDC + активных пан-DDC.
|
||||
procedure RebuildDDCSpecific;
|
||||
|
||||
protected
|
||||
// Полиморфные геттеры состояния (свойства Connected/Running/Device/LastError/
|
||||
@@ -219,6 +240,9 @@ type
|
||||
procedure SendDDCSpecific(const Pkt: TDDCSpecificPacket); override;
|
||||
procedure ConfigureDDCs(NumDDCs: Byte; SampleRate: Word; ADCSource: Byte = 0;
|
||||
DitherEnabled: Boolean = True; RandomEnabled: Boolean = True); override;
|
||||
procedure SetPanDDC(DDCIdx: Integer; Enabled: Boolean; FreqHz: Double;
|
||||
RateKHz: Word; ADCSource: Byte); override;
|
||||
procedure SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double); override;
|
||||
procedure SendDUCSpecific(const Pkt: TDUCSpecificPacket); override;
|
||||
procedure SendHighPriority(const Pkt: THighPriorityPacket); override;
|
||||
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
||||
@@ -665,6 +689,10 @@ begin
|
||||
Result.RatePresets[5] := 1536000;
|
||||
Result.MinFreqHz := 0;
|
||||
Result.MaxFreqHz := 61440000;
|
||||
// Панадаптеры на аппаратных DDC: главный + до 3 доп. (кап по Ethernet/CPU,
|
||||
// см. doc/SLICES_PLAN.md 3.1). Каждый DDC тюнится независимо.
|
||||
Result.MaxPans := 4;
|
||||
Result.IndependentPanFreq := True;
|
||||
end;
|
||||
|
||||
constructor THPSDRNetwork.Create;
|
||||
@@ -1019,6 +1047,9 @@ begin
|
||||
FSeqHP := 0;
|
||||
FSeqAudio := 0;
|
||||
FSeqDUCIQ := 0;
|
||||
// Пан-DDC не переживают сессию (контроллер гасит их в StopRunning; здесь —
|
||||
// страховка от грязного завершения прошлой сессии).
|
||||
FillChar(FPanDDCEnabled, SizeOf(FPanDDCEnabled), 0);
|
||||
ResetDUCIQQueue;
|
||||
|
||||
StartThreads;
|
||||
@@ -1457,13 +1488,52 @@ begin
|
||||
if FConnected then SendFullHP;
|
||||
end;
|
||||
|
||||
function THPSDRNetwork.DDCBaseIndex: Integer;
|
||||
begin
|
||||
// Для ANGELIA/ORION/ORION2/SATURN DDC начинается с индекса 2 (DDC0/1 — PureSignal)
|
||||
// Для HERMES/HL2 — с 0
|
||||
if FDevice.BoardType in [3, 4, 5, 10] then // ANGELIA=3, ORION=4, ORION2=5, SATURN=10
|
||||
Result := 2
|
||||
else
|
||||
Result := 0;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.ConfigureDDCs(NumDDCs: Byte; SampleRate: Word;
|
||||
ADCSource: Byte;
|
||||
DitherEnabled: Boolean; RandomEnabled: Boolean);
|
||||
begin
|
||||
// Кэш главного DDC — RebuildDDCSpecific пере-собирает пакет и при
|
||||
// добавлении/удалении пан-DDC (SetPanDDC) без повторной передачи rate.
|
||||
FMainNumDDCs := NumDDCs;
|
||||
FMainRateKHz := SampleRate;
|
||||
FMainADC := ADCSource;
|
||||
FMainDither := DitherEnabled;
|
||||
FMainRandom := RandomEnabled;
|
||||
RebuildDDCSpecific;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.RebuildDDCSpecific;
|
||||
var
|
||||
Pkt: TDDCSpecificPacket;
|
||||
i, ddc: Integer;
|
||||
DDCBase: Integer;
|
||||
|
||||
procedure AddDDC(ddc: Integer; RateKHz: Word; ADC: Byte);
|
||||
begin
|
||||
// Enable bit для DDC ddc
|
||||
Pkt.DDCEnable[ddc div 8] := Pkt.DDCEnable[ddc div 8] or Byte(1 shl (ddc mod 8));
|
||||
// Config: 6 байт на DDC начиная с offset 17 в пакете → в массиве DDCConfig
|
||||
// DDCConfig[ddc*6 + 0] = ADC source
|
||||
// DDCConfig[ddc*6 + 1..2] = sample rate / 1000 (ksps)
|
||||
// DDCConfig[ddc*6 + 5] = bits per sample
|
||||
Pkt.DDCConfig[ddc * 6] := ADC;
|
||||
Pkt.DDCConfig[ddc * 6 + 1] := (RateKHz shr 8) and $FF;
|
||||
Pkt.DDCConfig[ddc * 6 + 2] := RateKHz and $FF;
|
||||
Pkt.DDCConfig[ddc * 6 + 3] := 0;
|
||||
Pkt.DDCConfig[ddc * 6 + 4] := 0;
|
||||
Pkt.DDCConfig[ddc * 6 + 5] := 24; // 24 bits per sample
|
||||
end;
|
||||
|
||||
begin
|
||||
FillChar(Pkt, SizeOf(Pkt), 0);
|
||||
|
||||
@@ -1474,34 +1544,21 @@ begin
|
||||
Pkt.NumADCs := 1;
|
||||
|
||||
// Dither/Random — биты 0..NumADCs-1 (один бит на ADC)
|
||||
if DitherEnabled then
|
||||
if FMainDither then
|
||||
Pkt.DitherADC := (1 shl Pkt.NumADCs) - 1;
|
||||
if RandomEnabled then
|
||||
if FMainRandom then
|
||||
Pkt.RandomADC := (1 shl Pkt.NumADCs) - 1;
|
||||
|
||||
// Для ANGELIA/ORION/ORION2/SATURN DDC начинается с индекса 2 (DDC0/1 — PureSignal)
|
||||
// Для HERMES/HL2 — с 0
|
||||
if FDevice.BoardType in [3, 4, 5, 10] then // ANGELIA=3, ORION=4, ORION2=5, SATURN=10
|
||||
DDCBase := 2
|
||||
else
|
||||
DDCBase := 0;
|
||||
DDCBase := DDCBaseIndex;
|
||||
|
||||
for i := 0 to NumDDCs - 1 do
|
||||
begin
|
||||
ddc := DDCBase + i;
|
||||
// Enable bit для DDC ddc
|
||||
Pkt.DDCEnable[ddc div 8] := Pkt.DDCEnable[ddc div 8] or Byte(1 shl (ddc mod 8));
|
||||
// Config: 6 байт на DDC начиная с offset 17 в пакете → в массиве DDCConfig
|
||||
// DDCConfig[ddc*6 + 0] = ADC source
|
||||
// DDCConfig[ddc*6 + 1..2] = sample rate / 1000 (ksps)
|
||||
// DDCConfig[ddc*6 + 5] = bits per sample
|
||||
Pkt.DDCConfig[ddc * 6] := ADCSource;
|
||||
Pkt.DDCConfig[ddc * 6 + 1] := (SampleRate shr 8) and $FF;
|
||||
Pkt.DDCConfig[ddc * 6 + 2] := SampleRate and $FF;
|
||||
Pkt.DDCConfig[ddc * 6 + 3] := 0;
|
||||
Pkt.DDCConfig[ddc * 6 + 4] := 0;
|
||||
Pkt.DDCConfig[ddc * 6 + 5] := 24; // 24 bits per sample
|
||||
end;
|
||||
for i := 0 to FMainNumDDCs - 1 do
|
||||
AddDDC(DDCBase + i, FMainRateKHz, FMainADC);
|
||||
|
||||
// Пан-DDC (этап 3): доп. панадаптеры на своих rate/ADC. Контроллер
|
||||
// аллоцирует hw-индексы после главного, конфликтов с циклом выше нет.
|
||||
for ddc := 0 to MAX_DDCS - 1 do
|
||||
if FPanDDCEnabled[ddc] then
|
||||
AddDDC(ddc, FPanDDCRateKHz[ddc], FPanDDCADC[ddc]);
|
||||
|
||||
SendDDCSpecific(Pkt);
|
||||
FCachedDDCSpec := Pkt;
|
||||
@@ -1509,6 +1566,28 @@ begin
|
||||
FResendCount := 0;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.SetPanDDC(DDCIdx: Integer; Enabled: Boolean;
|
||||
FreqHz: Double; RateKHz: Word; ADCSource: Byte);
|
||||
begin
|
||||
if (DDCIdx < 0) or (DDCIdx >= MAX_DDCS) then Exit;
|
||||
FPanDDCEnabled[DDCIdx] := Enabled;
|
||||
FPanDDCFreqHz[DDCIdx] := FreqHz;
|
||||
FPanDDCRateKHz[DDCIdx] := RateKHz;
|
||||
FPanDDCADC[DDCIdx] := ADCSource;
|
||||
if FConnected and FRunning then
|
||||
begin
|
||||
RebuildDDCSpecific; // enable-бит + rate/ADC
|
||||
SendFullHP; // частотное слово DDC
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double);
|
||||
begin
|
||||
if (DDCIdx < 0) or (DDCIdx >= MAX_DDCS) then Exit;
|
||||
FPanDDCFreqHz[DDCIdx] := FreqHz;
|
||||
if FConnected and FPanDDCEnabled[DDCIdx] then SendFullHP;
|
||||
end;
|
||||
|
||||
procedure THPSDRNetwork.SendDUCSpecific(const Pkt: TDUCSpecificPacket);
|
||||
var
|
||||
B: TDUCSpecificPacket;
|
||||
@@ -1557,6 +1636,7 @@ var
|
||||
Alex1: LongWord;
|
||||
IsOrion2: Boolean;
|
||||
DDCBase: Integer; // 0 для HERMES, 2 для ORION/ORION2/ANGELIA
|
||||
ddc: Integer;
|
||||
begin
|
||||
if not FConnected then Exit;
|
||||
|
||||
@@ -1580,13 +1660,9 @@ begin
|
||||
if FPTTActive then Buf[4] := Buf[4] or HP_PTT0;
|
||||
end;
|
||||
|
||||
// DDC base: ORION/ORION2/ANGELIA/SATURN начинают с DDC2
|
||||
// IsOrion2: платы с Alex BPF и TxRx Status bit18 (ORION_MK2=5, SATURN=10)
|
||||
IsOrion2 := FDevice.BoardType in [5, 10]; // ORION_MK2=5, SATURN=10
|
||||
if FDevice.BoardType in [3, 4, 5, 10] then // ANGELIA=3, ORION=4, ORION_MK2=5, SATURN=10
|
||||
DDCBase := 2
|
||||
else
|
||||
DDCBase := 0;
|
||||
DDCBase := DDCBaseIndex;
|
||||
|
||||
// DDC RX frequency (bytes 9 + DDC*4)
|
||||
Ph := FreqToPhaseWord(FCurrentRXFreq);
|
||||
@@ -1595,6 +1671,18 @@ begin
|
||||
Buf[11 + DDCBase*4] := (Ph shr 8) and $FF;
|
||||
Buf[12 + DDCBase*4] := Ph and $FF;
|
||||
|
||||
// Частоты пан-DDC (этап 3): своё частотное слово каждому активному DDC.
|
||||
// Макс. смещение 9+79*4=325 < 329 (DUC) — в частотную зону влезают все.
|
||||
for ddc := 0 to MAX_DDCS - 1 do
|
||||
if FPanDDCEnabled[ddc] then
|
||||
begin
|
||||
Ph := FreqToPhaseWord(FPanDDCFreqHz[ddc]);
|
||||
Buf[9 + ddc*4] := (Ph shr 24) and $FF;
|
||||
Buf[10 + ddc*4] := (Ph shr 16) and $FF;
|
||||
Buf[11 + ddc*4] := (Ph shr 8) and $FF;
|
||||
Buf[12 + ddc*4] := Ph and $FF;
|
||||
end;
|
||||
|
||||
// DUC TX frequency (bytes 329-332)
|
||||
Ph := FreqToPhaseWord(FCurrentTXFreq);
|
||||
Buf[329] := (Ph shr 24) and $FF;
|
||||
|
||||
@@ -432,6 +432,10 @@ 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.)
|
||||
Result.MaxPans := 1;
|
||||
Result.IndependentPanFreq := False;
|
||||
end;
|
||||
|
||||
function TPlutoBackend.ProbeURI(const AURI: string; out Dev: TRadioDevice): Boolean;
|
||||
|
||||
@@ -43,6 +43,12 @@ type
|
||||
HasHWGain: Boolean; // manual gain / hw-AGC (Pluto)
|
||||
HasRFBandwidth: Boolean; // настройка аналоговой RF-полосы (Pluto)
|
||||
HasFullDuplex: Boolean; // одновременный RX+TX (Pluto / QO-100)
|
||||
// Панадаптеры (этап 3): сколько всего панов может показать бэкенд
|
||||
// (1 = только главный; HPSDR P2 = главный + доп. DDC). Паны N>0 живут
|
||||
// на своих аппаратных DDC (SetPanDDC*).
|
||||
MaxPans: Integer;
|
||||
// False = частоты панов связаны с паном 0 (Pluto+ 2r2t: общий RX LO).
|
||||
IndependentPanFreq: Boolean;
|
||||
MinSampleRate: Integer;
|
||||
RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay
|
||||
MinFreqHz: Double;
|
||||
@@ -118,6 +124,14 @@ type
|
||||
DitherEnabled: Boolean = True;
|
||||
RandomEnabled: Boolean = True); virtual; abstract;
|
||||
|
||||
// ---- Панадаптеры на доп. аппаратных DDC (этап 3, только HPSDR) ----
|
||||
// SetPanDDC включает/выключает и конфигурирует DDC с hw-индексом DDCIdx
|
||||
// (rate/ADC → DDC Specific, частота → High Priority). SetPanDDCFreq —
|
||||
// только ретюн (дёшево, без реконфига DDC). База — no-op (Caps.MaxPans=1).
|
||||
procedure SetPanDDC(DDCIdx: Integer; Enabled: Boolean; FreqHz: Double;
|
||||
RateKHz: Word; ADCSource: Byte); virtual;
|
||||
procedure SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double); virtual;
|
||||
|
||||
// ---- Частота / drive / TX-состояние ----
|
||||
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
|
||||
DriveLevel: Byte = 100); virtual; abstract;
|
||||
@@ -268,4 +282,13 @@ procedure TRadioBackend.SetTxAtten(Db: Double);
|
||||
begin
|
||||
end;
|
||||
|
||||
procedure TRadioBackend.SetPanDDC(DDCIdx: Integer; Enabled: Boolean;
|
||||
FreqHz: Double; RateKHz: Word; ADCSource: Byte);
|
||||
begin
|
||||
end;
|
||||
|
||||
procedure TRadioBackend.SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double);
|
||||
begin
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+136
-4
@@ -52,6 +52,14 @@ uses
|
||||
WDSPEngine, AudioOutput, AudioInput, BeaconDecoder, BeaconFEC,
|
||||
Settings, ChannelStore, FMRepeater, BoardUtils, DeviceStore;
|
||||
|
||||
const
|
||||
// Панадаптеры (этап 3): потолок панов = главный + до 3 доп. аппаратных DDC
|
||||
// (кап по Ethernet/CPU, см. doc/SLICES_PLAN.md 3.1). Фактический лимит
|
||||
// бэкенда — Caps.MaxPans (Pluto = 1).
|
||||
MAX_PANS = 4;
|
||||
// Допустимые rate доп. пан-DDC, кГц (дефолт 384).
|
||||
PAN_DDC_RATES: array[0..2] of Word = (192, 384, 768);
|
||||
|
||||
type
|
||||
// QO-100 beacon lock — статус контура (для UI/маркеров).
|
||||
TBeaconState = (bsIdle, // лок выключен
|
||||
@@ -159,6 +167,8 @@ type
|
||||
procedure OnSliceAudioReady(SliceId: Integer;
|
||||
const Left, Right: array of Single; Count: Integer);
|
||||
procedure RefreshSliceShifts; // пересчёт ShiftHz всех слайсов при смене центра
|
||||
// --- Панадаптеры на аппаратных DDC (этап 3.1) ---
|
||||
procedure ResetPanDDCMaps; // все пан-слоты свободны, DDC→Pan пуст
|
||||
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
|
||||
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
|
||||
procedure SanitizeFilterForMode; // FM: индекс фильтра 0/1 + BW/девиация (state)
|
||||
@@ -336,6 +346,14 @@ type
|
||||
FRXStartTime: QWord; // GetTickCount64 момента START
|
||||
FRXLastPktTime: QWord; // GetTickCount64 последнего принятого пакета
|
||||
FActiveDDC: Integer; // DDC index для текущей платы (0 или 2)
|
||||
// ---- Панадаптеры на аппаратных DDC (этап 3.1) ----
|
||||
// PanId 1..MAX_PANS-1 → hw DDC index (-1 = пан не создан); пан 0 = главный
|
||||
// тракт (FActiveDDC). FDDCPanMap — обратный маппинг DDC→PanId для роутинга
|
||||
// IQ в OnDDCIQ (пишется под FSliceLock-дисциплиной: только UI-поток).
|
||||
FPanDDCIdx: array[1..MAX_PANS-1] of Integer;
|
||||
FPanFreqHz: array[1..MAX_PANS-1] of Double;
|
||||
FPanRateKHz: array[1..MAX_PANS-1] of Word;
|
||||
FDDCPanMap: array[0..MAX_DDCS-1] of Integer;
|
||||
FLastDDCSeq: LongWord; // последний seq (из сетевого потока)
|
||||
FLastDDCIndex: Integer; // последний DDC index
|
||||
FDDCLastSeq: array[0..6] of LongWord;
|
||||
@@ -540,6 +558,18 @@ type
|
||||
procedure CapturedSpan(out LoHz, HiHz: Double);
|
||||
function SliceFitsCapture(TargetHz: Double): Boolean;
|
||||
|
||||
// ---- Панадаптеры на аппаратных DDC (этап 3.1; UI/движок — 3.2/3.3) ----
|
||||
// Создаёт пан PanId (1..MAX_PANS-1) на своём DDC: FreqHz = центр (сырые Гц
|
||||
// железа, XVTR-трансляции у панов нет), RateKHz ∈ PAN_DDC_RATES (иначе 384),
|
||||
// ADCSource — АЦП платы. False = нет слота/бэкенд не умеет/не Running.
|
||||
function AddPanDDC(PanId: Integer; FreqHz: Double; RateKHz: Word = 384;
|
||||
ADCSource: Byte = 0): Boolean;
|
||||
procedure RemovePanDDC(PanId: Integer);
|
||||
procedure SetPanDDCFreq(PanId: Integer; FreqHz: Double); // ретюн (HP-пакет)
|
||||
function PanDDCActive(PanId: Integer): Boolean;
|
||||
function PanDDCFreq(PanId: Integer): Double;
|
||||
function PanDDCRateKHz(PanId: Integer): Word;
|
||||
|
||||
// Pluto TX-мощность через аттенюацию: drive% → дБ (потолок FPlutoTxMaxAttDb на
|
||||
// 100%, выкл на приёме). Логика/маппинг здесь; бэкенд лишь пишет hardwaregain.
|
||||
function PlutoTxAttForDrive: Double; // дБ<0 под текущий FDrivePercent
|
||||
@@ -711,6 +741,7 @@ begin
|
||||
FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000;
|
||||
FNextSliceId := 1; // мультислайсы: генератор id
|
||||
FillChar(FSlices, SizeOf(FSlices), 0);
|
||||
ResetPanDDCMaps; // паны: все слоты свободны
|
||||
FZoomFactor := 0.0; FPanSlider := 0.5; // 0.0 = весь span (без зума)
|
||||
FCurrentBand := 5; FCurrentXvtr := -1;
|
||||
FSpectrumBufCount := 1024; FWaterfallBufCount := 1024;
|
||||
@@ -871,13 +902,17 @@ begin
|
||||
FDDCSeqValid[DDCIndex] := True;
|
||||
end;
|
||||
|
||||
// Подаём IQ в DSP — только с активного DDC
|
||||
if (DDCIndex = FActiveDDC) and FWDSPReady then
|
||||
// Подаём IQ в DSP: активный DDC → главный тракт, пан-DDC → его пан (3.2).
|
||||
if FWDSPReady then
|
||||
begin
|
||||
SamplesPerFrame := (Integer(Data.SamplesPerFrame[0]) shl 8) or
|
||||
Integer(Data.SamplesPerFrame[1]);
|
||||
if SamplesPerFrame <= 0 then SamplesPerFrame := 238; // fallback
|
||||
FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame);
|
||||
if DDCIndex = FActiveDDC then
|
||||
FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame)
|
||||
else if (DDCIndex >= 0) and (DDCIndex < MAX_DDCS)
|
||||
and (FDDCPanMap[DDCIndex] > 0) then
|
||||
FDSPEngine.PushPanIQ(FDDCPanMap[DDCIndex], Data.IQData, 0, SamplesPerFrame);
|
||||
end;
|
||||
|
||||
// Запоминаем последний seq — статус обновит таймер (без Synchronize в hot path)
|
||||
@@ -1117,6 +1152,94 @@ begin
|
||||
Result := (TargetHz >= Lo) and (TargetHz <= Hi);
|
||||
end;
|
||||
|
||||
{ ---- Панадаптеры на аппаратных DDC (этап 3.1) ---- }
|
||||
|
||||
procedure TRadioController.ResetPanDDCMaps;
|
||||
var i: Integer;
|
||||
begin
|
||||
for i := 1 to MAX_PANS - 1 do
|
||||
begin
|
||||
FPanDDCIdx[i] := -1;
|
||||
FPanFreqHz[i] := 0;
|
||||
FPanRateKHz[i] := 0;
|
||||
end;
|
||||
for i := 0 to MAX_DDCS - 1 do
|
||||
FDDCPanMap[i] := -1;
|
||||
end;
|
||||
|
||||
function TRadioController.AddPanDDC(PanId: Integer; FreqHz: Double;
|
||||
RateKHz: Word; ADCSource: Byte): Boolean;
|
||||
var
|
||||
i, ddc: Integer;
|
||||
RateOk: Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
|
||||
if FPanDDCIdx[PanId] >= 0 then Exit; // уже создан
|
||||
if not (Assigned(FNetwork) and FRunning) then Exit;
|
||||
if PanId >= FNetwork.Caps.MaxPans then Exit; // бэкенд не умеет (Pluto=1)
|
||||
|
||||
RateOk := False;
|
||||
for i := Low(PAN_DDC_RATES) to High(PAN_DDC_RATES) do
|
||||
if PAN_DDC_RATES[i] = RateKHz then begin RateOk := True; Break; end;
|
||||
if not RateOk then RateKHz := 384;
|
||||
|
||||
// hw-индекс: следующий свободный после главного DDC (слот-стабильности не
|
||||
// нужно — hw-индекс скрыт за PanId, наружу торчит только PanId).
|
||||
ddc := -1;
|
||||
for i := FActiveDDC + 1 to FActiveDDC + MAX_PANS - 1 do
|
||||
if (i < MAX_DDCS) and (FDDCPanMap[i] < 0) then begin ddc := i; Break; end;
|
||||
if ddc < 0 then Exit;
|
||||
|
||||
FPanDDCIdx[PanId] := ddc;
|
||||
FPanFreqHz[PanId] := FreqHz;
|
||||
FPanRateKHz[PanId] := RateKHz;
|
||||
FDDCPanMap[ddc] := PanId;
|
||||
FNetwork.SetPanDDC(ddc, True, FreqHz, RateKHz, ADCSource);
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
procedure TRadioController.RemovePanDDC(PanId: Integer);
|
||||
var ddc: Integer;
|
||||
begin
|
||||
if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
|
||||
ddc := FPanDDCIdx[PanId];
|
||||
if ddc < 0 then Exit;
|
||||
// Сначала снимаем роутинг (OnDDCIQ в сетевом потоке перестаёт брать пакеты
|
||||
// этого DDC), потом гасим DDC в железе.
|
||||
FDDCPanMap[ddc] := -1;
|
||||
FPanDDCIdx[PanId] := -1;
|
||||
FPanFreqHz[PanId] := 0;
|
||||
FPanRateKHz[PanId] := 0;
|
||||
if Assigned(FNetwork) then FNetwork.SetPanDDC(ddc, False, 0, 0, 0);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetPanDDCFreq(PanId: Integer; FreqHz: Double);
|
||||
begin
|
||||
if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
|
||||
if FPanDDCIdx[PanId] < 0 then Exit;
|
||||
FPanFreqHz[PanId] := FreqHz;
|
||||
if Assigned(FNetwork) then
|
||||
FNetwork.SetPanDDCFreq(FPanDDCIdx[PanId], FreqHz);
|
||||
end;
|
||||
|
||||
function TRadioController.PanDDCActive(PanId: Integer): Boolean;
|
||||
begin
|
||||
Result := (PanId >= 1) and (PanId < MAX_PANS) and (FPanDDCIdx[PanId] >= 0);
|
||||
end;
|
||||
|
||||
function TRadioController.PanDDCFreq(PanId: Integer): Double;
|
||||
begin
|
||||
if PanDDCActive(PanId) then Result := FPanFreqHz[PanId]
|
||||
else Result := 0;
|
||||
end;
|
||||
|
||||
function TRadioController.PanDDCRateKHz(PanId: Integer): Word;
|
||||
begin
|
||||
if PanDDCActive(PanId) then Result := FPanRateKHz[PanId]
|
||||
else Result := 0;
|
||||
end;
|
||||
|
||||
function TRadioController.AddSlice(TargetHz: Double;
|
||||
Mode, FilterLo, FilterHi: Integer; AGC: TWDSPAGCMode; Vol: Double;
|
||||
DevIndex: Integer; const DevName: string): Integer;
|
||||
@@ -3409,10 +3532,13 @@ begin
|
||||
FNetwork.SendGeneralPacket(GenPkt);
|
||||
FNetwork.ConfigureWideband(0, FShowWideband, 512, 16, 70, 32);
|
||||
|
||||
if FNetwork.Device.BoardType in [3, 4, 5] then
|
||||
// ANGELIA=3, ORION=4, ORION2=5, SATURN=10: DDC0/1 — PureSignal, юзерский с 2
|
||||
// (тот же список, что DDCBaseIndex бэкенда; Saturn раньше был пропущен).
|
||||
if FNetwork.Device.BoardType in [3, 4, 5, 10] then
|
||||
FActiveDDC := 2
|
||||
else
|
||||
FActiveDDC := 0;
|
||||
ResetPanDDCMaps; // пул пан-DDC заново от нового FActiveDDC
|
||||
|
||||
// Синхронизируем samplerate WDSP если нужно
|
||||
if FWDSPReady and (FDSPEngine.SampleRate <> FSampleRate) then
|
||||
@@ -3473,9 +3599,15 @@ procedure TRadioController.StopRunning;
|
||||
// Graceful-останов RX-потока (Run=0) + сброс TX и RX-телеметрии. Сеть остаётся
|
||||
// Connected — Disconnect отдельно (UI-обёртка делает это после persistence).
|
||||
// Рендер run-кнопки/sleep — UI через OnControllerState(rfRunning); MOX — rfTransmitting.
|
||||
var
|
||||
p: Integer;
|
||||
begin
|
||||
if FRunning then
|
||||
begin
|
||||
// Паны на доп. DDC живут только внутри Run-сессии — гасим до Run=0
|
||||
// (3.3 UI пересоздаст по персисту при следующем старте).
|
||||
for p := 1 to MAX_PANS - 1 do
|
||||
RemovePanDDC(p);
|
||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), 0, False, True, True);
|
||||
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
|
||||
FRunning := False;
|
||||
|
||||
@@ -477,6 +477,11 @@ type
|
||||
procedure PushDDCPacket(const Buf: array of Byte;
|
||||
DataOffset: Integer;
|
||||
IQPairs: Integer);
|
||||
// IQ доп. панадаптера (аппаратный DDC пана PanId, этап 3). Пока (3.1)
|
||||
// заглушка-дроп; 3.2 добавит per-pan аккумулятор + Spectrum0(PAN_DISP_BASE+
|
||||
// PanId) + ProcessSlices по слайсам этого пана.
|
||||
procedure PushPanIQ(PanId: Integer; const Buf: array of Byte;
|
||||
DataOffset: Integer; IQPairs: Integer);
|
||||
procedure FlushRX;
|
||||
// Замьютить RX-аудио на DurationMs от текущего момента. UI зовёт это
|
||||
// на TX→RX в non-DUP только под Windows — там OS-уровневый PortAudio
|
||||
@@ -1523,6 +1528,13 @@ begin
|
||||
RTLEventSetEvent(FQueueSem); // будим DSP поток (как sem_post в piHPSDR)
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.PushPanIQ(PanId: Integer; const Buf: array of Byte;
|
||||
DataOffset: Integer; IQPairs: Integer);
|
||||
// Сетевой поток. Этап 3.2 положит пакет в аккумулятор пана PanId; до тех пор
|
||||
// поток доп. пан-DDC отбрасывается (панов ещё никто не создаёт).
|
||||
begin
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.FlushRX;
|
||||
begin
|
||||
FQueueHead := FQueueTail;
|
||||
|
||||
+29
-2
@@ -128,8 +128,10 @@ Pluto+ (AD9363 2r2t, libiio):
|
||||
|
||||
### 3.0 — TPanafallPanel: выделить панадаптер из MainForm
|
||||
|
||||
**Статус 2026-07-10: фазы A и B1 СДЕЛАНЫ (коммиты c125e59, cc9919c; фаза A
|
||||
проверена пользователем на экране).** Создан `PanafallPanel.pas`:
|
||||
**Статус: фазы A и B1 СДЕЛАНЫ (коммиты c125e59, cc9919c) и ПРОВЕРЕНЫ
|
||||
пользователем на экране (A — 2026-07-10, B1 — 2026-07-11: создание/закрытие
|
||||
флагов, видимость флага A, мышь/колесо/drag, тик S-метров — работает).**
|
||||
Создан `PanafallPanel.pas`:
|
||||
- Фаза A: панель владеет view (CPU/GL), контролами (спектр/линейка/сплиттер/
|
||||
водопад/пан-зум/кнопки зума), полной геометрией стека (`Layout`), механикой
|
||||
сплиттера и мышью пан/зум-полосы. MainForm держит алиасы (`FSpecView`,
|
||||
@@ -218,6 +220,31 @@ static/глобального. (CPU-LUT палитры общий — это о
|
||||
Ретюн пана N>0 = запись `FDDCFreq[ddc]` + `SendFullHP` (без реконфига DDC);
|
||||
смена rate/ADC = `ConfigureDDCs` (как сейчас при смене sample rate).
|
||||
|
||||
**СТАТУС 3.1 (2026-07-11): РЕАЛИЗОВАНО, собирается (ewsdr qt6 + ewsdrd),
|
||||
на эфире не проверено (проверяемо только с 3.2/3.3 — панов ещё никто не
|
||||
создаёт).** Отличие от текста плана: per-DDC состояние (freq/rate/ADC/enable)
|
||||
живёт в `THPSDRNetwork` (`FPanDDC*`, пишет `SetPanDDC`/`SetPanDDCFreq` из
|
||||
`TRadioBackend`), а контроллер держит только per-pan маппинг — эквивалентно
|
||||
`FDDCFreq[]` из плана, но HP-кадр собирается из одного места. Сделано:
|
||||
|
||||
- `TBackendCaps.MaxPans`/`IndependentPanFreq` (HPSDR 4/True, Pluto 1/False);
|
||||
`SetPanDDC`/`SetPanDDCFreq` — виртуальные no-op в базе, override в HPSDR.
|
||||
- `HPSDRNetwork`: `ConfigureDDCs` → кэш главного DDC + `RebuildDDCSpecific`
|
||||
(главный + активные пан-DDC, каждый со своим rate/ADC); `SendFullHP` пишет
|
||||
частотные слова всех активных пан-DDC (`Buf[9+ddc*4]`); хелпер
|
||||
`DDCBaseIndex` (дедуп трёх списков плат); сброс пан-состояния в `Connect`.
|
||||
- `RadioController`: `MAX_PANS=4`, `PAN_DDC_RATES=(192,384,768)`;
|
||||
`AddPanDDC/RemovePanDDC/SetPanDDCFreq/PanDDC{Active,Freq,RateKHz}`
|
||||
(аллокатор hw-индексов от `FActiveDDC+1`, обратный маппинг `FDDCPanMap`);
|
||||
`OnDDCIQ` роутит: активный DDC → главный тракт, пан-DDC →
|
||||
`WDSPEngine.PushPanIQ` (пока заглушка-дроп, тело — 3.2); паны гасятся в
|
||||
`StopRunning`, пул сбрасывается в `StartRunning`.
|
||||
- ⚠ Попутный фикс: `StartRunning` не включал Saturn (BoardType 10) в список
|
||||
плат с DDCBase=2 → на Saturn гейт `FActiveDDC` ждал DDC0. Исправлено
|
||||
(`[3,4,5,10]`, как в `DDCBaseIndex`).
|
||||
- Частоты панов — сырые Гц железа: XVTR-трансляция на паны НЕ распространяется
|
||||
(XVTR — свойство главного тракта; решить при 3.3/3.4, если понадобится).
|
||||
|
||||
### 3.2 — WDSPEngine: анализатор и IQ-тракт на панадаптер
|
||||
|
||||
Сейчас: анализаторы `RX_DISP_ID=0` / `TX_DISP_ID=1` (`WDSPEngine.pas:133`),
|
||||
|
||||
Reference in New Issue
Block a user