mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 20:57:32 +00:00
feature(calibration): частотная калибровка опорника (ppm) + TO Error в слоте QO-100
Пункт 3 плана. Ручные подгонки остаются: ppm работает ПОД ними, а не вместо — уход тракта где-то больше, где-то меньше. - Settings: TCalibration.FreqCalPPM (JSON cal_freq_ppm, +-100 ppm, per-device). Смысл знака: «опора выше номинала на N ppm», положительное значение поднимает показываемые частоты. - RadioBackend: виртуальный SetFreqCalPPM. - HPSDRNetwork: CalFreq(Hz) = Hz / (1 + ppm) в частотном слове — все четыре точки (главный DDC, DDC панов, feedback-DDC PureSignal, DUC передачи). Тактовый генератор радио менять нечем, поэтому правим запрос. - PlutoBackend: штатный xo_correction в ad9361-phy (device-attr биндинги добавлены в IIOBindings). Номинал XO снимается при коннекте с округлением до 100 кГц, чтобы наша же коррекция с прошлой сессии не накапливалась; после записи LO переписываются, иначе правка ждала бы следующей перестройки. Одна цифра лечит RX и TX на всех диапазонах. - QO-100: TXvtrEntry.TxLOError (JSON tx_lo_error) + поле «TO Error (Hz)» на странице QO-100. Сам транспондер-offset держим стандартным (8089.5 МГц), уход тракта правим поправкой; ActiveTXFreqHz учитывает её, подсказка внизу страницы считает TX-частоту уже с ней. - UI-фиксы вкладки Calibration: длинные подсказки переведены на несколько строк (WordWrap — TLabel по умолчанию режется по краю группы), заголовки таблиц укорочены и не заезжают под кнопку «= S-meter»; RelayoutCalTab сбрасывает прокрутку страницы перед перекладкой (SetBounds в TScrollBox задаёт координаты относительно прокрученного клиента). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+67
-8
@@ -183,6 +183,8 @@ type
|
||||
FLblDispBandHdr: TLabel;
|
||||
FLblDispXvtrHdr: TLabel;
|
||||
FBtnDispFromSM: TFlatButton; // «= S-meter» — копия таблицы
|
||||
FCalGrpFreq: TPanel; // Reference frequency
|
||||
FEdFreqCalPPM: TFlatFloatSpinEdit; // калибровка опорника, ppm
|
||||
FCalIsPluto: Boolean; // активное железо — AD936x
|
||||
FCalXvtrOn: array[0..CFG_XVTR_COUNT-1] of Boolean; // слот трансвертера включён
|
||||
FCalXvtrName: array[0..CFG_XVTR_COUNT-1] of string; // подпись слота
|
||||
@@ -316,6 +318,7 @@ type
|
||||
FQoLnbLO: TFlatFloatSpinEdit; // МГц — LNB LO (= LOOffset, RX)
|
||||
FQoLoErr: TFlatFloatSpinEdit; // Гц — LO Error (= LOError, живая калибровка LNB / beacon-lock)
|
||||
FQoTxLO: TFlatFloatSpinEdit; // МГц — транспондер-offset (= TxLOOffset)
|
||||
FQoTxLOErr: TFlatFloatSpinEdit; // Гц — поправка offset'а (TO Error)
|
||||
FQoBegin: TFlatFloatSpinEdit; // МГц — downlink begin
|
||||
FQoEnd: TFlatFloatSpinEdit; // МГц — downlink end
|
||||
FQoRXGain: TFlatFloatSpinEdit; // dB
|
||||
@@ -1946,6 +1949,17 @@ procedure TSettingsForm.BuildCalibrationTab;
|
||||
const
|
||||
GRP3_H0 = 200; // стартовая высота S-метра, уточняется в RelayoutCalTab
|
||||
|
||||
// Длинные подсказки не влезают в ширину группы — переводим на несколько
|
||||
// строк (TLabel по умолчанию AutoSize=True и режется по краю).
|
||||
procedure WrapLbl(L: TLabel; AW, AH: Integer);
|
||||
begin
|
||||
if L = nil then Exit;
|
||||
L.AutoSize := False;
|
||||
L.WordWrap := True;
|
||||
L.Width := AW;
|
||||
L.Height := AH;
|
||||
end;
|
||||
|
||||
function MakeCalSpin(AParent: TWinControl; ALeft, ATop: Integer;
|
||||
AMin, AMax, AInc: Double; ADec: Integer;
|
||||
ADefault: Double): TFlatFloatSpinEdit;
|
||||
@@ -2021,7 +2035,8 @@ begin
|
||||
Lbl := MakeLbl(Grp,
|
||||
'FWD/REV multiplier per band: 1.00 = no correction, >1 if meter under-reads '
|
||||
+ '(e.g. 88 W shown at real 100 W -> 1.14). SWR is not affected.',
|
||||
CAL_PAD, CAL_R1 + CAL_ROW_H + 10, 650);
|
||||
CAL_PAD, CAL_R1 + CAL_ROW_H + 8, 650);
|
||||
WrapLbl(Lbl, 650, 34);
|
||||
|
||||
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||
begin
|
||||
@@ -2054,6 +2069,7 @@ begin
|
||||
Lbl := MakeLbl(Grp, 'Defaults 360 mV / 120 mV/A (ACS712-style, Orion MkII)',
|
||||
CAL_PAD + CAL_VA_LBL_W + 10 + CAL_ED_W + 14,
|
||||
CAL_R1 + CAL_ROW_H + 6, 320);
|
||||
WrapLbl(Lbl, 320, 34);
|
||||
|
||||
// ── S-meter ─────────────────────────────────────────────────────────────
|
||||
GTop := GTop + CAL_GRP2_H + 14;
|
||||
@@ -2063,7 +2079,8 @@ begin
|
||||
|
||||
FLblSMBandHdr := MakeLbl(Grp,
|
||||
'Offset (dB) added to S-meter readings — per band (+ = meter reads higher).',
|
||||
CAL_PAD, 30, 650);
|
||||
CAL_PAD, 30, 540);
|
||||
WrapLbl(FLblSMBandHdr, 540, 18);
|
||||
|
||||
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||
begin
|
||||
@@ -2093,8 +2110,9 @@ begin
|
||||
Grp := FCalGrpDisp;
|
||||
|
||||
FLblDispBandHdr := MakeLbl(Grp,
|
||||
'Offset (dB) added to spectrum/waterfall levels — per band (moves the whole '
|
||||
+ 'trace incl. noise floor).', CAL_PAD, 30, 650);
|
||||
'Offset (dB) added to spectrum/waterfall — per band (moves the whole trace).',
|
||||
CAL_PAD, 30, 540);
|
||||
WrapLbl(FLblDispBandHdr, 540, 18);
|
||||
|
||||
FBtnDispFromSM := TFlatButton.Create(Self);
|
||||
FBtnDispFromSM.Parent := Grp;
|
||||
@@ -2130,6 +2148,27 @@ begin
|
||||
FEdDispXvtr[i].Tag := i;
|
||||
end;
|
||||
|
||||
// ── Reference frequency ─────────────────────────────────────────────────
|
||||
// Одна поправка опорника на всё железо (Frequency Calibration в Thetis):
|
||||
// у openHPSDR правится частотное слово DDC/DUC, у AD936x — xo_correction.
|
||||
// Ручные LO Error / TO Error трансвертеров остаются сверху: тракт тракту
|
||||
// рознь, и где-то уход больше, где-то меньше.
|
||||
FCalGrpFreq := MakeGroupPanel(FPageCalib, 'Reference frequency',
|
||||
CAL_MARGIN, GTop, CAL_GRP_W, CAL_GRP2_H);
|
||||
Grp := FCalGrpFreq;
|
||||
|
||||
Lbl := MakeLbl(Grp, 'Oscillator error (ppm)', CAL_PAD, CAL_R1 + 6, CAL_VA_LBL_W);
|
||||
FEdFreqCalPPM := MakeCalSpin(Grp, CAL_PAD + CAL_VA_LBL_W + 10, CAL_R1,
|
||||
-100.0, 100.0, 0.01, 3, 0.0);
|
||||
|
||||
Lbl := MakeLbl(Grp,
|
||||
'Positive value raises displayed frequencies. Measure against a known '
|
||||
+ 'reference (beacon, WWV, GPSDO): if a 10 MHz signal reads 10 Hz low, '
|
||||
+ 'that is +1.0 ppm. Applies to RX and TX on every band; per-transverter '
|
||||
+ 'LO Error / TO Error still apply on top.',
|
||||
CAL_PAD, CAL_R1 + CAL_ROW_H + 6, 650);
|
||||
WrapLbl(Lbl, 650, 52);
|
||||
|
||||
RelayoutCalTab; // стартовая раскладка (HF-план, трансвертеры скрыты)
|
||||
|
||||
FLoading := False;
|
||||
@@ -2146,7 +2185,16 @@ var
|
||||
begin
|
||||
// Гард: вкладка ещё строится (или LoadXvtrSettings пришёл раньше неё).
|
||||
if (FCalGrpDet = nil) or (FCalGrpVA = nil) or (FCalGrpSM = nil)
|
||||
or (FCalGrpDisp = nil) or (FLblDispXvtrHdr = nil) then Exit;
|
||||
or (FCalGrpDisp = nil) or (FLblDispXvtrHdr = nil)
|
||||
or (FCalGrpFreq = nil) then Exit;
|
||||
|
||||
// TScrollBox двигает детей при прокрутке, и SetBounds ниже задаёт координаты
|
||||
// ОТНОСИТЕЛЬНО прокрученного клиента. Если страницу успели прокрутить (у
|
||||
// openHPSDR она длиннее на две группы), после перекладки прокрутка
|
||||
// схлопывается — и группы остаются смещёнными вниз ровно на прежний скролл.
|
||||
// Поэтому сначала возвращаем страницу в ноль.
|
||||
FPageCalib.VertScrollBar.Position := 0;
|
||||
FPageCalib.HorzScrollBar.Position := 0;
|
||||
|
||||
// ---- подписи/видимость строк Det. Cal. (своя сетка, уже с подсказкой) ----
|
||||
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||
@@ -2189,6 +2237,8 @@ begin
|
||||
FCalGrpSM.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, HSM);
|
||||
GTop := GTop + HSM + 14;
|
||||
FCalGrpDisp.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, HDisp);
|
||||
GTop := GTop + HDisp + 14;
|
||||
FCalGrpFreq.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, CAL_GRP2_H);
|
||||
end;
|
||||
|
||||
function TSettingsForm.LayoutCalOffsets(const BandLbl: array of TLabel;
|
||||
@@ -2292,6 +2342,7 @@ begin
|
||||
if FEdSMXvtr[i] <> nil then FCalConfig.SMeterXvtrDB[i] := FEdSMXvtr[i].Value;
|
||||
if FEdDispXvtr[i] <> nil then FCalConfig.DispXvtrDB[i] := FEdDispXvtr[i].Value;
|
||||
end;
|
||||
FCalConfig.FreqCalPPM := FEdFreqCalPPM.Value;
|
||||
FCalConfig.SupplyVoltCal := FEdSupplyVoltCal.Value;
|
||||
FCalConfig.AmpVoffMV := FEdAmpVoff.Value;
|
||||
FCalConfig.AmpSensMVA := FEdAmpSens.Value;
|
||||
@@ -2343,6 +2394,7 @@ begin
|
||||
if FEdSMXvtr[i] <> nil then FEdSMXvtr[i].Value := C.SMeterXvtrDB[i];
|
||||
if FEdDispXvtr[i] <> nil then FEdDispXvtr[i].Value := C.DispXvtrDB[i];
|
||||
end;
|
||||
FEdFreqCalPPM.Value := EnsureRange(C.FreqCalPPM, -100.0, 100.0);
|
||||
FEdSupplyVoltCal.Value := C.SupplyVoltCal;
|
||||
FEdAmpVoff.Value := C.AmpVoffMV;
|
||||
FEdAmpSens.Value := C.AmpSensMVA;
|
||||
@@ -4355,7 +4407,7 @@ begin
|
||||
L.Font.Name := UI_FONT; L.Font.Size := 9; L.Font.Color := CLR_TEXTDIM;
|
||||
L.WordWrap := True;
|
||||
|
||||
Grp := MakeGroupPanel(FPageQO, 'Transponder', MARGIN, 100, 560, GRP_TITLE_H + 8 * 30 + 16);
|
||||
Grp := MakeGroupPanel(FPageQO, 'Transponder', MARGIN, 100, 560, GRP_TITLE_H + 9 * 30 + 16);
|
||||
|
||||
Y := GRP_TITLE_H + 8;
|
||||
FQoEn := TFlatCheckBox.Create(Self);
|
||||
@@ -4377,6 +4429,11 @@ begin
|
||||
FQoTxLO := QoFloat(Y, 0.0, 50000.0, 0.001, 6);
|
||||
FQoTxLO.Value := 8089.5;
|
||||
|
||||
// TO Error (Гц): сам транспондер-offset держим стандартным (8089.5 МГц),
|
||||
// а уход конкретного тракта (TCXO uplink-SDR, наводка LNB) правим здесь.
|
||||
Inc(Y, 30); QoLabel('TO Error (Hz)', Y);
|
||||
FQoTxLOErr := QoFloat(Y, -999999.0, 999999.0, 1.0, 0);
|
||||
|
||||
Inc(Y, 30); QoLabel('Downlink begin (MHz)', Y);
|
||||
FQoBegin := QoFloat(Y, 0.0, 50000.0, 0.001, 3);
|
||||
FQoBegin.Value := 10489.5;
|
||||
@@ -4398,7 +4455,7 @@ begin
|
||||
|
||||
FQoInfo := TLabel.Create(Self);
|
||||
FQoInfo.Parent := FPageQO;
|
||||
FQoInfo.SetBounds(MARGIN, 100 + GRP_TITLE_H + 8 * 30 + 28, 700, 52);
|
||||
FQoInfo.SetBounds(MARGIN, 100 + GRP_TITLE_H + 9 * 30 + 28, 700, 52);
|
||||
FQoInfo.Font.Color := CLR_TEXTDIM; FQoInfo.Font.Name := 'Courier New';
|
||||
FQoInfo.Font.Size := 9; FQoInfo.WordWrap := True;
|
||||
finally
|
||||
@@ -4412,7 +4469,7 @@ begin
|
||||
if FQoInfo = nil then Exit;
|
||||
dlMid := (FQoBegin.Value + FQoEnd.Value) / 2.0; // МГц
|
||||
rxIf := dlMid - FQoLnbLO.Value; // RX IF в SDR
|
||||
txF := dlMid - FQoTxLO.Value; // TX uplink
|
||||
txF := dlMid - FQoTxLO.Value + FQoTxLOErr.Value / 1.0e6; // TX uplink + TO Error
|
||||
FQoInfo.Caption := Format(
|
||||
'At downlink %.3f MHz: RX IF = %.3f MHz TX = %.3f MHz',
|
||||
[dlMid, rxIf, txF]);
|
||||
@@ -4427,6 +4484,7 @@ begin
|
||||
FXvtrConfig.Entries[QO100_SLOT].LOOffset := FQoLnbLO.Value * 1.0e6;
|
||||
FXvtrConfig.Entries[QO100_SLOT].LOError := FQoLoErr.Value;
|
||||
FXvtrConfig.Entries[QO100_SLOT].TxLOOffset := FQoTxLO.Value * 1.0e6;
|
||||
FXvtrConfig.Entries[QO100_SLOT].TxLOError := FQoTxLOErr.Value;
|
||||
FXvtrConfig.Entries[QO100_SLOT].FreqBegin := FQoBegin.Value * 1.0e6;
|
||||
FXvtrConfig.Entries[QO100_SLOT].FreqEnd := FQoEnd.Value * 1.0e6;
|
||||
FXvtrConfig.Entries[QO100_SLOT].RXGain := FQoRXGain.Value;
|
||||
@@ -4442,6 +4500,7 @@ begin
|
||||
FQoLnbLO.Value := E.LOOffset / 1.0e6;
|
||||
FQoLoErr.Value := E.LOError;
|
||||
FQoTxLO.Value := E.TxLOOffset / 1.0e6;
|
||||
FQoTxLOErr.Value := E.TxLOError;
|
||||
FQoBegin.Value := E.FreqBegin / 1.0e6;
|
||||
FQoEnd.Value := E.FreqEnd / 1.0e6;
|
||||
FQoRXGain.Value := E.RXGain;
|
||||
|
||||
Reference in New Issue
Block a user