feat(cw): телеграф — FPGA-кейер, pitch, сайдтон, передача текста

CW не работал ни на приём, ни на передачу: фильтр стоял симметрично вокруг
нуля (тона не было), понятия pitch не существовало, MOX в CWL отправлял в
эфир голос 2.9 кГц (в WDSP TXA_CWL — ветка SSB), байт CW-опций DUC всегда
был нулевым, поэтому кейер, сайдтон и break-in в прошивке молчали.

Pitch сделан ОДНИМ сдвигом в движке. Кромки фильтра везде остаются
относительно VFO (у CW — симметрично нулю), а гетеродин и полосу двигают
три метода WDSPEngine: CWLOOffset / PushShift / PushPassband. Прямых
вызовов SetRXAShiftFreq и RXASetPassband в проекте больше нет. Так таблица
фильтров не перестраивается при смене pitch (в отличие от Thetis), полоска
на спектре и маркер VFO верны без правок в UI, а слайсы получают свой
сдвиг по своему режиму.

Правила эфира:
- голосовой TXA в CW не запускается вообще (гард по режиму), MOX = только
  PTT; несущую даёт кейер, бит CWX или тон TUN;
- байт 5 собирается из настроек и перепосылается на каждой смене режима и
  TX-слайса — вне CW он обязан быть нулевым, иначе прошивка поднимет PTT на
  замыкание ключа посреди SSB;
- TUN в CW: тон = pitch и встречный сдвиг DUC, несущая встаёт ровно на VFO;
- приёмник на передаче в CW не глушится, иначе умолкает программный
  сайдтон и эфир между посылками.

Программная передача текста (CWMorse): поток с абсолютными дедлайнами
дёргает бит CWX в High Priority, элементы рисует прошивка. Касание
манипулятора или снятие MOX обрывают передачу. Память сообщений — окно
CW Messages и F1..F8 в главном окне. Оживлены CAT KS/KY/ZZKM/ZZKS/ZZKY.

Настройки — вкладка Transmit, подвкладка Hardware, перед блоком FM/CTCSS.

