mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 04:47:35 +00:00
feat(cw): программный кейер — телеграф на Pluto/LibreSDR
У openHPSDR точки и тире формирует кейер в FPGA, и это остаётся путём по умолчанию: тайминги в железе, джиттер PC не влияет вовсе. У AD936x такого кейера нет вовсе, поэтому телеграфа на Pluto не было в принципе — голосовой TXA в CW не запускается, а несущую давать некому. CWKeyer.pas: TCWLocalKeyer рисует манипулированную несущую прямо в поток IQ, мимо WDSP (эталон pihpsdr cw_shape_buffer: в WDSP режим TXA_CWL — ветка SSB, да и PostGen не умеет огибающей). Длительности живут В СЭМПЛАХ, а не в миллисекундах: планировщик ОС влияет только на темп выдачи блоков, а его сглаживает FIFO бэкенда. Умеет текст, иамбик A/B и прямой ключ; TCWKeyPort читает манипулятор с модемных линий COM/USB-serial (DTR/RTS питают ключ, CTS/DSR/DCD/RI — лепестки). Оффлайн-прогон: PARIS занимает ровно 43 точки, все посылки и паузы 1/3/7 dit с точностью до сэмпла. Сессия = одна передача, а не одна посылка: PTT, реле T/R, антенна, OC и аттенюатор Pluto поднимаются один раз и держатся до конца hang — внутри манипуляция чисто цифровая. Предзаполнение FIFO задано железом: TX-поток Pluto наполняет буфер целиком (16384 пары ≈ 28 мс на 576k) и добивает нулями, чего не хватило. Сайдтон эту латентность не наследует — кольцо огибающей листается, если отставание больше 20 мс. Сдвиг несущей: у zero-IF ноль бейсбенда совпадает с частотой гетеродина, и там же его утечка — манипуляция на нуле дала бы backwave ровно на рабочей частоте. Поэтому несущая идёт на сдвиге, а гетеродин уезжает навстречу через новую единую дверь TXTuneFreqHz (все пуши TX-частоты переведены на неё). Знак (CW_TX_BB_SIGN) замерен на эфире, а не выведен. Развилка источника в UI — один список Keyer: firmware / software (PC) / off; хранится прежней парой флагов, и на железе без кейера выбор «прошивка» вырождается в программный, поэтому Pluto работает из коробки. Новое поле caps HasCWKeyer (раньше это неявно жило в HasHWMic). Попутно: у AD9361 calib_mode стоял manual_tx_quad, то есть драйвер не переигрывал калибровку TX quad (она нулит утечку гетеродина) на сменах TX LO — выставляем auto при подключении. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+51
-1
@@ -318,7 +318,10 @@ type
|
||||
Pitch: Integer; // Гц, тон приёма/сайдтона (Thetis default 600)
|
||||
// Память сообщений для программной передачи (окно CW Messages, F1..F8).
|
||||
Messages: array[0..CW_MSG_COUNT-1] of string[63];
|
||||
FWKeyer: Boolean; // кейер в прошивке (иначе телеграф выключен)
|
||||
// Манипуляция разрешена вообще (False = телеграф без кейера: MOX + голосом
|
||||
// ключа нет). ВМЕСТЕ с LocalKeyer образует одну развилку «кто рисует точки»,
|
||||
// в UI это один список: прошивка / программа / выкл.
|
||||
FWKeyer: Boolean;
|
||||
KeyerMode: Integer; // 0=Straight, 1=Iambic A, 2=Iambic B
|
||||
ReverseKeys: Boolean; // поменять точку и тире местами
|
||||
StrictSpacing: Boolean; // строгие межзнаковые интервалы
|
||||
@@ -338,6 +341,26 @@ type
|
||||
// состояние лепестков манипулятора — там верен только аппаратный тон.
|
||||
SidetoneSW: Boolean;
|
||||
SidetoneSWLevel: Integer; // 0..100 % от полной шкалы аудио
|
||||
// ---- Локальный (программный) кейер --------------------------------------
|
||||
// Манипуляцию формирует САМ EWSDR: генератор рисует несущую с огибающей
|
||||
// прямо в поток IQ (CWKeyer.pas). Для Pluto/LibreSDR это единственный
|
||||
// возможный путь — кейера в железе там нет, и выбор «прошивка» вырождается
|
||||
// туда сам; для openHPSDR это опция, по умолчанию выключенная (тайминги в
|
||||
// FPGA точнее любой программы).
|
||||
LocalKeyer: Boolean;
|
||||
// Сдвиг несущей манипуляции от гетеродина. 0 = авто: у openHPSDR 0 (DUC —
|
||||
// цифровой, утекать нечему), у zero-IF AD936x — CW_AUTO_OFFSET_HZ, иначе
|
||||
// утечка LO сидит РОВНО на рабочей частоте и слышна между посылками.
|
||||
CarrierOffsetHz: Integer;
|
||||
// Манипулятор на модемных линиях COM/USB-serial (DTR/RTS питают ключ,
|
||||
// CTS/DSR/DCD/RI читаются как лепестки).
|
||||
KeyPortEnabled: Boolean;
|
||||
KeyPort: string[63];
|
||||
KeyDotLine: Integer; // 0=CTS, 1=DSR, 2=DCD, 3=RI
|
||||
KeyDashLine: Integer;
|
||||
KeyInvert: Boolean;
|
||||
KeyPowerDTR: Boolean;
|
||||
KeyPowerRTS: Boolean;
|
||||
end;
|
||||
|
||||
// ---- TX-профили (именованные снимки «как я звучу») ----------------------
|
||||
@@ -1789,6 +1812,15 @@ begin
|
||||
C.SidetoneHWLevel := 50;
|
||||
C.SidetoneSW := False;
|
||||
C.SidetoneSWLevel := 30;
|
||||
C.LocalKeyer := False; // у openHPSDR тайминги в FPGA; Pluto включит сам
|
||||
C.CarrierOffsetHz := 0; // 0 = авто (по типу бэкенда)
|
||||
C.KeyPortEnabled := False;
|
||||
C.KeyPort := '';
|
||||
C.KeyDotLine := 0; // CTS
|
||||
C.KeyDashLine := 1; // DSR
|
||||
C.KeyInvert := False;
|
||||
C.KeyPowerDTR := True; // типовая распайка: ключ питается от DTR/RTS
|
||||
C.KeyPowerRTS := True;
|
||||
// Заводские заготовки: типовой вызов, ответ и стандартные концовки.
|
||||
C.Messages[0] := 'CQ CQ DE ';
|
||||
C.Messages[1] := 'DE ';
|
||||
@@ -1826,6 +1858,15 @@ begin
|
||||
C.SidetoneHWLevel := EnsureRange(JI(O,'sidetone_hw_level', C.SidetoneHWLevel), 0, 127);
|
||||
C.SidetoneSW := JB(O,'sidetone_sw', C.SidetoneSW);
|
||||
C.SidetoneSWLevel := EnsureRange(JI(O,'sidetone_sw_level', C.SidetoneSWLevel), 0, 100);
|
||||
C.LocalKeyer := JB(O,'local_keyer', C.LocalKeyer);
|
||||
C.CarrierOffsetHz := EnsureRange(JI(O,'carrier_offset_hz', C.CarrierOffsetHz), -1, 48000);
|
||||
C.KeyPortEnabled := JB(O,'key_port_on', C.KeyPortEnabled);
|
||||
C.KeyPort := Copy(JS(O,'key_port', C.KeyPort), 1, 63);
|
||||
C.KeyDotLine := EnsureRange(JI(O,'key_dot_line', C.KeyDotLine), 0, 3);
|
||||
C.KeyDashLine := EnsureRange(JI(O,'key_dash_line', C.KeyDashLine), 0, 3);
|
||||
C.KeyInvert := JB(O,'key_invert', C.KeyInvert);
|
||||
C.KeyPowerDTR := JB(O,'key_power_dtr', C.KeyPowerDTR);
|
||||
C.KeyPowerRTS := JB(O,'key_power_rts', C.KeyPowerRTS);
|
||||
for i := 0 to CW_MSG_COUNT - 1 do
|
||||
C.Messages[i] := Copy(JS(O, 'msg_' + IntToStr(i), C.Messages[i]), 1, 63);
|
||||
end;
|
||||
@@ -1850,6 +1891,15 @@ begin
|
||||
JW(O,'sidetone_hw_level', C.SidetoneHWLevel);
|
||||
JW(O,'sidetone_sw', C.SidetoneSW);
|
||||
JW(O,'sidetone_sw_level', C.SidetoneSWLevel);
|
||||
JW(O,'local_keyer', C.LocalKeyer);
|
||||
JW(O,'carrier_offset_hz', C.CarrierOffsetHz);
|
||||
JW(O,'key_port_on', C.KeyPortEnabled);
|
||||
JWS(O,'key_port', C.KeyPort);
|
||||
JW(O,'key_dot_line', C.KeyDotLine);
|
||||
JW(O,'key_dash_line', C.KeyDashLine);
|
||||
JW(O,'key_invert', C.KeyInvert);
|
||||
JW(O,'key_power_dtr', C.KeyPowerDTR);
|
||||
JW(O,'key_power_rts', C.KeyPowerRTS);
|
||||
for i := 0 to CW_MSG_COUNT - 1 do
|
||||
JWS(O, 'msg_' + IntToStr(i), C.Messages[i]);
|
||||
end;
|
||||
|
||||
Reference in New Issue
Block a user