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__