mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
feature(pluto): компенсация hardwaregain в уровнях + живое усиление в блоке RF AGC
На Pluto шкала dBm ничего не значила: усиление тракта 0..73 дБ входит в показания напрямую, а в режимах AGC железо крутит его само, и картинка «дышит» вместе с ним. - RadioBackend: виртуальный RxGainNowDb (TELEMETRY_NONE = регулируемого усиления нет, как у openHPSDR с его аттенюатором). - PlutoBackend: в MANUAL отдаёт FGainDb (точно, без опроса железа), в режимах AGC — hardwaregain из телеметрического потока (читается там же, где rssi, ~2 Гц, не блокируя UI). - RadioController: AttenCalDB -> FrontEndCalDB, общее понятие «регулируемое усиление входа»: openHPSDR +FAtten (аттенюатор режет), AD936x -hardwaregain (усиление поднимает). Постоянная часть тракта (LNB, кабель, конвертер) остаётся за таблицами калибровки на диапазон/трансвертер. - UI: в блоке RF AGC при режиме != MANUAL вместо надписи «AGC» — фактическое усиление («~38dB», тильда = измеренное), ползунок ездит сам. Видно, что цифра гуляет, а уровни на спектре стоят. Ограничения: FAST AGC крутит усиление быстрее опроса (2 Гц), для стабильной шкалы нужен MANUAL или SLOW; шумовая полка при смене усиления немного едет — это физика (input-referred NF), компенсацию проверять по сигналу. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+12
-2
@@ -8004,10 +8004,20 @@ begin
|
|||||||
for i := 0 to 3 do
|
for i := 0 to 3 do
|
||||||
if BtnPlutoGain[i] <> nil then
|
if BtnPlutoGain[i] <> nil then
|
||||||
StyleButton(BtnPlutoGain[i], BtnPlutoGain[i].Tag = FController.FRxGainMode);
|
StyleButton(BtnPlutoGain[i], BtnPlutoGain[i].Tag = FController.FRxGainMode);
|
||||||
TrkRxGain.Position := FController.FRxGainDb;
|
|
||||||
TrkRxGain.Enabled := (FController.FRxGainMode = 0); // активен только в manual
|
TrkRxGain.Enabled := (FController.FRxGainMode = 0); // активен только в manual
|
||||||
if FController.FRxGainMode = 0 then
|
if FController.FRxGainMode = 0 then
|
||||||
LblRxGain.Caption := Format('%ddB', [FController.FRxGainDb])
|
begin
|
||||||
|
TrkRxGain.Position := FController.FRxGainDb;
|
||||||
|
LblRxGain.Caption := Format('%ddB', [FController.FRxGainDb]);
|
||||||
|
end
|
||||||
|
else if FController.FLastPlutoGainDb > TELEMETRY_NONE then
|
||||||
|
begin
|
||||||
|
// AGC: показываем ФАКТИЧЕСКОЕ усиление железа (опрос ~2 Гц) — ползунок
|
||||||
|
// ездит сам, тильда отличает измеренное от заданного. На это же значение
|
||||||
|
// компенсируются уровни (FrontEndCalDB), поэтому шкала dBm стоит.
|
||||||
|
TrkRxGain.Position := EnsureRange(Round(FController.FLastPlutoGainDb), 0, 73);
|
||||||
|
LblRxGain.Caption := Format('~%ddB', [Round(FController.FLastPlutoGainDb)]);
|
||||||
|
end
|
||||||
else
|
else
|
||||||
LblRxGain.Caption := 'AGC';
|
LblRxGain.Caption := 'AGC';
|
||||||
finally
|
finally
|
||||||
|
|||||||
+34
-4
@@ -109,11 +109,13 @@ type
|
|||||||
FTeleTemp: Double; // TELEMETRY_NONE, пока не прочитано
|
FTeleTemp: Double; // TELEMETRY_NONE, пока не прочитано
|
||||||
FTeleSoc: Double;
|
FTeleSoc: Double;
|
||||||
FTeleRssi: Double;
|
FTeleRssi: Double;
|
||||||
|
FTeleGain: Double; // hardwaregain RX, дБ (в AGC-режимах крутит железо)
|
||||||
FTeleValid: Boolean; // был ли хоть один успешный опрос
|
FTeleValid: Boolean; // был ли хоть один успешный опрос
|
||||||
procedure StartTelemetry;
|
procedure StartTelemetry;
|
||||||
procedure StopTelemetry;
|
procedure StopTelemetry;
|
||||||
// Блокирующий опрос железа: только из FTeleThread.
|
// Блокирующий опрос железа: только из FTeleThread.
|
||||||
function PollTelemetryBlocking(out TempC, SocTempC, RSSIdB: Double): Boolean;
|
function PollTelemetryBlocking(out TempC, SocTempC, RSSIdB,
|
||||||
|
GainDb: Double): Boolean;
|
||||||
|
|
||||||
// Низкоуровневые помощники применения атрибутов к ad9361-phy
|
// Низкоуровневые помощники применения атрибутов к ad9361-phy
|
||||||
procedure ApplyRxLO(Hz: Double);
|
procedure ApplyRxLO(Hz: Double);
|
||||||
@@ -174,6 +176,7 @@ type
|
|||||||
procedure SendDUCIQ(const IData, QData: array of Integer); override;
|
procedure SendDUCIQ(const IData, QData: array of Integer); override;
|
||||||
procedure ClearDUCIQQueue; override;
|
procedure ClearDUCIQQueue; override;
|
||||||
function ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; override;
|
function ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; override;
|
||||||
|
function RxGainNowDb: Double; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Стабильный 6-байтовый ключ (synthetic MAC) из serial/URI — для per-device
|
// Стабильный 6-байтовый ключ (synthetic MAC) из serial/URI — для per-device
|
||||||
@@ -413,6 +416,7 @@ begin
|
|||||||
FTxStreaming := False; FTxRatio := 8;
|
FTxStreaming := False; FTxRatio := 8;
|
||||||
FTeleThread := nil; FTeleValid := False;
|
FTeleThread := nil; FTeleValid := False;
|
||||||
FTeleTemp := TELEMETRY_NONE; FTeleSoc := TELEMETRY_NONE; FTeleRssi := TELEMETRY_NONE;
|
FTeleTemp := TELEMETRY_NONE; FTeleSoc := TELEMETRY_NONE; FTeleRssi := TELEMETRY_NONE;
|
||||||
|
FTeleGain := TELEMETRY_NONE;
|
||||||
InitCriticalSection(FTeleLock);
|
InitCriticalSection(FTeleLock);
|
||||||
FTxAttDb := PLUTO_TX_OFF; // на старте TX выключен (макс. аттенюация)
|
FTxAttDb := PLUTO_TX_OFF; // на старте TX выключен (макс. аттенюация)
|
||||||
// FIFO на ~4 TX-буфера запаса под джиттер продюсера/консьюмера. Окончательный
|
// FIFO на ~4 TX-буфера запаса под джиттер продюсера/консьюмера. Окончательный
|
||||||
@@ -1133,7 +1137,8 @@ begin
|
|||||||
LeaveCriticalSection(FTxLock);
|
LeaveCriticalSection(FTxLock);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TPlutoBackend.PollTelemetryBlocking(out TempC, SocTempC, RSSIdB: Double): Boolean;
|
function TPlutoBackend.PollTelemetryBlocking(out TempC, SocTempC, RSSIdB,
|
||||||
|
GainDb: Double): Boolean;
|
||||||
// БЛОКИРУЮЩИЙ опрос железа — только из FTeleThread (каждое чтение = USB round-trip):
|
// БЛОКИРУЮЩИЙ опрос железа — только из FTeleThread (каждое чтение = USB round-trip):
|
||||||
// ad9361-phy temp0/input — температура трансивера в миллиградусах C (longlong);
|
// ad9361-phy temp0/input — температура трансивера в миллиградусах C (longlong);
|
||||||
// xadc temp0 — температура Zynq SoC/FPGA; сырой raw + offset (longlong)
|
// xadc temp0 — температура Zynq SoC/FPGA; сырой raw + offset (longlong)
|
||||||
@@ -1149,6 +1154,7 @@ begin
|
|||||||
TempC := TELEMETRY_NONE;
|
TempC := TELEMETRY_NONE;
|
||||||
SocTempC := TELEMETRY_NONE;
|
SocTempC := TELEMETRY_NONE;
|
||||||
RSSIdB := TELEMETRY_NONE;
|
RSSIdB := TELEMETRY_NONE;
|
||||||
|
GainDb := TELEMETRY_NONE;
|
||||||
Result := False;
|
Result := False;
|
||||||
if (not FConnected) or (FPhy = nil) or (not IIOLoaded) then Exit;
|
if (not FConnected) or (FPhy = nil) or (not IIOLoaded) then Exit;
|
||||||
|
|
||||||
@@ -1190,6 +1196,29 @@ begin
|
|||||||
RSSIdB := d;
|
RSSIdB := d;
|
||||||
Result := True;
|
Result := True;
|
||||||
end;
|
end;
|
||||||
|
// hardwaregain: в режимах AGC железо крутит усиление само, и без него шкала
|
||||||
|
// dBm «дышит» вместе с ним (см. TRadioController.FrontEndCalDB).
|
||||||
|
if (Ch <> nil) and Assigned(iio_channel_attr_read_double)
|
||||||
|
and (iio_channel_attr_read_double(Ch, 'hardwaregain', d) = 0) then
|
||||||
|
begin
|
||||||
|
GainDb := d;
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPlutoBackend.RxGainNowDb: Double;
|
||||||
|
// В ручном режиме значение известно точно и без опроса железа; в AGC-режимах
|
||||||
|
// отдаём последний снимок телеметрии (обновляется ~2 Гц — быстрый AGC всё
|
||||||
|
// равно шевелится быстрее, для стабильной шкалы нужен MANUAL или SLOW).
|
||||||
|
begin
|
||||||
|
if FGainMode = 'manual' then Exit(FGainDb);
|
||||||
|
EnterCriticalSection(FTeleLock);
|
||||||
|
try
|
||||||
|
if FTeleValid and (FTeleGain > TELEMETRY_NONE) then Result := FTeleGain
|
||||||
|
else Result := FGainDb;
|
||||||
|
finally
|
||||||
|
LeaveCriticalSection(FTeleLock);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1244,7 +1273,7 @@ const
|
|||||||
POLL_MS = 500; // ~2 Гц: чаще незачем, каждый опрос ~30 мс USB-транзакций
|
POLL_MS = 500; // ~2 Гц: чаще незачем, каждый опрос ~30 мс USB-транзакций
|
||||||
STEP_MS = 25; // мелкий шаг сна — чтобы Terminate отрабатывал быстро
|
STEP_MS = 25; // мелкий шаг сна — чтобы Terminate отрабатывал быстро
|
||||||
var
|
var
|
||||||
t, s, r: Double;
|
t, s, r, g: Double;
|
||||||
ok: Boolean;
|
ok: Boolean;
|
||||||
Waited: Integer;
|
Waited: Integer;
|
||||||
begin
|
begin
|
||||||
@@ -1252,7 +1281,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
if FBk.FConnected then
|
if FBk.FConnected then
|
||||||
begin
|
begin
|
||||||
ok := FBk.PollTelemetryBlocking(t, s, r);
|
ok := FBk.PollTelemetryBlocking(t, s, r, g);
|
||||||
if ok then
|
if ok then
|
||||||
begin
|
begin
|
||||||
EnterCriticalSection(FBk.FTeleLock);
|
EnterCriticalSection(FBk.FTeleLock);
|
||||||
@@ -1260,6 +1289,7 @@ begin
|
|||||||
FBk.FTeleTemp := t;
|
FBk.FTeleTemp := t;
|
||||||
FBk.FTeleSoc := s;
|
FBk.FTeleSoc := s;
|
||||||
FBk.FTeleRssi := r;
|
FBk.FTeleRssi := r;
|
||||||
|
FBk.FTeleGain := g;
|
||||||
FBk.FTeleValid := True;
|
FBk.FTeleValid := True;
|
||||||
finally
|
finally
|
||||||
LeaveCriticalSection(FBk.FTeleLock);
|
LeaveCriticalSection(FBk.FTeleLock);
|
||||||
|
|||||||
@@ -199,6 +199,11 @@ type
|
|||||||
// RSSI приёмника (дБ). HPSDR — нет такой (использует HP-status), возвращает False.
|
// RSSI приёмника (дБ). HPSDR — нет такой (использует HP-status), возвращает False.
|
||||||
// Недоступные значения = TELEMETRY_NONE.
|
// Недоступные значения = TELEMETRY_NONE.
|
||||||
function ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; virtual;
|
function ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; virtual;
|
||||||
|
// Текущее усиление приёмного тракта (дБ), если оно есть в железе и может
|
||||||
|
// меняться само: AD936x hardwaregain (в режимах AGC железо крутит его сам).
|
||||||
|
// Нужно для абсолютной шкалы уровней — контроллер вычитает его из показаний.
|
||||||
|
// HPSDR: усиления нет (есть аттенюатор), возвращает TELEMETRY_NONE.
|
||||||
|
function RxGainNowDb: Double; virtual;
|
||||||
|
|
||||||
// ---- Свойства состояния (полиморфные через виртуальные геттеры) ----
|
// ---- Свойства состояния (полиморфные через виртуальные геттеры) ----
|
||||||
property Connected: Boolean read GetConnected;
|
property Connected: Boolean read GetConnected;
|
||||||
@@ -300,6 +305,11 @@ begin
|
|||||||
Result := False;
|
Result := False;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TRadioBackend.RxGainNowDb: Double;
|
||||||
|
begin
|
||||||
|
Result := TELEMETRY_NONE;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioBackend.SetTxAtten(Db: Double);
|
procedure TRadioBackend.SetTxAtten(Db: Double);
|
||||||
begin
|
begin
|
||||||
end;
|
end;
|
||||||
|
|||||||
+28
-10
@@ -391,6 +391,9 @@ type
|
|||||||
// RSSI приёмника дБ. Опрашиваются ServicePlutoTelemetry с UI-таймера;
|
// RSSI приёмника дБ. Опрашиваются ServicePlutoTelemetry с UI-таймера;
|
||||||
// TELEMETRY_NONE = нет данных.
|
// TELEMETRY_NONE = нет данных.
|
||||||
FLastPlutoTempC, FLastPlutoSocTempC, FLastPlutoRSSIdB: Double;
|
FLastPlutoTempC, FLastPlutoSocTempC, FLastPlutoRSSIdB: Double;
|
||||||
|
// Фактическое усиление RX-тракта AD936x (дБ) — в режимах AGC его крутит
|
||||||
|
// железо. Снимается тем же опросом; UI показывает его в блоке RF AGC.
|
||||||
|
FLastPlutoGainDb: Double;
|
||||||
FPlutoTeleTick: QWord; // GetTickCount64 последнего опроса (троттлинг)
|
FPlutoTeleTick: QWord; // GetTickCount64 последнего опроса (троттлинг)
|
||||||
FPlutoTempBad: Integer; // подряд отброшенных выбросов температуры (анти-залипание)
|
FPlutoTempBad: Integer; // подряд отброшенных выбросов температуры (анти-залипание)
|
||||||
|
|
||||||
@@ -635,8 +638,9 @@ type
|
|||||||
// Офсет спектра/водопада (дБ) для дисплея: PanId 0 = главный, 1..N — пан.
|
// Офсет спектра/водопада (дБ) для дисплея: PanId 0 = главный, 1..N — пан.
|
||||||
// Колбэк движка (display-поток), см. TOnDispCalQuery.
|
// Колбэк движка (display-поток), см. TOnDispCalQuery.
|
||||||
function DispCalFor(PanId: Integer): Double;
|
function DispCalFor(PanId: Integer): Double;
|
||||||
// Поправка на шаговый аттенюатор ADC (дБ) — входит в оба офсета выше.
|
// Поправка на регулируемое усиление входа (аттенюатор ADC у openHPSDR,
|
||||||
function AttenCalDB: Double;
|
// hardwaregain у AD936x) — входит в оба офсета выше.
|
||||||
|
function FrontEndCalDB: Double;
|
||||||
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
|
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
|
||||||
function SliceCount: Integer;
|
function SliceCount: Integer;
|
||||||
function GetSlice(Id: Integer; out S: TCtrlSlice): Boolean;
|
function GetSlice(Id: Integer; out S: TCtrlSlice): Boolean;
|
||||||
@@ -966,6 +970,7 @@ begin
|
|||||||
FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1;
|
FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1;
|
||||||
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoSocTempC := TELEMETRY_NONE;
|
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoSocTempC := TELEMETRY_NONE;
|
||||||
FLastPlutoRSSIdB := TELEMETRY_NONE;
|
FLastPlutoRSSIdB := TELEMETRY_NONE;
|
||||||
|
FLastPlutoGainDb := TELEMETRY_NONE;
|
||||||
FPlutoTeleTick := 0; FPlutoTempBad := 0;
|
FPlutoTeleTick := 0; FPlutoTempBad := 0;
|
||||||
FillChar(FDevMAC, SizeOf(FDevMAC), 0);
|
FillChar(FDevMAC, SizeOf(FDevMAC), 0);
|
||||||
end;
|
end;
|
||||||
@@ -2097,16 +2102,25 @@ end;
|
|||||||
function TRadioController.SMeterCalFor(VisibleHz: Double): Double;
|
function TRadioController.SMeterCalFor(VisibleHz: Double): Double;
|
||||||
begin
|
begin
|
||||||
Result := CalLookup(VisibleHz, FCal.SMeterBandDB, FCal.SMeterXvtrDB)
|
Result := CalLookup(VisibleHz, FCal.SMeterBandDB, FCal.SMeterXvtrDB)
|
||||||
+ AttenCalDB;
|
+ FrontEndCalDB;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TRadioController.AttenCalDB: Double;
|
function TRadioController.FrontEndCalDB: Double;
|
||||||
// Шаговый аттенюатор ADC режет вход — чтобы шкала dBm оставалась абсолютной,
|
// Поправка на регулируемое усиление входного тракта — то, что меняется ручкой
|
||||||
// его дБ возвращаются в показания (так же делают Thetis и piHPSDR). У AD936x
|
// или само и не должно двигать абсолютную шкалу dBm.
|
||||||
// такого аттенюатора нет: там усиление тракта — hardwaregain.
|
// openHPSDR: шаговый аттенюатор ADC режет вход → его дБ возвращаем в
|
||||||
|
// показания (так же делают Thetis и piHPSDR).
|
||||||
|
// AD936x: hardwaregain поднимает уровень (а в режимах AGC железо крутит
|
||||||
|
// его само) → вычитаем текущее значение.
|
||||||
|
// Постоянная часть тракта (LNB, кабель, конвертер) остаётся за таблицами
|
||||||
|
// калибровки на диапазон/трансвертер.
|
||||||
|
var G: Double;
|
||||||
begin
|
begin
|
||||||
if IsPluto then Result := 0.0
|
if not IsPluto then Exit(FAtten);
|
||||||
else Result := FAtten;
|
Result := 0.0;
|
||||||
|
if FNetwork = nil then Exit;
|
||||||
|
G := FNetwork.RxGainNowDb;
|
||||||
|
if G > TELEMETRY_NONE then Result := -G;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TRadioController.DispCalFor(PanId: Integer): Double;
|
function TRadioController.DispCalFor(PanId: Integer): Double;
|
||||||
@@ -2117,7 +2131,7 @@ begin
|
|||||||
if (PanId > 0) and (PanId < MAX_PANS) then Hz := FPanFreqHz[PanId]
|
if (PanId > 0) and (PanId < MAX_PANS) then Hz := FPanFreqHz[PanId]
|
||||||
else Hz := FCenterFreq;
|
else Hz := FCenterFreq;
|
||||||
if Hz <= 0 then Exit(0.0);
|
if Hz <= 0 then Exit(0.0);
|
||||||
Result := CalLookup(Hz, FCal.DispBandDB, FCal.DispXvtrDB) + AttenCalDB;
|
Result := CalLookup(Hz, FCal.DispBandDB, FCal.DispXvtrDB) + FrontEndCalDB;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetSliceDevice(Id, DevIndex: Integer;
|
procedure TRadioController.SetSliceDevice(Id, DevIndex: Integer;
|
||||||
@@ -5046,6 +5060,7 @@ begin
|
|||||||
FActiveDDC := 0;
|
FActiveDDC := 0;
|
||||||
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoSocTempC := TELEMETRY_NONE;
|
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoSocTempC := TELEMETRY_NONE;
|
||||||
FLastPlutoRSSIdB := TELEMETRY_NONE;
|
FLastPlutoRSSIdB := TELEMETRY_NONE;
|
||||||
|
FLastPlutoGainDb := TELEMETRY_NONE;
|
||||||
FPlutoTeleTick := 0; FPlutoTempBad := 0;
|
FPlutoTeleTick := 0; FPlutoTempBad := 0;
|
||||||
Changed(rfRunning); // безусловный сброс run-кнопки/MOX.Enabled/sleep (как старый хвост STOP)
|
Changed(rfRunning); // безусловный сброс run-кнопки/MOX.Enabled/sleep (как старый хвост STOP)
|
||||||
end;
|
end;
|
||||||
@@ -5504,6 +5519,9 @@ begin
|
|||||||
|
|
||||||
// RSSI быстро меняется с сигналом — без сглаживания; обновляем, только если валиден.
|
// RSSI быстро меняется с сигналом — без сглаживания; обновляем, только если валиден.
|
||||||
if r > TELEMETRY_NONE then FLastPlutoRSSIdB := r;
|
if r > TELEMETRY_NONE then FLastPlutoRSSIdB := r;
|
||||||
|
// Фактическое усиление тракта (в MANUAL = заданное, в AGC — снятое с железа).
|
||||||
|
FLastPlutoGainDb := FNetwork.RxGainNowDb;
|
||||||
|
Changed(rfRxGain); // UI: живая цифра усиления в блоке RF AGC
|
||||||
|
|
||||||
Changed(rfSupply); // поле телеметрии статус-бара (поле 3); web-адаптер тоже
|
Changed(rfSupply); // поле телеметрии статус-бара (поле 3); web-адаптер тоже
|
||||||
end;
|
end;
|
||||||
|
|||||||
Reference in New Issue
Block a user