mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
fix(cw): первый прогон на железе — частота, мощность, индикация, полоса
Семь дефектов, вскрытых проверкой в эфире. Общий корень у большинства: с кейером в прошивке FTransmitting не поднимается вовсе — передачей владеет FPGA, — а половина проекта считала этот флаг признаком эфира. Обрыв посылки после первого элемента. При break-in железо само поднимает T/R на каждую посылку, HPS_PTT это отражает, и разбор статуса гонял по фронту SetMOX(True/False); снятие MOX гасило CWX. Теперь в телеграфе фронт HPS_PTT не управляет передачей. Без этого и работа манипулятором пересобирала бы передающий тракт на каждой точке. Обрыв по «key hit» переведён на фронт лепестка: на плате без ключа входы могут стоять в единице, и проверка уровня убивала бы передачу сразу. Посылка уходила по центру дисплея, а не на VFO. StartRunning ставил DUC на центр DDC; до первой перестройки VFO или до первого MOX там и оставалось. Без CTUN центр совпадает с VFO, поэтому баг и дожил до телеграфа, где MOX не бывает. SetRunAndFreq получает TX-частоту, SetSliceTarget толкает кадр, когда перестраивают слайс-источник. Правило: DUC обязан стоять правильно всегда, а не только на передаче. Мощность ниже, чем на TUN: уровень (байт 345) клался в кадр только при FIsTransmitting. Введён SetCWKeyerArmed — пока кейер вооружён, уровень лежит в каждом HP-кадре. Там же пересчитывается FDriveLevel, иначе до первого касания ручки в железо уходил ноль. Индикация не показывала передачу: события rfTransmitting в телеграфе не бывает, поэтому блок рендера не выполнялся ни разу. ApplyHPStatus шлёт его сам на фронтах RadioKeyed («железо в эфире» = передача или замыкание ключа прошивкой, выдержка 8 точек и не меньше 500 мс). На RadioKeyed переведены S-метр, красная полоса главного VFO и TX-бейджи флагов вместе с красной полосой слайса на панадаптерах. Полоса на спектре рисовалась как SSB: TXSignedEdges обязана отдавать для CW голосовую боковую (через bp0 идёт тон pitch при настройке), поэтому экранные кромки развязаны в TXFilterEdgesHz — занимаемая полоса манипуляции симметрично несущей. Тот же класс, что был у ЧМ. F1..F8 молчали, пока не откроешь окно памяти: обработчик упирался в проверку лениво создаваемой формы. Текст берётся из настроек. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+14
-2
@@ -170,6 +170,7 @@ type
|
|||||||
FAlexConfig: TAlexSettings; // per-band antenna + routing config
|
FAlexConfig: TAlexSettings; // per-band antenna + routing config
|
||||||
FStepAtten: Byte; // ADC0 step attenuator 0-31 dB
|
FStepAtten: Byte; // ADC0 step attenuator 0-31 dB
|
||||||
FCWXKey: Boolean; // бит CWX (HP байт 5): программный ключ
|
FCWXKey: Boolean; // бит CWX (HP байт 5): программный ключ
|
||||||
|
FCWKeyerArmed: Boolean; // кейер прошивки вооружён (см. байт 345)
|
||||||
// ---- OC Control (Open Collector выходы Penny/Alex, byte 1401) ----
|
// ---- OC Control (Open Collector выходы Penny/Alex, byte 1401) ----
|
||||||
FOCConfig: TOCSettings;
|
FOCConfig: TOCSettings;
|
||||||
FTuning: Boolean; // TUN активен (для TX pin action)
|
FTuning: Boolean; // TUN активен (для TX pin action)
|
||||||
@@ -271,6 +272,7 @@ type
|
|||||||
Tuning: Boolean = False; TwoTone: Boolean = False); override;
|
Tuning: Boolean = False; TwoTone: Boolean = False); override;
|
||||||
procedure SetStepAtten(dB: Byte); override;
|
procedure SetStepAtten(dB: Byte); override;
|
||||||
procedure SetCWXKey(Down: Boolean); override;
|
procedure SetCWXKey(Down: Boolean); override;
|
||||||
|
procedure SetCWKeyerArmed(Armed: Boolean); override;
|
||||||
procedure SetFreqCalPPM(P: Double); override;
|
procedure SetFreqCalPPM(P: Double); override;
|
||||||
// Частота с поправкой опорника — для частотного слова DDC/DUC.
|
// Частота с поправкой опорника — для частотного слова DDC/DUC.
|
||||||
function CalFreq(Hz: Double): Double;
|
function CalFreq(Hz: Double): Double;
|
||||||
@@ -1812,6 +1814,13 @@ begin
|
|||||||
if FConnected then SendFullHP;
|
if FConnected then SendFullHP;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure THPSDRNetwork.SetCWKeyerArmed(Armed: Boolean);
|
||||||
|
begin
|
||||||
|
if FCWKeyerArmed = Armed then Exit;
|
||||||
|
FCWKeyerArmed := Armed;
|
||||||
|
if FConnected then SendFullHP;
|
||||||
|
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;
|
||||||
@@ -1899,8 +1908,11 @@ begin
|
|||||||
Buf[331] := (Ph shr 8) and $FF;
|
Buf[331] := (Ph shr 8) and $FF;
|
||||||
Buf[332] := Ph and $FF;
|
Buf[332] := Ph and $FF;
|
||||||
|
|
||||||
// Drive level (byte 345)
|
// Drive level (byte 345). Обычно уровень нужен только на передаче, но с
|
||||||
if FIsTransmitting then
|
// вооружённым кейером телеграфа «передачи» в понимании софта не бывает:
|
||||||
|
// прошивка замыкает ключ сама, и в кадре, который она в этот момент видит,
|
||||||
|
// уровень уже обязан стоять — иначе посылка уходит с чужой мощностью.
|
||||||
|
if FIsTransmitting or FCWKeyerArmed then
|
||||||
Buf[345] := FCurrentDrive;
|
Buf[345] := FCurrentDrive;
|
||||||
|
|
||||||
// ALEX0 filter bits (bytes 1432-1435)
|
// ALEX0 filter bits (bytes 1432-1435)
|
||||||
|
|||||||
+18
-7
@@ -1476,7 +1476,9 @@ begin
|
|||||||
FSpecView.TXMode := FController.FTransmitting and not FController.FDisplayDuplex;
|
FSpecView.TXMode := FController.FTransmitting and not FController.FDisplayDuplex;
|
||||||
// Красная полоса главного VFO — только когда передаём с него: при
|
// Красная полоса главного VFO — только когда передаём с него: при
|
||||||
// TX со слайса краснеет полоса ТОГО слайса (DrawSliceFilterMarkers).
|
// TX со слайса краснеет полоса ТОГО слайса (DrawSliceFilterMarkers).
|
||||||
FSpecView.TXOverlay := FController.FTransmitting and
|
// ★RadioKeyed, а не FTransmitting: в телеграфе ключ замыкает прошивка,
|
||||||
|
// софт «не передаёт», и полоса оставалась бы приёмной всю связь.
|
||||||
|
FSpecView.TXOverlay := FController.RadioKeyed and
|
||||||
(FController.FTxSliceId = 0);
|
(FController.FTxSliceId = 0);
|
||||||
PushFilterEdgesToView; // на передаче полоса рисуется по TX-фильтру
|
PushFilterEdgesToView; // на передаче полоса рисуется по TX-фильтру
|
||||||
ApplySpecViewGridFromState; // сетка спектра под активный тракт
|
ApplySpecViewGridFromState; // сетка спектра под активный тракт
|
||||||
@@ -3711,7 +3713,9 @@ begin
|
|||||||
FSpecView.SMeterMin := FSMeterMin;
|
FSpecView.SMeterMin := FSMeterMin;
|
||||||
FSpecView.LastFwdW := FController.FLastFwdW;
|
FSpecView.LastFwdW := FController.FLastFwdW;
|
||||||
FSpecView.LastSWR := FController.FLastSWR;
|
FSpecView.LastSWR := FController.FLastSWR;
|
||||||
FSpecView.Transmitting := FController.FTransmitting;
|
// Метр — по RadioKeyed, а не по FTransmitting: в телеграфе софт «не передаёт»,
|
||||||
|
// ключ замыкает прошивка, и стрелка осталась бы на S-метре всю связь.
|
||||||
|
FSpecView.Transmitting := FController.RadioKeyed;
|
||||||
|
|
||||||
if PbSMeterRight <> nil then PbSMeterRight.Invalidate;
|
if PbSMeterRight <> nil then PbSMeterRight.Invalidate;
|
||||||
// PWR/SWR метки и бары обновляем здесь (10 Гц), а не в DoUpdateStatus —
|
// PWR/SWR метки и бары обновляем здесь (10 Гц), а не в DoUpdateStatus —
|
||||||
@@ -8140,6 +8144,10 @@ procedure TMainForm.OnCWSettingsChange(const C: TCWSettings);
|
|||||||
// прошивку, персист — там же. Форме остаётся только передать запись.
|
// прошивку, персист — там же. Форме остаётся только передать запись.
|
||||||
begin
|
begin
|
||||||
FController.SetCWSettings(C);
|
FController.SetCWSettings(C);
|
||||||
|
// Скорость кейера = ширина полосы телеграфа на экране. Движок вьюху сам не
|
||||||
|
// уведомляет — правило то же, что для профилей и кромок TX.
|
||||||
|
PushFilterEdgesToView;
|
||||||
|
if FController.FTransmitting then FSpectrumDirty := True;
|
||||||
// Окно памяти показывает те же ячейки — держим его в курсе правок из настроек.
|
// Окно памяти показывает те же ячейки — держим его в курсе правок из настроек.
|
||||||
if FCWMsgForm <> nil then TCWMessagesForm(FCWMsgForm).LoadFrom(FController.CWSettings);
|
if FCWMsgForm <> nil then TCWMessagesForm(FCWMsgForm).LoadFrom(FController.CWSettings);
|
||||||
end;
|
end;
|
||||||
@@ -8170,17 +8178,20 @@ end;
|
|||||||
|
|
||||||
procedure TMainForm.FormKeyDownCW(Sender: TObject; var Key: Word;
|
procedure TMainForm.FormKeyDownCW(Sender: TObject; var Key: Word;
|
||||||
Shift: TShiftState);
|
Shift: TShiftState);
|
||||||
// F1..F8 — ячейки памяти телеграфа. Работают только в CW и только когда окно
|
// F1..F8 — ячейки памяти телеграфа. Единственное условие — телеграф на
|
||||||
// памяти уже открывали (иначе клавиши остаются свободными для всего прочего).
|
// передающем источнике; в прочих режимах клавиши свободны для всего другого.
|
||||||
|
// ★Текст берём ИЗ НАСТРОЕК, а не из окна памяти: окно создаётся лениво, и
|
||||||
|
// раньше клавиши молчали, пока его хоть раз не открыли.
|
||||||
var Slot: Integer;
|
var Slot: Integer;
|
||||||
begin
|
begin
|
||||||
if FCWMsgForm = nil then Exit;
|
|
||||||
if Shift <> [] then Exit;
|
if Shift <> [] then Exit;
|
||||||
if not (FController.ActiveTXMode in [MODE_CWL, MODE_CWU]) then Exit;
|
|
||||||
if (Key < VK_F1) or (Key > VK_F8) then Exit;
|
if (Key < VK_F1) or (Key > VK_F8) then Exit;
|
||||||
|
if not (FController.ActiveTXMode in [MODE_CWL, MODE_CWU]) then Exit;
|
||||||
Slot := Key - VK_F1;
|
Slot := Key - VK_F1;
|
||||||
TCWMessagesForm(FCWMsgForm).SendSlot(Slot);
|
if (Slot < 0) or (Slot >= CW_MSG_COUNT) then Exit;
|
||||||
Key := 0;
|
Key := 0;
|
||||||
|
if FController.CWSettings.Messages[Slot] = '' then Exit;
|
||||||
|
FController.CWXSend(FController.CWSettings.Messages[Slot]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnAlexSettingsChange(const A: TAlexSettings);
|
procedure TMainForm.OnAlexSettingsChange(const A: TAlexSettings);
|
||||||
|
|||||||
+2
-2
@@ -1366,7 +1366,7 @@ begin
|
|||||||
O.SetSquelchState(S.FMSQOn, S.FMSQLevel);
|
O.SetSquelchState(S.FMSQOn, S.FMSQLevel);
|
||||||
// TxSel: слайс — источник передачи; Tx: передача идёт именно с него.
|
// TxSel: слайс — источник передачи; Tx: передача идёт именно с него.
|
||||||
O.SetExtState(Round(S.Volume * 100), S.Muted, False,
|
O.SetExtState(Round(S.Volume * 100), S.Muted, False,
|
||||||
FController.FTransmitting and (FController.FTxSliceId = SliceId),
|
FController.RadioKeyed and (FController.FTxSliceId = SliceId),
|
||||||
FController.FTxSliceId = SliceId);
|
FController.FTxSliceId = SliceId);
|
||||||
// Auto TX (настройка CAT-порта слайса) — бейдж TX подписан AutoTX.
|
// Auto TX (настройка CAT-порта слайса) — бейдж TX подписан AutoTX.
|
||||||
O.SetAutoTxState(FController.SliceAutoTx(SliceId));
|
O.SetAutoTxState(FController.SliceAutoTx(SliceId));
|
||||||
@@ -1417,7 +1417,7 @@ begin
|
|||||||
// передаём со слайса, ActiveTXFreqHz берёт его частоту, а не VFO B.
|
// передаём со слайса, ActiveTXFreqHz берёт его частоту, а не VFO B.
|
||||||
FMainFlag.SetExtState(FController.ActiveVolume, FController.FMuted,
|
FMainFlag.SetExtState(FController.ActiveVolume, FController.FMuted,
|
||||||
FController.FSplitTxB and (FController.FTxSliceId = 0),
|
FController.FSplitTxB and (FController.FTxSliceId = 0),
|
||||||
FController.FTransmitting and (FController.FTxSliceId = 0),
|
FController.RadioKeyed and (FController.FTxSliceId = 0),
|
||||||
FController.FTxSliceId = 0);
|
FController.FTxSliceId = 0);
|
||||||
PushFlagTXProfiles(FMainFlag, 0);
|
PushFlagTXProfiles(FMainFlag, 0);
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -184,6 +184,11 @@ type
|
|||||||
// передачи телеграфа — несущую по нему формирует прошивка. Пакет уходит
|
// передачи телеграфа — несущую по нему формирует прошивка. Пакет уходит
|
||||||
// немедленно, каждый фронт (тайминги держит PC). Pluto — no-op.
|
// немедленно, каждый фронт (тайминги держит PC). Pluto — no-op.
|
||||||
procedure SetCWXKey(Down: Boolean); virtual;
|
procedure SetCWXKey(Down: Boolean); virtual;
|
||||||
|
// Кейер в прошивке вооружён: железо может замкнуть ключ САМО, без нашего
|
||||||
|
// PTT. Пока флаг взведён, уровень мощности обязан лежать в каждом HP-кадре
|
||||||
|
// (обычно он шлётся только на передаче — а «передачи» с точки зрения софта
|
||||||
|
// тут не происходит вовсе). Pluto — no-op.
|
||||||
|
procedure SetCWKeyerArmed(Armed: Boolean); virtual;
|
||||||
procedure SetStepAtten(dB: Byte); virtual;
|
procedure SetStepAtten(dB: Byte); virtual;
|
||||||
// RX-усиление (Pluto/AD936x). ModeIdx: 0=manual, 1=fast_attack, 2=slow_attack,
|
// RX-усиление (Pluto/AD936x). ModeIdx: 0=manual, 1=fast_attack, 2=slow_attack,
|
||||||
// 3=hybrid. GainDb применяется только в manual. HPSDR — no-op.
|
// 3=hybrid. GainDb применяется только в manual. HPSDR — no-op.
|
||||||
@@ -285,6 +290,10 @@ procedure TRadioBackend.SetCWXKey(Down: Boolean);
|
|||||||
begin
|
begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TRadioBackend.SetCWKeyerArmed(Armed: Boolean);
|
||||||
|
begin
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioBackend.SetStepAtten(dB: Byte);
|
procedure TRadioBackend.SetStepAtten(dB: Byte);
|
||||||
begin
|
begin
|
||||||
end;
|
end;
|
||||||
|
|||||||
+93
-5
@@ -442,6 +442,14 @@ type
|
|||||||
// разбором HP-статуса (прямой ключ), читается DSP-потоком в MixSidetone —
|
// разбором HP-статуса (прямой ключ), читается DSP-потоком в MixSidetone —
|
||||||
// толерантная гонка на одном булеве, как FHPp*.
|
// толерантная гонка на одном булеве, как FHPp*.
|
||||||
FCWKeyDown: Boolean;
|
FCWKeyDown: Boolean;
|
||||||
|
// Прошлое состояние лепестков — обрыв программной передачи по фронту.
|
||||||
|
FCWPrevDot: Boolean;
|
||||||
|
FCWPrevDash: Boolean;
|
||||||
|
// Момент последнего замыкания ключа прошивкой — только для ИНДИКАЦИИ
|
||||||
|
// (см. RadioKeyed): без выдержки метр моргал бы RX/TX на каждой точке.
|
||||||
|
FCWKeyedAt: QWord;
|
||||||
|
// Предыдущее значение RadioKeyed — событие рендера шлём только на фронте.
|
||||||
|
FCWKeyedShown: Boolean;
|
||||||
// Последний отправленный байт 5 — чтобы не гнать DUC-пакет на каждую
|
// Последний отправленный байт 5 — чтобы не гнать DUC-пакет на каждую
|
||||||
// смену режима, а только когда телеграфная конфигурация реально поменялась.
|
// смену режима, а только когда телеграфная конфигурация реально поменялась.
|
||||||
FLastCWOptions: Byte;
|
FLastCWOptions: Byte;
|
||||||
@@ -945,6 +953,10 @@ type
|
|||||||
// ---- Телеграф ----------------------------------------------------------
|
// ---- Телеграф ----------------------------------------------------------
|
||||||
// Настройки применяются целиком: pitch уезжает в движок (там он двигает
|
// Настройки применяются целиком: pitch уезжает в движок (там он двигает
|
||||||
// гетеродин и полосу), остальное — в DUC Specific прошивке.
|
// гетеродин и полосу), остальное — в DUC Specific прошивке.
|
||||||
|
// «Железо сейчас в эфире» — ДЛЯ ИНДИКАЦИИ (S-метр, лампочки), не для тракта.
|
||||||
|
// В телеграфе FTransmitting не поднимается: передачей владеет кейер прошивки,
|
||||||
|
// и единственный признак эфира — HPS_PTT в статусе.
|
||||||
|
function RadioKeyed: Boolean;
|
||||||
function CWSettings: TCWSettings;
|
function CWSettings: TCWSettings;
|
||||||
procedure SetCWSettings(const C: TCWSettings);
|
procedure SetCWSettings(const C: TCWSettings);
|
||||||
// Программная передача текста: PC сам выдерживает тайминги и дёргает бит
|
// Программная передача текста: PC сам выдерживает тайминги и дёргает бит
|
||||||
@@ -1495,11 +1507,16 @@ begin
|
|||||||
FHPpHWPTT := (Status.StatusBits and HPS_PTT) <> 0;
|
FHPpHWPTT := (Status.StatusBits and HPS_PTT) <> 0;
|
||||||
FHPpOverload := Status.ADCOverload;
|
FHPpOverload := Status.ADCOverload;
|
||||||
// Состояние ключа/манипулятора в разъёме трансивера (сетевой поток).
|
// Состояние ключа/манипулятора в разъёме трансивера (сетевой поток).
|
||||||
// Любое касание обрывает программную передачу — как в Thetis/pihpsdr
|
// Касание обрывает программную передачу — как в Thetis/pihpsdr («key hit»
|
||||||
// («key hit» останавливает CAT CW), иначе оператор не может вклиниться.
|
// останавливает CAT CW), иначе оператор не может вклиниться. ★Обрываем по
|
||||||
|
// ФРОНТУ, а не по уровню: на плате без подключённого ключа входы могут стоять
|
||||||
|
// в единице, и проверка уровня убивала бы передачу на первом же HP-статусе.
|
||||||
KeyDot := (Status.StatusBits and HPS_DOT) <> 0;
|
KeyDot := (Status.StatusBits and HPS_DOT) <> 0;
|
||||||
KeyDash := (Status.StatusBits and HPS_DASH) <> 0;
|
KeyDash := (Status.StatusBits and HPS_DASH) <> 0;
|
||||||
if (KeyDot or KeyDash) and CWXBusy then CWXAbort;
|
if ((KeyDot and not FCWPrevDot) or (KeyDash and not FCWPrevDash))
|
||||||
|
and CWXBusy then CWXAbort;
|
||||||
|
FCWPrevDot := KeyDot;
|
||||||
|
FCWPrevDash := KeyDash;
|
||||||
// Программный сайдтон от лепестков верен ТОЛЬКО у прямого ключа: при иамбике
|
// Программный сайдтон от лепестков верен ТОЛЬКО у прямого ключа: при иамбике
|
||||||
// элементы считает FPGA, а PC видит лишь замыкание контакта.
|
// элементы считает FPGA, а PC видит лишь замыкание контакта.
|
||||||
if (FCWSettings.KeyerMode = 0) and (ActiveTXMode in [MODE_CWL, MODE_CWU])
|
if (FCWSettings.KeyerMode = 0) and (ActiveTXMode in [MODE_CWL, MODE_CWU])
|
||||||
@@ -1553,6 +1570,32 @@ begin
|
|||||||
Changed(rfADCOverload);
|
Changed(rfADCOverload);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// ★Телеграф с кейером в прошивке: HPS_PTT здесь — ЭХО собственной манипуляции
|
||||||
|
// (при break-in железо само поднимает T/R на каждую посылку), а не действие
|
||||||
|
// оператора. Гонять по нему SetMOX нельзя: на каждой точке пересобирался бы
|
||||||
|
// передающий тракт, а программная передача обрывалась бы после ПЕРВОЙ посылки
|
||||||
|
// (снятие MOX гасит CWX). Флаг держим актуальным — им выбирается источник
|
||||||
|
// модуляции, — но передачей по нему не управляем.
|
||||||
|
if CWTXActive then
|
||||||
|
begin
|
||||||
|
FHWPTTActive := FHPpHWPTT;
|
||||||
|
if FHPpHWPTT then FCWKeyedAt := GetTickCount64;
|
||||||
|
// UI про телеграфную передачу иначе не узнает вовсе: SetMOX здесь не
|
||||||
|
// вызывается, а значит и rfTransmitting никто не шлёт. Фронт RadioKeyed
|
||||||
|
// (с выдержкой) — единственный сигнал «покрасить бейджи и полосы».
|
||||||
|
if FCWKeyedShown <> RadioKeyed then
|
||||||
|
begin
|
||||||
|
FCWKeyedShown := RadioKeyed;
|
||||||
|
Changed(rfTransmitting);
|
||||||
|
end;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
// Ушли из телеграфа/сняли вооружение — вернуть индикацию в исходное.
|
||||||
|
if FCWKeyedShown then
|
||||||
|
begin
|
||||||
|
FCWKeyedShown := False;
|
||||||
|
Changed(rfTransmitting);
|
||||||
|
end;
|
||||||
// Hardware PTT (footswitch / mic PTT): фронт на rate HP Status — не пропустить
|
// Hardware PTT (footswitch / mic PTT): фронт на rate HP Status — не пропустить
|
||||||
// короткое нажатие. HWPTT не перекрывает ручной MOX; выключаем TX только если
|
// короткое нажатие. HWPTT не перекрывает ручной MOX; выключаем TX только если
|
||||||
// его включил именно HWPTT.
|
// его включил именно HWPTT.
|
||||||
@@ -2291,6 +2334,10 @@ begin
|
|||||||
FSlices[idx].TargetHz := TargetHz;
|
FSlices[idx].TargetHz := TargetHz;
|
||||||
if Assigned(FDSPEngine) then
|
if Assigned(FDSPEngine) then
|
||||||
FDSPEngine.SetSliceShift(Id, TargetHz - PanCenterHz(FSlices[idx].PanId));
|
FDSPEngine.SetSliceShift(Id, TargetHz - PanCenterHz(FSlices[idx].PanId));
|
||||||
|
// Слайс — источник передачи ⇒ он же задаёт частоту DUC. Раньше её дошлёт бы
|
||||||
|
// первый MOX, но в телеграфе ключ замыкает прошивка без всякого MOX: DUC
|
||||||
|
// обязан стоять правильно ВСЕГДА, а не только на передаче.
|
||||||
|
if FTxSliceId = Id then PushNetworkState;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetSliceMode(Id, Mode: Integer);
|
procedure TRadioController.SetSliceMode(Id, Mode: Integer);
|
||||||
@@ -5628,6 +5675,23 @@ end;
|
|||||||
// Телеграф
|
// Телеграф
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
|
function TRadioController.RadioKeyed: Boolean;
|
||||||
|
// Выдержка после последней посылки: точка на 20 WPM длится 60 мс, а индикаторы
|
||||||
|
// перерисовываются 10 раз в секунду — без неё бейджи и метр мигали бы внутри
|
||||||
|
// одного знака. Держим 8 точек, но не меньше 500 мс: на медленной скорости
|
||||||
|
// межсловный интервал (7 точек) сам по себе длиннее полусекунды, и фиксированная
|
||||||
|
// выдержка гасила бы индикацию между словами.
|
||||||
|
var Hold: QWord;
|
||||||
|
begin
|
||||||
|
Result := FTransmitting or FTuning;
|
||||||
|
if Result then Exit;
|
||||||
|
if not CWTXActive then Exit(False);
|
||||||
|
if FCWKeyedAt = 0 then Exit(False);
|
||||||
|
Hold := 8 * (1200 div EnsureRange(FCWSettings.Speed, 5, 60));
|
||||||
|
if Hold < 500 then Hold := 500;
|
||||||
|
Result := (GetTickCount64 - FCWKeyedAt) < Hold;
|
||||||
|
end;
|
||||||
|
|
||||||
function TRadioController.CWSettings: TCWSettings;
|
function TRadioController.CWSettings: TCWSettings;
|
||||||
begin
|
begin
|
||||||
Result := FCWSettings;
|
Result := FCWSettings;
|
||||||
@@ -5645,10 +5709,14 @@ begin
|
|||||||
FCWSettings.SidetoneHWLevel := EnsureRange(FCWSettings.SidetoneHWLevel, 0, 127);
|
FCWSettings.SidetoneHWLevel := EnsureRange(FCWSettings.SidetoneHWLevel, 0, 127);
|
||||||
FCWSettings.SidetoneSWLevel := EnsureRange(FCWSettings.SidetoneSWLevel, 0, 100);
|
FCWSettings.SidetoneSWLevel := EnsureRange(FCWSettings.SidetoneSWLevel, 0, 100);
|
||||||
if FWDSPReady and Assigned(FDSPEngine) then
|
if FWDSPReady and Assigned(FDSPEngine) then
|
||||||
|
begin
|
||||||
FDSPEngine.SetCWPitch(FCWSettings.Pitch);
|
FDSPEngine.SetCWPitch(FCWSettings.Pitch);
|
||||||
|
FDSPEngine.SetCWSpeed(FCWSettings.Speed); // ширина полосы CW на экране
|
||||||
|
end;
|
||||||
if Assigned(FCWSender) then
|
if Assigned(FCWSender) then
|
||||||
FCWSender.SetSpeed(FCWSettings.Speed, FCWSettings.Weight);
|
FCWSender.SetSpeed(FCWSettings.Speed, FCWSettings.Weight);
|
||||||
SendDUCSpecificFromSettings;
|
SendDUCSpecificFromSettings;
|
||||||
|
if Assigned(FNetwork) then FNetwork.SetCWKeyerArmed(CWTXActive);
|
||||||
// Тон TUN в телеграфе равен pitch (несущая обязана лечь на VFO, см. SetTune):
|
// Тон TUN в телеграфе равен pitch (несущая обязана лечь на VFO, см. SetTune):
|
||||||
// если крутят pitch прямо на настройке — обновляем живьём.
|
// если крутят pitch прямо на настройке — обновляем живьём.
|
||||||
if FTuning and FWDSPReady and Assigned(FDSPEngine)
|
if FTuning and FWDSPReady and Assigned(FDSPEngine)
|
||||||
@@ -5667,6 +5735,9 @@ procedure TRadioController.SyncCWKeyer;
|
|||||||
// иначе прошивка поднимет PTT на замыкание ключа посреди SSB.
|
// иначе прошивка поднимет PTT на замыкание ключа посреди SSB.
|
||||||
begin
|
begin
|
||||||
if not (ActiveTXMode in [MODE_CWL, MODE_CWU]) then CWXAbort;
|
if not (ActiveTXMode in [MODE_CWL, MODE_CWU]) then CWXAbort;
|
||||||
|
// Вооружённый кейер = железо может выйти в эфир само ⇒ бэкенд обязан класть
|
||||||
|
// уровень мощности в КАЖДЫЙ HP-кадр, а не только на передаче.
|
||||||
|
if Assigned(FNetwork) then FNetwork.SetCWKeyerArmed(CWTXActive);
|
||||||
if CWOptionsByte <> FLastCWOptions then SendDUCSpecificFromSettings;
|
if CWOptionsByte <> FLastCWOptions then SendDUCSpecificFromSettings;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -6224,8 +6295,17 @@ begin
|
|||||||
// Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread
|
// Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread
|
||||||
// (порт 1025) и rx_thread (порт 1035+) только после получения HP Run=1.
|
// (порт 1025) и rx_thread (порт 1035+) только после получения HP Run=1.
|
||||||
// DDC Specific и DUC Specific отправляем ПОСЛЕ, иначе порты ещё не открыты.
|
// DDC Specific и DUC Specific отправляем ПОСЛЕ, иначе порты ещё не открыты.
|
||||||
|
// Уровень пересчитываем здесь же: с вооружённым кейером он уедет в железо
|
||||||
|
// сразу, до всякой передачи, а после конструктора FDriveLevel ещё нулевой.
|
||||||
|
FDriveLevel := CalcDriveByte;
|
||||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FDriveLevel, False, True, True, FTuning, FTwoTone);
|
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FDriveLevel, False, True, True, FTuning, FTwoTone);
|
||||||
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel);
|
// ★DUC — на TX-ЧАСТОТУ, а не на центр дисплея. Раньше здесь стоял центр, и до
|
||||||
|
// первой перестройки VFO (или до первого MOX, который частоту дошлёт сам)
|
||||||
|
// передатчик стоял не там, где маркер: при CTUN центр и VFO — разные частоты.
|
||||||
|
// С кейером в прошивке это стало слышно в эфире: железо может замкнуть ключ
|
||||||
|
// сразу после старта, никакого MOX перед этим не будет.
|
||||||
|
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq),
|
||||||
|
XvtrTranslateTX(ActiveTXFreqHz), FDriveLevel);
|
||||||
FRunning := True;
|
FRunning := True;
|
||||||
FRXStartTime := GetTickCount64;
|
FRXStartTime := GetTickCount64;
|
||||||
FRXLastPktTime := 0;
|
FRXLastPktTime := 0;
|
||||||
@@ -6251,8 +6331,12 @@ begin
|
|||||||
// Тон телеграфа в движок: на момент ConnectDevice канал WDSP мог быть ещё
|
// Тон телеграфа в движок: на момент ConnectDevice канал WDSP мог быть ещё
|
||||||
// не открыт, а здесь он гарантированно жив.
|
// не открыт, а здесь он гарантированно жив.
|
||||||
if FWDSPReady and Assigned(FDSPEngine) then
|
if FWDSPReady and Assigned(FDSPEngine) then
|
||||||
|
begin
|
||||||
FDSPEngine.SetCWPitch(FCWSettings.Pitch);
|
FDSPEngine.SetCWPitch(FCWSettings.Pitch);
|
||||||
|
FDSPEngine.SetCWSpeed(FCWSettings.Speed);
|
||||||
|
end;
|
||||||
SendDUCSpecificFromSettings;
|
SendDUCSpecificFromSettings;
|
||||||
|
SyncCWKeyer; // вооружение кейера + уровень мощности в HP-кадре
|
||||||
// Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...).
|
// Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...).
|
||||||
ApplyTXSettingsToDSP;
|
ApplyTXSettingsToDSP;
|
||||||
ApplyModeFilter;
|
ApplyModeFilter;
|
||||||
@@ -6280,7 +6364,8 @@ begin
|
|||||||
for p := 1 to MAX_PANS - 1 do
|
for p := 1 to MAX_PANS - 1 do
|
||||||
RemovePanDDC(p);
|
RemovePanDDC(p);
|
||||||
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), 0, False, True, True, FTuning, FTwoTone);
|
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), 0, False, True, True, FTuning, FTwoTone);
|
||||||
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
|
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq),
|
||||||
|
XvtrTranslateTX(ActiveTXFreqHz), 0);
|
||||||
FRunning := False;
|
FRunning := False;
|
||||||
FTransmitting := False;
|
FTransmitting := False;
|
||||||
// Стоп при активном TX/TUN: хвост IQ в DUC-очереди не должен пережить
|
// Стоп при активном TX/TUN: хвост IQ в DUC-очереди не должен пережить
|
||||||
@@ -6465,7 +6550,10 @@ begin
|
|||||||
FSettings.LoadTX(FDevMAC, FTXSettings);
|
FSettings.LoadTX(FDevMAC, FTXSettings);
|
||||||
FSettings.LoadCW(FDevMAC, FCWSettings);
|
FSettings.LoadCW(FDevMAC, FCWSettings);
|
||||||
if FWDSPReady and Assigned(FDSPEngine) then
|
if FWDSPReady and Assigned(FDSPEngine) then
|
||||||
|
begin
|
||||||
FDSPEngine.SetCWPitch(FCWSettings.Pitch);
|
FDSPEngine.SetCWPitch(FCWSettings.Pitch);
|
||||||
|
FDSPEngine.SetCWSpeed(FCWSettings.Speed);
|
||||||
|
end;
|
||||||
FSettings.LoadAlex(FDevMAC, FAlexSettings);
|
FSettings.LoadAlex(FDevMAC, FAlexSettings);
|
||||||
FSettings.LoadAnt936x(FDevMAC, FAnt936x);
|
FSettings.LoadAnt936x(FDevMAC, FAnt936x);
|
||||||
FNetwork.SetAlexConfig(FAlexSettings);
|
FNetwork.SetAlexConfig(FAlexSettings);
|
||||||
|
|||||||
@@ -473,6 +473,7 @@ type
|
|||||||
FDisplayResLimit: Integer; // потолок точек анализатора (настройка display_pixels)
|
FDisplayResLimit: Integer; // потолок точек анализатора (настройка display_pixels)
|
||||||
FShiftHz: Double; // NCO shift для CTUN
|
FShiftHz: Double; // NCO shift для CTUN
|
||||||
FCWPitch: Integer; // Гц, тон телеграфа (см. CWLOOffset)
|
FCWPitch: Integer; // Гц, тон телеграфа (см. CWLOOffset)
|
||||||
|
FCWSpeed: Integer; // WPM — только для занимаемой полосы на экране
|
||||||
FNRMode: Integer;
|
FNRMode: Integer;
|
||||||
FNBMode: Integer;
|
FNBMode: Integer;
|
||||||
FSNBEnabled: Boolean;
|
FSNBEnabled: Boolean;
|
||||||
@@ -654,6 +655,9 @@ type
|
|||||||
// применяется немедленно (главный + слайсы), персист делает контроллер.
|
// применяется немедленно (главный + слайсы), персист делает контроллер.
|
||||||
procedure SetCWPitch(Hz: Integer);
|
procedure SetCWPitch(Hz: Integer);
|
||||||
function CWPitch: Integer;
|
function CWPitch: Integer;
|
||||||
|
// Скорость кейера нужна движку ровно для одного — ширины полосы телеграфа
|
||||||
|
// на экране (TXFilterEdgesHz). Тайминги делает прошивка, не мы.
|
||||||
|
procedure SetCWSpeed(WPM: Integer);
|
||||||
// Смещение гетеродина демодулятора относительно VFO в телеграфе (см.
|
// Смещение гетеродина демодулятора относительно VFO в телеграфе (см.
|
||||||
// комментарий у PushShift). Публично: тем же сдвигом контроллер двигает
|
// комментарий у PushShift). Публично: тем же сдвигом контроллер двигает
|
||||||
// DUC при настройке TUN, чтобы несущая легла ровно на VFO.
|
// DUC при настройке TUN, чтобы несущая легла ровно на VFO.
|
||||||
@@ -1370,6 +1374,7 @@ begin
|
|||||||
FAGCMode := agcMedium;
|
FAGCMode := agcMedium;
|
||||||
FShiftHz := 0.0;
|
FShiftHz := 0.0;
|
||||||
FCWPitch := 600;
|
FCWPitch := 600;
|
||||||
|
FCWSpeed := 20;
|
||||||
FAGCTop := -90.0;
|
FAGCTop := -90.0;
|
||||||
FVolume := 0.7;
|
FVolume := 0.7;
|
||||||
FSMeter := -130.0;
|
FSMeter := -130.0;
|
||||||
@@ -3090,6 +3095,13 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TWDSPEngine.SetCWSpeed(WPM: Integer);
|
||||||
|
begin
|
||||||
|
if WPM < 5 then WPM := 5;
|
||||||
|
if WPM > 60 then WPM := 60;
|
||||||
|
FCWSpeed := WPM;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.SetAGCTop(TopDBm: Double);
|
procedure TWDSPEngine.SetAGCTop(TopDBm: Double);
|
||||||
begin
|
begin
|
||||||
FAGCTop := TopDBm;
|
FAGCTop := TopDBm;
|
||||||
@@ -3821,6 +3833,17 @@ begin
|
|||||||
MODE_FM: Half := FTXFMDeviation + FTXFMHighCut;
|
MODE_FM: Half := FTXFMDeviation + FTXFMHighCut;
|
||||||
MODE_WFM: Half := WFM_DEVIATION + WFM_AF_HIGH;
|
MODE_WFM: Half := WFM_DEVIATION + WFM_AF_HIGH;
|
||||||
MODE_FMRAW: Half := FRawFMDeviation + RAW_AF_HIGH;
|
MODE_FMRAW: Half := FRawFMDeviation + RAW_AF_HIGH;
|
||||||
|
// ★Телеграф. TXSignedEdges отдаёт для CWL/CWU ГОЛОСОВУЮ боковую (bp0 обязан
|
||||||
|
// оставаться открытым: через него идёт тон pitch при настройке TUN), но в
|
||||||
|
// эфир уходит манипулируемая НЕСУЩАЯ на самом VFO. Рисовать её юбкой SSB
|
||||||
|
// нельзя — полоса выглядела как в SSB и уезжала вбок от несущей.
|
||||||
|
// Занимаемая полоса телеграфа: B = WPM/1.2 бод, BW ≈ 5·B при мягкой
|
||||||
|
// манипуляции (фронт CWRampPeriod). Симметрично несущей.
|
||||||
|
MODE_CWL, MODE_CWU:
|
||||||
|
begin
|
||||||
|
Half := 5.0 * (FCWSpeed / 1.2) / 2.0;
|
||||||
|
if Half < 25.0 then Half := 25.0; // не вырождаться в нулевую полоску
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
if Half > 0.0 then
|
if Half > 0.0 then
|
||||||
begin
|
begin
|
||||||
|
|||||||
Reference in New Issue
Block a user