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 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 // Board 0 (Atlas) has no supply voltage measurement (protocol: "not Atlas bus systems") // Boards 1,2,3 (Hermes/Angelia): V = ADC/4095 * 3.3 (Appendix A) // Boards 4,5 (Orion): V = ADC/4095 * 5.0 (Appendix A) case BoardType of 1, 2, 3: Result := (RawADC / 4095.0) * 3.3; 4, 5: Result := (RawADC / 4095.0) * 5.0; else Result := -1.0; // not documented or not applicable end; 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.