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:
2026-07-11 10:52:31 +03:00
co-authored by Claude Fable 5
parent 26bfacc0a3
commit 5dfd7bbb6c
6 changed files with 321 additions and 35 deletions
+117 -29
View File
@@ -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;