diff --git a/MainForm.pas b/MainForm.pas index 394da30..1efe48d 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -8004,10 +8004,20 @@ begin for i := 0 to 3 do if BtnPlutoGain[i] <> nil then StyleButton(BtnPlutoGain[i], BtnPlutoGain[i].Tag = FController.FRxGainMode); - TrkRxGain.Position := FController.FRxGainDb; TrkRxGain.Enabled := (FController.FRxGainMode = 0); // активен только в manual 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 LblRxGain.Caption := 'AGC'; finally diff --git a/PlutoBackend.pas b/PlutoBackend.pas index 8e65373..040b949 100644 --- a/PlutoBackend.pas +++ b/PlutoBackend.pas @@ -109,11 +109,13 @@ type FTeleTemp: Double; // TELEMETRY_NONE, пока не прочитано FTeleSoc: Double; FTeleRssi: Double; + FTeleGain: Double; // hardwaregain RX, дБ (в AGC-режимах крутит железо) FTeleValid: Boolean; // был ли хоть один успешный опрос procedure StartTelemetry; procedure StopTelemetry; // Блокирующий опрос железа: только из FTeleThread. - function PollTelemetryBlocking(out TempC, SocTempC, RSSIdB: Double): Boolean; + function PollTelemetryBlocking(out TempC, SocTempC, RSSIdB, + GainDb: Double): Boolean; // Низкоуровневые помощники применения атрибутов к ad9361-phy procedure ApplyRxLO(Hz: Double); @@ -174,6 +176,7 @@ type procedure SendDUCIQ(const IData, QData: array of Integer); override; procedure ClearDUCIQQueue; override; function ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; override; + function RxGainNowDb: Double; override; end; // Стабильный 6-байтовый ключ (synthetic MAC) из serial/URI — для per-device @@ -413,6 +416,7 @@ begin FTxStreaming := False; FTxRatio := 8; FTeleThread := nil; FTeleValid := False; FTeleTemp := TELEMETRY_NONE; FTeleSoc := TELEMETRY_NONE; FTeleRssi := TELEMETRY_NONE; + FTeleGain := TELEMETRY_NONE; InitCriticalSection(FTeleLock); FTxAttDb := PLUTO_TX_OFF; // на старте TX выключен (макс. аттенюация) // FIFO на ~4 TX-буфера запаса под джиттер продюсера/консьюмера. Окончательный @@ -1133,7 +1137,8 @@ begin LeaveCriticalSection(FTxLock); end; -function TPlutoBackend.PollTelemetryBlocking(out TempC, SocTempC, RSSIdB: Double): Boolean; +function TPlutoBackend.PollTelemetryBlocking(out TempC, SocTempC, RSSIdB, + GainDb: Double): Boolean; // БЛОКИРУЮЩИЙ опрос железа — только из FTeleThread (каждое чтение = USB round-trip): // ad9361-phy temp0/input — температура трансивера в миллиградусах C (longlong); // xadc temp0 — температура Zynq SoC/FPGA; сырой raw + offset (longlong) @@ -1149,6 +1154,7 @@ begin TempC := TELEMETRY_NONE; SocTempC := TELEMETRY_NONE; RSSIdB := TELEMETRY_NONE; + GainDb := TELEMETRY_NONE; Result := False; if (not FConnected) or (FPhy = nil) or (not IIOLoaded) then Exit; @@ -1190,6 +1196,29 @@ begin RSSIdB := d; Result := True; 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; @@ -1244,7 +1273,7 @@ const POLL_MS = 500; // ~2 Гц: чаще незачем, каждый опрос ~30 мс USB-транзакций STEP_MS = 25; // мелкий шаг сна — чтобы Terminate отрабатывал быстро var - t, s, r: Double; + t, s, r, g: Double; ok: Boolean; Waited: Integer; begin @@ -1252,7 +1281,7 @@ begin begin if FBk.FConnected then begin - ok := FBk.PollTelemetryBlocking(t, s, r); + ok := FBk.PollTelemetryBlocking(t, s, r, g); if ok then begin EnterCriticalSection(FBk.FTeleLock); @@ -1260,6 +1289,7 @@ begin FBk.FTeleTemp := t; FBk.FTeleSoc := s; FBk.FTeleRssi := r; + FBk.FTeleGain := g; FBk.FTeleValid := True; finally LeaveCriticalSection(FBk.FTeleLock); diff --git a/RadioBackend.pas b/RadioBackend.pas index ad73241..744a03b 100644 --- a/RadioBackend.pas +++ b/RadioBackend.pas @@ -199,6 +199,11 @@ type // RSSI приёмника (дБ). HPSDR — нет такой (использует HP-status), возвращает False. // Недоступные значения = TELEMETRY_NONE. function ReadTelemetry(out TempC, SocTempC, RSSIdB: Double): Boolean; virtual; + // Текущее усиление приёмного тракта (дБ), если оно есть в железе и может + // меняться само: AD936x hardwaregain (в режимах AGC железо крутит его сам). + // Нужно для абсолютной шкалы уровней — контроллер вычитает его из показаний. + // HPSDR: усиления нет (есть аттенюатор), возвращает TELEMETRY_NONE. + function RxGainNowDb: Double; virtual; // ---- Свойства состояния (полиморфные через виртуальные геттеры) ---- property Connected: Boolean read GetConnected; @@ -300,6 +305,11 @@ begin Result := False; end; +function TRadioBackend.RxGainNowDb: Double; +begin + Result := TELEMETRY_NONE; +end; + procedure TRadioBackend.SetTxAtten(Db: Double); begin end; diff --git a/RadioController.pas b/RadioController.pas index 04c3c28..31da849 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -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;