fix(hpsdr): TX-антенна тонула в Alex1, OC-пины били через один, SO_EXCLUSIVEADDRUSE

Три дефекта, найденные сверкой с прошивкой (Anvelina_PROIII, board_type=5)
и с Thetis.

1. CalcAlex1 жёстко ставил бит 24 (ANT1) с комментарием «ADC1 дефолт».
   Старшие 16 бит Alex1 — не фильтры ADC1, а TX-СЛОВО Alex: прошивка кладёт
   байты 1428-1429 в Alex_Tx_data и на PTT подменяет им верхнюю половину
   Alex-слова целиком (Orion.v:2346, Alex_Tx_data_ok = Alex_Tx_data[10:8] —
   те же ANT1/2/3). Захардкоженный ANT1 делал условие истинным и затирал
   антенну, посчитанную в CalcAlex0: оператор с TX на ANT2/ANT3 передавал
   в ANT1. Thetis делает ровно то, что теперь делаем мы —
   netInterface.c:489 чистит биты 24-26 и ставит _TXANT_1/2/3 из tx_ant.

2. Байт 1401 (Open Collector) уходил без сдвига. Бит 0 не разведён, пины
   сидят на битах 1..7 (Orion.v:913, USEROUT0..6 = Open_Collector[1..7]) —
   та же раскладка, что у C2[7:1] в протоколе 1. Каждый включённый пин
   срабатывал на соседнем выходе, седьмой не срабатывал никогда.
   CalcOCBits остаётся в логических координатах (бит i = пин i+1),
   сдвиг делает SendFullHP на границе с проводом.

3. SO_EXCLUSIVEADDRUSE = not 5 ($FFFFFFFA) вместо ~SO_REUSEADDR = not 4
   ($FFFFFFFB) — как и написано в собственном комментарии. Опции с таким
   номером нет: setsockopt возвращал ошибку (её результат не проверяется),
   и порт оставался перехватываемым, ровно от чего строка и спасала.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 16:25:47 +03:00
