mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +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:
+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;
|
||||
|
||||
Reference in New Issue
Block a user