mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +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:
+24
-4
@@ -156,6 +156,9 @@ type
|
|||||||
FMainDither: Boolean;
|
FMainDither: Boolean;
|
||||||
FMainRandom: Boolean;
|
FMainRandom: Boolean;
|
||||||
|
|
||||||
|
// Частотная калибровка опорника радио (ppm) — вносится в частотное слово
|
||||||
|
// (тактовый генератор радио менять нечем, поэтому правим запрос).
|
||||||
|
FFreqCalPPM: Double;
|
||||||
// Текущее состояние для построения HP пакетов
|
// Текущее состояние для построения HP пакетов
|
||||||
FCurrentRXFreq: Double;
|
FCurrentRXFreq: Double;
|
||||||
FCurrentTXFreq: Double;
|
FCurrentTXFreq: Double;
|
||||||
@@ -260,6 +263,9 @@ type
|
|||||||
procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
|
procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte;
|
||||||
Transmitting, PAEnabled, AlexEnabled: Boolean); override;
|
Transmitting, PAEnabled, AlexEnabled: Boolean); override;
|
||||||
procedure SetStepAtten(dB: Byte); override;
|
procedure SetStepAtten(dB: Byte); override;
|
||||||
|
procedure SetFreqCalPPM(P: Double); override;
|
||||||
|
// Частота с поправкой опорника — для частотного слова DDC/DUC.
|
||||||
|
function CalFreq(Hz: Double): Double;
|
||||||
procedure SetAlexConfig(const A: TAlexSettings); override;
|
procedure SetAlexConfig(const A: TAlexSettings); override;
|
||||||
// Включение/настройка XVTR-режима. Все параметры применяются мгновенно
|
// Включение/настройка XVTR-режима. Все параметры применяются мгновенно
|
||||||
// (отправляется HP-пакет). Когда AEnable=False, XVTR-bit гасится,
|
// (отправляется HP-пакет). Когда AEnable=False, XVTR-bit гасится,
|
||||||
@@ -736,6 +742,7 @@ begin
|
|||||||
FWBUpdateRateMS := 70;
|
FWBUpdateRateMS := 70;
|
||||||
FWBFrameCount := 0;
|
FWBFrameCount := 0;
|
||||||
FWBSeqValid := False;
|
FWBSeqValid := False;
|
||||||
|
FFreqCalPPM := 0.0;
|
||||||
FCurrentRXFreq := 7100000;
|
FCurrentRXFreq := 7100000;
|
||||||
FCurrentTXFreq := 7100000;
|
FCurrentTXFreq := 7100000;
|
||||||
FCurrentDrive := 0;
|
FCurrentDrive := 0;
|
||||||
@@ -1693,6 +1700,19 @@ begin
|
|||||||
FAlexEnabled := AlexEnabled;
|
FAlexEnabled := AlexEnabled;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function THPSDRNetwork.CalFreq(Hz: Double): Double;
|
||||||
|
// Опора радио выше номинала на FFreqCalPPM ⇒ DDC/DUC уходят вверх на столько
|
||||||
|
// же. Компенсируем запрошенной частотой: просим ниже ровно на ppm.
|
||||||
|
begin
|
||||||
|
if FFreqCalPPM = 0 then Exit(Hz);
|
||||||
|
Result := Hz / (1.0 + FFreqCalPPM * 1.0E-6);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure THPSDRNetwork.SetFreqCalPPM(P: Double);
|
||||||
|
begin
|
||||||
|
FFreqCalPPM := P;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure THPSDRNetwork.SetStepAtten(dB: Byte);
|
procedure THPSDRNetwork.SetStepAtten(dB: Byte);
|
||||||
begin
|
begin
|
||||||
if dB > 31 then dB := 31;
|
if dB > 31 then dB := 31;
|
||||||
@@ -1737,7 +1757,7 @@ begin
|
|||||||
DDCBase := DDCBaseIndex;
|
DDCBase := DDCBaseIndex;
|
||||||
|
|
||||||
// DDC RX frequency (bytes 9 + DDC*4)
|
// DDC RX frequency (bytes 9 + DDC*4)
|
||||||
Ph := FreqToPhaseWord(FCurrentRXFreq);
|
Ph := FreqToPhaseWord(CalFreq(FCurrentRXFreq));
|
||||||
Buf[9 + DDCBase*4] := (Ph shr 24) and $FF;
|
Buf[9 + DDCBase*4] := (Ph shr 24) and $FF;
|
||||||
Buf[10 + DDCBase*4] := (Ph shr 16) and $FF;
|
Buf[10 + DDCBase*4] := (Ph shr 16) and $FF;
|
||||||
Buf[11 + DDCBase*4] := (Ph shr 8) and $FF;
|
Buf[11 + DDCBase*4] := (Ph shr 8) and $FF;
|
||||||
@@ -1748,7 +1768,7 @@ begin
|
|||||||
for ddc := 0 to MAX_DDCS - 1 do
|
for ddc := 0 to MAX_DDCS - 1 do
|
||||||
if FPanDDCEnabled[ddc] then
|
if FPanDDCEnabled[ddc] then
|
||||||
begin
|
begin
|
||||||
Ph := FreqToPhaseWord(FPanDDCFreqHz[ddc]);
|
Ph := FreqToPhaseWord(CalFreq(FPanDDCFreqHz[ddc]));
|
||||||
Buf[9 + ddc*4] := (Ph shr 24) and $FF;
|
Buf[9 + ddc*4] := (Ph shr 24) and $FF;
|
||||||
Buf[10 + ddc*4] := (Ph shr 16) and $FF;
|
Buf[10 + ddc*4] := (Ph shr 16) and $FF;
|
||||||
Buf[11 + ddc*4] := (Ph shr 8) and $FF;
|
Buf[11 + ddc*4] := (Ph shr 8) and $FF;
|
||||||
@@ -1759,7 +1779,7 @@ begin
|
|||||||
// DDCBase=2 ⇒ с частотным словом главного DDC (bytes 17..20) не пересекаемся.
|
// DDCBase=2 ⇒ с частотным словом главного DDC (bytes 17..20) не пересекаемся.
|
||||||
if FPSTXActive then
|
if FPSTXActive then
|
||||||
begin
|
begin
|
||||||
Ph := FreqToPhaseWord(FCurrentTXFreq);
|
Ph := FreqToPhaseWord(CalFreq(FCurrentTXFreq));
|
||||||
Buf[9] := (Ph shr 24) and $FF;
|
Buf[9] := (Ph shr 24) and $FF;
|
||||||
Buf[10] := (Ph shr 16) and $FF;
|
Buf[10] := (Ph shr 16) and $FF;
|
||||||
Buf[11] := (Ph shr 8) and $FF;
|
Buf[11] := (Ph shr 8) and $FF;
|
||||||
@@ -1771,7 +1791,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// DUC TX frequency (bytes 329-332)
|
// DUC TX frequency (bytes 329-332)
|
||||||
Ph := FreqToPhaseWord(FCurrentTXFreq);
|
Ph := FreqToPhaseWord(CalFreq(FCurrentTXFreq));
|
||||||
Buf[329] := (Ph shr 24) and $FF;
|
Buf[329] := (Ph shr 24) and $FF;
|
||||||
Buf[330] := (Ph shr 16) and $FF;
|
Buf[330] := (Ph shr 16) and $FF;
|
||||||
Buf[331] := (Ph shr 8) and $FF;
|
Buf[331] := (Ph shr 8) and $FF;
|
||||||
|
|||||||
@@ -72,6 +72,14 @@ var
|
|||||||
iio_channel_attr_write_double: function(chn: Piio_channel; attr: PAnsiChar;
|
iio_channel_attr_write_double: function(chn: Piio_channel; attr: PAnsiChar;
|
||||||
val: cdouble): cint; cdecl;
|
val: cdouble): cint; cdecl;
|
||||||
|
|
||||||
|
// ---- Атрибуты устройства (не канальные) ----
|
||||||
|
// ad9361-phy: 'xo_correction' — фактическая частота опорного XO в Гц, какой её
|
||||||
|
// считает драйвер. Правка = частотная калибровка всего чипа (RX и TX LO).
|
||||||
|
iio_device_attr_read_longlong: function(dev: Piio_device; attr: PAnsiChar;
|
||||||
|
out val: clonglong): cint; cdecl;
|
||||||
|
iio_device_attr_write_longlong: function(dev: Piio_device; attr: PAnsiChar;
|
||||||
|
val: clonglong): cint; cdecl;
|
||||||
|
|
||||||
// ---- Debug-атрибуты устройства (debugfs) ----
|
// ---- Debug-атрибуты устройства (debugfs) ----
|
||||||
// Нужны для выбора антенного разъёма у AD936x в режиме 1R1T:
|
// Нужны для выбора антенного разъёма у AD936x в режиме 1R1T:
|
||||||
// 'adi,1rx-1tx-mode-use-rx-num'/'-tx-num' (какой физический канал кормит тракт)
|
// 'adi,1rx-1tx-mode-use-rx-num'/'-tx-num' (какой физический канал кормит тракт)
|
||||||
@@ -185,6 +193,8 @@ begin
|
|||||||
iio_channel_attr_write := Sym('iio_channel_attr_write');
|
iio_channel_attr_write := Sym('iio_channel_attr_write');
|
||||||
iio_channel_attr_write_bool := Sym('iio_channel_attr_write_bool');
|
iio_channel_attr_write_bool := Sym('iio_channel_attr_write_bool');
|
||||||
iio_channel_attr_write_double := Sym('iio_channel_attr_write_double');
|
iio_channel_attr_write_double := Sym('iio_channel_attr_write_double');
|
||||||
|
iio_device_attr_read_longlong := Sym('iio_device_attr_read_longlong');
|
||||||
|
iio_device_attr_write_longlong := Sym('iio_device_attr_write_longlong');
|
||||||
iio_device_debug_attr_read := Sym('iio_device_debug_attr_read');
|
iio_device_debug_attr_read := Sym('iio_device_debug_attr_read');
|
||||||
iio_device_debug_attr_write := Sym('iio_device_debug_attr_write');
|
iio_device_debug_attr_write := Sym('iio_device_debug_attr_write');
|
||||||
iio_device_debug_attr_write_longlong := Sym('iio_device_debug_attr_write_longlong');
|
iio_device_debug_attr_write_longlong := Sym('iio_device_debug_attr_write_longlong');
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ type
|
|||||||
FTXFreq: Double;
|
FTXFreq: Double;
|
||||||
FSampleRate: Integer;
|
FSampleRate: Integer;
|
||||||
FTransmitting: Boolean;
|
FTransmitting: Boolean;
|
||||||
|
FFreqCalPPM: Double; // частотная калибровка опорника, ppm (xo_correction)
|
||||||
|
FXoNomHz: Int64; // номинал XO, Гц (снимается при коннекте, 0 = не знаем)
|
||||||
FGainMode: string; // gain_control_mode (manual/slow_attack/...)
|
FGainMode: string; // gain_control_mode (manual/slow_attack/...)
|
||||||
FGainDb: Integer; // hardwaregain при ручном режиме
|
FGainDb: Integer; // hardwaregain при ручном режиме
|
||||||
|
|
||||||
@@ -118,6 +120,7 @@ type
|
|||||||
GainDb: Double): Boolean;
|
GainDb: Double): Boolean;
|
||||||
|
|
||||||
// Низкоуровневые помощники применения атрибутов к ad9361-phy
|
// Низкоуровневые помощники применения атрибутов к ad9361-phy
|
||||||
|
procedure ApplyFreqCal;
|
||||||
procedure ApplyRxLO(Hz: Double);
|
procedure ApplyRxLO(Hz: Double);
|
||||||
procedure ApplyTxLO(Hz: Double);
|
procedure ApplyTxLO(Hz: Double);
|
||||||
procedure SetTxLOPowerdown(PoweredOn: Boolean); // TX LO вкл/выкл (по PTT)
|
procedure SetTxLOPowerdown(PoweredOn: Boolean); // TX LO вкл/выкл (по PTT)
|
||||||
@@ -177,6 +180,7 @@ type
|
|||||||
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;
|
function RxGainNowDb: Double; override;
|
||||||
|
procedure SetFreqCalPPM(P: Double); override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Стабильный 6-байтовый ключ (synthetic MAC) из serial/URI — для per-device
|
// Стабильный 6-байтовый ключ (synthetic MAC) из serial/URI — для per-device
|
||||||
@@ -410,6 +414,7 @@ begin
|
|||||||
FSampleRate := 1536000;
|
FSampleRate := 1536000;
|
||||||
FGainMode := 'slow_attack'; // аппаратный AGC по умолчанию
|
FGainMode := 'slow_attack'; // аппаратный AGC по умолчанию
|
||||||
FGainDb := 40;
|
FGainDb := 40;
|
||||||
|
FFreqCalPPM := 0.0; FXoNomHz := 0;
|
||||||
FRxBuf := nil; FRxChI := nil; FRxChQ := nil; FRxThread := nil;
|
FRxBuf := nil; FRxChI := nil; FRxChQ := nil; FRxThread := nil;
|
||||||
FStreaming := False; FRxSeq := 0;
|
FStreaming := False; FRxSeq := 0;
|
||||||
FTxBuf := nil; FTxChI := nil; FTxChQ := nil; FTxThread := nil;
|
FTxBuf := nil; FTxChI := nil; FTxChQ := nil; FTxThread := nil;
|
||||||
@@ -635,6 +640,7 @@ begin
|
|||||||
ApplyTxMux;
|
ApplyTxMux;
|
||||||
ApplyGain(True);
|
ApplyGain(True);
|
||||||
ApplyTxAtten; // TX заглушён (макс. аттенюация) до явного PTT
|
ApplyTxAtten; // TX заглушён (макс. аттенюация) до явного PTT
|
||||||
|
ApplyFreqCal; // частотная калибровка опорника (xo_correction)
|
||||||
StartTelemetry;
|
StartTelemetry;
|
||||||
Result := True;
|
Result := True;
|
||||||
end;
|
end;
|
||||||
@@ -656,6 +662,40 @@ end;
|
|||||||
|
|
||||||
// ---- Применение атрибутов к ad9361-phy ----
|
// ---- Применение атрибутов к ad9361-phy ----
|
||||||
|
|
||||||
|
procedure TPlutoBackend.ApplyFreqCal;
|
||||||
|
// xo_correction — частота опорного XO в Гц, какой её считает драйвер. Правка
|
||||||
|
// разъезжается на ВСЁ: RX LO, TX LO, sample rate — один параметр вместо
|
||||||
|
// подгонки offset'ов по трансвертерам.
|
||||||
|
// Номинал снимаем округлением прочитанного до 100 кГц: в железе могла
|
||||||
|
// остаться наша же коррекция с прошлой сессии (она сбрасывается только по
|
||||||
|
// перезагрузке платы), а ±100 ppm от 40 МГц — это ±4 кГц, округление их
|
||||||
|
// съедает и компаундинга не будет.
|
||||||
|
var
|
||||||
|
V: clonglong;
|
||||||
|
begin
|
||||||
|
if (FPhy = nil) or (not IIOLoaded)
|
||||||
|
or (not Assigned(iio_device_attr_read_longlong))
|
||||||
|
or (not Assigned(iio_device_attr_write_longlong)) then Exit;
|
||||||
|
if FXoNomHz = 0 then
|
||||||
|
begin
|
||||||
|
if iio_device_attr_read_longlong(FPhy, 'xo_correction', V) <> 0 then Exit;
|
||||||
|
if V <= 0 then Exit;
|
||||||
|
FXoNomHz := (Int64(V) + 50000) div 100000 * 100000;
|
||||||
|
end;
|
||||||
|
iio_device_attr_write_longlong(FPhy, 'xo_correction',
|
||||||
|
Round(FXoNomHz * (1.0 + FFreqCalPPM * 1.0E-6)));
|
||||||
|
// Синтезаторы считают делители в момент записи частоты — переписываем LO,
|
||||||
|
// иначе правка подхватится только на следующей перестройке.
|
||||||
|
if FRXFreq > 0 then ApplyRxLO(FRXFreq);
|
||||||
|
if FTXFreq > 0 then ApplyTxLO(FTXFreq);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPlutoBackend.SetFreqCalPPM(P: Double);
|
||||||
|
begin
|
||||||
|
FFreqCalPPM := P;
|
||||||
|
ApplyFreqCal;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TPlutoBackend.ApplyRxLO(Hz: Double);
|
procedure TPlutoBackend.ApplyRxLO(Hz: Double);
|
||||||
var Ch: Piio_channel;
|
var Ch: Piio_channel;
|
||||||
begin
|
begin
|
||||||
|
|||||||
@@ -204,6 +204,9 @@ type
|
|||||||
// Нужно для абсолютной шкалы уровней — контроллер вычитает его из показаний.
|
// Нужно для абсолютной шкалы уровней — контроллер вычитает его из показаний.
|
||||||
// HPSDR: усиления нет (есть аттенюатор), возвращает TELEMETRY_NONE.
|
// HPSDR: усиления нет (есть аттенюатор), возвращает TELEMETRY_NONE.
|
||||||
function RxGainNowDb: Double; virtual;
|
function RxGainNowDb: Double; virtual;
|
||||||
|
// Частотная калибровка опорника, ppm: «опора выше номинала на N ppm».
|
||||||
|
// HPSDR — масштаб частотного слова, AD936x — xo_correction в ad9361-phy.
|
||||||
|
procedure SetFreqCalPPM(P: Double); virtual;
|
||||||
|
|
||||||
// ---- Свойства состояния (полиморфные через виртуальные геттеры) ----
|
// ---- Свойства состояния (полиморфные через виртуальные геттеры) ----
|
||||||
property Connected: Boolean read GetConnected;
|
property Connected: Boolean read GetConnected;
|
||||||
@@ -310,6 +313,10 @@ begin
|
|||||||
Result := TELEMETRY_NONE;
|
Result := TELEMETRY_NONE;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TRadioBackend.SetFreqCalPPM(P: Double);
|
||||||
|
begin
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioBackend.SetTxAtten(Db: Double);
|
procedure TRadioBackend.SetTxAtten(Db: Double);
|
||||||
begin
|
begin
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -2295,7 +2295,9 @@ begin
|
|||||||
// QO-100 / split-LO: TX идёт на собственный uplink-LO (visible - TxLOOffset),
|
// QO-100 / split-LO: TX идёт на собственный uplink-LO (visible - TxLOOffset),
|
||||||
// без LOError — дрейф LNB к передаче отношения не имеет.
|
// без LOError — дрейф LNB к передаче отношения не имеет.
|
||||||
if FXvtrSettings.Entries[FCurrentXvtr].FullDuplexTx then
|
if FXvtrSettings.Entries[FCurrentXvtr].FullDuplexTx then
|
||||||
|
// TO Error: сам транспондер-offset держим стандартным, уход тракта — здесь.
|
||||||
Result := VisibleHz - FXvtrSettings.Entries[FCurrentXvtr].TxLOOffset
|
Result := VisibleHz - FXvtrSettings.Entries[FCurrentXvtr].TxLOOffset
|
||||||
|
+ FXvtrSettings.Entries[FCurrentXvtr].TxLOError
|
||||||
else
|
else
|
||||||
// Обычный трансвертер: RX=TX LO (с калибровкой LOError) — как раньше.
|
// Обычный трансвертер: RX=TX LO (с калибровкой LOError) — как раньше.
|
||||||
Result := VisibleHz
|
Result := VisibleHz
|
||||||
@@ -3664,6 +3666,10 @@ begin
|
|||||||
// Санитизация: битый конфиг не должен дать деление на ноль/абсурдные ватты.
|
// Санитизация: битый конфиг не должен дать деление на ноль/абсурдные ватты.
|
||||||
FCal.PwrDetModel := EnsureRange(FCal.PwrDetModel, 0, 3);
|
FCal.PwrDetModel := EnsureRange(FCal.PwrDetModel, 0, 3);
|
||||||
if FCal.AmpSensMVA <= 0 then FCal.AmpSensMVA := 120.0;
|
if FCal.AmpSensMVA <= 0 then FCal.AmpSensMVA := 120.0;
|
||||||
|
FCal.FreqCalPPM := EnsureRange(FCal.FreqCalPPM, -100.0, 100.0);
|
||||||
|
// Опорник: у HPSDR правим частотное слово, у AD936x — xo_correction.
|
||||||
|
// Ручные LOError/TO Error трансвертеров остаются сверху.
|
||||||
|
if Assigned(FNetwork) then FNetwork.SetFreqCalPPM(FCal.FreqCalPPM);
|
||||||
// Офсет S-метра берётся из FCal на каждом чтении (SMeterCalFor) — применять
|
// Офсет S-метра берётся из FCal на каждом чтении (SMeterCalFor) — применять
|
||||||
// отдельно нечего: он зависит от диапазона/трансвертера, а не от движка.
|
// отдельно нечего: он зависит от диапазона/трансвертера, а не от движка.
|
||||||
SaveGlobalSettings; // персист (гейт по FDevConnected внутри)
|
SaveGlobalSettings; // персист (гейт по FDevConnected внутри)
|
||||||
|
|||||||
@@ -270,6 +270,10 @@ type
|
|||||||
// собственный у SDR). FullDuplexTx помечает слот как такой транспондер.
|
// собственный у SDR). FullDuplexTx помечает слот как такой транспондер.
|
||||||
FullDuplexTx: Boolean; // True — раздельный TX-LO (QO-100); авто display-duplex
|
FullDuplexTx: Boolean; // True — раздельный TX-LO (QO-100); авто display-duplex
|
||||||
TxLOOffset: Double; // Hz, TX LO-offset (для QO-100 = транспондер-offset, ~8089.5 МГц)
|
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 тракта
|
RXGain: Double; // dB, компенсация усиления RX тракта
|
||||||
RXOnly: Boolean; // True — запрещает TX на этом трансверторе
|
RXOnly: Boolean; // True — запрещает TX на этом трансверторе
|
||||||
TXPower: Integer; // 0..100 % (drive level специально для XVTR)
|
TXPower: Integer; // 0..100 % (drive level специально для XVTR)
|
||||||
@@ -354,6 +358,12 @@ type
|
|||||||
// уровень отличается от показаний стрелки на пару дБ.
|
// уровень отличается от показаний стрелки на пару дБ.
|
||||||
DispBandDB: array[0..CFG_BAND_COUNT-1] of Double;
|
DispBandDB: array[0..CFG_BAND_COUNT-1] of Double;
|
||||||
DispXvtrDB: array[0..CFG_XVTR_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;
|
end;
|
||||||
|
|
||||||
TGlobalSettings = record
|
TGlobalSettings = record
|
||||||
@@ -922,6 +932,7 @@ begin
|
|||||||
for i := 0 to CFG_XVTR_COUNT-1 do
|
for i := 0 to CFG_XVTR_COUNT-1 do
|
||||||
G.Cal.DispXvtrDB[i] :=
|
G.Cal.DispXvtrDB[i] :=
|
||||||
EnsureRange(JD(GObj,'cal_disp_xvtr_'+IntToStr(i), 0.0), -30.0, 30.0);
|
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
|
for i := 0 to CFG_BAND_COUNT-1 do
|
||||||
begin
|
begin
|
||||||
@@ -1047,6 +1058,7 @@ begin
|
|||||||
JW(O,'cal_disp_band_'+IntToStr(i),G.Cal.DispBandDB[i]);
|
JW(O,'cal_disp_band_'+IntToStr(i),G.Cal.DispBandDB[i]);
|
||||||
for i := 0 to CFG_XVTR_COUNT-1 do
|
for i := 0 to CFG_XVTR_COUNT-1 do
|
||||||
JW(O,'cal_disp_xvtr_'+IntToStr(i),G.Cal.DispXvtrDB[i]);
|
JW(O,'cal_disp_xvtr_'+IntToStr(i),G.Cal.DispXvtrDB[i]);
|
||||||
|
JW(O,'cal_freq_ppm',G.Cal.FreqCalPPM);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TSettingsManager.SaveBand(const MAC: array of Byte; BandIdx: Integer;
|
procedure TSettingsManager.SaveBand(const MAC: array of Byte; BandIdx: Integer;
|
||||||
@@ -1598,6 +1610,7 @@ begin
|
|||||||
X.Entries[i].LOError := 0.0;
|
X.Entries[i].LOError := 0.0;
|
||||||
X.Entries[i].FullDuplexTx := False;
|
X.Entries[i].FullDuplexTx := False;
|
||||||
X.Entries[i].TxLOOffset := 0.0;
|
X.Entries[i].TxLOOffset := 0.0;
|
||||||
|
X.Entries[i].TxLOError := 0.0;
|
||||||
X.Entries[i].RXGain := 0.0;
|
X.Entries[i].RXGain := 0.0;
|
||||||
X.Entries[i].RXOnly := False;
|
X.Entries[i].RXOnly := False;
|
||||||
X.Entries[i].TXPower := 100;
|
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].LOError := JD(EObj, 'lo_err', X.Entries[i].LOError);
|
||||||
X.Entries[i].FullDuplexTx := JB(EObj, 'full_dup', X.Entries[i].FullDuplexTx);
|
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].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].RXGain := JD(EObj, 'rx_gain', X.Entries[i].RXGain);
|
||||||
X.Entries[i].RXOnly := JB(EObj, 'rx_only', X.Entries[i].RXOnly);
|
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);
|
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, 'lo_err', X.Entries[i].LOError);
|
||||||
JW(EObj, 'full_dup', X.Entries[i].FullDuplexTx);
|
JW(EObj, 'full_dup', X.Entries[i].FullDuplexTx);
|
||||||
JW(EObj, 'tx_lo_off', X.Entries[i].TxLOOffset);
|
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_gain', X.Entries[i].RXGain);
|
||||||
JW(EObj, 'rx_only', X.Entries[i].RXOnly);
|
JW(EObj, 'rx_only', X.Entries[i].RXOnly);
|
||||||
JW(EObj, 'tx_pwr', X.Entries[i].TXPower);
|
JW(EObj, 'tx_pwr', X.Entries[i].TXPower);
|
||||||
|
|||||||
+67
-8
@@ -183,6 +183,8 @@ type
|
|||||||
FLblDispBandHdr: TLabel;
|
FLblDispBandHdr: TLabel;
|
||||||
FLblDispXvtrHdr: TLabel;
|
FLblDispXvtrHdr: TLabel;
|
||||||
FBtnDispFromSM: TFlatButton; // «= S-meter» — копия таблицы
|
FBtnDispFromSM: TFlatButton; // «= S-meter» — копия таблицы
|
||||||
|
FCalGrpFreq: TPanel; // Reference frequency
|
||||||
|
FEdFreqCalPPM: TFlatFloatSpinEdit; // калибровка опорника, ppm
|
||||||
FCalIsPluto: Boolean; // активное железо — AD936x
|
FCalIsPluto: Boolean; // активное железо — AD936x
|
||||||
FCalXvtrOn: array[0..CFG_XVTR_COUNT-1] of Boolean; // слот трансвертера включён
|
FCalXvtrOn: array[0..CFG_XVTR_COUNT-1] of Boolean; // слот трансвертера включён
|
||||||
FCalXvtrName: array[0..CFG_XVTR_COUNT-1] of string; // подпись слота
|
FCalXvtrName: array[0..CFG_XVTR_COUNT-1] of string; // подпись слота
|
||||||
@@ -316,6 +318,7 @@ type
|
|||||||
FQoLnbLO: TFlatFloatSpinEdit; // МГц — LNB LO (= LOOffset, RX)
|
FQoLnbLO: TFlatFloatSpinEdit; // МГц — LNB LO (= LOOffset, RX)
|
||||||
FQoLoErr: TFlatFloatSpinEdit; // Гц — LO Error (= LOError, живая калибровка LNB / beacon-lock)
|
FQoLoErr: TFlatFloatSpinEdit; // Гц — LO Error (= LOError, живая калибровка LNB / beacon-lock)
|
||||||
FQoTxLO: TFlatFloatSpinEdit; // МГц — транспондер-offset (= TxLOOffset)
|
FQoTxLO: TFlatFloatSpinEdit; // МГц — транспондер-offset (= TxLOOffset)
|
||||||
|
FQoTxLOErr: TFlatFloatSpinEdit; // Гц — поправка offset'а (TO Error)
|
||||||
FQoBegin: TFlatFloatSpinEdit; // МГц — downlink begin
|
FQoBegin: TFlatFloatSpinEdit; // МГц — downlink begin
|
||||||
FQoEnd: TFlatFloatSpinEdit; // МГц — downlink end
|
FQoEnd: TFlatFloatSpinEdit; // МГц — downlink end
|
||||||
FQoRXGain: TFlatFloatSpinEdit; // dB
|
FQoRXGain: TFlatFloatSpinEdit; // dB
|
||||||
@@ -1946,6 +1949,17 @@ procedure TSettingsForm.BuildCalibrationTab;
|
|||||||
const
|
const
|
||||||
GRP3_H0 = 200; // стартовая высота S-метра, уточняется в RelayoutCalTab
|
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;
|
function MakeCalSpin(AParent: TWinControl; ALeft, ATop: Integer;
|
||||||
AMin, AMax, AInc: Double; ADec: Integer;
|
AMin, AMax, AInc: Double; ADec: Integer;
|
||||||
ADefault: Double): TFlatFloatSpinEdit;
|
ADefault: Double): TFlatFloatSpinEdit;
|
||||||
@@ -2021,7 +2035,8 @@ begin
|
|||||||
Lbl := MakeLbl(Grp,
|
Lbl := MakeLbl(Grp,
|
||||||
'FWD/REV multiplier per band: 1.00 = no correction, >1 if meter under-reads '
|
'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.',
|
+ '(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
|
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||||
begin
|
begin
|
||||||
@@ -2054,6 +2069,7 @@ begin
|
|||||||
Lbl := MakeLbl(Grp, 'Defaults 360 mV / 120 mV/A (ACS712-style, Orion MkII)',
|
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_PAD + CAL_VA_LBL_W + 10 + CAL_ED_W + 14,
|
||||||
CAL_R1 + CAL_ROW_H + 6, 320);
|
CAL_R1 + CAL_ROW_H + 6, 320);
|
||||||
|
WrapLbl(Lbl, 320, 34);
|
||||||
|
|
||||||
// ── S-meter ─────────────────────────────────────────────────────────────
|
// ── S-meter ─────────────────────────────────────────────────────────────
|
||||||
GTop := GTop + CAL_GRP2_H + 14;
|
GTop := GTop + CAL_GRP2_H + 14;
|
||||||
@@ -2063,7 +2079,8 @@ begin
|
|||||||
|
|
||||||
FLblSMBandHdr := MakeLbl(Grp,
|
FLblSMBandHdr := MakeLbl(Grp,
|
||||||
'Offset (dB) added to S-meter readings — per band (+ = meter reads higher).',
|
'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
|
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||||
begin
|
begin
|
||||||
@@ -2093,8 +2110,9 @@ begin
|
|||||||
Grp := FCalGrpDisp;
|
Grp := FCalGrpDisp;
|
||||||
|
|
||||||
FLblDispBandHdr := MakeLbl(Grp,
|
FLblDispBandHdr := MakeLbl(Grp,
|
||||||
'Offset (dB) added to spectrum/waterfall levels — per band (moves the whole '
|
'Offset (dB) added to spectrum/waterfall — per band (moves the whole trace).',
|
||||||
+ 'trace incl. noise floor).', CAL_PAD, 30, 650);
|
CAL_PAD, 30, 540);
|
||||||
|
WrapLbl(FLblDispBandHdr, 540, 18);
|
||||||
|
|
||||||
FBtnDispFromSM := TFlatButton.Create(Self);
|
FBtnDispFromSM := TFlatButton.Create(Self);
|
||||||
FBtnDispFromSM.Parent := Grp;
|
FBtnDispFromSM.Parent := Grp;
|
||||||
@@ -2130,6 +2148,27 @@ begin
|
|||||||
FEdDispXvtr[i].Tag := i;
|
FEdDispXvtr[i].Tag := i;
|
||||||
end;
|
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-план, трансвертеры скрыты)
|
RelayoutCalTab; // стартовая раскладка (HF-план, трансвертеры скрыты)
|
||||||
|
|
||||||
FLoading := False;
|
FLoading := False;
|
||||||
@@ -2146,7 +2185,16 @@ var
|
|||||||
begin
|
begin
|
||||||
// Гард: вкладка ещё строится (или LoadXvtrSettings пришёл раньше неё).
|
// Гард: вкладка ещё строится (или LoadXvtrSettings пришёл раньше неё).
|
||||||
if (FCalGrpDet = nil) or (FCalGrpVA = nil) or (FCalGrpSM = nil)
|
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. (своя сетка, уже с подсказкой) ----
|
// ---- подписи/видимость строк Det. Cal. (своя сетка, уже с подсказкой) ----
|
||||||
for i := 0 to CFG_BAND_COUNT - 1 do
|
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||||
@@ -2189,6 +2237,8 @@ begin
|
|||||||
FCalGrpSM.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, HSM);
|
FCalGrpSM.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, HSM);
|
||||||
GTop := GTop + HSM + 14;
|
GTop := GTop + HSM + 14;
|
||||||
FCalGrpDisp.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, HDisp);
|
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;
|
end;
|
||||||
|
|
||||||
function TSettingsForm.LayoutCalOffsets(const BandLbl: array of TLabel;
|
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 FEdSMXvtr[i] <> nil then FCalConfig.SMeterXvtrDB[i] := FEdSMXvtr[i].Value;
|
||||||
if FEdDispXvtr[i] <> nil then FCalConfig.DispXvtrDB[i] := FEdDispXvtr[i].Value;
|
if FEdDispXvtr[i] <> nil then FCalConfig.DispXvtrDB[i] := FEdDispXvtr[i].Value;
|
||||||
end;
|
end;
|
||||||
|
FCalConfig.FreqCalPPM := FEdFreqCalPPM.Value;
|
||||||
FCalConfig.SupplyVoltCal := FEdSupplyVoltCal.Value;
|
FCalConfig.SupplyVoltCal := FEdSupplyVoltCal.Value;
|
||||||
FCalConfig.AmpVoffMV := FEdAmpVoff.Value;
|
FCalConfig.AmpVoffMV := FEdAmpVoff.Value;
|
||||||
FCalConfig.AmpSensMVA := FEdAmpSens.Value;
|
FCalConfig.AmpSensMVA := FEdAmpSens.Value;
|
||||||
@@ -2343,6 +2394,7 @@ begin
|
|||||||
if FEdSMXvtr[i] <> nil then FEdSMXvtr[i].Value := C.SMeterXvtrDB[i];
|
if FEdSMXvtr[i] <> nil then FEdSMXvtr[i].Value := C.SMeterXvtrDB[i];
|
||||||
if FEdDispXvtr[i] <> nil then FEdDispXvtr[i].Value := C.DispXvtrDB[i];
|
if FEdDispXvtr[i] <> nil then FEdDispXvtr[i].Value := C.DispXvtrDB[i];
|
||||||
end;
|
end;
|
||||||
|
FEdFreqCalPPM.Value := EnsureRange(C.FreqCalPPM, -100.0, 100.0);
|
||||||
FEdSupplyVoltCal.Value := C.SupplyVoltCal;
|
FEdSupplyVoltCal.Value := C.SupplyVoltCal;
|
||||||
FEdAmpVoff.Value := C.AmpVoffMV;
|
FEdAmpVoff.Value := C.AmpVoffMV;
|
||||||
FEdAmpSens.Value := C.AmpSensMVA;
|
FEdAmpSens.Value := C.AmpSensMVA;
|
||||||
@@ -4355,7 +4407,7 @@ begin
|
|||||||
L.Font.Name := UI_FONT; L.Font.Size := 9; L.Font.Color := CLR_TEXTDIM;
|
L.Font.Name := UI_FONT; L.Font.Size := 9; L.Font.Color := CLR_TEXTDIM;
|
||||||
L.WordWrap := True;
|
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;
|
Y := GRP_TITLE_H + 8;
|
||||||
FQoEn := TFlatCheckBox.Create(Self);
|
FQoEn := TFlatCheckBox.Create(Self);
|
||||||
@@ -4377,6 +4429,11 @@ begin
|
|||||||
FQoTxLO := QoFloat(Y, 0.0, 50000.0, 0.001, 6);
|
FQoTxLO := QoFloat(Y, 0.0, 50000.0, 0.001, 6);
|
||||||
FQoTxLO.Value := 8089.5;
|
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);
|
Inc(Y, 30); QoLabel('Downlink begin (MHz)', Y);
|
||||||
FQoBegin := QoFloat(Y, 0.0, 50000.0, 0.001, 3);
|
FQoBegin := QoFloat(Y, 0.0, 50000.0, 0.001, 3);
|
||||||
FQoBegin.Value := 10489.5;
|
FQoBegin.Value := 10489.5;
|
||||||
@@ -4398,7 +4455,7 @@ begin
|
|||||||
|
|
||||||
FQoInfo := TLabel.Create(Self);
|
FQoInfo := TLabel.Create(Self);
|
||||||
FQoInfo.Parent := FPageQO;
|
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.Color := CLR_TEXTDIM; FQoInfo.Font.Name := 'Courier New';
|
||||||
FQoInfo.Font.Size := 9; FQoInfo.WordWrap := True;
|
FQoInfo.Font.Size := 9; FQoInfo.WordWrap := True;
|
||||||
finally
|
finally
|
||||||
@@ -4412,7 +4469,7 @@ begin
|
|||||||
if FQoInfo = nil then Exit;
|
if FQoInfo = nil then Exit;
|
||||||
dlMid := (FQoBegin.Value + FQoEnd.Value) / 2.0; // МГц
|
dlMid := (FQoBegin.Value + FQoEnd.Value) / 2.0; // МГц
|
||||||
rxIf := dlMid - FQoLnbLO.Value; // RX IF в SDR
|
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(
|
FQoInfo.Caption := Format(
|
||||||
'At downlink %.3f MHz: RX IF = %.3f MHz TX = %.3f MHz',
|
'At downlink %.3f MHz: RX IF = %.3f MHz TX = %.3f MHz',
|
||||||
[dlMid, rxIf, txF]);
|
[dlMid, rxIf, txF]);
|
||||||
@@ -4427,6 +4484,7 @@ begin
|
|||||||
FXvtrConfig.Entries[QO100_SLOT].LOOffset := FQoLnbLO.Value * 1.0e6;
|
FXvtrConfig.Entries[QO100_SLOT].LOOffset := FQoLnbLO.Value * 1.0e6;
|
||||||
FXvtrConfig.Entries[QO100_SLOT].LOError := FQoLoErr.Value;
|
FXvtrConfig.Entries[QO100_SLOT].LOError := FQoLoErr.Value;
|
||||||
FXvtrConfig.Entries[QO100_SLOT].TxLOOffset := FQoTxLO.Value * 1.0e6;
|
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].FreqBegin := FQoBegin.Value * 1.0e6;
|
||||||
FXvtrConfig.Entries[QO100_SLOT].FreqEnd := FQoEnd.Value * 1.0e6;
|
FXvtrConfig.Entries[QO100_SLOT].FreqEnd := FQoEnd.Value * 1.0e6;
|
||||||
FXvtrConfig.Entries[QO100_SLOT].RXGain := FQoRXGain.Value;
|
FXvtrConfig.Entries[QO100_SLOT].RXGain := FQoRXGain.Value;
|
||||||
@@ -4442,6 +4500,7 @@ begin
|
|||||||
FQoLnbLO.Value := E.LOOffset / 1.0e6;
|
FQoLnbLO.Value := E.LOOffset / 1.0e6;
|
||||||
FQoLoErr.Value := E.LOError;
|
FQoLoErr.Value := E.LOError;
|
||||||
FQoTxLO.Value := E.TxLOOffset / 1.0e6;
|
FQoTxLO.Value := E.TxLOOffset / 1.0e6;
|
||||||
|
FQoTxLOErr.Value := E.TxLOError;
|
||||||
FQoBegin.Value := E.FreqBegin / 1.0e6;
|
FQoBegin.Value := E.FreqBegin / 1.0e6;
|
||||||
FQoEnd.Value := E.FreqEnd / 1.0e6;
|
FQoEnd.Value := E.FreqEnd / 1.0e6;
|
||||||
FQoRXGain.Value := E.RXGain;
|
FQoRXGain.Value := E.RXGain;
|
||||||
|
|||||||
Reference in New Issue
Block a user