Программный сайдтон точен для передачи текста и прямого ключа; при иамбике
таймингом владеет FPGA, поэтому там верен только аппаратный тон.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 12:20:56 +03:00
co-authored by Claude Opus 5
parent 0ec120c2d5
commit 53329e82e8
12 changed files with 1421 additions and 46 deletions
+66 -1
View File
@@ -43,7 +43,7 @@ uses
WinFirewall,
BoardUtils, WisdomBuilder, UISync,
FlatEdit, FMRepeater,
ChannelStore, ChannelsForm, BeaconScopeForm, DMRDecoder,
ChannelStore, ChannelsForm, CWMessagesForm, BeaconScopeForm, DMRDecoder,
PowerInhibit,
DeviceStore,
RadioBackend, PlutoBackend,
@@ -367,6 +367,7 @@ type
BtnTXProfile: TFlatButton;
FTXProfileDropDown: TFlatDropDown;
FChannelsForm: TObject; // TChannelsForm (cast при использовании)
FCWMsgForm: TObject; // TCWMessagesForm (память телеграфа)
FBeaconScopeForm: TBeaconScopeForm; // окно констелляции маяка (ПКМ по BEACON)
// ---- Right panel ----
@@ -763,6 +764,11 @@ type
procedure OnAnt936xChange(const A: TAD936xAnt); // разъёмы AD936x (Antenna)
procedure OnVHFCalSettingsChange(const VHFCal: array of Double);
procedure OnTXSettingsChange(const T: TTXSettings);
procedure OnCWSettingsChange(const C: TCWSettings);
procedure ShowCWMessages;
procedure OnCWMessageSend(const Text: string);
procedure OnCWMessageAbort(Sender: TObject);
procedure FormKeyDownCW(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure BtnAGCModeClick(Sender: TObject);
procedure TrkAGCChange(Sender: TObject);
procedure TrkRxGainChange(Sender: TObject);
@@ -1065,7 +1071,12 @@ begin
FController.FTXSpecGridStep := 10.0;
FSettingsForm := nil;
FChannelsForm := nil;
FCWMsgForm := nil;
FBeaconScopeForm := nil;
// F1..F8 для памяти телеграфа. Обработчик молчит, пока окно памяти не
// открывали и пока режим не CW, поэтому прочим клавишам ничего не мешает.
KeyPreview := True;
OnKeyDown := FormKeyDownCW;
FillChar(FController.FDevMAC, SizeOf(FController.FDevMAC), 0);
// Инициализируем кэш диапазонов умолчаниями
@@ -3400,6 +3411,7 @@ begin
T := CurrentAppTheme;
if FDeviceDialog <> nil then FDeviceDialog.SetTheme(T);
if FChannelsForm <> nil then TChannelsForm(FChannelsForm).ApplyTheme(T);
if FCWMsgForm <> nil then TCWMessagesForm(FCWMsgForm).ApplyTheme(T);
if FBeaconScopeForm <> nil then FBeaconScopeForm.ApplyTheme(T);
FController.FSettings.SaveTheme(V);
FController.FSettings.Save;
@@ -5831,6 +5843,7 @@ begin
TSettingsForm(FSettingsForm).LoadTXSettings(FController.FTXSettings,
IfThen(FController.FNetwork.Connected,
FController.FNetwork.Device.BoardType, FPendingBoardType));
TSettingsForm(FSettingsForm).LoadCWSettings(FController.CWSettings);
PushTXProfilesToSettings;
end;
// Пилюли профиля на флагах: список общий, привязки могли съехать после
@@ -8122,6 +8135,54 @@ begin
end;
end;
procedure TMainForm.OnCWSettingsChange(const C: TCWSettings);
// Телеграф целиком живёт в контроллере: pitch → движок, кейер/сайдтон → в
// прошивку, персист — там же. Форме остаётся только передать запись.
begin
FController.SetCWSettings(C);
// Окно памяти показывает те же ячейки — держим его в курсе правок из настроек.
if FCWMsgForm <> nil then TCWMessagesForm(FCWMsgForm).LoadFrom(FController.CWSettings);
end;
procedure TMainForm.ShowCWMessages;
begin
if FCWMsgForm = nil then
begin
FCWMsgForm := TCWMessagesForm.Create(Self);
TCWMessagesForm(FCWMsgForm).OnSend := OnCWMessageSend;
TCWMessagesForm(FCWMsgForm).OnAbort := OnCWMessageAbort;
TCWMessagesForm(FCWMsgForm).OnChanged := OnCWSettingsChange;
end;
TCWMessagesForm(FCWMsgForm).LoadFrom(FController.CWSettings);
TCWMessagesForm(FCWMsgForm).ApplyTheme(CurrentAppTheme);
TCWMessagesForm(FCWMsgForm).Show;
end;
procedure TMainForm.OnCWMessageSend(const Text: string);
begin
FController.CWXSend(Text);
end;
procedure TMainForm.OnCWMessageAbort(Sender: TObject);
begin
FController.CWXAbort;
end;
procedure TMainForm.FormKeyDownCW(Sender: TObject; var Key: Word;
Shift: TShiftState);
// F1..F8 — ячейки памяти телеграфа. Работают только в CW и только когда окно
// памяти уже открывали (иначе клавиши остаются свободными для всего прочего).
var Slot: Integer;
begin
if FCWMsgForm = nil 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;
Slot := Key - VK_F1;
TCWMessagesForm(FCWMsgForm).SendSlot(Slot);
Key := 0;
end;
procedure TMainForm.OnAlexSettingsChange(const A: TAlexSettings);
begin
FController.FAlexSettings := A;
@@ -9047,6 +9108,8 @@ begin
SF.OnThemeChange := SetLightTheme;
SF.OnFreqMhzDigitsChange := ApplyFreqMhzDigits;
SF.OnTXChange := OnTXSettingsChange;
SF.OnCWChange := OnCWSettingsChange;
SF.OnCWMessagesOpen := ShowCWMessages;
SF.OnTXProfileSelect := OnTXProfileSelectFromSettings;
SF.OnTXProfileAdd := OnTXProfileAddFromSettings;
SF.OnTXProfileRename := OnTXProfileRenameFromSettings;
@@ -9063,6 +9126,7 @@ begin
PushTXProfilesToSettings;
SF.LoadTXSettings(FController.FTXSettings,
IfThen(FController.FNetwork.Connected, FController.FNetwork.Device.BoardType, FPendingBoardType));
SF.LoadCWSettings(FController.CWSettings);
SF.LoadAlexSettings(FController.FAlexSettings,
IfThen(FController.FNetwork.Connected, FController.FNetwork.Device.BoardType, FPendingBoardType));
// Вкладка Antenna: у AD936x (Pluto/LibreSDR) вместо Alex-таблицы — таблица
@@ -9159,6 +9223,7 @@ begin
EnsureWDSPWisdom;
end;
// ===========================================================================
// CAT subsystem
// ===========================================================================