Files
ewsdr/RadioBackend.pas
T
ew8bakandClaude Fable 5 577b8add9d fix(tx): чистый openHPSDR TX-тракт — задержка DUP, всплески TUN, фриз PTT-off
Диагностика временным TXLog (снят) вскрыла цепочку проблем TX-тракта:

- Задержка ~200мс своего сигнала в DUP: за время приёма ринг PortAudio-
  микрофона наполнялся до упора (9600 сэмплов) и на старте TX бёрстом
  оседал стоячим запасом в DUC-очереди. Фикс: TAudioInput.Flush на RX→TX
  (заодно убрана передача в эфир аудио, записанного до нажатия PTT).

- Всплески на TUN: (1) старт — радио начинало с пустого DUC-FIFO; (2) по
  ходу — пейсинг со строгой каденцией держал запас FIFO у нуля, джиттер
  3-6мс давал underrun; (3) клип: голос из бэклога складывался с PostGen-
  тоном (стоит после ALC) поверх шкалы, а сам тон при mag=1.0 давал
  выброс Гиббса x1.18 на старте. Фиксы: pre-roll PrimeDUCIQ (THROTTLE +
  3 блока подушки в очереди — sender метрит непрерывно, FIFO радио прижат
  2000..2400), анти-старв доливка нулями до REANCHOR при просадке,
  TUN_TONE_MAG=0.83, мьют mic-входа TXA при активном тоне.

- Огрызок TUN-тона в эфир на первом блоке следующего TX: стоп канала
  dmode=1 ждал фейд, который некому прокрутить (продюсер уже остановлен) —
  канал стопился по таймауту Sleep(1)x100 с недобранным сигналом в
  буферах. Это же было и «остаточным фризом ~106мс» GUI на отпускании
  PTT: чистый таймаут, фейд никогда не исполнялся. Фикс: dmode=0 +
  синхронный слив/продувка канала 4 нулевыми fexchange0 на стопе и
  старте (<1мс, штатные down-slew и flush).

- Дисплей: ResetPixelBuffers(TX_DISP_ID) на старте TX — усредняющая
  история TX-анализатора показывала спектр тона прошлой сессии.

- StopRunning при активном TUN не чистил DUC-очередь и не сбрасывал
  FTuning/тон — закрыто.

Латентность тракта mic→радио теперь ~43мс (WDSP 11 + подушка 21 + FIFO
10.4). Проверено на эфире: q=0..30, fill/drops/underrun=0, clip=0,
клоки радио/ПК без дрейфа (micRx 48000/с).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:46:19 +03:00

