Files
ewsdr/HPSDRProtocol.pas

589 lines
23 KiB
ObjectPascal
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
Copyright (C)
2026 - Uladzimir Karpenka, EW8BAK
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}
unit HPSDRProtocol;
{
openHPSDR Ethernet Protocol V4.3 constants and packet structures.
Big-Endian (Network Byte Order) unless otherwise noted.
}
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
// ---------------------------------------------------------------------------
// UDP Ports (defaults)
// ---------------------------------------------------------------------------
const
PORT_COMMAND = 1024; // Discovery, Erase, Program, Set IP
PORT_DDC_SPECIFIC = 1025; // DDC Specific packet (PC -> HW)
PORT_DUC_SPECIFIC = 1026; // DUC/TXA Specific packet (PC -> HW)
PORT_HP_FROM_PC = 1027; // High Priority from PC
PORT_HP_TO_PC = 1025; // High Priority Status from HW
PORT_DDC_AUDIO = 1028; // DDC Audio (PC -> HW)
PORT_DUC_IQ = 1029; // DUC I&Q data (PC -> HW)
PORT_MIC_DATA = 1026; // Microphone data (HW -> PC)
PORT_WIDEBAND_ADC0 = 1027; // Wideband data (HW -> PC)
PORT_DDC0_IQ = 1035; // DDC0 I&Q (HW -> PC), DDC1=1036 etc.
// ---------------------------------------------------------------------------
// Command bytes (Byte 4 of packet)
// ---------------------------------------------------------------------------
CMD_GENERAL = $00;
CMD_DISCOVERY = $02;
CMD_SET_IP = $03;
CMD_ERASE = $04;
CMD_PROGRAM = $05;
// ---------------------------------------------------------------------------
// Discovery Reply Byte 4
// ---------------------------------------------------------------------------
REPLY_DISCOVERY_FREE = $02; // Hardware available
REPLY_DISCOVERY_INUSE = $03; // Hardware in use by another host
REPLY_XML_DESC = $FE; // XML hardware description follows
REPLY_FULL_DESC = $FF; // Full hardware description follows
// ---------------------------------------------------------------------------
// Board types (Discovery Reply Byte 11)
// ---------------------------------------------------------------------------
BOARD_ATLAS = 0;
BOARD_HERMES = 1; // ANAN-10, 100
BOARD_HERMES_10E = 2; // ANAN-10E, 100B
BOARD_ANGELA = 3; // ANAN-100D
BOARD_ORION = 4; // ANAN-200D
BOARD_ORION_MK2 = 5; // ANAN-7/8000DLE
BOARD_HERMES_LITE = 6;
BOARD_SATURN = 10; // ANAN-G2
// ---------------------------------------------------------------------------
// Packet sizes
// ---------------------------------------------------------------------------
DISCOVERY_PACKET_SIZE = 60;
GENERAL_PACKET_SIZE = 60;
DDC_SPECIFIC_SIZE = 1444;
DUC_SPECIFIC_SIZE = 60;
HP_DATA_PC_SIZE = 1444;
HP_STATUS_HW_SIZE = 60;
DDC_AUDIO_SIZE = 260; // 4 hdr + 64*2*2
DUC_IQ_SIZE = 1444; // 4 hdr + 240*6
MIC_DATA_SIZE = 132; // 4 hdr + 64*2
DDC_IQ_SIZE = 1444; // 4 hdr + 12 info + 238*6
// ---------------------------------------------------------------------------
// DDC sample rates (ksps)
// ---------------------------------------------------------------------------
DDC_RATE_48 = 48;
DDC_RATE_96 = 96;
DDC_RATE_192 = 192;
DDC_RATE_384 = 384;
DDC_RATE_768 = 768;
DDC_RATE_1536 = 1536;
// ---------------------------------------------------------------------------
// DSP clock (Hz)
// ---------------------------------------------------------------------------
DSP_CLOCK_HZ: Double = 122880000.0;
// ---------------------------------------------------------------------------
// Maximum DDCs / ADCs
// ---------------------------------------------------------------------------
MAX_DDCS = 80;
MAX_ADCS = 8;
MAX_DUCS = 4;
// ---------------------------------------------------------------------------
// High Priority Byte 4 bits (PC -> HW)
// ---------------------------------------------------------------------------
HP_RUN = $01;
HP_PTT0 = $02;
HP_PTT1 = $04;
HP_PTT2 = $08;
HP_PTT3 = $10;
// ---------------------------------------------------------------------------
// High Priority Status Byte 4 bits (HW -> PC)
// ---------------------------------------------------------------------------
HPS_PTT = $01;
HPS_DOT = $02;
HPS_DASH = $04;
HPS_PLL_LOCKED = $10;
HPS_FIFO_EMPTY = $20;
HPS_FIFO_FULL = $40;
// ---------------------------------------------------------------------------
// DUC Specific Byte 5 bits (CW options)
// ---------------------------------------------------------------------------
CW_EER = $01;
CW_MODE = $02;
CW_REVERSE_KEYS = $04;
CW_IAMBIC = $08;
CW_SIDETONE = $10;
CW_MODE_B = $20;
CW_STRICT_SPACING = $40;
CW_BREAK_IN = $80;
// ---------------------------------------------------------------------------
// Packet record types
// ---------------------------------------------------------------------------
type
// Raw byte array for a generic 60-byte command packet
TRawPacket60 = array[0..59] of Byte;
TRawPacket1444 = array[0..1443] of Byte;
PRawPacket1444 = ^TRawPacket1444;
PRawPacket60 = ^TRawPacket60;
// -------------------------------------------------------------------------
// Discovery packet (PC -> HW)
// -------------------------------------------------------------------------
TDiscoveryPacket = packed record
SeqHi, SeqMidHi, SeqMidLo, SeqLo: Byte; // Bytes 0-3: always 0
Command: Byte; // Byte 4: CMD_DISCOVERY=$02
Zeros: array[5..59] of Byte; // Bytes 5-59: zero
end;
// -------------------------------------------------------------------------
// Discovery Reply (HW -> PC)
// -------------------------------------------------------------------------
TDiscoveryReply = packed record
SeqHi, SeqMidHi, SeqMidLo, SeqLo: Byte; // 0-3
Status: Byte; // 4: $02 free/$03 in use
MAC: array[0..5] of Byte; // 5-10
BoardType: Byte; // 11
ProtocolVersion: Byte; // 12
FirmwareVersion: Byte; // 13
Mercury0: Byte; // 14
Mercury1: Byte; // 15
Mercury2: Byte; // 16
Mercury3: Byte; // 17
PennyVersion: Byte; // 18
MetisVersion: Byte; // 19
NumDDCs: Byte; // 20
FreqOrPhase: Byte; // 21: 0=freq, 1=phase
EndianModes: Byte; // 22
BetaVersion: Byte; // 23
Reserved: array[24..59] of Byte;
end;
// -------------------------------------------------------------------------
// General Packet (PC -> HW, port 1024)
// -------------------------------------------------------------------------
TGeneralPacket = packed record
Seq: array[0..3] of Byte; // 0-3
Command: Byte; // 4: CMD_GENERAL=$00
DDCSpecPort: array[0..1] of Byte; // 5-6: default 1025
DUCSpecPort: array[0..1] of Byte; // 7-8: default 1026
HPFromPCPort: array[0..1] of Byte; // 9-10: default 1027
HPToPCPort: array[0..1] of Byte; // 11-12: default 1025
DDCAudioPort: array[0..1] of Byte; // 13-14: default 1028
DUCIQPort: array[0..1] of Byte; // 15-16: default 1029
DDC0Port: array[0..1] of Byte; // 17-18: default 1035
MicPort: array[0..1] of Byte; // 19-20: default 1026
WBPort: array[0..1] of Byte; // 21-22: default 1027
WBEnable: Byte; // 23
WBSamplesPerPkt: array[0..1] of Byte; // 24-25: default 512
WBSampleSize: Byte; // 26: default 16
WBUpdateRate: Byte; // 27: 0-255ms
WBPacketsPerFrame: Byte; // 28: default 32
MemMapFromPCPort: array[0..1] of Byte; // 29-30
MemMapToPCPort: array[0..1] of Byte; // 31-32
EnvPWMMin: array[0..1] of Byte; // 33-34
EnvPWMMax: array[0..1] of Byte; // 35-36
Flags37: Byte; // 37: timestamp/VITA/VNA/phase
Flags38: Byte; // 38: HW timer enable
DataFormat: Byte; // 39: endian/format
Reserved40: array[40..55] of Byte;
AtlasBusConfig: Byte; // 56: Atlas config [2:0]
Ref10MHz: Byte; // 57: 10MHz source [1:0]
PAConfig: Byte; // 58: PA/Apollo/Mercury/clock
AlexEnable: Byte; // 59: Alex[0..7] enable
end;
// -------------------------------------------------------------------------
// DDC Specific Packet (PC -> HW)
// -------------------------------------------------------------------------
TDDCSpecificPacket = packed record
Seq: array[0..3] of Byte; // 0-3
NumADCs: Byte; // 4: number of ADCs
DitherADC: Byte; // 5: dither enable bits
RandomADC: Byte; // 6: random enable bits
DDCEnable: array[0..9] of Byte; // 7-16: enable bits DDC0..79
// DDC config: 6 bytes each for DDC0..79
// Byte 17+n*6: ADC selection
// Byte 18+n*6: SampleRate [15:8]
// Byte 19+n*6: SampleRate [7:0]
// Byte 20+n*6: CIC1 (future)
// Byte 21+n*6: CIC2 (future)
// Byte 22+n*6: SampleSize (default 24)
DDCConfig: array[0..479] of Byte; // 17-496
Filler: array[497..1362] of Byte;
SyncDDC: array[0..79] of Byte; // 1363-1442
Unused: Byte; // 1443
end;
// Per-DDC config helper (maps into DDCConfig array above)
TDDCConfig = packed record
ADCSource: Byte; // which ADC (0..NumADCs-1) or NumADCs for DAC
SampleRateHi, SampleRateLo: Byte;
CIC1, CIC2: Byte;
SampleSize: Byte; // default 24
end;
// -------------------------------------------------------------------------
// DUC (Transmitter) Specific Packet (PC -> HW)
// -------------------------------------------------------------------------
TDUCSpecificPacket = packed record
Seq: array[0..3] of Byte;
NumDACs: Byte; // 4
CWOptions: Byte; // 5: CW_* flags
SidetoneLevel: Byte; // 6: 0..127
SidetoneFreqHi: Byte; // 7
SidetoneFreqLo: Byte; // 8
KeyerSpeed: Byte; // 9: 0..60 WPM
KeyerWeight: Byte; // 10: 33..66
HangDelayHi: Byte; // 11
HangDelayLo: Byte; // 12
RFDelay: Byte; // 13
DUC0RateHi: Byte; // 14
DUC0RateLo: Byte; // 15
DUC0Bits: Byte; // 16: default 24
CWRampPeriod: Byte; // 17: ms, 0=default
Reserved18: array[18..49] of Byte;
MicLineSelect: Byte; // 50
LineInGain: Byte; // 51: 0=-34.5dB .. 31=+12dB (шаг 1.5 dB)
Reserved52: array[52..56] of Byte;
StepAtten2: Byte; // 57: ADC2 atten during TX (0-31dB)
StepAtten1: Byte; // 58: ADC1 atten during TX
StepAtten0: Byte; // 59: ADC0 atten during TX
end;
// -------------------------------------------------------------------------
// High Priority Packet (PC -> HW, port 1027)
// Full packet is 1444 bytes but we define the key fields
// -------------------------------------------------------------------------
THighPriorityPacket = packed record
Seq: array[0..3] of Byte;
RunPTT: Byte; // 4: HP_RUN | HP_PTTn
CWX0: Byte; // 5: [0]=CWX [1]=Dot [2]=Dash
CWX1: Byte; // 6: reserved
CWX2: Byte; // 7: reserved
CWX3: Byte; // 8: reserved
// Bytes 9-12: DDC0 frequency/phase word (Big-Endian)
// Bytes 13-16: DDC1 ...
// Bytes 17-328: DDC2..DDC79
// Bytes 329-332: DUC0
// Byte 345: DUC0 drive level (0-255)
// Bytes 1398-1399: CAT over TCP port
// Byte 1400: Transverter/audio enable
// Byte 1401: Open Collector outputs
// Bytes 1432-1435: Alex0 filter config
// Byte 1443: Step Attenuator 0
Data: array[9..1443] of Byte;
end;
// -------------------------------------------------------------------------
// High Priority Status Packet (HW -> PC, port 1025)
// -------------------------------------------------------------------------
THighPriorityStatus = packed record
Seq: array[0..3] of Byte;
StatusBits: Byte; // 4: HPS_PTT/DOT/DASH/PLL_LOCKED
ADCOverload: Byte; // 5: [0..7] = ADC0..7 overload
ExciterPwr0Hi: Byte; // 6
ExciterPwr0Lo: Byte; // 7
ExciterPwr1: array[0..1] of Byte; // 8-9 (reserved)
ExciterPwr2: array[0..1] of Byte; // 10-11 (reserved)
ExciterPwr3: array[0..1] of Byte; // 12-13 (reserved)
FwdPwrAlex0Hi: Byte; // 14
FwdPwrAlex0Lo: Byte; // 15
FwdPwrAlex1: array[0..1] of Byte; // 16-17 (reserved)
FwdPwrAlex2: array[0..1] of Byte; // 18-19 (reserved)
FwdPwrAlex3: array[0..1] of Byte; // 20-21 (reserved)
RevPwrAlex0Hi: Byte; // 22
RevPwrAlex0Lo: Byte; // 23
RevPwrAlex1: array[0..1] of Byte; // 24-25 (reserved)
RevPwrAlex2: array[0..1] of Byte; // 26-27 (reserved)
RevPwrAlex3: array[0..1] of Byte; // 28-29 (reserved)
FIFOOverflow: Byte; // 30: [3:0] overflow bits
DDCFIFODepthHi: Byte; // 31
DDCFIFODepthLo: Byte; // 32
MicFIFODepthHi: Byte; // 33
MicFIFODepthLo: Byte; // 34
DUCFIFODepthHi: Byte; // 35
DUCFIFODepthLo: Byte; // 36
SpkFIFODepthHi: Byte; // 37
SpkFIFODepthLo: Byte; // 38
Unused39: array[39..48] of Byte;
SupplyVoltsHi: Byte; // 49
SupplyVoltsLo: Byte; // 50
UserADC3Hi: Byte; // 51
UserADC3Lo: Byte; // 52
UserADC2Hi: Byte; // 53
UserADC2Lo: Byte; // 54
UserADC1Hi: Byte; // 55
UserADC1Lo: Byte; // 56
UserADC0Hi: Byte; // 57
UserADC0Lo: Byte; // 58
UserInputBits: Byte; // 59: IO4/IO5/IO6/IO8/IO2 etc.
end;
// -------------------------------------------------------------------------
// Microphone Data Packet (HW -> PC, default port 1026)
// -------------------------------------------------------------------------
TMicDataPacket = packed record
Seq: array[0..3] of Byte;
Samples: array[0..63] of SmallInt; // 64 x 16-bit signed, big-endian
end;
// -------------------------------------------------------------------------
// DDC I&Q Packet (HW -> PC, DDC0 default port 1035)
// -------------------------------------------------------------------------
TDDCIQPacket = packed record
Seq: array[0..3] of Byte; // 0-3
TimeStamp: array[0..7] of Byte; // 4-11: 64-bit VITA-49 timestamp
BitsPerSample: array[0..1] of Byte; // 12-13
SamplesPerFrame: array[0..1] of Byte;// 14-15
// From byte 16: interleaved I/Q samples
// For 24-bit: 3 bytes I, 3 bytes Q repeated
// Max 238 IQ pairs for 24-bit = 1428 bytes, total packet = 1444
IQData: array[0..1427] of Byte; // 16-1443
end;
// -------------------------------------------------------------------------
// DDC Audio Packet (PC -> HW, default port 1028)
// 64 Left + 64 Right 16-bit samples at 48ksps
// -------------------------------------------------------------------------
TDDCAudioPacket = packed record
Seq: array[0..3] of Byte;
// Interleaved Left/Right 16-bit samples (big-endian)
// [L0_hi, L0_lo, R0_hi, R0_lo, L1_hi, L1_lo, R1_hi, R1_lo, ...]
AudioData: array[0..255] of Byte; // 64 * 4 bytes
end;
// -------------------------------------------------------------------------
// DUC I&Q Data Packet (PC -> HW, default port 1029)
// 240 IQ pairs at 192ksps, 24-bit
// -------------------------------------------------------------------------
TDUCIQPacket = packed record
Seq: array[0..3] of Byte;
// 240 * 6 bytes = 1440 bytes
IQData: array[0..1439] of Byte;
end;
// ---------------------------------------------------------------------------
// Helper functions for Big-Endian packing/unpacking
// ---------------------------------------------------------------------------
// Pack a 32-bit word into 4 bytes (Big-Endian)
procedure PackU32BE(Value: LongWord; out B: array of Byte; Offset: Integer = 0);
// Unpack 4 bytes to a 32-bit word (Big-Endian)
function UnpackU32BE(const B: array of Byte; Offset: Integer = 0): LongWord;
// Pack a 16-bit word into 2 bytes (Big-Endian)
procedure PackU16BE(Value: Word; out B: array of Byte; Offset: Integer = 0);
// Unpack 2 bytes to a 16-bit word (Big-Endian)
function UnpackU16BE(const B: array of Byte; Offset: Integer = 0): Word;
// Convert frequency in Hz to phase word for HPSDR hardware
function FreqToPhaseWord(FreqHz: Double): LongWord;
// Convert phase word back to frequency
function PhaseWordToFreq(PhaseWord: LongWord): Double;
// Convert raw ADC value to supply voltage.
// Returns -1.0 if the board has no supply voltage measurement.
function ADCToSupplyVolts(RawADC: Word; BoardType: Integer): Double;
// True when the board type reports supply voltage in HP Status.
function BoardSupportsSupplyVoltage(BoardType: Integer): Boolean;
// True when the board type reports supply current in HP Status.
function BoardSupportsSupplyCurrent(BoardType: Integer): Boolean;
// Convert raw ADC value to supply current in Amps (Board 5 / Orion MkII only)
// Uses UserADC1 (bytes 55-56). Returns -1 for unsupported boards.
// VoffMV/SensMVA — калибровка датчика (Thetis AmpVoff/AmpSens, ACS712-style).
function ADCToSupplyCurrent(RawADC: Word; BoardType: Integer;
VoffMV: Double = 360.0; SensMVA: Double = 120.0): Double;
// Convert raw ADC value to RF power in Watts (ANAN-100)
function ADCToWatts100(RawADC: Word): Double;
// Convert raw ADC value to RF power in Watts (ANAN-10)
function ADCToWatts10(RawADC: Word): Double;
// Alex bridge FWD/REV detector → Watts, константы Thetis computeAlexFwdPower/
// computeRefPower по модели трансивера:
// 0 = ANAN-10/100/100B/100D (Vref 3.3V)
// 1 = ANAN-200D / Orion (Vref 5.0V)
// 2 = ANAN-7000DLE / Anvelina Pro 3 / G2 / Red Pitaya
// 3 = ANAN-8000DLE / Orion MkII
// IsRev — REV-детектор (свой bridge_volt/offset), Is6m — 6m-коррекция REV.
function AlexDetToWatts(RawADC: Word; DetModel: Integer;
IsRev, Is6m: Boolean): Double;
implementation
procedure PackU32BE(Value: LongWord; out B: array of Byte; Offset: Integer);
begin
B[Offset + 0] := (Value shr 24) and $FF;
B[Offset + 1] := (Value shr 16) and $FF;
B[Offset + 2] := (Value shr 8) and $FF;
B[Offset + 3] := Value and $FF;
end;
function UnpackU32BE(const B: array of Byte; Offset: Integer): LongWord;
begin
Result := (LongWord(B[Offset]) shl 24)
or (LongWord(B[Offset+1]) shl 16)
or (LongWord(B[Offset+2]) shl 8)
or LongWord(B[Offset+3]);
end;
procedure PackU16BE(Value: Word; out B: array of Byte; Offset: Integer);
begin
B[Offset + 0] := (Value shr 8) and $FF;
B[Offset + 1] := Value and $FF;
end;
function UnpackU16BE(const B: array of Byte; Offset: Integer): Word;
begin
Result := (Word(B[Offset]) shl 8) or Word(B[Offset+1]);
end;
function FreqToPhaseWord(FreqHz: Double): LongWord;
begin
// phase_word = 2^32 * freq / DSP_CLOCK
Result := Round(4294967296.0 * FreqHz / DSP_CLOCK_HZ);
end;
function PhaseWordToFreq(PhaseWord: LongWord): Double;
begin
Result := PhaseWord * DSP_CLOCK_HZ / 4294967296.0;
end;
function ADCToSupplyVolts(RawADC: Word; BoardType: Integer): Double;
begin
// Protocol Appendix A gives the ADC reference voltage only.
// The PCB voltage divider (from Thetis source) must also be applied.
// Board 0 (Atlas): no supply voltage measurement.
// Boards 1,2,3 (Hermes/Angelia): Vref=3.3V, PCB divider R1=4.7kΩ/R2=0.82kΩ.
// Board 4 (Orion): protocol reports supply voltage with a 5.0V constant.
// Board 5 (Orion MkII): Vref=5.0V, PCB divider R1=22kΩ/R2=1.1kΩ.
// NOTE: for board 5 caller must pass UserADC0, not supply_volts bytes.
// Other boards: not available (return -1).
case BoardType of
1, 2, 3: Result := (RawADC / 4095.0) * 3.3 * ((4.7 + 0.82) / 0.82);
4: Result := (RawADC / 4095.0) * 5.0;
5: Result := (RawADC / 4095.0) * 5.0 * ((22.0 + 1.0) / 1.1);
else Result := -1.0;
end;
end;
function BoardSupportsSupplyVoltage(BoardType: Integer): Boolean;
begin
Result := BoardType in [1, 2, 3, 4, 5];
end;
function BoardSupportsSupplyCurrent(BoardType: Integer): Boolean;
begin
Result := BoardType = 5;
end;
function ADCToSupplyCurrent(RawADC: Word; BoardType: Integer;
VoffMV: Double; SensMVA: Double): Double;
begin
// Board 5 (Orion MkII): UserADC1, Vref=5V → mV = adc/4095*5000.
// Formula: amps = (mV - Voff) / Sens. Дефолты Voff=360мВ, Sens=120мВ/А
// (Thetis, ACS712-style); переопределяются калибровкой.
if (BoardType = 5) and (SensMVA > 0) then
begin
Result := ((RawADC / 4095.0) * 5000.0 - VoffMV) / SensMVA;
if Result < 0.0 then Result := 0.0;
end else
Result := -1.0;
end;
function ADCToWatts100(RawADC: Word): Double;
var
V: Double;
begin
// W = (ADC/4095 * 3.3)^2 / 0.095 (ANAN-100, 0-150W range)
V := (RawADC / 4095.0) * 3.3;
Result := (V * V) / 0.095;
end;
function ADCToWatts10(RawADC: Word): Double;
var
V: Double;
begin
// W = (ADC/4095 * 3.3)^2 / 0.09 (ANAN-10, 0-20W range)
V := (RawADC / 4095.0) * 3.3;
Result := (V * V) / 0.09;
end;
function AlexDetToWatts(RawADC: Word; DetModel: Integer;
IsRev, Is6m: Boolean): Double;
var
Bridge, Vref, V: Double;
CalOff: Integer;
begin
// Константы из Thetis (console.cs computeAlexFwdPower / computeRefPower).
// W = ((ADC - CalOff)/4095 * Vref)^2 / Bridge.
case DetModel of
1: begin // ANAN-200D / Orion
Vref := 5.0;
if IsRev then begin
Bridge := 0.108; if Is6m then Bridge := 0.5;
CalOff := 2;
end else begin
Bridge := 0.108; CalOff := 4;
end;
end;
2: begin // ANAN-7000DLE / Anvelina Pro 3 / G2 / Red Pitaya
Vref := 5.0;
if IsRev then begin
Bridge := 0.15; if Is6m then Bridge := 0.7;
CalOff := 28;
end else begin
Bridge := 0.12; CalOff := 32;
end;
end;
3: begin // ANAN-8000DLE / Orion MkII
Vref := 5.0; Bridge := 0.08;
if IsRev then CalOff := 16 else CalOff := 18;
end;
else begin // 0: ANAN-10/100/100B/100D
Vref := 3.3;
if IsRev then begin
Bridge := 0.095; if Is6m then Bridge := 0.5;
CalOff := 3;
end else begin
Bridge := 0.095; CalOff := 6;
end;
end;
end;
V := ((RawADC - CalOff) / 4095.0) * Vref;
if V < 0 then V := 0;
Result := (V * V) / Bridge;
end;
end.