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:
2026-07-21 11:50:28 +03:00
co-authored by Claude Opus 4.8
parent 6de8c00689
commit a95292d2c0
4 changed files with 84 additions and 16 deletions
+28 -10
View File
@@ -391,6 +391,9 @@ type
// RSSI приёмника дБ. Опрашиваются ServicePlutoTelemetry с UI-таймера;
// TELEMETRY_NONE = нет данных.
FLastPlutoTempC, FLastPlutoSocTempC, FLastPlutoRSSIdB: Double;
// Фактическое усиление RX-тракта AD936x (дБ) — в режимах AGC его крутит
// железо. Снимается тем же опросом; UI показывает его в блоке RF AGC.
FLastPlutoGainDb: Double;
FPlutoTeleTick: QWord; // GetTickCount64 последнего опроса (троттлинг)
FPlutoTempBad: Integer; // подряд отброшенных выбросов температуры (анти-залипание)
@@ -635,8 +638,9 @@ type
// Офсет спектра/водопада (дБ) для дисплея: PanId 0 = главный, 1..N — пан.
// Колбэк движка (display-поток), см. TOnDispCalQuery.
function DispCalFor(PanId: Integer): Double;
// Поправка на шаговый аттенюатор ADC (дБ) — входит в оба офсета выше.
function AttenCalDB: Double;
// Поправка на регулируемое усиление входа (аттенюатор ADC у openHPSDR,
// hardwaregain у AD936x) — входит в оба офсета выше.
function FrontEndCalDB: Double;
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
function SliceCount: Integer;
function GetSlice(Id: Integer; out S: TCtrlSlice): Boolean;
@@ -966,6 +970,7 @@ begin
FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1;
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoSocTempC := TELEMETRY_NONE;
FLastPlutoRSSIdB := TELEMETRY_NONE;
FLastPlutoGainDb := TELEMETRY_NONE;
FPlutoTeleTick := 0; FPlutoTempBad := 0;
FillChar(FDevMAC, SizeOf(FDevMAC), 0);
end;
@@ -2097,16 +2102,25 @@ end;
function TRadioController.SMeterCalFor(VisibleHz: Double): Double;
begin
Result := CalLookup(VisibleHz, FCal.SMeterBandDB, FCal.SMeterXvtrDB)
+ AttenCalDB;
+ FrontEndCalDB;
end;
function TRadioController.AttenCalDB: Double;
// Шаговый аттенюатор ADC режет вход — чтобы шкала dBm оставалась абсолютной,
// его дБ возвращаются в показания (так же делают Thetis и piHPSDR). У AD936x
// такого аттенюатора нет: там усиление тракта — hardwaregain.
function TRadioController.FrontEndCalDB: Double;
// Поправка на регулируемое усиление входного тракта — то, что меняется ручкой
// или само и не должно двигать абсолютную шкалу dBm.
// openHPSDR: шаговый аттенюатор ADC режет вход → его дБ возвращаем в
// показания (так же делают Thetis и piHPSDR).
// AD936x: hardwaregain поднимает уровень (а в режимах AGC железо крутит
// его само) → вычитаем текущее значение.
// Постоянная часть тракта (LNB, кабель, конвертер) остаётся за таблицами
// калибровки на диапазон/трансвертер.
var G: Double;
begin
if IsPluto then Result := 0.0
else Result := FAtten;
if not IsPluto then Exit(FAtten);
Result := 0.0;
if FNetwork = nil then Exit;
G := FNetwork.RxGainNowDb;
if G > TELEMETRY_NONE then Result := -G;
end;
function TRadioController.DispCalFor(PanId: Integer): Double;
@@ -2117,7 +2131,7 @@ begin
if (PanId > 0) and (PanId < MAX_PANS) then Hz := FPanFreqHz[PanId]
else Hz := FCenterFreq;
if Hz <= 0 then Exit(0.0);
Result := CalLookup(Hz, FCal.DispBandDB, FCal.DispXvtrDB) + AttenCalDB;
Result := CalLookup(Hz, FCal.DispBandDB, FCal.DispXvtrDB) + FrontEndCalDB;
end;
procedure TRadioController.SetSliceDevice(Id, DevIndex: Integer;
@@ -5046,6 +5060,7 @@ begin
FActiveDDC := 0;
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoSocTempC := TELEMETRY_NONE;
FLastPlutoRSSIdB := TELEMETRY_NONE;
FLastPlutoGainDb := TELEMETRY_NONE;
FPlutoTeleTick := 0; FPlutoTempBad := 0;
Changed(rfRunning); // безусловный сброс run-кнопки/MOX.Enabled/sleep (как старый хвост STOP)
end;
@@ -5504,6 +5519,9 @@ begin
// RSSI быстро меняется с сигналом — без сглаживания; обновляем, только если валиден.
if r > TELEMETRY_NONE then FLastPlutoRSSIdB := r;
// Фактическое усиление тракта (в MANUAL = заданное, в AGC — снятое с железа).
FLastPlutoGainDb := FNetwork.RxGainNowDb;
Changed(rfRxGain); // UI: живая цифра усиления в блоке RF AGC
Changed(rfSupply); // поле телеметрии статус-бара (поле 3); web-адаптер тоже
end;