272 lines
12 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
unit RadioBackend;
{
Абстракция бэкенда железа. Базовый класс TRadioBackend, который реализуют
конкретные транспорты:
- THPSDRNetwork (openHPSDR Ethernet protocol) — HPSDRNetwork.pas
- TPlutoBackend (libiio / AD936x) — PlutoBackend.pas (позже)
TRadioController держит FNetwork: TRadioBackend и работает только через этот
интерфейс. Возможности бэкенда описывает TBackendCaps (контроллер гейтит по
ним UI и поведение).
Здесь же — общая запись устройства (TRadioDevice) и типы колбэков данных,
чтобы оба бэкенда и контроллер ссылались на один источник правды.
TDDCIQPacket/THighPriorityStatus/TMicDataPacket берём из HPSDRProtocol —
это сетевой формат HPSDR, а Pluto-бэкенд синтезирует те же структуры, поэтому
WDSP-вход остаётся неизменным.
}
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
uses
Classes, SysUtils, HPSDRProtocol, Settings;
type
TBackendKind = (bkHPSDR, bkPluto);
TBackendRateArray = array of Integer; // пресеты sample-rate (общий тип UI)
// Описание возможностей бэкенда. Контроллер гейтит UI/поведение по этим полям.
TBackendCaps = record
Kind: TBackendKind;
HasTX: Boolean; // есть передатчик
HasPA: Boolean; // fwd power / SWR / supply (HP status)
HasAlex: Boolean; // Alex фильтры / антенный коммутатор
HasWideband: Boolean; // wideband ADC дисплей
HasDitherRandom: Boolean; // dither / random на ADC
HasHWMic: Boolean; // mic over network / HW keyer / sidetone
HasPLLStatus: Boolean; // PLL lock в статусе
HasHWGain: Boolean; // manual gain / hw-AGC (Pluto)
HasRFBandwidth: Boolean; // настройка аналоговой RF-полосы (Pluto)
HasFullDuplex: Boolean; // одновременный RX+TX (Pluto / QO-100)
MinSampleRate: Integer;
RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay
MinFreqHz: Double;
MaxFreqHz: Double;
end;
// Общая запись найденного/подключённого устройства. Поля HPSDR (MAC/BoardType/
// NumDDCs…) и Pluto (URI/Serial) сосуществуют; актуальность по Kind.
TRadioDevice = record
Kind: TBackendKind;
IPAddress: string;
Port: Word;
MAC: array[0..5] of Byte; // HPSDR; для Pluto — synthetic из Serial
BoardType: Byte;
ProtocolVersion: Byte;
FirmwareVersion: Byte;
NumDDCs: Byte;
FreqOrPhase: Byte;
EndianModes: Byte;
URI: string; // Pluto: 'usb:1.5.5' / 'ip:192.168.2.1'
Serial: string; // Pluto serial (hw_serial)
Model: string; // Pluto hw_model (для отображения)
InUse: Boolean;
Valid: Boolean;
end;
PRadioDevice = ^TRadioDevice;
TRadioDeviceArray = array of TRadioDevice;
const
// Признак «значение телеметрии недоступно» (ReadTelemetry). Заведомо вне
// физического диапазона температур/RSSI.
TELEMETRY_NONE = -1.0E9;
type
// Колбэки данных (вызываются из рабочих потоков бэкенда).
TOnDeviceFound = procedure(const Dev: TRadioDevice) of object;
TOnDDCIQPacket = procedure(DDCIndex: Integer;
const Data: TDDCIQPacket) of object;
TOnMicPacket = procedure(const Data: TMicDataPacket) of object;
TOnHPStatus = procedure(const Status: THighPriorityStatus) of object;
TOnWidebandFrame = procedure(ADCIndex: Integer;
const Samples: array of SmallInt;
Count: Integer) of object;
{ TRadioBackend — абстрактный бэкенд железа }
TRadioBackend = class
protected
// Колбэки — общее хранилище для всех бэкендов (свойства ниже).
FOnDeviceFound: TOnDeviceFound;
FOnDDCIQ: TOnDDCIQPacket;
FOnMic: TOnMicPacket;
FOnHPStatus: TOnHPStatus;
FOnWideband: TOnWidebandFrame;
function GetConnected: Boolean; virtual; abstract;
function GetRunning: Boolean; virtual; abstract;
function GetDevice: TRadioDevice; virtual; abstract;
function GetLastError: string; virtual; abstract;
function GetDirectIP: string; virtual;
procedure SetDirectIP(const V: string); virtual;
public
// ---- Возможности ----
function Caps: TBackendCaps; virtual; abstract;
// ---- Жизненный цикл / discovery ----
function Discover(TimeoutMs: Integer = 3000): TRadioDeviceArray; virtual; abstract;
function Connect(const Dev: TRadioDevice): Boolean; virtual; abstract;
procedure Disconnect; virtual; abstract;
// ---- Конфигурация потока RX ----
procedure ConfigureDDCs(NumDDCs: Byte; SampleRate: Word; ADCSource: Byte = 0;
DitherEnabled: Boolean = True;
RandomEnabled: Boolean = True); virtual; abstract;
// ---- Частота / drive / TX-состояние ----
procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double;
DriveLevel: Byte = 100); virtual; abstract;
procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
Transmitting, PAEnabled, AlexEnabled: Boolean); virtual; abstract;
procedure SendFullHP; virtual; abstract;
procedure SendPTT(Active: Boolean); virtual; abstract;
// ---- TX IQ ----
procedure SendDUCIQ(const IData, QData: array of Integer); virtual;
procedure ClearDUCIQQueue; virtual;
// Pre-roll: закачать Pairs нулевых IQ-пар в TX-очередь на старте передачи —
// радио начинает с наполненным DUC-FIFO (подавление underrun-всплеска
// первых мс, как pre-roll 1024 в piHPSDR). База/Pluto — no-op: у Pluto
// TX-стрим непрерывный, его FIFO при недоборе сам добивается нулями.
procedure PrimeDUCIQ(Pairs: Integer); virtual;
// ---- Опциональные (HPSDR-специфичные): no-op по умолчанию ----
procedure SendGeneralPacket(const Pkt: TGeneralPacket); virtual;
procedure ConfigureWideband(ADCIndex: Integer; Enabled: Boolean;
SamplesPerPacket: Integer = 512; SampleBits: Integer = 16;
UpdateRateMS: Integer = 70; PacketsPerFrame: Integer = 32); virtual;
procedure SendDDCSpecific(const Pkt: TDDCSpecificPacket); virtual;
procedure SendDUCSpecific(const Pkt: TDUCSpecificPacket); virtual;
procedure SendHighPriority(const Pkt: THighPriorityPacket); virtual;
procedure SetStepAtten(dB: Byte); virtual;
// RX-усиление (Pluto/AD936x). ModeIdx: 0=manual, 1=fast_attack, 2=slow_attack,
// 3=hybrid. GainDb применяется только в manual. HPSDR — no-op.
procedure SetRxGain(ModeIdx: Integer; GainDb: Integer); virtual;
// TX-аттенюация выходного тракта (Pluto/AD936x): hardwaregain на voltage0
// (output), дБ в диапазоне ~ -89.75..0 (0 = макс. мощность). Контроллер ставит
// максимум на приёме (≈выкл) и значение от drive на передаче. HPSDR — no-op
// (мощность там через DriveLevel/DUC). Db < 0.
procedure SetTxAtten(Db: Double); virtual;
procedure SetAlexConfig(const A: TAlexSettings); virtual;
procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte); virtual;
procedure SendDDCAudio(const LeftRight: array of SmallInt); virtual;
procedure SendSpeakerAudio(const Left, Right: array of Single; Count: Integer); virtual;
procedure SetSpeakerAudio(AEnabled: Boolean); virtual;
// Аппаратная телеметрия, не приходящая в потоке (опрашивается контроллером).
// Pluto/AD936x: температура трансивера (TempC) и Zynq SoC/FPGA (SocTempC) в °C,
// RSSI приёмника (дБ). HPSDR — нет такой (использует HP-status), возвращает False.
// Недоступные значения = TELEMETRY_NONE.
function ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; virtual;
// ---- Свойства состояния (полиморфные через виртуальные геттеры) ----
property Connected: Boolean read GetConnected;
property Running: Boolean read GetRunning;
property Device: TRadioDevice read GetDevice;
property LastError: string read GetLastError;
property DirectIP: string read GetDirectIP write SetDirectIP;
// ---- Колбэки данных ----
property OnDeviceFound: TOnDeviceFound read FOnDeviceFound write FOnDeviceFound;
property OnDDCIQ: TOnDDCIQPacket read FOnDDCIQ write FOnDDCIQ;
property OnMicPacket: TOnMicPacket read FOnMic write FOnMic;
property OnHPStatus: TOnHPStatus read FOnHPStatus write FOnHPStatus;
property OnWideband: TOnWidebandFrame read FOnWideband write FOnWideband;
end;
implementation
{ TRadioBackend — пустые реализации опциональных методов (no-op).
Конкретные бэкенды переопределяют то, что поддерживают. }
function TRadioBackend.GetDirectIP: string;
begin
Result := '';
end;
procedure TRadioBackend.SetDirectIP(const V: string);
begin
// no-op по умолчанию (unicast discovery — фича HPSDR)
end;
procedure TRadioBackend.SendDUCIQ(const IData, QData: array of Integer);
begin
end;
procedure TRadioBackend.ClearDUCIQQueue;
begin
end;
procedure TRadioBackend.PrimeDUCIQ(Pairs: Integer);
begin
end;
procedure TRadioBackend.SendGeneralPacket(const Pkt: TGeneralPacket);
begin
end;
procedure TRadioBackend.ConfigureWideband(ADCIndex: Integer; Enabled: Boolean;
SamplesPerPacket: Integer; SampleBits: Integer;
UpdateRateMS: Integer; PacketsPerFrame: Integer);
begin
end;
procedure TRadioBackend.SendDDCSpecific(const Pkt: TDDCSpecificPacket);
begin
end;
procedure TRadioBackend.SendDUCSpecific(const Pkt: TDUCSpecificPacket);
begin
end;
procedure TRadioBackend.SendHighPriority(const Pkt: THighPriorityPacket);
begin
end;
procedure TRadioBackend.SetStepAtten(dB: Byte);
begin
end;
procedure TRadioBackend.SetRxGain(ModeIdx: Integer; GainDb: Integer);
begin
end;
procedure TRadioBackend.SetAlexConfig(const A: TAlexSettings);
begin
end;
procedure TRadioBackend.SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte);
begin
end;
procedure TRadioBackend.SendDDCAudio(const LeftRight: array of SmallInt);
begin
end;
procedure TRadioBackend.SendSpeakerAudio(const Left, Right: array of Single; Count: Integer);
begin
end;
procedure TRadioBackend.SetSpeakerAudio(AEnabled: Boolean);
begin
end;
function TRadioBackend.ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean;
begin
TempC := TELEMETRY_NONE;
SocTempC := TELEMETRY_NONE;
RSSIdB := TELEMETRY_NONE;
Result := False;
end;
procedure TRadioBackend.SetTxAtten(Db: Double);
begin
end;
end.