Files
ewsdr/HPSDRProtocol.pas
T
ew8bakandClaude Sonnet 4.6 d8050576a9 Add supply current display for Orion MkII (Board 5)
UserADC1 (bytes 55-56) carries current sensor data on the ANAN-8000DLE.
Formula from Thetis: amps = ((adc/4095 * 5000) - 360) / 120 (Voff=360mV,
Sens=120mV/A). Status bar now shows "Supply: 13.8V / 2.3A | PLL OK"
when current is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 12:29:45 +03:00

493 lines
20 KiB
ObjectPascal

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=+12dB, 31=-34.5dB
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;
// Convert raw ADC value to supply current in Amps (Board 5 / Orion MkII only)
// Uses UserADC1 (bytes 55-56). Returns -1 for unsupported boards.
function ADCToSupplyCurrent(RawADC: Word; BoardType: Integer): 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;
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 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.
// Board 4 (Orion) and others: not available (return -1).
case BoardType of
1, 2, 3: Result := (RawADC / 4095.0) * 3.3 * ((4.7 + 0.82) / 0.82);
5: Result := (RawADC / 4095.0) * 5.0 * ((22.0 + 1.0) / 1.1);
else Result := -1.0;
end;
end;
function ADCToSupplyCurrent(RawADC: Word; BoardType: Integer): Double;
begin
// Board 5 (Orion MkII): UserADC1, Vref=5V → mV = adc/4095*5000.
// Current sensor: Voff=360mV, Sens=120mV/A (Thetis defaults, ACS712-style).
// Formula: amps = (mV - Voff) / Sens.
if BoardType = 5 then
begin
Result := ((RawADC / 4095.0) * 5000.0 - 360.0) / 120.0;
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;
end.