co-authored by Claude Opus 5
parent c2fa1da2b0
commit ced8581ada
+39 -7
View File
@@ -32,7 +32,12 @@ const
SOCK_INVALID = TSocket(INVALID_SOCKET);
INADDR_ANY = 0;
IPPROTO_UDP = 17;
SO_EXCLUSIVEADDRUSE = LongInt(not 5); // = $FFFFFFFB — исключительное владение портом
// Winsock: SO_EXCLUSIVEADDRUSE == ~SO_REUSEADDR == ~$0004 == $FFFFFFFB
// (то же значение объявляет и сам WinSock2 — держим локально, как соседние
// INADDR_ANY/IPPROTO_UDP). Стояло `not 5` = $FFFFFFFA — несуществующая
// опция: setsockopt возвращал ошибку (её результат не проверяется), и порт
// оставался перехватываемым, ровно от чего эта строка и должна была спасать.
SO_EXCLUSIVEADDRUSE = LongInt(not 4); // = -5 = $FFFFFFFB
type
TInetSockAddr = WinSock2.TSockAddrIn;
@@ -1486,12 +1491,25 @@ end;
// Bit 8 = ALEX1_ANAN7000_RX_GNDonTX: заземляет ADC1-вход во время TX
// чтобы предотвратить перегрузку второго приёмника.
// Остальные биты — те же LPF/BPF/ANT что и у ALEX0.
//
// ★СТАРШИЕ 16 БИТ ЭТОГО СЛОВА — НЕ «фильтры ADC1», А TX-СЛОВО ALEX.
// Прошивка (Orion.v/High_Priority_CC.v) раскладывает байты так:
// 1428-1429 -> Alex_Tx_data[15:0] (TX-фильтры + выбор TX-антенны)
// 1430-1431 -> Alex_data[31:16] (RX1-фильтры — это НИЖНИЕ 16 бит Alex1)
// и на передаче ПОДМЕНЯЕТ верхнюю половину Alex-слова:
// Alex_Tx_data_ok = Alex_Tx_data[10:8] in {001,010,100} // ANT1/2/3
// Alex_upper = (FPGA_PTT && Alex_Tx_data_ok) ? Alex_Tx_data : Alex_data[47:32]
// Биты 24..26 здесь — те же ANT1/2/3, что и в ALEX0, поэтому захардкоженный
// ANT1 делал Alex_Tx_data_ok=1 и на КАЖДОМ PTT затирал антенну, посчитанную
// в CalcAlex0: оператор с TX на ANT2/ANT3 передавал в ANT1. Антенну берём
// из той же таблицы и по той же частоте, что и CalcAlex0.
function CalcAlex1(RXFreqHz, TXFreqHz: Double;
Transmitting: Boolean;
const Alex: TAlexSettings;
XvtrDisablePA: Boolean = False): LongWord;
var
txf: Double;
txf: Double;
AntVal: Byte;
begin
Result := 0;
@@ -1517,7 +1535,15 @@ begin
else if txf > 2500000 then Result := Result or $00400000 // bit 22: 80m LPF
else Result := Result or $00800000; // bit 23: 160m LPF
Result := Result or $01000000; // bit 24: ANT1 (ADC1 дефолт)
// Биты 24-26: ANT 1/2/3 TX-слова (см. шапку). Ровно та же величина, что
// CalcAlex0 кладёт в TX-ветке, иначе прошивка на PTT её перебьёт.
AntVal := Alex.TxAnt[FreqToBandIndex(TXFreqHz)];
if AntVal < 1 then AntVal := 1;
case AntVal of
2: Result := Result or $02000000; // bit 25: ANT 2
3: Result := Result or $04000000; // bit 26: ANT 3
else Result := Result or $01000000; // bit 24: ANT 1 (дефолт)
end;
// Bit 8: заземление ADC1-входа во время TX (галочка "Ground BPF2 on TX")
if Transmitting and Alex.GndBpf2OnTx then
@@ -1552,7 +1578,9 @@ begin
if FConnected then SendFullHP;
end;
// Строит 8-битное слово OC (byte 1401 HP-пакета, биты 0..6 = пины 1..7).
// Строит ЛОГИЧЕСКУЮ маску OC-пинов: бит i = пин i+1 (i = 0..OC_PIN_COUNT-1) —
// в тех же координатах, в которых маски лежат в настройках и рисуются в UI.
// Сдвиг в проводное представление байта 1401 делает вызывающий (SendFullHP).
// Портировано из Thetis (Penny.UpdateExtCtrl/adjustForTXAction), урезано:
// без PA-override и split-pins VFO A/B (см. project_oc_control память).
// RX: маска банда/слота как есть. TX: маска банда/слота, каждый включённый
@@ -1969,10 +1997,14 @@ begin
if not FSpeakerAudioEnabled then
Buf[1400] := Buf[1400] or $02;
// OC Control — Open Collector outputs (byte 1401)
// OC Control — Open Collector outputs (byte 1401).
// ★СДВИГ НА БИТ ОБЯЗАТЕЛЕН. Бит 0 байта 1401 не разведён, пины сидят на
// битах 1..7 (Orion.v: USEROUT0..6 = Open_Collector[1..7]) — та же раскладка,
// что у C2[7:1] в протоколе 1. Без shl 1 каждый включённый пин срабатывал
// на соседнем физическом выходе, а седьмой не срабатывал никогда.
if FOCConfig.Enabled then
Buf[1401] := CalcOCBits(FCurrentRXFreq, FCurrentTXFreq, FIsTransmitting,
FTuning, FTwoTone, FXvtrSlot, FOCConfig);
Buf[1401] := Byte(CalcOCBits(FCurrentRXFreq, FCurrentTXFreq, FIsTransmitting,
FTuning, FTwoTone, FXvtrSlot, FOCConfig) shl 1);
// Step attenuators ADC0/ADC1 (bytes 1443/1442)
// 0 dB = 0, max 31 dB