mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +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:
@@ -270,6 +270,10 @@ type
|
||||
// собственный у SDR). FullDuplexTx помечает слот как такой транспондер.
|
||||
FullDuplexTx: Boolean; // True — раздельный TX-LO (QO-100); авто display-duplex
|
||||
TxLOOffset: Double; // Hz, TX LO-offset (для QO-100 = транспондер-offset, ~8089.5 МГц)
|
||||
// Hz, поправка к TX LO-offset (TO Error). Сам offset держим стандартным
|
||||
// (8089.5 МГц у QO-100), а уход конкретного тракта правим здесь.
|
||||
// Знак как у LOError: положительная — TX IF растёт.
|
||||
TxLOError: Double;
|
||||
RXGain: Double; // dB, компенсация усиления RX тракта
|
||||
RXOnly: Boolean; // True — запрещает TX на этом трансверторе
|
||||
TXPower: Integer; // 0..100 % (drive level специально для XVTR)
|
||||
@@ -354,6 +358,12 @@ type
|
||||
// уровень отличается от показаний стрелки на пару дБ.
|
||||
DispBandDB: array[0..CFG_BAND_COUNT-1] of Double;
|
||||
DispXvtrDB: array[0..CFG_XVTR_COUNT-1] of Double;
|
||||
// Частотная калибровка опорника, ppm (Frequency Calibration в Thetis).
|
||||
// Смысл: «опора выше номинала на N ppm» ⇒ до правки сигналы показываются
|
||||
// ниже истинной частоты; положительное значение поднимает показания.
|
||||
// Действует на всё железо разом; ручные LOError/TO Error трансвертеров
|
||||
// остаются сверху — тракт тракту рознь.
|
||||
FreqCalPPM: Double;
|
||||
end;
|
||||
|
||||
TGlobalSettings = record
|
||||
@@ -922,6 +932,7 @@ begin
|
||||
for i := 0 to CFG_XVTR_COUNT-1 do
|
||||
G.Cal.DispXvtrDB[i] :=
|
||||
EnsureRange(JD(GObj,'cal_disp_xvtr_'+IntToStr(i), 0.0), -30.0, 30.0);
|
||||
G.Cal.FreqCalPPM := EnsureRange(JD(GObj,'cal_freq_ppm', 0.0), -100.0, 100.0);
|
||||
|
||||
for i := 0 to CFG_BAND_COUNT-1 do
|
||||
begin
|
||||
@@ -1047,6 +1058,7 @@ begin
|
||||
JW(O,'cal_disp_band_'+IntToStr(i),G.Cal.DispBandDB[i]);
|
||||
for i := 0 to CFG_XVTR_COUNT-1 do
|
||||
JW(O,'cal_disp_xvtr_'+IntToStr(i),G.Cal.DispXvtrDB[i]);
|
||||
JW(O,'cal_freq_ppm',G.Cal.FreqCalPPM);
|
||||
end;
|
||||
|
||||
procedure TSettingsManager.SaveBand(const MAC: array of Byte; BandIdx: Integer;
|
||||
@@ -1598,6 +1610,7 @@ begin
|
||||
X.Entries[i].LOError := 0.0;
|
||||
X.Entries[i].FullDuplexTx := False;
|
||||
X.Entries[i].TxLOOffset := 0.0;
|
||||
X.Entries[i].TxLOError := 0.0;
|
||||
X.Entries[i].RXGain := 0.0;
|
||||
X.Entries[i].RXOnly := False;
|
||||
X.Entries[i].TXPower := 100;
|
||||
@@ -1680,6 +1693,7 @@ begin
|
||||
X.Entries[i].LOError := JD(EObj, 'lo_err', X.Entries[i].LOError);
|
||||
X.Entries[i].FullDuplexTx := JB(EObj, 'full_dup', X.Entries[i].FullDuplexTx);
|
||||
X.Entries[i].TxLOOffset := JD(EObj, 'tx_lo_off', X.Entries[i].TxLOOffset);
|
||||
X.Entries[i].TxLOError := JD(EObj, 'tx_lo_error', X.Entries[i].TxLOError);
|
||||
X.Entries[i].RXGain := JD(EObj, 'rx_gain', X.Entries[i].RXGain);
|
||||
X.Entries[i].RXOnly := JB(EObj, 'rx_only', X.Entries[i].RXOnly);
|
||||
X.Entries[i].TXPower := EnsureRange(JI(EObj, 'tx_pwr', X.Entries[i].TXPower), 0, 100);
|
||||
@@ -1781,6 +1795,7 @@ begin
|
||||
JW(EObj, 'lo_err', X.Entries[i].LOError);
|
||||
JW(EObj, 'full_dup', X.Entries[i].FullDuplexTx);
|
||||
JW(EObj, 'tx_lo_off', X.Entries[i].TxLOOffset);
|
||||
JW(EObj, 'tx_lo_error', X.Entries[i].TxLOError);
|
||||
JW(EObj, 'rx_gain', X.Entries[i].RXGain);
|
||||
JW(EObj, 'rx_only', X.Entries[i].RXOnly);
|
||||
JW(EObj, 'tx_pwr', X.Entries[i].TXPower);
|
||||
|
||||
Reference in New Issue
Block a user