From 7e9f4079f3b9b813bb99941e9fb42a770cb042a0 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Wed, 3 Aug 2022 19:53:16 -0400 Subject: [PATCH] implement initial NXDN support (EXPERIMENTAL); --- ADF7021.cpp | 124 +++++++++++++++++++++++- ADF7021.h | 14 +++ Defines.h | 3 + FirmwareMain.cpp | 16 ++++ Globals.h | 9 ++ IO.cpp | 7 ++ IO.h | 6 +- IOSTM.cpp | 28 ++++++ Makefile.STM32FX | 4 +- SerialPort.cpp | 154 ++++++++++++++++++++++++++--- SerialPort.h | 14 ++- nxdn/CalNXDN.cpp | 128 +++++++++++++++++++++++++ nxdn/CalNXDN.h | 73 ++++++++++++++ nxdn/NXDNDefines.h | 74 ++++++++++++++ nxdn/NXDNRX.cpp | 201 ++++++++++++++++++++++++++++++++++++++ nxdn/NXDNRX.h | 83 ++++++++++++++++ nxdn/NXDNTX.cpp | 234 +++++++++++++++++++++++++++++++++++++++++++++ nxdn/NXDNTX.h | 88 +++++++++++++++++ 18 files changed, 1241 insertions(+), 19 deletions(-) create mode 100644 nxdn/CalNXDN.cpp create mode 100644 nxdn/CalNXDN.h create mode 100644 nxdn/NXDNDefines.h create mode 100644 nxdn/NXDNRX.cpp create mode 100644 nxdn/NXDNRX.h create mode 100644 nxdn/NXDNTX.cpp create mode 100644 nxdn/NXDNTX.h diff --git a/ADF7021.cpp b/ADF7021.cpp index 6f38ebc..70be3a9 100644 --- a/ADF7021.cpp +++ b/ADF7021.cpp @@ -70,11 +70,14 @@ uint16_t TX_F_Divider; // Tx - 15-bit Frational_N uint16_t dmrDev; uint16_t p25Dev; +uint16_t nxdnDev; int8_t m_dmrDiscBWAdj; int8_t m_p25DiscBWAdj; +int8_t m_nxdnDiscBWAdj; int8_t m_dmrPostBWAdj; int8_t m_p25PostBWAdj; +int8_t m_nxdnPostBWAdj; // --------------------------------------------------------------------------- // Global Functions @@ -315,6 +318,9 @@ void IO::rf1Conf(DVM_STATE modemState, bool reset) case STATE_P25: AFC_OFFSET = AFC_OFFSET_P25; break; + case STATE_NXDN: + AFC_OFFSET = AFC_OFFSET_NXDN; + break; default: break; } @@ -614,10 +620,12 @@ void IO::rf2Conf(DVM_STATE modemState) /// /// /// -void IO::setDeviations(uint8_t dmrTXLevel, uint8_t p25TXLevel) +/// +void IO::setDeviations(uint8_t dmrTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel) { dmrDev = uint16_t((ADF7021_DEV_DMR * uint16_t(dmrTXLevel)) / 128U); p25Dev = uint16_t((ADF7021_DEV_P25 * uint16_t(p25TXLevel)) / 128U); + nxdnDev = uint16_t((ADF7021_DEV_NXDN * uint16_t(nxdnTXLevel)) / 128U); } /// @@ -627,14 +635,18 @@ void IO::setDeviations(uint8_t dmrTXLevel, uint8_t p25TXLevel) /// /// /// +/// /// /// -void IO::setRFAdjust(int8_t dmrDiscBWAdj, int8_t p25DiscBWAdj, int8_t dmrPostBWAdj, int8_t p25PostBWAdj) +/// +void IO::setRFAdjust(int8_t dmrDiscBWAdj, int8_t p25DiscBWAdj, int8_t nxdnDiscBWAdj, int8_t dmrPostBWAdj, int8_t p25PostBWAdj, int8_t nxdnPostBWADJ) { m_dmrDiscBWAdj = dmrDiscBWAdj; m_p25DiscBWAdj = p25DiscBWAdj; + m_nxdnDiscBWAdj = nxdnDiscBWAdj; m_dmrPostBWAdj = dmrPostBWAdj; m_p25PostBWAdj = p25PostBWAdj; + m_nxdnPostBWAdj = nxdnPostBWADJ; } /// @@ -872,6 +884,7 @@ void IO::configureTxRx(DVM_STATE modemState) { uint16_t dmrDiscBW = ADF7021_DISC_BW_DMR, dmrPostBW = ADF7021_POST_BW_DMR; uint16_t p25DiscBW = ADF7021_DISC_BW_P25, p25PostBW = ADF7021_POST_BW_P25; + uint16_t nxdnDiscBW = ADF7021_DISC_BW_NXDN, nxdnPostBW = ADF7021_POST_BW_NXDN; // configure DMR discriminator and post demodulator BW if (dmrDiscBW + m_dmrDiscBWAdj < 0U) @@ -903,6 +916,21 @@ void IO::configureTxRx(DVM_STATE modemState) if (p25PostBW > ADF7021_POST_BW_MAX) p25PostBW = ADF7021_POST_BW_MAX; + // configure NXDN discriminator and post demodulator BW + if (nxdnDiscBW + m_nxdnDiscBWAdj < 0U) + nxdnDiscBW = 0U; + else + nxdnDiscBW = ADF7021_DISC_BW_NXDN + m_nxdnDiscBWAdj; + if (nxdnDiscBW > ADF7021_DISC_BW_MAX) + nxdnDiscBW = ADF7021_DISC_BW_MAX; + + if (nxdnPostBW + m_nxdnPostBWAdj < 0) + nxdnPostBW = 0U; + else + nxdnPostBW = ADF7021_POST_BW_NXDN + m_nxdnPostBWAdj; + if (nxdnPostBW > ADF7021_POST_BW_MAX) + nxdnPostBW = ADF7021_POST_BW_MAX; + /* ** Configure the remaining registers based on modem state. */ @@ -1171,6 +1199,93 @@ void IO::configureTxRx(DVM_STATE modemState) ADF7021_REG2 |= (uint32_t)ADF7021_REG2_RC_5 << 30; // R-Cosine Alpha } break; + case STATE_NXDN: // 4FSK + { + // Dev: +1 symb 350 Hz, symb rate = 2400 + + /* + ** Tx/Rx Clock (Register 3) + */ +/** Support for 14.7456 MHz TCXO (modified RF7021SE boards) */ +#if defined(ADF7021_14_7456) + ADF7021_REG3 = (uint32_t)ADF7021_REG3_ADDR; // Register Address 3 + ADF7021_REG3 |= (uint32_t)ADF7021_REG3_BBOS_DIV_8 << 4; // Base Band Clock Divider + ADF7021_REG3 |= (uint32_t)(3 & 0xFU) << 6; // Demodulator Clock Divider + ADF7021_REG3 |= (uint32_t)(32 & 0xFFU) << 10; // Data/Clock Recovery Divider (CDR) + ADF7021_REG3 |= (uint32_t)(147 & 0xFFU) << 18; // Sequencer Clock Divider + ADF7021_REG3 |= (uint32_t)(10 & 0x3FU) << 26; // AGC Clock Divider + +/** Support for 12.2880 MHz TCXO */ +#elif defined(ADF7021_12_2880) + ADF7021_REG3 = (uint32_t)ADF7021_REG3_ADDR; // Register Address 3 + ADF7021_REG3 |= (uint32_t)ADF7021_REG3_BBOS_DIV_8 << 4; // Base Band Clock Divider + ADF7021_REG3 |= (uint32_t)(2 & 0xFU) << 6; // Demodulator Clock Divider + ADF7021_REG3 |= (uint32_t)(40 & 0xFFU) << 10; // Data/Clock Recovery Divider (CDR) + ADF7021_REG3 |= (uint32_t)(123 & 0xFFU) << 18; // Sequencer Clock Divider + ADF7021_REG3 |= (uint32_t)(10 & 0x3FU) << 26; // AGC Clock Divider +#endif + + /* + ** AFC (Register 10) + */ +/** Support for 14.7456 MHz TCXO (modified RF7021SE boards) */ +#if defined(ADF7021_14_7456) + ADF7021_REG10 = (uint32_t)ADF7021_REG10_ADDR; // Register Address 10 +#if defined(ADF7021_ENABLE_4FSK_AFC) + ADF7021_REG10 |= (uint32_t)ADF7021_REG10_AFC_ENABLE << 4; // AFC Enable/Disable +#else + ADF7021_REG10 |= (uint32_t)ADF7021_REG10_AFC_DISABLE << 4; // AFC Enable/Disable +#endif + ADF7021_REG10 |= (uint32_t)(569 & 0xFFFU) << 5; // AFC Scaling Factor + ADF7021_REG10 |= (uint32_t)(15 & 0xFU) << 17; // KI + ADF7021_REG10 |= (uint32_t)(4 & 0x7U) << 21; // KP + ADF7021_REG10 |= (uint32_t)(4 & 0xFFU) << 24; // Maximum AFC Range + +/** Support for 12.2880 MHz TCXO */ +#elif defined(ADF7021_12_2880) + ADF7021_REG10 = (uint32_t)ADF7021_REG10_ADDR; // Register Address 10 +#if defined(ADF7021_ENABLE_4FSK_AFC) + ADF7021_REG10 |= (uint32_t)ADF7021_REG10_AFC_ENABLE << 4; // AFC Enable/Disable +#else + ADF7021_REG10 |= (uint32_t)ADF7021_REG10_AFC_DISABLE << 4; // AFC Enable/Disable +#endif + ADF7021_REG10 |= (uint32_t)(683 & 0xFFFU) << 5; // AFC Scaling Factor + ADF7021_REG10 |= (uint32_t)(15 & 0xFU) << 17; // KI + ADF7021_REG10 |= (uint32_t)(4 & 0x7U) << 21; // KP + ADF7021_REG10 |= (uint32_t)(4 & 0xFFU) << 24; // Maximum AFC Range + +#endif + + /* + ** Demodulator Setup (Register 4) + */ + // K=32 + ADF7021_REG4 = (uint32_t)ADF7021_REG4_ADDR; // Register Address 4 + ADF7021_REG4 |= (uint32_t)ADF7021_REG4_MODE_4FSK << 4; // Demodulation Scheme + ADF7021_REG4 |= (uint32_t)ADF7021_REG4_CROSS_PROD << 7; // Dot Product + ADF7021_REG4 |= (uint32_t)ADF7021_REG4_INV_CLKDAT << 8; // Clock/Data Inversion + ADF7021_REG4 |= (uint32_t)(nxdnDiscBW & 0x3FFU) << 10; // Discriminator BW + ADF7021_REG4 |= (uint32_t)(nxdnPostBW & 0xFFFU) << 20; // Post Demod BW + ADF7021_REG4 |= (uint32_t)ADF7021_REG4_IF_125K << 30; // IF Filter + + /* + ** 3FSK/4FSK Demod (Register 13) + */ + ADF7021_REG13 = (uint32_t)ADF70210_REG13_ADDR; // Register Address 13 + ADF7021_REG13 |= (uint32_t)ADF7021_SLICER_TH_NXDN << 4; // Slicer Threshold + + /* + ** Transmit Modulation (Register 2) + */ + ADF7021_REG2 = (uint32_t)ADF7021_REG2_ADDR; // Register Address 2 + ADF7021_REG2 |= (uint32_t)ADF7021_REG2_MOD_4FSKRC << 4; // Modulation Scheme + ADF7021_REG2 |= (uint32_t)ADF7021_REG2_PA_DEF << 7; // PA Enable & PA Bias + ADF7021_REG2 |= (uint32_t)(m_rfPower & 0x3FU) << 13; // PA Level (0 - Off, 63 - 13 dBm) + ADF7021_REG2 |= (uint32_t)(nxdnDev / div2) << 19; // Freq. Deviation + ADF7021_REG2 |= (uint32_t)ADF7021_REG2_INV_DATA << 28; // Clock/Data Inversion + ADF7021_REG2 |= (uint32_t)ADF7021_REG2_RC_5 << 30; // R-Cosine Alpha + } + break; default: // GMSK { // Dev: 1200 Hz, symb rate = 4800 @@ -1253,8 +1368,9 @@ void IO::configureTxRx(DVM_STATE modemState) } DEBUG5("IO::configureTxRx(): configuring ADF Tx/Rx values; dmrDiscBW/dmrPostBW/p25DiscBW/p25PostBW", dmrDiscBW, dmrPostBW, p25DiscBW, p25PostBW); - DEBUG4("IO::configureTxRx(): configuring ADF Tx/Rx values; dmrSymDev/p25SymDev/rfPower", (uint16_t)((ADF7021_PFD * dmrDev) / (f_div * 65536)), - (uint16_t)((ADF7021_PFD * p25Dev) / (f_div * 65536)), m_rfPower); + DEBUG3("IO::configureTxRx(): configuring ADF Tx/Rx values; nxdnDiscBW/nxdnPostBW", nxdnDiscBW, nxdnPostBW); + DEBUG5("IO::configureTxRx(): configuring ADF Tx/Rx values; dmrSymDev/p25SymDev/nxdnSymDev/rfPower", (uint16_t)((ADF7021_PFD * dmrDev) / (f_div * 65536)), + (uint16_t)((ADF7021_PFD * p25Dev) / (f_div * 65536)), (uint16_t)((ADF7021_PFD * nxdnDev) / (f_div * 65536)), m_rfPower); } /// diff --git a/ADF7021.h b/ADF7021.h index 273b4ca..79ae44a 100644 --- a/ADF7021.h +++ b/ADF7021.h @@ -96,11 +96,13 @@ #define AFC_OFFSET_DMR -125 #define AFC_OFFSET_P25 -125 +#define AFC_OFFSET_NXDN -125 #else #define AFC_OFFSET_DMR 125 #define AFC_OFFSET_P25 125 +#define AFC_OFFSET_NXDN 125 #endif // ADF7021_AFC_POS @@ -108,6 +110,7 @@ #define AFC_OFFSET_DMR 0 #define AFC_OFFSET_P25 0 +#define AFC_OFFSET_NXDN 0 #endif // ADF7021_ENABLE_4FSK_AFC @@ -169,6 +172,8 @@ #define ADF7021_DEV_P25 22U #endif // ENABLE_P25_WIDE +#define ADF7021_DEV_NXDN 13U + /* ** Demodulator Setup (Register 4) */ @@ -177,11 +182,13 @@ #define ADF7021_DISC_BW_DEFAULT 522U // K=85 #define ADF7021_DISC_BW_DMR 393U // K=32 #define ADF7021_DISC_BW_P25 393U // K=32 +#define ADF7021_DISC_BW_NXDN 295U // K=32 // Post demodulator bandwith #define ADF7021_POST_BW_DEFAULT 10U #define ADF7021_POST_BW_DMR 80U #define ADF7021_POST_BW_P25 6U +#define ADF7021_POST_BW_NXDN 7U /* ** IF Coarse Cal Setup (Register 5) @@ -235,6 +242,8 @@ #define ADF7021_DEV_P25 13U #endif // ENABLE_P25_WIDE +#define ADF7021_DEV_NXDN 8U + /* ** Demodulator Setup (Register 4) */ @@ -243,11 +252,13 @@ #define ADF7021_DISC_BW_DEFAULT 522U // K=85 #define ADF7021_DISC_BW_DMR 491U // K=32 #define ADF7021_DISC_BW_P25 491U // K=32 +#define ADF7021_DISC_BW_NXDN 246U // K=32 // Post demodulator bandwith #define ADF7021_POST_BW_DEFAULT 10U #define ADF7021_POST_BW_DMR 80U #define ADF7021_POST_BW_P25 6U +#define ADF7021_POST_BW_NXDN 8U /* ** IF Coarse Cal Setup (Register 5) @@ -336,11 +347,14 @@ #define ADF7021_SLICER_TH_DMR 51U #define ADF7021_SLICER_TH_P25 43U +#define ADF7021_SLICER_TH_NXDN 26U #else #define ADF7021_SLICER_TH_DMR 57U #define ADF7021_SLICER_TH_P25 47U +#define ADF7021_SLICER_TH_NXDN 26U + #endif // ADF7021_N_VER diff --git a/Defines.h b/Defines.h index 2b662c8..79ae004 100644 --- a/Defines.h +++ b/Defines.h @@ -98,6 +98,9 @@ typedef unsigned long long ulong64_t; // Allow the P25 protocol #define ENABLE_P25 +// Allow the NXDN protocol +#define ENABLE_NXDN + // Enable ADF7021 support #define ENABLE_ADF7021 diff --git a/FirmwareMain.cpp b/FirmwareMain.cpp index 0e34f45..f5c55e0 100644 --- a/FirmwareMain.cpp +++ b/FirmwareMain.cpp @@ -52,6 +52,11 @@ bool m_p25Enable = true; #else bool m_p25Enable = false; #endif +#ifdef ENABLE_NXDN +bool m_nxdnEnable = true; +#else +bool m_nxdnEnable = false; +#endif bool m_duplex = false; @@ -75,9 +80,14 @@ dmr::DMRDMOTX dmrDMOTX; p25::P25RX p25RX; p25::P25TX p25TX; +/** NXDN */ +nxdn::NXDNRX nxdnRX; +nxdn::NXDNTX nxdnTX; + /** Calibration */ dmr::CalDMR calDMR; p25::CalP25 calP25; +nxdn::CalNXDN calNXDN; CalRSSI calRSSI; /** CW */ @@ -117,6 +127,9 @@ void loop() if (m_p25Enable && m_modemState == STATE_P25) p25TX.process(); + if (m_nxdnEnable && m_modemState == STATE_NXDN) + nxdnTX.process(); + if (m_modemState == STATE_DMR_DMO_CAL_1K || m_modemState == STATE_DMR_CAL_1K || m_modemState == STATE_DMR_LF_CAL || m_modemState == STATE_DMR_CAL || m_modemState == STATE_INT_CAL) @@ -125,6 +138,9 @@ void loop() if (m_modemState == STATE_P25_CAL_1K || m_modemState == STATE_P25_LF_CAL || m_modemState == STATE_P25_CAL) calP25.process(); + if (m_modemState == STATE_NXDN_CAL) + calNXDN.process(); + if (m_modemState == STATE_RSSI_CAL) calRSSI.process(); diff --git a/Globals.h b/Globals.h index 07c9c00..b1e18c9 100644 --- a/Globals.h +++ b/Globals.h @@ -57,6 +57,9 @@ #include "p25/P25RX.h" #include "p25/P25TX.h" #include "p25/CalP25.h" +#include "nxdn/NXDNRX.h" +#include "nxdn/NXDNTX.h" +#include "nxdn/CalNXDN.h" #include "CalRSSI.h" #include "CWIdTX.h" #include "IO.h" @@ -94,6 +97,7 @@ extern uint8_t m_cwIdTXLevel; extern bool m_dmrEnable; extern bool m_p25Enable; +extern bool m_nxdnEnable; extern bool m_duplex; @@ -120,9 +124,14 @@ extern dmr::DMRDMOTX dmrDMOTX; extern p25::P25RX p25RX; extern p25::P25TX p25TX; +/** NXDN BS */ +extern nxdn::NXDNRX nxdnRX; +extern nxdn::NXDNTX nxdnTX; + /** Calibration */ extern dmr::CalDMR calDMR; extern p25::CalP25 calP25; +extern nxdn::CalNXDN calNXDN; extern CalRSSI calRSSI; /** CW */ diff --git a/IO.cpp b/IO.cpp index 0227567..0d55b60 100644 --- a/IO.cpp +++ b/IO.cpp @@ -63,6 +63,7 @@ IO::IO(): setPTTInt(LOW); setDMRInt(LOW); setP25Int(LOW); + setNXDNInt(LOW); setCOSInt(LOW); #if !defined(BIDIR_DATA_PIN) @@ -162,6 +163,10 @@ void IO::process() /** Project 25 */ p25RX.databit(bit); } + else if (m_modemState == STATE_NXDN) { + /* Next Generation Digital Narrowband */ + nxdnRX.databit(bit); + } } } @@ -229,6 +234,7 @@ void IO::setMode(DVM_STATE modemState) setDMRInt(relativeState == STATE_DMR); setP25Int(relativeState == STATE_P25); + setNXDNInt(relativeState == STATE_NXDN); } /// @@ -353,6 +359,7 @@ void IO::selfTest() setPTTInt(ledValue); setDMRInt(ledValue); setP25Int(ledValue); + setNXDNInt(ledValue); setCOSInt(ledValue); blinks++; diff --git a/IO.h b/IO.h index d11a6ce..86d14f6 100644 --- a/IO.h +++ b/IO.h @@ -109,11 +109,11 @@ public: #endif /// - void setDeviations(uint8_t dmrTXLevel, uint8_t p25TXLevel); + void setDeviations(uint8_t dmrTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel); /// Sets the RF parameters. uint8_t setRFParams(uint32_t rxFreq, uint32_t txFreq, uint8_t rfPower, ADF_GAIN_MODE gainMode); /// Sets the RF adjustment parameters. - void setRFAdjust(int8_t dmrDiscBWAdj, int8_t p25DiscBWAdj, int8_t dmrPostBWAdj, int8_t p25PostBWAdj); + void setRFAdjust(int8_t dmrDiscBWAdj, int8_t p25DiscBWAdj, int8_t nxdnDiscBWAdj, int8_t dmrPostBWAdj, int8_t p25PostBWAdj, int8_t nxdnPostBWAdj); /// Flag indicating the TX ring buffer has overflowed. bool hasTXOverflow(void); @@ -242,6 +242,8 @@ private: void setDMRInt(bool on); /// void setP25Int(bool on); + /// + void setNXDNInt(bool on); }; #endif diff --git a/IOSTM.cpp b/IOSTM.cpp index 985eadf..30d9e6d 100644 --- a/IOSTM.cpp +++ b/IOSTM.cpp @@ -85,6 +85,9 @@ #define PIN_DEB GPIO_Pin_11 #define PORT_DEB GPIOA +#define PIN_NXDN_LED GPIO_Pin_8 +#define PORT_NXDN_LED GPIOA + #define PIN_DMR_LED GPIO_Pin_15 #define PORT_DMR_LED GPIOB @@ -159,6 +162,13 @@ #define PIN_DEB GPIO_Pin_9 #define PORT_DEB GPIOB +#if defined(STM32_USB_HOST) +#define PIN_NXDN_LED GPIO_Pin_1 +#else +#define PIN_NXDN_LED GPIO_Pin_7 +#endif +#define PORT_NXDN_LED GPIOA + #define PIN_DMR_LED GPIO_Pin_13 #define PORT_DMR_LED GPIOB @@ -222,6 +232,9 @@ #define PIN_DEB GPIO_Pin_9 #define PORT_DEB GPIOB +#define PIN_NXDN_LED GPIO_Pin_8 +#define PORT_NXDN_LED GPIOA + #define PIN_DMR_LED GPIO_Pin_13 #define PORT_DMR_LED GPIOB @@ -667,6 +680,12 @@ void IO::initInt() GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(PORT_DEB, &GPIO_InitStruct); + // NXDN LED + GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; + GPIO_InitStruct.GPIO_Pin = PIN_NXDN_LED; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; + GPIO_Init(PORT_NXDN_LED, &GPIO_InitStruct); + // DMR LED GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_Pin = PIN_DMR_LED; @@ -877,4 +896,13 @@ void IO::setP25Int(bool on) GPIO_WriteBit(PORT_P25_LED, PIN_P25_LED, on ? Bit_SET : Bit_RESET); } +/// +/// +/// +/// +void IO::setNXDNInt(bool on) +{ + GPIO_WriteBit(PORT_NXDN_LED, PIN_NXDN_LED, on ? Bit_SET : Bit_RESET); +} + #endif // STM32F10X_MD diff --git a/Makefile.STM32FX b/Makefile.STM32FX index 5e0bb96..5c3cb07 100644 --- a/Makefile.STM32FX +++ b/Makefile.STM32FX @@ -74,7 +74,7 @@ ifndef $(OSC) endif # Build object lists -CXXSRC=$(wildcard ./*.cpp) $(wildcard ./dmr/*.cpp) $(wildcard ./p25/*.cpp) +CXXSRC=$(wildcard ./*.cpp) $(wildcard ./dmr/*.cpp) $(wildcard ./p25/*.cpp) $(wildcard ./nxdn/*.cpp) CSRC_STD_F1=$(wildcard $(STD_LIB_F1)/*.c) SYS_F1=$(wildcard $(SYS_DIR_F1)/*.c) STARTUP_F1=$(wildcard $(STARTUP_DIR_F1)/*.c) @@ -195,10 +195,12 @@ $(OBJDIR_F1): mkdir $@ mkdir $@/dmr mkdir $@/p25 + mkdir $@/nxdn $(OBJDIR_F4): mkdir $@ mkdir $@/dmr mkdir $@/p25 + mkdir $@/nxdn $(BINDIR)/$(BINBIN_F1BL): $(BINDIR)/$(BINELF_F1BL) $(CP) -O binary $< $@ diff --git a/SerialPort.cpp b/SerialPort.cpp index 14b7c82..6835162 100644 --- a/SerialPort.cpp +++ b/SerialPort.cpp @@ -48,6 +48,11 @@ #else #define DESCR_P25 "" #endif +#if defined(ENABLE_NXDN) +#define DESCR_NXDN "NXDN, " +#else +#define DESCR_NXDN "" +#endif #if defined(SEND_RSSI_DATA) #define DESCR_RSSI "RSSI, " @@ -86,7 +91,7 @@ #define RF_CHIP "ADF7021, " #endif -#define DESCRIPTION "Digital Voice Modem DSP Hotspot [" BOARD_INFO "] (" RF_CHIP DESCR_DMR DESCR_P25 DESCR_OSC DESCR_RSSI "CW Id)" +#define DESCRIPTION "Digital Voice Modem DSP Hotspot [" BOARD_INFO "] (" RF_CHIP DESCR_DMR DESCR_P25 DESCR_NXDN DESCR_OSC DESCR_RSSI "CW Id)" #define concat(a, b, c) a " (build " b " " c ")" const char HARDWARE[] = concat(DESCRIPTION, __TIME__, __DATE__); @@ -358,6 +363,22 @@ void SerialPort::process() } break; + /* Next Generation Digital Narrowband */ + case CMD_NXDN_DATA: + if (m_nxdnEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_NXDN) + err = p25TX.writeData(m_buffer + 3U, m_len - 3U); + } + if (err == RSN_OK) { + if (m_modemState == STATE_IDLE) + setMode(STATE_NXDN); + } + else { + DEBUG2("SerialPort: process(): Received invalid NXDN data", err); + sendNAK(err); + } + break; + default: // Handle this, send a NAK back sendNAK(RSN_NAK); @@ -388,7 +409,7 @@ bool SerialPort::isCalState(DVM_STATE state) state == STATE_DMR_DMO_CAL_1K || state == STATE_DMR_CAL_1K || state == STATE_DMR_LF_CAL || state == STATE_P25_LF_CAL || state == STATE_RSSI_CAL || - state == STATE_P25_CAL || state == STATE_DMR_CAL || + state == STATE_P25_CAL || state == STATE_DMR_CAL || state == STATE_NXDN_CAL || state == STATE_INT_CAL) { return true; } @@ -405,12 +426,14 @@ DVM_STATE SerialPort::calRelativeState(DVM_STATE state) { if (isCalState(state)) { if (state == STATE_DMR_DMO_CAL_1K || state == STATE_DMR_CAL_1K || - state == STATE_DMR_LF_CAL || state == STATE_DMR_CAL || + state == STATE_DMR_LF_CAL || state == STATE_DMR_CAL || state == STATE_NXDN_CAL || state == STATE_RSSI_CAL || state == STATE_INT_CAL) { return STATE_DMR; - } else if(state == STATE_P25_CAL_1K || state == STATE_P25_LF_CAL || + } else if (state == STATE_P25_CAL_1K || state == STATE_P25_LF_CAL || state == STATE_P25_CAL) { return STATE_P25; + } else if (state == STATE_NXDN_CAL) { + return STATE_NXDN; } } @@ -515,6 +538,54 @@ void SerialPort::writeP25Lost() writeInt(1U, reply, 3); } +/// +/// Write NXDN frame data to serial port. +/// +/// +/// +void SerialPort::writeNXDNData(const uint8_t* data, uint8_t length) +{ + if (m_modemState != STATE_NXDN && m_modemState != STATE_IDLE) + return; + + if (!m_nxdnEnable) + return; + + uint8_t reply[130U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = CMD_NXDN_DATA; + + uint8_t count = 3U; + for (uint8_t i = 0U; i < length; i++, count++) + reply[count] = data[i]; + + reply[1U] = count; + + writeInt(1U, reply, count); +} + +/// +/// Write lost NXDN frame data to serial port. +/// +void SerialPort::writeNXDNLost() +{ + if (m_modemState != STATE_NXDN && m_modemState != STATE_IDLE) + return; + + if (!m_nxdnEnable) + return; + + uint8_t reply[3U]; + + reply[0U] = DVM_FRAME_START; + reply[1U] = 3U; + reply[2U] = CMD_NXDN_LOST; + + writeInt(1U, reply, 3); +} + /// /// Write calibration frame data to serial port. /// @@ -812,6 +883,8 @@ void SerialPort::getStatus() reply[3U] |= 0x02U; if (m_p25Enable) reply[3U] |= 0x08U; + if (m_nxdnEnable) + reply[3U] |= 0x10U; reply[4U] = uint8_t(m_modemState); @@ -852,7 +925,12 @@ void SerialPort::getStatus() else reply[10U] = 0U; - writeInt(1U, reply, 11); + if (m_nxdnEnable) + reply[11U] = nxdnTX.getSpace(); + else + reply[11U] = 0U; + + writeInt(1U, reply, 12); } /// @@ -891,13 +969,13 @@ void SerialPort::getVersion() uint8_t SerialPort::modemStateCheck(DVM_STATE state) { // invalid mode check - if (state != STATE_IDLE && state != STATE_DMR && state != STATE_P25 && + if (state != STATE_IDLE && state != STATE_DMR && state != STATE_P25 && state != STATE_NXDN && state != STATE_P25_CAL_1K && state != STATE_DMR_DMO_CAL_1K && state != STATE_DMR_CAL_1K && state != STATE_DMR_LF_CAL && state != STATE_P25_LF_CAL && state != STATE_RSSI_CAL && state != STATE_P25_CAL && state != STATE_DMR_CAL && - state != STATE_INT_CAL) + state != STATE_NXDN_CAL) return RSN_INVALID_MODE; /* // DMR without DMR being enabled @@ -906,6 +984,9 @@ uint8_t SerialPort::modemStateCheck(DVM_STATE state) // P25 without P25 being enabled if (state == STATE_P25 && !m_p25Enable) return RSN_P25_DISABLED; + // NXDN without NXDN being enabled + if (state == STATE_NXDN && !m_nxdnEnable) + return RSN_NXDN_DISABLED; */ return RSN_OK; } @@ -918,7 +999,7 @@ uint8_t SerialPort::modemStateCheck(DVM_STATE state) /// uint8_t SerialPort::setConfig(const uint8_t* data, uint8_t length) { - if (length < 14U) + if (length < 15U) return RSN_ILLEGAL_LENGTH; bool simplex = (data[0U] & 0x80U) == 0x80U; @@ -927,6 +1008,7 @@ uint8_t SerialPort::setConfig(const uint8_t* data, uint8_t length) bool dmrEnable = (data[1U] & 0x02U) == 0x02U; bool p25Enable = (data[1U] & 0x08U) == 0x08U; + bool nxdnEnable = (data[1U] & 0x10U) == 0x10U; uint8_t fdmaPreamble = data[2U]; if (fdmaPreamble > 255U) @@ -954,14 +1036,20 @@ uint8_t SerialPort::setConfig(const uint8_t* data, uint8_t length) uint8_t dmrTXLevel = data[10U]; uint8_t p25TXLevel = data[12U]; + uint8_t nxdnTXLevel = data[15U]; m_modemState = modemState; m_dmrEnable = dmrEnable; m_p25Enable = p25Enable; + m_nxdnEnable = nxdnEnable; if (m_dmrEnable && m_p25Enable) return RSN_HS_NO_DUAL_MODE; + if (m_dmrEnable && m_nxdnEnable) + return RSN_HS_NO_DUAL_MODE; + if (m_p25Enable && m_nxdnEnable) + return RSN_HS_NO_DUAL_MODE; m_duplex = !simplex; @@ -977,10 +1065,11 @@ uint8_t SerialPort::setConfig(const uint8_t* data, uint8_t length) } #endif - io.setDeviations(dmrTXLevel, p25TXLevel); + io.setDeviations(dmrTXLevel, p25TXLevel, nxdnTXLevel); p25TX.setPreambleCount(fdmaPreamble); dmrDMOTX.setPreambleCount(fdmaPreamble); + //nxdnTX.setPreambleCount(fdmaPreamble); p25RX.setNAC(nac); @@ -1039,6 +1128,7 @@ void SerialPort::setMode(DVM_STATE modemState) case STATE_DMR: DEBUG1("SerialPort: setMode(): mode set to DMR"); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_P25: @@ -1048,6 +1138,17 @@ void SerialPort::setMode(DVM_STATE modemState) dmrRX.reset(); #endif dmrDMORX.reset(); + nxdnRX.reset(); + cwIdTX.reset(); + break; + case STATE_NXDN: + DEBUG1("SerialPort: setMode(): mode set to NXDN"); +#if defined(DUPLEX) + dmrIdleRX.reset(); + dmrRX.reset(); +#endif + dmrDMORX.reset(); + p25RX.reset(); cwIdTX.reset(); break; case STATE_DMR_CAL: @@ -1058,6 +1159,7 @@ void SerialPort::setMode(DVM_STATE modemState) #endif dmrDMORX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_P25_CAL: @@ -1068,6 +1170,18 @@ void SerialPort::setMode(DVM_STATE modemState) #endif dmrDMORX.reset(); p25RX.reset(); + nxdnRX.reset(); + cwIdTX.reset(); + break; + case STATE_NXDN_CAL: + DEBUG1("SerialPort: setMode(): mode set to NXDN Calibrate"); +#if defined(DUPLEX) + dmrIdleRX.reset(); + dmrRX.reset(); +#endif + dmrDMORX.reset(); + p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_P25_LF_CAL: @@ -1078,6 +1192,7 @@ void SerialPort::setMode(DVM_STATE modemState) #endif dmrDMORX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_RSSI_CAL: @@ -1088,6 +1203,7 @@ void SerialPort::setMode(DVM_STATE modemState) #endif dmrDMORX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_DMR_LF_CAL: @@ -1098,6 +1214,7 @@ void SerialPort::setMode(DVM_STATE modemState) #endif dmrDMORX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_DMR_CAL_1K: @@ -1108,6 +1225,7 @@ void SerialPort::setMode(DVM_STATE modemState) #endif dmrDMORX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_DMR_DMO_CAL_1K: @@ -1118,6 +1236,7 @@ void SerialPort::setMode(DVM_STATE modemState) #endif dmrDMORX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_P25_CAL_1K: @@ -1128,6 +1247,7 @@ void SerialPort::setMode(DVM_STATE modemState) #endif dmrDMORX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; default: @@ -1149,7 +1269,7 @@ void SerialPort::setMode(DVM_STATE modemState) /// uint8_t SerialPort::setRFParams(const uint8_t* data, uint8_t length) { - if (length < 15U) + if (length < 17U) return RSN_ILLEGAL_LENGTH; uint32_t rxFreq, txFreq; @@ -1180,6 +1300,12 @@ uint8_t SerialPort::setRFParams(const uint8_t* data, uint8_t length) if (p25DiscBWAdj < -128) return RSN_INVALID_REQUEST; + int8_t nxdnDiscBWAdj = int8_t(data[15U]) - 128; + if (nxdnDiscBWAdj > 128) + return RSN_INVALID_REQUEST; + if (nxdnDiscBWAdj < -128) + return RSN_INVALID_REQUEST; + int8_t dmrPostBWAdj = int8_t(data[12U]) - 128; if (dmrPostBWAdj > 128) return RSN_INVALID_REQUEST; @@ -1192,9 +1318,15 @@ uint8_t SerialPort::setRFParams(const uint8_t* data, uint8_t length) if (p25PostBWAdj < -128) return RSN_INVALID_REQUEST; + int8_t nxdnPostBWAdj = int8_t(data[16U]) - 128; + if (nxdnPostBWAdj > 128) + return RSN_INVALID_REQUEST; + if (nxdnPostBWAdj < -128) + return RSN_INVALID_REQUEST; + gainMode = (ADF_GAIN_MODE)data[14U]; - io.setRFAdjust(dmrDiscBWAdj, p25DiscBWAdj, dmrPostBWAdj, p25PostBWAdj); + io.setRFAdjust(dmrDiscBWAdj, p25DiscBWAdj, nxdnDiscBWAdj, dmrPostBWAdj, p25PostBWAdj, nxdnPostBWAdj); return io.setRFParams(rxFreq, txFreq, rfPower, gainMode); } diff --git a/SerialPort.h b/SerialPort.h index b788bee..93289c7 100644 --- a/SerialPort.h +++ b/SerialPort.h @@ -46,11 +46,14 @@ enum DVM_STATE { STATE_DMR = 1U, // Project 25 STATE_P25 = 2U, + // NXDN + STATE_NXDN = 3U, // CW STATE_CW = 10U, // Calibration States + STATE_INT_CAL = 90U, STATE_P25_LF_CAL = 91U, STATE_P25_CAL_1K = 92U, @@ -62,7 +65,7 @@ enum DVM_STATE { STATE_P25_CAL = 97U, STATE_DMR_CAL = 98U, - STATE_INT_CAL = 99U + STATE_NXDN_CAL = 99U }; enum DVM_COMMANDS { @@ -92,6 +95,9 @@ enum DVM_COMMANDS { CMD_P25_LOST = 0x32U, CMD_P25_CLEAR = 0x33U, + CMD_NXDN_DATA = 0x41U, + CMD_NXDN_LOST = 0x42U, + CMD_ACK = 0x70U, CMD_NAK = 0x7FU, @@ -133,6 +139,7 @@ enum CMD_REASON_CODE { RSN_DMR_DISABLED = 63U, RSN_P25_DISABLED = 64U, + RSN_NXDN_DISABLED = 65U }; const uint8_t DVM_FRAME_START = 0xFEU; @@ -171,6 +178,11 @@ public: /// Write lost P25 frame data to serial port. void writeP25Lost(); + /// Write NXDN frame data to serial port. + void writeNXDNData(const uint8_t* data, uint8_t length); + /// Write lost NXDN frame data to serial port. + void writeNXDNLost(); + /// Write calibration frame data to serial port. void writeCalData(const uint8_t* data, uint8_t length); /// Write RSSI frame data to serial port. diff --git a/nxdn/CalNXDN.cpp b/nxdn/CalNXDN.cpp new file mode 100644 index 0000000..d952cf9 --- /dev/null +++ b/nxdn/CalNXDN.cpp @@ -0,0 +1,128 @@ +/** +* Digital Voice Modem - DSP Firmware +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware +* +*/ +// +// Based on code from the MMDVM project. (https://github.com/g4klx/MMDVM) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* + * Copyright (C) 2018 by Andy Uribe CA6JAU + * Copyright (C) 2020 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include "Globals.h" +#include "nxdn/CalNXDN.h" + +using namespace nxdn; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +// NXDN 1031 Hz Test Pattern, RAN: 1, Unit ID: 1, Dst Group ID: 1, Outbound Direction +const uint8_t NXDN_CAL1K[4][49] = { + { 0x00U, + 0xCDU, 0xF5U, 0x9DU, 0x5DU, 0x7CU, 0xFAU, 0x0AU, 0x6EU, 0x8AU, 0x23U, 0x56U, 0xE8U, + 0x4CU, 0xAAU, 0xDEU, 0x8BU, 0x26U, 0xE4U, 0xF2U, 0x82U, 0x88U, + 0xC6U, 0x8AU, 0x74U, 0x29U, 0xA4U, 0xECU, 0xD0U, 0x08U, 0x22U, + 0xCEU, 0xA2U, 0xFCU, 0x01U, 0x8CU, 0xECU, 0xDAU, 0x0AU, 0xA0U, + 0xEEU, 0x8AU, 0x7EU, 0x2BU, 0x26U, 0xCCU, 0xF8U, 0x8AU, 0x08U }, + + { 0x00U, + 0xCDU, 0xF5U, 0x9DU, 0x5DU, 0x7CU, 0x6DU, 0xBBU, 0x0EU, 0xB3U, 0xA4U, 0x26U, 0xA8U, + 0x4CU, 0xAAU, 0xDEU, 0x8BU, 0x26U, 0xE4U, 0xF2U, 0x82U, 0x88U, + 0xC6U, 0x8AU, 0x74U, 0x29U, 0xA4U, 0xECU, 0xD0U, 0x08U, 0x22U, + 0xCEU, 0xA2U, 0xFCU, 0x01U, 0x8CU, 0xECU, 0xDAU, 0x0AU, 0xA0U, + 0xEEU, 0x8AU, 0x7EU, 0x2BU, 0x26U, 0xCCU, 0xF8U, 0x8AU, 0x08U }, + + { 0x00U, + 0xCDU, 0xF5U, 0x9DU, 0x5DU, 0x76U, 0x3AU, 0x1BU, 0x4AU, 0x81U, 0xA8U, 0xE2U, 0x80U, + 0x4CU, 0xAAU, 0xDEU, 0x8BU, 0x26U, 0xE4U, 0xF2U, 0x82U, 0x88U, + 0xC6U, 0x8AU, 0x74U, 0x29U, 0xA4U, 0xECU, 0xD0U, 0x08U, 0x22U, + 0xCEU, 0xA2U, 0xFCU, 0x01U, 0x8CU, 0xECU, 0xDAU, 0x0AU, 0xA0U, + 0xEEU, 0x8AU, 0x7EU, 0x2BU, 0x26U, 0xCCU, 0xF8U, 0x8AU, 0x08U }, + + { 0x00U, + 0xCDU, 0xF5U, 0x9DU, 0x5DU, 0x74U, 0x28U, 0x83U, 0x02U, 0xB0U, 0x2DU, 0x07U, 0xE2U, + 0x4CU, 0xAAU, 0xDEU, 0x8BU, 0x26U, 0xE4U, 0xF2U, 0x82U, 0x88U, + 0xC6U, 0x8AU, 0x74U, 0x29U, 0xA4U, 0xECU, 0xD0U, 0x08U, 0x22U, + 0xCEU, 0xA2U, 0xFCU, 0x01U, 0x8CU, 0xECU, 0xDAU, 0x0AU, 0xA0U, + 0xEEU, 0x8AU, 0x7EU, 0x2BU, 0x26U, 0xCCU, 0xF8U, 0x8AU, 0x08U } +}; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the CalNXDN class. +/// +CalNXDN::CalNXDN() : + m_transmit(false), + m_state(NXDNCAL1K_IDLE), + m_audioSeq(0U) +{ + /* stub */ +} + +/// +/// Process local state and transmit on the air interface. +/// +void CalNXDN::process() +{ + nxdnTX.process(); + + uint16_t space = nxdnTX.getSpace(); + if (space < 1U) + return; + + switch (m_state) { + case NXDNCAL1K_TX: + nxdnTX.writeData(NXDN_CAL1K[m_audioSeq], NXDN_FRAME_LENGTH_BYTES + 1U); + m_audioSeq = (m_audioSeq + 1U) % 4U; + if(!m_transmit) + m_state = NXDNCAL1K_IDLE; + break; + default: + m_state = NXDNCAL1K_IDLE; + m_audioSeq = 0U; + break; + } +} + +/// +/// Write P25 calibration data to the local buffer. +/// +/// +/// +/// +uint8_t CalNXDN::write(const uint8_t* data, uint16_t length) +{ + if (length != 1U) + return RSN_ILLEGAL_LENGTH; + + m_transmit = data[0U] == 1U; + + if(m_transmit && m_state == NXDNCAL1K_IDLE) + m_state = NXDNCAL1K_TX; + + return RSN_OK; +} diff --git a/nxdn/CalNXDN.h b/nxdn/CalNXDN.h new file mode 100644 index 0000000..37c4598 --- /dev/null +++ b/nxdn/CalNXDN.h @@ -0,0 +1,73 @@ +/** +* Digital Voice Modem - DSP Firmware +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware +* +*/ +// +// Based on code from the MMDVM project. (https://github.com/g4klx/MMDVM) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* + * Copyright (C) 2018 by Andy Uribe CA6JAU + * Copyright (C) 2020 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(__CAL_NXDN_H__) +#define __CAL_NXDN_H__ + +#include "Defines.h" +#include "nxdn/NXDNDefines.h" + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + enum NXDNCAL1K { + NXDNCAL1K_IDLE, + NXDNCAL1K_TX + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements logic for NXDN calibration mode. + // --------------------------------------------------------------------------- + + class DSP_FW_API CalNXDN { + public: + /// Initializes a new instance of the CalNXDN class. + CalNXDN(); + + /// Process local state and transmit on the air interface. + void process(); + + /// Write NXDN calibration state. + uint8_t write(const uint8_t* data, uint16_t length); + + private: + bool m_transmit; + NXDNCAL1K m_state; + + uint8_t m_audioSeq; + }; +} // namespace nxdn + +#endif // __CAL_NXDN_H__ diff --git a/nxdn/NXDNDefines.h b/nxdn/NXDNDefines.h new file mode 100644 index 0000000..77a6b01 --- /dev/null +++ b/nxdn/NXDNDefines.h @@ -0,0 +1,74 @@ +/** +* Digital Voice Modem - DSP Firmware +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware +* +*/ +// +// Based on code from the MMDVM project. (https://github.com/g4klx/MMDVM) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* + * Copyright (C) 2016,2017,2018 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#if !defined(__NXDN_DEFINES_H__) +#define __NXDN_DEFINES_H__ + +#include "Defines.h" + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + const uint32_t NXDN_RADIO_SYMBOL_LENGTH = 10U; // At 24 kHz sample rate + + const uint32_t NXDN_FRAME_LENGTH_BITS = 384U; + const uint32_t NXDN_FRAME_LENGTH_BYTES = NXDN_FRAME_LENGTH_BITS / 8U; + const uint32_t NXDN_FRAME_LENGTH_SYMBOLS = NXDN_FRAME_LENGTH_BITS / 2U; + const uint32_t NXDN_FRAME_LENGTH_SAMPLES = NXDN_FRAME_LENGTH_SYMBOLS * NXDN_RADIO_SYMBOL_LENGTH; + + const uint32_t NXDN_FSW_LENGTH_BITS = 20U; + const uint32_t NXDN_FSW_LENGTH_SYMBOLS = NXDN_FSW_LENGTH_BITS / 2U; + const uint32_t NXDN_FSW_LENGTH_SAMPLES = NXDN_FSW_LENGTH_SYMBOLS * NXDN_RADIO_SYMBOL_LENGTH; + + const uint8_t NXDN_FSW_BYTES[] = { 0xCDU, 0xF5U, 0x90U }; + const uint8_t NXDN_FSW_BYTES_MASK[] = { 0xFFU, 0xFFU, 0xF0U }; + const uint8_t NXDN_FSW_BYTES_LENGTH = 3U; + + const uint32_t NXDN_FSW_BITS = 0x000CDF59U; + const uint32_t NXDN_FSW_BITS_MASK = 0x000FFFFFU; + + const uint8_t NXDN_PREAMBLE[] = { 0x57U, 0x75U, 0xFDU }; + const uint8_t NXDN_SYNC = 0x5FU; + + // C D F 5 9 + // 11 00 11 01 11 11 01 01 10 01 + // -3 +1 -3 +3 -3 -3 +3 +3 -1 +3 + + const int8_t NXDN_FSW_SYMBOLS_VALUES[] = {-3, +1, -3, +3, -3, -3, +3, +3, -1, +3}; + + const uint16_t NXDN_FSW_SYMBOLS = 0x014DU; + const uint16_t NXDN_FSW_SYMBOLS_MASK = 0x03FFU; + + const uint32_t NXDN_TX_BUFFER_LEN = 2000U; +} // namespace nxdn + +#endif // __NXDN_DEFINES_H__ diff --git a/nxdn/NXDNRX.cpp b/nxdn/NXDNRX.cpp new file mode 100644 index 0000000..1ff07f1 --- /dev/null +++ b/nxdn/NXDNRX.cpp @@ -0,0 +1,201 @@ +/** +* Digital Voice Modem - DSP Firmware +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware +* +*/ +// +// Based on code from the MMDVM project. (https://github.com/g4klx/MMDVM) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* + * Copyright (C) 2009-2018,2020 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include "Globals.h" +#include "nxdn/NXDNRX.h" +#include "Utils.h" + +using namespace nxdn; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint8_t MAX_FSW_BIT_START_ERRS = 0U; +const uint8_t MAX_FSW_BIT_RUN_ERRS = 3U; + +const unsigned int MAX_FSW_FRAMES = 5U + 1U; + +const uint16_t NOENDPTR = 9999U; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the NXDNRX class. +/// +NXDNRX::NXDNRX() : + m_bitBuffer(0x00U), + m_buffer(), + m_dataPtr(0U), + m_endPtr(NOENDPTR), + m_lostCount(0U), + m_state(NXDNRXS_NONE) +{ + ::memset(m_buffer, 0x00U, NXDN_FRAME_LENGTH_BYTES + 3U); +} + +/// +/// Helper to reset data values to defaults. +/// +void NXDNRX::reset() +{ + m_bitBuffer = 0x00U; + m_dataPtr = 0U; + + m_endPtr = NOENDPTR; + + m_lostCount = 0U; + + m_state = NXDNRXS_NONE; +} + +/// +/// Sample NXDN bits from the air interface. +/// +/// +void NXDNRX::databit(bool bit) +{ + m_bitBuffer <<= 1; + if (bit) + m_bitBuffer |= 0x01U; + + if (m_state != NXDNRXS_NONE) { + _WRITE_BIT(m_buffer, m_dataPtr, bit); + + m_dataPtr++; + if (m_dataPtr > NXDN_FRAME_LENGTH_BITS) { + m_dataPtr = 0U; + } + } + + if (m_state == NXDNRXS_DATA) { + processData(bit); + } + else { + bool ret = correlateSync(); + if (ret) { + DEBUG3("P25RX: databit(): dataPtr/endPtr", m_dataPtr, m_endPtr); + m_state = NXDNRXS_DATA; + } + + io.setDecode(m_state != NXDNRXS_NONE); + } +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// Helper to process NXDN data bits. +/// +/// +void NXDNRX::processData(bool bit) +{ + // only search for a sync in the right place +-2 bits + if (m_dataPtr >= (NXDN_FSW_LENGTH_BITS - 2U) && m_dataPtr <= (NXDN_FSW_LENGTH_BITS + 2U)) { + correlateSync(); + } + + // process voice frame + if (m_dataPtr == m_endPtr) { + m_lostCount--; + + // we've not seen a data sync for too long, signal sync lost and change to NXDNRXS_NONE + if (m_lostCount == 0U) { + DEBUG1("NXDNRX: processData(): sync timeout in PDU, lost lock"); + + io.setDecode(false); + + serial.writeP25Lost(); + reset(); + } + else { + DEBUG2("NXDNRX: processData(): sync found in PDU pos", m_dataPtr); + + uint8_t frame[NXDN_FRAME_LENGTH_BYTES + 1U]; + ::memcpy(frame + 1U, m_buffer, m_endPtr / 8U); + + frame[0U] = m_lostCount == (MAX_FSW_FRAMES - 1U) ? 0x01U : 0x00U; // set sync flag + serial.writeP25Data(frame, NXDN_FRAME_LENGTH_BYTES + 1U); + } + } +} + +/// +/// Frame synchronization correlator. +/// +/// +bool NXDNRX::correlateSync() +{ + uint8_t maxErrs; + if (m_state == NXDNRXS_NONE) + maxErrs = MAX_FSW_BIT_START_ERRS; + else + maxErrs = MAX_FSW_BIT_RUN_ERRS; + + // unpack sync bytes + uint8_t sync[NXDN_FSW_BYTES_LENGTH]; + sync[0U] = (uint8_t)((m_bitBuffer >> 16) & 0xFFU); + sync[1U] = (uint8_t)((m_bitBuffer >> 8) & 0xFFU); + sync[2U] = (uint8_t)((m_bitBuffer >> 0) & 0xF0U); + + uint8_t errs = 0U; + for (uint8_t i = 0U; i < NXDN_FSW_BYTES_LENGTH; i++) + errs += countBits8(sync[i] ^ NXDN_FSW_BYTES[i]); + + if (errs <= maxErrs) { + ::memset(m_buffer, 0x00U, NXDN_FRAME_LENGTH_BYTES + 3U); + + DEBUG2("NXDNRX: correlateSync(): correlateSync errs", errs); + + DEBUG4("NXDNRX: correlateSync(): sync [b0 - b2]", sync[0], sync[1], sync[2]); + + for (uint8_t i = 0U; i < NXDN_FSW_BYTES_LENGTH; i++) + m_buffer[i] = sync[i]; + + // DEBUG1("NXDNRX: m_buffer dump"); + // DEBUG_DUMP(m_buffer, P25_LDU_FRAME_LENGTH_BYTES); + + m_endPtr = m_dataPtr + NXDN_FRAME_LENGTH_BITS - NXDN_FSW_LENGTH_BITS; + if (m_endPtr >= NXDN_FRAME_LENGTH_BITS) + m_endPtr -= NXDN_FRAME_LENGTH_BITS; + + m_lostCount = MAX_FSW_FRAMES; + m_dataPtr = NXDN_FSW_LENGTH_BITS; + + DEBUG3("NXDNRX: correlateSync(): dataPtr/endPtr", m_dataPtr, m_endPtr); + + return true; + } + + return false; +} diff --git a/nxdn/NXDNRX.h b/nxdn/NXDNRX.h new file mode 100644 index 0000000..2b6b448 --- /dev/null +++ b/nxdn/NXDNRX.h @@ -0,0 +1,83 @@ +/** +* Digital Voice Modem - DSP Firmware +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware +* +*/ +// +// Based on code from the MMDVM project. (https://github.com/g4klx/MMDVM) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* + * Copyright (C) 2015,2016,2017,2018,2020 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#if !defined(__NXDN_RX_H__) +#define __NXDN_RX_H__ + +#include "Defines.h" +#include "nxdn/NXDNDefines.h" + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Constants + // --------------------------------------------------------------------------- + + enum NXDNRX_STATE { + NXDNRXS_NONE, + NXDNRXS_DATA + }; + + // --------------------------------------------------------------------------- + // Class Declaration + // Implements receiver logic for DMR slots. + // --------------------------------------------------------------------------- + + class DSP_FW_API NXDNRX { + public: + /// Initializes a new instance of the NXDNRX class. + NXDNRX(); + + /// Helper to reset data values to defaults. + void reset(); + + /// Sample NXDN bits from the air interface. + void databit(bool bit); + + private: + uint64_t m_bitBuffer; + uint8_t m_buffer[NXDN_FRAME_LENGTH_BYTES + 3U]; + + uint16_t m_dataPtr; + + uint16_t m_endPtr; + + uint16_t m_lostCount; + + NXDNRX_STATE m_state; + + /// Helper to process NXDN data bits. + void processData(bool bit); + + /// Frame synchronization correlator. + bool correlateSync(); + }; +} // namespace nxdn + +#endif // __NXDN_RX_H__ diff --git a/nxdn/NXDNTX.cpp b/nxdn/NXDNTX.cpp new file mode 100644 index 0000000..95ca751 --- /dev/null +++ b/nxdn/NXDNTX.cpp @@ -0,0 +1,234 @@ +/** +* Digital Voice Modem - DSP Firmware +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware +* +*/ +// +// Based on code from the MMDVM project. (https://github.com/g4klx/MMDVM) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* + * Copyright (C) 2009-2018,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2017 by Andy Uribe CA6JAU + * Copyright (C) 2022 by Bryan Biedenkapp N2PLL + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include "Globals.h" +#include "nxdn/NXDNTX.h" +#include "nxdn/NXDNDefines.h" + +using namespace nxdn; + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- + +/// +/// Initializes a new instance of the NXDNTX class. +/// +NXDNTX::NXDNTX() : + m_fifo(NXDN_TX_BUFFER_LEN), + m_poBuffer(), + m_poLen(0U), + m_poPtr(0U), + m_preambleCnt(240U), // 200ms + m_txHang(3000U), // 5s + m_tailCnt(0U) +{ + /* stub */ +} + +/// +/// Process local buffer and transmit on the air interface. +/// +void NXDNTX::process() +{ + if (m_fifo.getData() == 0U && m_poLen == 0U && m_tailCnt > 0U) { + // transmit silence until the hang timer has expired + uint16_t space = io.getSpace(); + + while (space > 8U) { + writeSilence(); + + space -= 8U; + m_tailCnt--; + + if (m_tailCnt == 0U) + return; + if (m_fifo.getData() > 0U) { + m_tailCnt = 0U; + return; + } + } + + if (m_fifo.getData() == 0U && m_poLen == 0U) + return; + } + + if (m_poLen == 0U) { + if (m_fifo.getData() == 0U) + return; + + createData(); + DEBUG2("NXDNTX: process(): poLen", m_poLen); + } + + if (m_poLen > 0U) { + uint16_t space = io.getSpace(); + + while (space > 8U) { + uint8_t c = m_poBuffer[m_poPtr++]; + + writeByte(c); + + space -= 8U; + m_tailCnt = m_txHang; + + if (m_poPtr >= m_poLen) { + m_poPtr = 0U; + m_poLen = 0U; + return; + } + } + } +} + +/// +/// Write data to the local buffer. +/// +/// +/// +/// +uint8_t NXDNTX::writeData(const uint8_t* data, uint16_t length) +{ + if (length != (NXDN_FRAME_LENGTH_BYTES + 1U)) + return RSN_ILLEGAL_LENGTH; + + uint16_t space = m_fifo.getSpace(); + DEBUG3("NXDNTX: writeData(): dataLength/fifoLength", length, space); + if (space < length) { + m_fifo.reset(); + return RSN_RINGBUFF_FULL; + } + + if (space < NXDN_FRAME_LENGTH_BYTES) + return RSN_RINGBUFF_FULL; + + for (uint8_t i = 0U; i < NXDN_FRAME_LENGTH_BYTES; i++) + m_fifo.put(data[i + 1U]); + + return RSN_OK; +} + +/// +/// Clears the local buffer. +/// +void NXDNTX::clear() +{ + m_fifo.reset(); +} + +/// +/// Sets the FDMA preamble count. +/// +/// Count of preambles. +void NXDNTX::setPreambleCount(uint8_t preambleCnt) +{ + m_preambleCnt = 300U + uint16_t(preambleCnt) * 6U; // 500ms + tx delay + + if (m_preambleCnt > 1200U) + m_preambleCnt = 1200U; +} + +/// +/// Sets the Tx hang time. +/// +/// Transmit hang time in seconds. +void NXDNTX::setTxHang(uint8_t txHang) +{ + m_txHang = txHang * 600U; +} + +/// +/// Helper to get how much space the ring buffer has for samples. +/// +/// +uint8_t NXDNTX::getSpace() const +{ + return m_fifo.getSpace() / NXDN_FRAME_LENGTH_BYTES; +} + +// --------------------------------------------------------------------------- +// Private Class Members +// --------------------------------------------------------------------------- + +/// +/// +/// +void NXDNTX::createData() +{ + if (!m_tx) { + for (uint16_t i = 0U; i < m_preambleCnt; i++) + m_poBuffer[m_poLen++] = NXDN_SYNC; + + m_poBuffer[m_poLen++] = NXDN_PREAMBLE[0U]; + m_poBuffer[m_poLen++] = NXDN_PREAMBLE[1U]; + m_poBuffer[m_poLen++] = NXDN_PREAMBLE[2U]; + } + else { + uint8_t length = m_fifo.get(); + DEBUG3("P25TX: createData(): dataLength/fifoSpace", length, m_fifo.getSpace()); + for (uint8_t i = 0U; i < length; i++) { + m_poBuffer[m_poLen++] = m_fifo.get(); + } + } + + m_poPtr = 0U; +} + +/// +/// +/// +/// +void NXDNTX::writeByte(uint8_t c) +{ + uint8_t bit; + uint8_t mask = 0x80U; + + for (uint8_t i = 0U; i < 8U; i++, c <<= 1) { + if ((c & mask) == mask) + bit = 1U; + else + bit = 0U; + + io.write(&bit, 1); + } +} + +/// +/// +/// +void NXDNTX::writeSilence() +{ + uint8_t bit; + for (uint8_t i = 0U; i < 4U; i++) { + bit = 0U; + io.write(&bit, 1); + } +} diff --git a/nxdn/NXDNTX.h b/nxdn/NXDNTX.h new file mode 100644 index 0000000..6109167 --- /dev/null +++ b/nxdn/NXDNTX.h @@ -0,0 +1,88 @@ +/** +* Digital Voice Modem - DSP Firmware +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / DSP Firmware +* +*/ +// +// Based on code from the MMDVM project. (https://github.com/g4klx/MMDVM) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* + * Copyright (C) 2015,2016,2017,2018,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2022 by Bryan Biedenkapp N2PLL + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(__NXDN_TX_H__) +#define __NXDN_TX_H__ + +#include "Defines.h" +#include "SerialBuffer.h" + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Implements transmitter logic for NXDN mode operation. + // --------------------------------------------------------------------------- + + class DSP_FW_API NXDNTX { + public: + /// Initializes a new instance of the NXDNTX class. + NXDNTX(); + + /// Process local buffer and transmit on the air interface. + void process(); + + /// Write data to the local buffer. + uint8_t writeData(const uint8_t* data, uint16_t length); + + /// Clears the local buffer. + void clear(); + + /// Sets the FDMA preamble count. + void setPreambleCount(uint8_t preambleCnt); + /// Sets the transmit hang time. + void setTxHang(uint8_t txHang); + + /// Helper to get how much space the ring buffer has for samples. + uint8_t getSpace() const; + + private: + SerialBuffer m_fifo; + + uint8_t m_poBuffer[60U]; + uint16_t m_poLen; + uint16_t m_poPtr; + + uint16_t m_preambleCnt; + uint32_t m_txHang; + uint32_t m_tailCnt; + + /// + void createData(); + + /// + void writeByte(uint8_t c); + /// + void writeSilence(); + }; +} // namespace nxdn + +#endif // __NXDN_TX_H__