diff --git a/CalDMR.cpp b/CalDMR.cpp index 1fc4058..5a92fb0 100644 --- a/CalDMR.cpp +++ b/CalDMR.cpp @@ -21,25 +21,37 @@ #include "Globals.h" #include "CalDMR.h" -// Voice LC Header, CC: 1, srcID: 1, dstID: TG9 +// Voice LC BS Header, CC: 1, srcID: 1, dstID: TG9 const uint8_t VH_1K[] = {0x00U, 0x00U, 0x20U, 0x08U, 0x08U, 0x02U, 0x38U, 0x15U, 0x00U, 0x2CU, 0xA0U, 0x14U, 0x60U, 0x84U, 0x6DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xDEU, 0x30U, 0x30U, 0x01U, 0x10U, 0x01U, 0x40U, 0x03U, 0xC0U, 0x13U, 0xC1U, 0x1EU, 0x80U, 0x6FU}; -// Voice Term with LC, CC: 1, srcID: 1, dstID: TG9 +// Voice Term BS with LC, CC: 1, srcID: 1, dstID: TG9 const uint8_t VT_1K[] = {0x00U, 0x00U, 0x4FU, 0x08U, 0xDCU, 0x02U, 0x88U, 0x15U, 0x78U, 0x2CU, 0xD0U, 0x14U, 0xC0U, 0x84U, 0xADU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD9U, 0x65U, 0x24U, 0x02U, 0x28U, 0x06U, 0x20U, 0x0FU, 0x80U, 0x1BU, 0xC1U, 0x07U, 0x80U, 0x5CU}; +// Voice LC MS Header, CC: 1, srcID: 1, dstID: TG9 +const uint8_t VH_DMO1K[] = {0x00U, + 0x00U, 0x20U, 0x08U, 0x08U, 0x02U, 0x38U, 0x15U, 0x00U, 0x2CU, 0xA0U, 0x14U, + 0x60U, 0x84U, 0x6DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x7EU, 0x30U, 0x30U, + 0x01U, 0x10U, 0x01U, 0x40U, 0x03U, 0xC0U, 0x13U, 0xC1U, 0x1EU, 0x80U, 0x6FU}; + +// Voice Term MS with LC, CC: 1, srcID: 1, dstID: TG9 +const uint8_t VT_DMO1K[] = {0x00U, + 0x00U, 0x4FU, 0x08U, 0xDCU, 0x02U, 0x88U, 0x15U, 0x78U, 0x2CU, 0xD0U, 0x14U, + 0xC0U, 0x84U, 0xADU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x79U, 0x65U, 0x24U, + 0x02U, 0x28U, 0x06U, 0x20U, 0x0FU, 0x80U, 0x1BU, 0xC1U, 0x07U, 0x80U, 0x5CU}; + // Voice coding data + FEC, 1031 Hz Test Pattern const uint8_t VOICE_1K[] = {0x00U, 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU, 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xA0U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x0CU, 0xC4U, 0x58U, 0x20U, 0x0AU, 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU}; -// Embedded LC: CC: 1, srcID: 1, dstID: TG9 +// Embedded LC BS: CC: 1, srcID: 1, dstID: TG9 const uint8_t SYNCEMB_1K[6][7] = { {0x07U, 0x55U, 0xFDU, 0x7DU, 0xF7U, 0x5FU, 0x70U}, // BS VOICE SYNC (audio seq 0) {0x01U, 0x30U, 0x00U, 0x00U, 0x90U, 0x09U, 0x10U}, // EMB + Embedded LC1 (audio seq 1) @@ -48,6 +60,15 @@ const uint8_t SYNCEMB_1K[6][7] = { {0x01U, 0x50U, 0xA1U, 0x71U, 0xD1U, 0x70U, 0x70U}, // EMB + Embedded LC4 (audio seq 4) {0x01U, 0x10U, 0x00U, 0x00U, 0x00U, 0x0EU, 0x20U}}; // EMB (audio seq 5) +// Embedded LC MS: CC: 1, srcID: 1, dstID: TG9 +const uint8_t SYNCEMB_DMO1K[6][7] = { + {0x07U, 0xF7U, 0xD5U, 0xDDU, 0x57U, 0xDFU, 0xD0U}, // MS VOICE SYNC (audio seq 0) + {0x01U, 0x30U, 0x00U, 0x00U, 0x90U, 0x09U, 0x10U}, // EMB + Embedded LC1 (audio seq 1) + {0x01U, 0x70U, 0x00U, 0x90U, 0x00U, 0x07U, 0x40U}, // EMB + Embedded LC2 (audio seq 2) + {0x01U, 0x70U, 0x00U, 0x31U, 0x40U, 0x07U, 0x40U}, // EMB + Embedded LC3 (audio seq 3) + {0x01U, 0x50U, 0xA1U, 0x71U, 0xD1U, 0x70U, 0x70U}, // EMB + Embedded LC4 (audio seq 4) + {0x01U, 0x10U, 0x00U, 0x00U, 0x00U, 0x0EU, 0x20U}}; // EMB (audio seq 5) + // Short LC: // TS1: dstID: 0, ACTIVITY_NONE // TS2: dstID: TG9, ACTIVITY_VOICE @@ -78,6 +99,9 @@ void CCalDMR::process() case STATE_DMRCAL1K: dmr1kcal(); break; + case STATE_DMRDMO1K: + dmrdmo1k(); + break; default: break; } @@ -94,6 +118,17 @@ void CCalDMR::createData1k(uint8_t n) m_dmr1k[20U] |= SYNCEMB_1K[n][6] & 0xF0U; } +void CCalDMR::createDataDMO1k(uint8_t n) +{ + for(uint8_t i = 0; i < 5U; i++) + m_dmr1k[i + 15U] = SYNCEMB_DMO1K[n][i + 1U]; + + m_dmr1k[14U] &= 0xF0U; + m_dmr1k[20U] &= 0x0FU; + m_dmr1k[14U] |= SYNCEMB_DMO1K[n][0] & 0x0FU; + m_dmr1k[20U] |= SYNCEMB_DMO1K[n][6] & 0xF0U; +} + void CCalDMR::dmr1kcal() { dmrTX.process(); @@ -139,6 +174,40 @@ void CCalDMR::dmr1kcal() } } +void CCalDMR::dmrdmo1k() +{ + dmrDMOTX.process(); + + uint16_t space = dmrDMOTX.getSpace(); + if (space < 1U) + return; + + switch (m_state) { + case DMRCAL1K_VH: + dmrDMOTX.writeData(VH_DMO1K, DMR_FRAME_LENGTH_BYTES + 1U); + m_state = DMRCAL1K_VOICE; + break; + case DMRCAL1K_VOICE: + createDataDMO1k(m_audioSeq); + dmrDMOTX.writeData(m_dmr1k, DMR_FRAME_LENGTH_BYTES + 1U); + if(m_audioSeq == 5U) { + m_audioSeq = 0U; + if(!m_transmit) + m_state = DMRCAL1K_VT; + } else + m_audioSeq++; + break; + case DMRCAL1K_VT: + dmrDMOTX.writeData(VT_DMO1K, DMR_FRAME_LENGTH_BYTES + 1U); + m_state = DMRCAL1K_IDLE; + break; + default: + m_state = DMRCAL1K_IDLE; + m_audioSeq = 0U; + break; + } +} + uint8_t CCalDMR::write(const uint8_t* data, uint8_t length) { if (length != 1U) @@ -146,7 +215,7 @@ uint8_t CCalDMR::write(const uint8_t* data, uint8_t length) m_transmit = data[0U] == 1U; - if(m_transmit && m_state == DMRCAL1K_IDLE && m_modemState == STATE_DMRCAL1K) + if(m_transmit && m_state == DMRCAL1K_IDLE && (m_modemState == STATE_DMRCAL1K || m_modemState == STATE_DMRDMO1K)) m_state = DMRCAL1K_VH; return 0U; diff --git a/CalDMR.h b/CalDMR.h index 8ff5ae5..06ff6e6 100644 --- a/CalDMR.h +++ b/CalDMR.h @@ -37,7 +37,9 @@ public: void process(); void dmr1kcal(); + void dmrdmo1k(); void createData1k(uint8_t n); + void createDataDMO1k(uint8_t n); uint8_t write(const uint8_t* data, uint8_t length); diff --git a/CalNXDN.cpp b/CalNXDN.cpp new file mode 100644 index 0000000..c223f59 --- /dev/null +++ b/CalNXDN.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018 by Andy Uribe CA6JAU + * + * 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 "Config.h" +#include "Globals.h" +#include "CalNXDN.h" + +// 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}}; + +CCalNXDN::CCalNXDN() : +m_transmit(false), +m_state(NXDNCAL1K_IDLE), +m_audioSeq(0U) +{ +} + +void CCalNXDN::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; + } +} + +uint8_t CCalNXDN::write(const uint8_t* data, uint8_t length) +{ + if (length != 1U) + return 4U; + + m_transmit = data[0U] == 1U; + + if(m_transmit && m_state == NXDNCAL1K_IDLE) + m_state = NXDNCAL1K_TX; + + return 0U; +} + diff --git a/CalNXDN.h b/CalNXDN.h new file mode 100644 index 0000000..f0051ef --- /dev/null +++ b/CalNXDN.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 by Andy Uribe CA6JAU + * + * 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(CALNXDN_H) +#define CALNXDN_H + +#include "Config.h" + +enum NXDNCAL1K { + NXDNCAL1K_IDLE, + NXDNCAL1K_TX +}; + +class CCalNXDN { +public: + CCalNXDN(); + + void process(); + + uint8_t write(const uint8_t* data, uint8_t length); + +private: + bool m_transmit; + NXDNCAL1K m_state; + uint8_t m_audioSeq; +}; + +#endif + diff --git a/Config.h b/Config.h index b201991..1de81a0 100644 --- a/Config.h +++ b/Config.h @@ -66,4 +66,7 @@ // Use the modem as a serial repeater for Nextion displays // #define SERIAL_REPEATER +// To reduce CPU load, you can remove the DC blocker by commenting out the next line +#define USE_DCBLOCKER + #endif diff --git a/DMRTX.cpp b/DMRTX.cpp old mode 100644 new mode 100755 index 9824f9c..81437ec --- a/DMRTX.cpp +++ b/DMRTX.cpp @@ -54,6 +54,7 @@ const uint8_t BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02 #define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) const uint32_t STARTUP_COUNT = 20U; +const uint32_t ABORT_COUNT = 6U; CDMRTX::CDMRTX() : m_fifo(), @@ -69,6 +70,7 @@ m_poBuffer(), m_poLen(0U), m_poPtr(0U), m_frameCount(0U), +m_abortCount(), m_abort() { ::memset(m_modState, 0x00U, 16U * sizeof(q15_t)); @@ -83,6 +85,9 @@ m_abort() m_abort[0U] = false; m_abort[1U] = false; + + m_abortCount[0U] = 0U; + m_abortCount[1U] = 0U; } void CDMRTX::process() @@ -210,10 +215,12 @@ uint8_t CDMRTX::writeAbort(const uint8_t* data, uint8_t length) switch (data[0U]) { case 1U: + m_abortCount[0U] = 0U; m_abort[0U] = true; return 0U; case 2U: + m_abortCount[1U] = 0U; m_abort[1U] = true; return 0U; @@ -227,6 +234,8 @@ void CDMRTX::setStart(bool start) m_state = start ? DMRTXSTATE_SLOT1 : DMRTXSTATE_IDLE; m_frameCount = 0U; + m_abortCount[0U] = 0U; + m_abortCount[1U] = 0U; m_abort[0U] = false; m_abort[1U] = false; @@ -282,7 +291,7 @@ uint8_t CDMRTX::getSpace2() const void CDMRTX::createData(uint8_t slotIndex) { - if (m_fifo[slotIndex].getData() > 0U && m_frameCount >= STARTUP_COUNT) { + if (m_fifo[slotIndex].getData() >= DMR_FRAME_LENGTH_BYTES && m_frameCount >= STARTUP_COUNT && m_abortCount[slotIndex] >= ABORT_COUNT) { for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) { m_poBuffer[i] = m_fifo[slotIndex].get(); m_markBuffer[i] = MARK_NONE; @@ -335,6 +344,8 @@ void CDMRTX::createCal() void CDMRTX::createCACH(uint8_t txSlotIndex, uint8_t rxSlotIndex) { m_frameCount++; + m_abortCount[0U]++; + m_abortCount[1U]++; if (m_cachPtr >= 12U) m_cachPtr = 0U; diff --git a/DMRTX.h b/DMRTX.h old mode 100644 new mode 100755 index 00977d5..1e2f617 --- a/DMRTX.h +++ b/DMRTX.h @@ -72,6 +72,7 @@ private: uint16_t m_poLen; uint16_t m_poPtr; uint32_t m_frameCount; + uint32_t m_abortCount[2U]; bool m_abort[2U]; void createData(uint8_t slotIndex); diff --git a/Globals.h b/Globals.h index d671e7b..9ab7d31 100644 --- a/Globals.h +++ b/Globals.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,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 @@ -48,8 +48,11 @@ enum MMDVM_STATE { STATE_DMR = 2, STATE_YSF = 3, STATE_P25 = 4, + STATE_NXDN = 5, // Dummy states start at 90 + STATE_NXDNCAL1K = 91, + STATE_DMRDMO1K = 92, STATE_P25CAL1K = 93, STATE_DMRCAL1K = 94, STATE_LFCAL = 95, @@ -71,10 +74,13 @@ enum MMDVM_STATE { #include "YSFTX.h" #include "P25RX.h" #include "P25TX.h" +#include "NXDNRX.h" +#include "NXDNTX.h" #include "CalDStarRX.h" #include "CalDStarTX.h" #include "CalDMR.h" #include "CalP25.h" +#include "CalNXDN.h" #include "CalRSSI.h" #include "CWIdTX.h" #include "Debug.h" @@ -95,6 +101,7 @@ extern bool m_dstarEnable; extern bool m_dmrEnable; extern bool m_ysfEnable; extern bool m_p25Enable; +extern bool m_nxdnEnable; extern bool m_duplex; @@ -120,10 +127,14 @@ extern CYSFTX ysfTX; extern CP25RX p25RX; extern CP25TX p25TX; +extern CNXDNRX nxdnRX; +extern CNXDNTX nxdnTX; + extern CCalDStarRX calDStarRX; extern CCalDStarTX calDStarTX; extern CCalDMR calDMR; extern CCalP25 calP25; +extern CCalNXDN calNXDN; extern CCalRSSI calRSSI; extern CCWIdTX cwIdTX; diff --git a/IO.cpp b/IO.cpp index ac7d373..9024da0 100644 --- a/IO.cpp +++ b/IO.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX * Copyright (C) 2015 by Jim Mclaughlin KI6ZUM * Copyright (C) 2016 by Colin Durbridge G4EML * @@ -26,9 +26,12 @@ static q31_t DC_FILTER[] = {3367972, 0, 3367972, 0, 2140747704, 0}; // {b0, 0, b1, b2, -a1, -a2} const uint32_t DC_FILTER_STAGES = 1U; // One Biquad stage -// One symbol boxcar filter -static q15_t BOXCAR_FILTER[] = {12000, 12000, 12000, 12000, 12000, 0}; -const uint16_t BOXCAR_FILTER_LEN = 6U; +// One symbol boxcar filters +static q15_t BOXCAR5_FILTER[] = {12000, 12000, 12000, 12000, 12000, 0}; +const uint16_t BOXCAR5_FILTER_LEN = 6U; + +static q15_t BOXCAR10_FILTER[] = {6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000}; +const uint16_t BOXCAR10_FILTER_LEN = 10U; const uint16_t DC_OFFSET = 2048U; @@ -39,8 +42,12 @@ m_txBuffer(TX_RINGBUFFER_SIZE), m_rssiBuffer(RX_RINGBUFFER_SIZE), m_dcFilter(), m_dcState(), -m_boxcarFilter(), -m_boxcarState(), +m_boxcar5Filter1(), +m_boxcar5Filter2(), +m_boxcar10Filter(), +m_boxcar5State1(), +m_boxcar5State2(), +m_boxcar10State(), m_pttInvert(false), m_rxLevel(128 * 128), m_cwIdTXLevel(128 * 128), @@ -48,6 +55,7 @@ m_dstarTXLevel(128 * 128), m_dmrTXLevel(128 * 128), m_ysfTXLevel(128 * 128), m_p25TXLevel(128 * 128), +m_nxdnTXLevel(128 * 128), m_rxDCOffset(DC_OFFSET), m_txDCOffset(DC_OFFSET), m_ledCount(0U), @@ -58,18 +66,28 @@ m_dacOverflow(0U), m_watchdog(0U), m_lockout(false) { - ::memset(m_boxcarState, 0x00U, 30U * sizeof(q15_t)); - ::memset(m_dcState, 0x00U, 4U * sizeof(q31_t)); + ::memset(m_boxcar5State1, 0x00U, 30U * sizeof(q15_t)); + ::memset(m_boxcar5State2, 0x00U, 30U * sizeof(q15_t)); + ::memset(m_boxcar10State, 0x00U, 40U * sizeof(q15_t)); + ::memset(m_dcState, 0x00U, 4U * sizeof(q31_t)); m_dcFilter.numStages = DC_FILTER_STAGES; m_dcFilter.pState = m_dcState; m_dcFilter.pCoeffs = DC_FILTER; m_dcFilter.postShift = 0; - m_boxcarFilter.numTaps = BOXCAR_FILTER_LEN; - m_boxcarFilter.pState = m_boxcarState; - m_boxcarFilter.pCoeffs = BOXCAR_FILTER; - + m_boxcar5Filter1.numTaps = BOXCAR5_FILTER_LEN; + m_boxcar5Filter1.pState = m_boxcar5State1; + m_boxcar5Filter1.pCoeffs = BOXCAR5_FILTER; + + m_boxcar5Filter2.numTaps = BOXCAR5_FILTER_LEN; + m_boxcar5Filter2.pState = m_boxcar5State2; + m_boxcar5Filter2.pCoeffs = BOXCAR5_FILTER; + + m_boxcar10Filter.numTaps = BOXCAR10_FILTER_LEN; + m_boxcar10Filter.pState = m_boxcar10State; + m_boxcar10Filter.pCoeffs = BOXCAR10_FILTER; + initInt(); selfTest(); @@ -90,6 +108,7 @@ void CIO::selfTest() setDMRInt(ledValue); setYSFInt(ledValue); setP25Int(ledValue); + setNXDNInt(ledValue); #endif delayInt(250); } @@ -99,6 +118,7 @@ void CIO::selfTest() setDMRInt(false); setYSFInt(false); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -106,6 +126,7 @@ void CIO::selfTest() setDMRInt(true); setYSFInt(false); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -113,6 +134,7 @@ void CIO::selfTest() setDMRInt(true); setYSFInt(true); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -120,6 +142,23 @@ void CIO::selfTest() setDMRInt(true); setYSFInt(true); setP25Int(true); + setNXDNInt(false); + + delayInt(250); + + setDStarInt(true); + setDMRInt(true); + setYSFInt(true); + setP25Int(true); + setNXDNInt(true); + + delayInt(250); + + setDStarInt(true); + setDMRInt(true); + setYSFInt(true); + setP25Int(true); + setNXDNInt(false); delayInt(250); @@ -127,6 +166,7 @@ void CIO::selfTest() setDMRInt(true); setYSFInt(true); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -134,6 +174,7 @@ void CIO::selfTest() setDMRInt(true); setYSFInt(false); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -141,6 +182,7 @@ void CIO::selfTest() setDMRInt(false); setYSFInt(false); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -148,6 +190,7 @@ void CIO::selfTest() setDMRInt(false); setYSFInt(false); setP25Int(false); + setNXDNInt(false); #endif } @@ -169,7 +212,7 @@ void CIO::process() if (m_started) { // Two seconds timeout if (m_watchdog >= 48000U) { - if (m_modemState == STATE_DSTAR || m_modemState == STATE_DMR || m_modemState == STATE_YSF) { + if (m_modemState == STATE_DSTAR || m_modemState == STATE_DMR || m_modemState == STATE_YSF || m_modemState == STATE_P25 || m_modemState == STATE_NXDN) { if (m_modemState == STATE_DMR && m_tx) dmrTX.setStart(false); m_modemState = STATE_IDLE; @@ -225,47 +268,80 @@ void CIO::process() if (m_lockout) return; - q15_t vals[RX_BLOCK_SIZE]; - ::arm_fir_fast_q15(&m_boxcarFilter, samples, vals, RX_BLOCK_SIZE); +#if defined(USE_DCBLOCKER) + q31_t q31Samples[RX_BLOCK_SIZE]; + ::arm_q15_to_q31(samples, q31Samples, RX_BLOCK_SIZE); + + q31_t dcValues[RX_BLOCK_SIZE]; + ::arm_biquad_cascade_df1_q31(&m_dcFilter, q31Samples, dcValues, RX_BLOCK_SIZE); q31_t dcLevel = 0; - q31_t dcValues[RX_BLOCK_SIZE]; - q31_t q31Vals[RX_BLOCK_SIZE]; - - ::arm_q15_to_q31(vals, q31Vals, RX_BLOCK_SIZE); - ::arm_biquad_cascade_df1_q31(&m_dcFilter, q31Vals, dcValues, RX_BLOCK_SIZE); - for (uint8_t i = 0U; i < RX_BLOCK_SIZE; i++) dcLevel += dcValues[i]; dcLevel /= RX_BLOCK_SIZE; q15_t offset = q15_t(__SSAT((dcLevel >> 16), 16));; - q15_t dcVals[RX_BLOCK_SIZE]; + q15_t dcSamples[RX_BLOCK_SIZE]; for (uint8_t i = 0U; i < RX_BLOCK_SIZE; i++) - dcVals[i] = vals[i] - offset; + dcSamples[i] = samples[i] - offset; +#endif if (m_modemState == STATE_IDLE) { - if (m_dstarEnable) - dstarRX.samples(dcVals, rssi, RX_BLOCK_SIZE); + if (m_dstarEnable || m_ysfEnable || m_p25Enable) { + q15_t vals[RX_BLOCK_SIZE]; +#if defined(USE_DCBLOCKER) + ::arm_fir_fast_q15(&m_boxcar5Filter1, dcSamples, vals, RX_BLOCK_SIZE); +#else + ::arm_fir_fast_q15(&m_boxcar5Filter1, samples, vals, RX_BLOCK_SIZE); +#endif - if (m_p25Enable) - p25RX.samples(dcVals, rssi, RX_BLOCK_SIZE); + if (m_dstarEnable) + dstarRX.samples(vals, rssi, RX_BLOCK_SIZE); + + if (m_p25Enable) + p25RX.samples(vals, rssi, RX_BLOCK_SIZE); + + if (m_ysfEnable) + ysfRX.samples(vals, rssi, RX_BLOCK_SIZE); + } if (m_dmrEnable) { + q15_t vals[RX_BLOCK_SIZE]; + ::arm_fir_fast_q15(&m_boxcar5Filter2, samples, vals, RX_BLOCK_SIZE); + if (m_duplex) dmrIdleRX.samples(vals, RX_BLOCK_SIZE); else dmrDMORX.samples(vals, rssi, RX_BLOCK_SIZE); } - if (m_ysfEnable) - ysfRX.samples(dcVals, rssi, RX_BLOCK_SIZE); + if (m_nxdnEnable) { + q15_t vals[RX_BLOCK_SIZE]; +#if defined(USE_DCBLOCKER) + ::arm_fir_fast_q15(&m_boxcar10Filter, dcSamples, vals, RX_BLOCK_SIZE); +#else + ::arm_fir_fast_q15(&m_boxcar10Filter, samples, vals, RX_BLOCK_SIZE); +#endif + + nxdnRX.samples(vals, rssi, RX_BLOCK_SIZE); + } } else if (m_modemState == STATE_DSTAR) { - if (m_dstarEnable) - dstarRX.samples(dcVals, rssi, RX_BLOCK_SIZE); + if (m_dstarEnable) { + q15_t vals[RX_BLOCK_SIZE]; +#if defined(USE_DCBLOCKER) + ::arm_fir_fast_q15(&m_boxcar5Filter1, dcSamples, vals, RX_BLOCK_SIZE); +#else + ::arm_fir_fast_q15(&m_boxcar5Filter1, samples, vals, RX_BLOCK_SIZE); +#endif + + dstarRX.samples(vals, rssi, RX_BLOCK_SIZE); + } } else if (m_modemState == STATE_DMR) { if (m_dmrEnable) { + q15_t vals[RX_BLOCK_SIZE]; + ::arm_fir_fast_q15(&m_boxcar5Filter2, samples, vals, RX_BLOCK_SIZE); + if (m_duplex) { // If the transmitter isn't on, use the DMR idle RX to detect the wakeup CSBKs if (m_tx) @@ -277,12 +353,42 @@ void CIO::process() } } } else if (m_modemState == STATE_YSF) { - if (m_ysfEnable) - ysfRX.samples(dcVals, rssi, RX_BLOCK_SIZE); + if (m_ysfEnable) { + q15_t vals[RX_BLOCK_SIZE]; +#if defined(USE_DCBLOCKER) + ::arm_fir_fast_q15(&m_boxcar5Filter1, dcSamples, vals, RX_BLOCK_SIZE); +#else + ::arm_fir_fast_q15(&m_boxcar5Filter1, samples, vals, RX_BLOCK_SIZE); +#endif + ysfRX.samples(vals, rssi, RX_BLOCK_SIZE); + } } else if (m_modemState == STATE_P25) { - if (m_p25Enable) - p25RX.samples(dcVals, rssi, RX_BLOCK_SIZE); + if (m_p25Enable) { + q15_t vals[RX_BLOCK_SIZE]; +#if defined(USE_DCBLOCKER) + ::arm_fir_fast_q15(&m_boxcar5Filter1, dcSamples, vals, RX_BLOCK_SIZE); +#else + ::arm_fir_fast_q15(&m_boxcar5Filter1, samples, vals, RX_BLOCK_SIZE); +#endif + p25RX.samples(vals, rssi, RX_BLOCK_SIZE); + } + } else if (m_modemState == STATE_NXDN) { + if (m_nxdnEnable) { + q15_t vals[RX_BLOCK_SIZE]; +#if defined(USE_DCBLOCKER) + ::arm_fir_fast_q15(&m_boxcar10Filter, dcSamples, vals, RX_BLOCK_SIZE); +#else + ::arm_fir_fast_q15(&m_boxcar10Filter, samples, vals, RX_BLOCK_SIZE); +#endif + nxdnRX.samples(vals, rssi, RX_BLOCK_SIZE); + } } else if (m_modemState == STATE_DSTARCAL) { + q15_t vals[RX_BLOCK_SIZE]; +#if defined(USE_DCBLOCKER) + ::arm_fir_fast_q15(&m_boxcar5Filter1, dcSamples, vals, RX_BLOCK_SIZE); +#else + ::arm_fir_fast_q15(&m_boxcar5Filter1, samples, vals, RX_BLOCK_SIZE); +#endif calDStarRX.samples(vals, RX_BLOCK_SIZE); } else if (m_modemState == STATE_RSSICAL) { calRSSI.samples(rssi, RX_BLOCK_SIZE); @@ -318,6 +424,9 @@ void CIO::write(MMDVM_STATE mode, q15_t* samples, uint16_t length, const uint8_t case STATE_P25: txLevel = m_p25TXLevel; break; + case STATE_NXDN: + txLevel = m_nxdnTXLevel; + break; default: txLevel = m_cwIdTXLevel; break; @@ -364,10 +473,11 @@ void CIO::setMode() setDMRInt(m_modemState == STATE_DMR); setYSFInt(m_modemState == STATE_YSF); setP25Int(m_modemState == STATE_P25); + setNXDNInt(m_modemState == STATE_NXDN); #endif } -void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, int16_t txDCOffset, int16_t rxDCOffset) +void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel, int16_t txDCOffset, int16_t rxDCOffset) { m_pttInvert = pttInvert; @@ -377,6 +487,7 @@ void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rx m_dmrTXLevel = q15_t(dmrTXLevel * 128); m_ysfTXLevel = q15_t(ysfTXLevel * 128); m_p25TXLevel = q15_t(p25TXLevel * 128); + m_nxdnTXLevel = q15_t(nxdnTXLevel * 128); m_rxDCOffset = DC_OFFSET + rxDCOffset; m_txDCOffset = DC_OFFSET + txDCOffset; @@ -389,6 +500,7 @@ void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rx m_dmrTXLevel = -m_dmrTXLevel; m_ysfTXLevel = -m_ysfTXLevel; m_p25TXLevel = -m_p25TXLevel; + m_nxdnTXLevel = -m_nxdnTXLevel; } } diff --git a/IO.h b/IO.h index 940cd58..76e8314 100644 --- a/IO.h +++ b/IO.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,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 @@ -42,7 +42,7 @@ public: void interrupt(); - void setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, int16_t txDCOffset, int16_t rxDCOffset); + void setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, uint8_t nxdnLevel, int16_t txDCOffset, int16_t rxDCOffset); void getOverflow(bool& adcOverflow, bool& dacOverflow); @@ -66,8 +66,12 @@ private: arm_biquad_casd_df1_inst_q31 m_dcFilter; q31_t m_dcState[4]; - arm_fir_instance_q15 m_boxcarFilter; - q15_t m_boxcarState[30U]; // NoTaps + BlockSize - 1, 6 + 20 - 1 plus some spare + arm_fir_instance_q15 m_boxcar5Filter1; + arm_fir_instance_q15 m_boxcar5Filter2; + arm_fir_instance_q15 m_boxcar10Filter; + q15_t m_boxcar5State1[30U]; // NoTaps + BlockSize - 1, 6 + 20 - 1 plus some spare + q15_t m_boxcar5State2[30U]; // NoTaps + BlockSize - 1, 6 + 20 - 1 plus some spare + q15_t m_boxcar10State[40U]; // NoTaps + BlockSize - 1, 10 + 20 - 1 plus some spare bool m_pttInvert; q15_t m_rxLevel; @@ -76,6 +80,7 @@ private: q15_t m_dmrTXLevel; q15_t m_ysfTXLevel; q15_t m_p25TXLevel; + q15_t m_nxdnTXLevel; uint16_t m_rxDCOffset; uint16_t m_txDCOffset; @@ -106,6 +111,7 @@ private: void setDMRInt(bool on); void setYSFInt(bool on); void setP25Int(bool on); + void setNXDNInt(bool on); void delayInt(unsigned int dly); }; diff --git a/IODue.cpp b/IODue.cpp index 6eeeb82..7e86ae5 100644 --- a/IODue.cpp +++ b/IODue.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX * Copyright (C) 2015 by Jim Mclaughlin KI6ZUM * Copyright (C) 2016 by Colin Durbridge G4EML * @@ -33,6 +33,7 @@ #define PIN_DMR 17 #define PIN_YSF 18 #define PIN_P25 19 +#define PIN_NXDN 20 #define ADC_CHER_Chan (1<<7) // ADC on Due pin A0 - Due AD7 - (1 << 7) #define ADC_ISR_EOC_Chan ADC_ISR_EOC7 #define ADC_CDR_Chan 7 @@ -46,6 +47,7 @@ #define PIN_DMR 8 #define PIN_YSF 7 #define PIN_P25 6 +#define PIN_NXDN 5 #define ADC_CHER_Chan (1<<13) // ADC on Due pin A11 - Due AD13 - (1 << 13) #define ADC_ISR_EOC_Chan ADC_ISR_EOC13 #define ADC_CDR_Chan 13 @@ -61,6 +63,7 @@ #define PIN_DMR 8 #define PIN_YSF 7 #define PIN_P25 6 +#define PIN_NXDN 5 #define ADC_CHER_Chan (1<<7) // ADC on Due pin A0 - Due AD7 - (1 << 7) #define ADC_ISR_EOC_Chan ADC_ISR_EOC7 #define ADC_CDR_Chan 7 @@ -95,6 +98,7 @@ void CIO::initInt() pinMode(PIN_DMR, OUTPUT); pinMode(PIN_YSF, OUTPUT); pinMode(PIN_P25, OUTPUT); + pinMode(PIN_NXDN, OUTPUT); #endif } @@ -226,9 +230,15 @@ void CIO::setP25Int(bool on) digitalWrite(PIN_P25, on ? HIGH : LOW); } +void CIO::setNXDNInt(bool on) +{ + digitalWrite(PIN_NXDN, on ? HIGH : LOW); +} + void CIO::delayInt(unsigned int dly) { delay(dly); } #endif + diff --git a/IOSTM.cpp b/IOSTM.cpp index 3b2081c..c8df9da 100644 --- a/IOSTM.cpp +++ b/IOSTM.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 2016 by Jim McLaughlin KI6ZUM - * Copyright (C) 2016, 2017 by Andy Uribe CA6JAU - * Copyright (C) 2017 by Jonathan Naylor G4KLX + * Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU + * Copyright (C) 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 @@ -37,6 +37,7 @@ DSTAR PD12 output P1 Pin44 DMR PD13 output P1 Pin45 YSF PD14 output P1 Pin46 P25 PD11 output P1 Pin43 +NXDN PD10 output P1 Pin42 RX PA0 analog input P1 Pin12 RSSI PA1 analog input P1 Pin11 @@ -65,6 +66,10 @@ EXT_CLK PA15 input P2 Pin40 #define PORT_P25 GPIOD #define RCC_Per_P25 RCC_AHB1Periph_GPIOD +#define PIN_NXDN GPIO_Pin_10 +#define PORT_NXDN GPIOD +#define RCC_Per_NXDN RCC_AHB1Periph_GPIOD + #define PIN_DSTAR GPIO_Pin_12 #define PORT_DSTAR GPIOD #define RCC_Per_DSTAR RCC_AHB1Periph_GPIOD @@ -107,6 +112,7 @@ DSTAR PC7 output DMR PC8 output YSF PA8 output P25 PC9 output +NXDN PB1 output RX PA0 analog input RSSI PA7 analog input @@ -135,6 +141,10 @@ EXT_CLK PA15 input #define PORT_P25 GPIOC #define RCC_Per_P25 RCC_AHB1Periph_GPIOC +#define PIN_NXDN GPIO_Pin_1 +#define PORT_NXDN GPIOB +#define RCC_Per_NXDN RCC_AHB1Periph_GPIOB + #define PIN_DSTAR GPIO_Pin_7 #define PORT_DSTAR GPIOC #define RCC_Per_DSTAR RCC_AHB1Periph_GPIOC @@ -177,6 +187,7 @@ DSTAR PC7 output DMR PC8 output YSF PA8 output P25 PC9 output +NXDN PB1 output RX PA0 analog input RSSI PA7 analog input @@ -205,6 +216,10 @@ EXT_CLK PA15 input #define PORT_P25 GPIOC #define RCC_Per_P25 RCC_AHB1Periph_GPIOC +#define PIN_NXDN GPIO_Pin_1 +#define PORT_NXDN GPIOB +#define RCC_Per_NXDN RCC_AHB1Periph_GPIOB + #define PIN_DSTAR GPIO_Pin_7 #define PORT_DSTAR GPIOC #define RCC_Per_DSTAR RCC_AHB1Periph_GPIOC @@ -247,6 +262,7 @@ DSTAR PC7 output DMR PC8 output YSF PA8 output P25 PC9 output +NXDN PB1 output RX PA0 analog input RSSI PA7 analog input @@ -275,6 +291,10 @@ EXT_CLK PA15 input #define PORT_P25 GPIOC #define RCC_Per_P25 RCC_AHB1Periph_GPIOC +#define PIN_NXDN GPIO_Pin_1 +#define PORT_NXDN GPIOB +#define RCC_Per_NXDN RCC_AHB1Periph_GPIOB + #define PIN_DSTAR GPIO_Pin_7 #define PORT_DSTAR GPIOC #define RCC_Per_DSTAR RCC_AHB1Periph_GPIOC @@ -319,11 +339,13 @@ DSTAR PB10 output CN10 Pin25 DMR PB4 output CN10 Pin27 YSF PB5 output CN10 Pin29 P25 PB3 output CN10 Pin31 +NXDN PA10 output CN10 Pin33 MDSTAR PC4 output CN10 Pin34 MDMR PC5 output CN10 Pin6 MYSF PC2 output CN7 Pin35 MP25 PC3 output CN7 Pin37 +MNXDN PC6 output CN10 Pin4 RX PA0 analog input CN7 Pin28 RSSI PA1 analog input CN7 Pin30 @@ -352,6 +374,10 @@ EXT_CLK PA15 input CN7 Pin17 #define PORT_P25 GPIOB #define RCC_Per_P25 RCC_AHB1Periph_GPIOB +#define PIN_NXDN GPIO_Pin_10 +#define PORT_NXDN GPIOA +#define RCC_Per_NXDN RCC_AHB1Periph_GPIOA + #define PIN_DSTAR GPIO_Pin_10 #define PORT_DSTAR GPIOB #define RCC_Per_DSTAR RCC_AHB1Periph_GPIOB @@ -369,6 +395,10 @@ EXT_CLK PA15 input CN7 Pin17 #define PORT_MP25 GPIOC #define RCC_Per_MP25 RCC_AHB1Periph_GPIOC +#define PIN_MNXDN GPIO_Pin_6 +#define PORT_MNXDN GPIOC +#define RCC_Per_MNXDN RCC_AHB1Periph_GPIOC + #define PIN_MDSTAR GPIO_Pin_4 #define PORT_MDSTAR GPIOC #define RCC_Per_MDSTAR RCC_AHB1Periph_GPIOC @@ -412,6 +442,7 @@ DSTAR PA1 output CN8 Pin2 DMR PA4 output CN8 Pin3 YSF PB0 output CN8 Pin4 P25 PC1 output CN8 Pin5 +NXDN PC0 output CN8 Pin6 RX PA0 analog input CN8 Pin1 RSSI PC0 analog input CN8 Pin6 @@ -440,6 +471,10 @@ EXT_CLK PB8 input CN5 Pin10 #define PORT_P25 GPIOC #define RCC_Per_P25 RCC_AHB1Periph_GPIOC +#define PIN_NXDN GPIO_Pin_0 +#define PORT_NXDN GPIOC +#define RCC_Per_NXDN RCC_AHB1Periph_GPIOC + #define PIN_DSTAR GPIO_Pin_1 #define PORT_DSTAR GPIOA #define RCC_Per_DSTAR RCC_AHB1Periph_GPIOA @@ -486,11 +521,13 @@ DSTAR PB10 output CN12 Pin25 DMR PB4 output CN12 Pin27 YSF PB5 output CN12 Pin29 P25 PB3 output CN12 Pin31 +NXDN PA10 output CN12 Pin33 MDSTAR PC4 output CN12 Pin34 MDMR PC5 output CN12 Pin6 MYSF PC2 output CN11 Pin35 MP25 PC3 output CN11 Pin37 +MNXDN PC6 output CN12 Pin4 RX PA0 analog input CN11 Pin28 RSSI PA1 analog input CN11 Pin30 @@ -519,6 +556,10 @@ EXT_CLK PA15 input CN11 Pin17 #define PORT_P25 GPIOB #define RCC_Per_P25 RCC_AHB1Periph_GPIOB +#define PIN_NXDN GPIO_Pin_10 +#define PORT_NXDN GPIOA +#define RCC_Per_NXDN RCC_AHB1Periph_GPIOA + #define PIN_DSTAR GPIO_Pin_10 #define PORT_DSTAR GPIOB #define RCC_Per_DSTAR RCC_AHB1Periph_GPIOB @@ -536,6 +577,10 @@ EXT_CLK PA15 input CN11 Pin17 #define PORT_MP25 GPIOC #define RCC_Per_MP25 RCC_AHB1Periph_GPIOC +#define PIN_MNXDN GPIO_Pin_6 +#define PORT_MNXDN GPIOC +#define RCC_Per_MNXDN RCC_AHB1Periph_GPIOC + #define PIN_MDSTAR GPIO_Pin_4 #define PORT_MDSTAR GPIOC #define RCC_Per_MDSTAR RCC_AHB1Periph_GPIOC @@ -640,6 +685,12 @@ void CIO::initInt() GPIO_InitStruct.GPIO_Pin = PIN_P25; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_Init(PORT_P25, &GPIO_InitStruct); + + // NXDN pin + RCC_AHB1PeriphClockCmd(RCC_Per_NXDN, ENABLE); + GPIO_InitStruct.GPIO_Pin = PIN_NXDN; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; + GPIO_Init(PORT_NXDN, &GPIO_InitStruct); #endif #if defined(STM32F4_NUCLEO_MODE_PINS) && defined(STM32F4_NUCLEO_MORPHO_HEADER) && defined(STM32F4_NUCLEO) @@ -666,6 +717,12 @@ void CIO::initInt() GPIO_InitStruct.GPIO_Pin = PIN_MP25; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_Init(PORT_MP25, &GPIO_InitStruct); + + // NXDN mode pin + RCC_AHB1PeriphClockCmd(RCC_Per_MNXDN, ENABLE); + GPIO_InitStruct.GPIO_Pin = PIN_MNXDN; + GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; + GPIO_Init(PORT_MNXDN, &GPIO_InitStruct); #endif } @@ -855,7 +912,7 @@ void CIO::interrupt() bool CIO::getCOSInt() { - return GPIO_ReadOutputDataBit(PORT_COS, PIN_COS) == Bit_SET; + return GPIO_ReadInputDataBit(PORT_COS, PIN_COS) == Bit_SET; } void CIO::setLEDInt(bool on) @@ -905,6 +962,14 @@ void CIO::setP25Int(bool on) #endif } +void CIO::setNXDNInt(bool on) +{ + GPIO_WriteBit(PORT_NXDN, PIN_NXDN, on ? Bit_SET : Bit_RESET); +#if defined(STM32F4_NUCLEO_MODE_PINS) && defined(STM32F4_NUCLEO_MORPHO_HEADER) && defined(STM32F4_NUCLEO) + GPIO_WriteBit(PORT_MNXDN, PIN_MNXDN, on ? Bit_SET : Bit_RESET); +#endif +} + // Simple delay function for STM32 // Example from: http://thehackerworkshop.com/?p=1209 void CIO::delayInt(unsigned int dly) diff --git a/IOSTM_CMSIS.cpp b/IOSTM_CMSIS.cpp index 4ae144d..997c92b 100644 --- a/IOSTM_CMSIS.cpp +++ b/IOSTM_CMSIS.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 2016 by Jim McLaughlin KI6ZUM * Copyright (C) 2016, 2017 by Andy Uribe CA6JAU - * Copyright (C) 2017 by Jonathan Naylor G4KLX + * Copyright (C) 2017,2018 by Jonathan Naylor G4KLX * Copyright (C) 2017 by Wojciech Krutnik N0CALL * * This program is free software; you can redistribute it and/or modify @@ -39,6 +39,7 @@ DSTAR PB7 output DMR PB6 output YSF PB8 output P25 PB9 output +NXDN PB10 output RX PB0 analog input (ADC1_8) RSSI PB1 analog input (ADC2_9) @@ -76,6 +77,9 @@ USART1_RXD PA10 input (AF) #define PIN_P25 9 #define PORT_P25 GPIOB #define BB_P25 *((bitband_t)BITBAND_PERIPH(&PORT_P25->ODR, PIN_P25)) +#define PIN_NXDN 10 +#define PORT_NXDN GPIOB +#define BB_NXDN *((bitband_t)BITBAND_PERIPH(&PORT_NXDN->ODR, PIN_NXDN)) #define PIN_RX 0 #define PIN_RX_ADC_CH 8 @@ -214,6 +218,7 @@ static inline void GPIOInit() GPIOConfigPin(PORT_DMR, PIN_DMR, GPIO_CRL_MODE0_1); GPIOConfigPin(PORT_YSF, PIN_YSF, GPIO_CRL_MODE0_1); GPIOConfigPin(PORT_P25, PIN_P25, GPIO_CRL_MODE0_1); + GPIOConfigPin(PORT_NXDN, PIN_NXDN, GPIO_CRL_MODE0_1); GPIOConfigPin(PORT_RX, PIN_RX, 0); #if defined(SEND_RSSI_DATA) @@ -424,6 +429,11 @@ void CIO::setP25Int(bool on) BB_P25 = !!on; } +void CIO::setNXDNInt(bool on) +{ + BB_NXDN = !!on; +} + void CIO::delayInt(unsigned int dly) { delay(dly); diff --git a/IOTeensy.cpp b/IOTeensy.cpp index de0651a..0f551a0 100644 --- a/IOTeensy.cpp +++ b/IOTeensy.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016,2017 by Jonathan Naylor G4KLX + * 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 @@ -34,6 +34,7 @@ #define PIN_DMR 10 #define PIN_YSF 11 #define PIN_P25 12 +#define PIN_NXDN 8 #define PIN_ADC 5 // A0, Pin 14 #define PIN_RSSI 4 // Teensy 3.5/3.6, A16, Pin 35. Teensy 3.1/3.2, A17, Pin 28 @@ -63,6 +64,7 @@ void CIO::initInt() pinMode(PIN_DMR, OUTPUT); pinMode(PIN_YSF, OUTPUT); pinMode(PIN_P25, OUTPUT); + pinMode(PIN_NXDN, OUTPUT); #endif } @@ -210,6 +212,11 @@ void CIO::setP25Int(bool on) digitalWrite(PIN_P25, on ? HIGH : LOW); } +void CIO::setNXDNInt(bool on) +{ + digitalWrite(PIN_NXDN, on ? HIGH : LOW); +} + void CIO::delayInt(unsigned int dly) { delay(dly); diff --git a/MMDVM.cpp b/MMDVM.cpp index 37c0a41..d4a12a9 100644 --- a/MMDVM.cpp +++ b/MMDVM.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Mathis Schmieder DB9MAT * Copyright (C) 2016 by Colin Durbridge G4EML * @@ -30,6 +30,7 @@ bool m_dstarEnable = true; bool m_dmrEnable = true; bool m_ysfEnable = true; bool m_p25Enable = true; +bool m_nxdnEnable = true; bool m_duplex = true; @@ -52,10 +53,14 @@ CYSFTX ysfTX; CP25RX p25RX; CP25TX p25TX; +CNXDNRX nxdnRX; +CNXDNTX nxdnTX; + CCalDStarRX calDStarRX; CCalDStarTX calDStarTX; CCalDMR calDMR; CCalP25 calP25; +CCalNXDN calNXDN; CCalRSSI calRSSI; CCWIdTX cwIdTX; @@ -91,15 +96,21 @@ void loop() if (m_p25Enable && m_modemState == STATE_P25) p25TX.process(); + if (m_nxdnEnable && m_modemState == STATE_NXDN) + nxdnTX.process(); + if (m_modemState == STATE_DSTARCAL) calDStarTX.process(); - if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K) + if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K || m_modemState == STATE_DMRDMO1K) calDMR.process(); if (m_modemState == STATE_P25CAL1K) calP25.process(); + if (m_modemState == STATE_NXDNCAL1K) + calNXDN.process(); + if (m_modemState == STATE_IDLE) cwIdTX.process(); } @@ -113,4 +124,3 @@ int main() } #endif - diff --git a/MMDVM.ino b/MMDVM.ino index d132f2a..0b0f852 100644 --- a/MMDVM.ino +++ b/MMDVM.ino @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Colin Durbridge G4EML * * This program is free software; you can redistribute it and/or modify @@ -27,6 +27,7 @@ bool m_dstarEnable = true; bool m_dmrEnable = true; bool m_ysfEnable = true; bool m_p25Enable = true; +bool m_nxdnEnable = true; bool m_duplex = true; @@ -49,10 +50,14 @@ CYSFTX ysfTX; CP25RX p25RX; CP25TX p25TX; +CNXDNRX nxdnRX; +CNXDNTX nxdnTX; + CCalDStarRX calDStarRX; CCalDStarTX calDStarTX; CCalDMR calDMR; CCalP25 calP25; +CCalNXDN calNXDN; CCalRSSI calRSSI; CCWIdTX cwIdTX; @@ -88,15 +93,21 @@ void loop() if (m_p25Enable && m_modemState == STATE_P25) p25TX.process(); + if (m_nxdnEnable && m_modemState == STATE_NXDN) + nxdnTX.process(); + if (m_modemState == STATE_DSTARCAL) calDStarTX.process(); - if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K) + if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K || m_modemState == STATE_DMRDMO1K) calDMR.process(); if (m_modemState == STATE_P25CAL1K) calP25.process(); + if (m_modemState == STATE_NXDNCAL1K) + calNXDN.process(); + if (m_modemState == STATE_IDLE) cwIdTX.process(); } diff --git a/NXDNDefines.h b/NXDNDefines.h new file mode 100644 index 0000000..a98166f --- /dev/null +++ b/NXDNDefines.h @@ -0,0 +1,50 @@ +/* + * 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(NXDNDEFINES_H) +#define NXDNDEFINES_H + +const unsigned int NXDN_RADIO_SYMBOL_LENGTH = 10U; // At 24 kHz sample rate + +const unsigned int NXDN_FRAME_LENGTH_BITS = 384U; +const unsigned int NXDN_FRAME_LENGTH_BYTES = NXDN_FRAME_LENGTH_BITS / 8U; +const unsigned int NXDN_FRAME_LENGTH_SYMBOLS = NXDN_FRAME_LENGTH_BITS / 2U; +const unsigned int NXDN_FRAME_LENGTH_SAMPLES = NXDN_FRAME_LENGTH_SYMBOLS * NXDN_RADIO_SYMBOL_LENGTH; + +const unsigned int NXDN_FSW_LENGTH_BITS = 20U; +const unsigned int NXDN_FSW_LENGTH_SYMBOLS = NXDN_FSW_LENGTH_BITS / 2U; +const unsigned int 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; + +// 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; + +#endif + diff --git a/NXDNRX.cpp b/NXDNRX.cpp new file mode 100644 index 0000000..e9bd41b --- /dev/null +++ b/NXDNRX.cpp @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2009-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. + */ + +#include "Config.h" +#include "Globals.h" +#include "NXDNRX.h" +#include "Utils.h" + +const q15_t SCALING_FACTOR = 18750; // Q15(0.55) + +const uint8_t MAX_FSW_BIT_START_ERRS = 1U; +const uint8_t MAX_FSW_BIT_RUN_ERRS = 3U; + +const uint8_t MAX_FSW_SYMBOLS_ERRS = 2U; + +const uint8_t BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U}; + +#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) + +const uint8_t NOAVEPTR = 99U; + +const uint16_t NOENDPTR = 9999U; + +const unsigned int MAX_FSW_FRAMES = 5U + 1U; + +CNXDNRX::CNXDNRX() : +m_state(NXDNRXS_NONE), +m_bitBuffer(), +m_buffer(), +m_bitPtr(0U), +m_dataPtr(0U), +m_startPtr(NOENDPTR), +m_endPtr(NOENDPTR), +m_fswPtr(NOENDPTR), +m_minFSWPtr(NOENDPTR), +m_maxFSWPtr(NOENDPTR), +m_maxCorr(0), +m_lostCount(0U), +m_countdown(0U), +m_centre(), +m_centreVal(0), +m_threshold(), +m_thresholdVal(0), +m_averagePtr(NOAVEPTR), +m_rssiAccum(0U), +m_rssiCount(0U) +{ +} + +void CNXDNRX::reset() +{ + m_state = NXDNRXS_NONE; + m_dataPtr = 0U; + m_bitPtr = 0U; + m_maxCorr = 0; + m_averagePtr = NOAVEPTR; + m_startPtr = NOENDPTR; + m_endPtr = NOENDPTR; + m_fswPtr = NOENDPTR; + m_minFSWPtr = NOENDPTR; + m_maxFSWPtr = NOENDPTR; + m_centreVal = 0; + m_thresholdVal = 0; + m_lostCount = 0U; + m_countdown = 0U; + m_rssiAccum = 0U; + m_rssiCount = 0U; +} + +void CNXDNRX::samples(const q15_t* samples, uint16_t* rssi, uint8_t length) +{ + for (uint8_t i = 0U; i < length; i++) { + q15_t sample = samples[i]; + + m_rssiAccum += rssi[i]; + m_rssiCount++; + + m_bitBuffer[m_bitPtr] <<= 1; + if (sample < 0) + m_bitBuffer[m_bitPtr] |= 0x01U; + + m_buffer[m_dataPtr] = sample; + + switch (m_state) { + case NXDNRXS_DATA: + processData(sample); + break; + default: + processNone(sample); + break; + } + + m_dataPtr++; + if (m_dataPtr >= NXDN_FRAME_LENGTH_SAMPLES) + m_dataPtr = 0U; + + m_bitPtr++; + if (m_bitPtr >= NXDN_RADIO_SYMBOL_LENGTH) + m_bitPtr = 0U; + } +} + +void CNXDNRX::processNone(q15_t sample) +{ + bool ret = correlateFSW(); + if (ret) { + // On the first sync, start the countdown to the state change + if (m_countdown == 0U) { + m_rssiAccum = 0U; + m_rssiCount = 0U; + + io.setDecode(true); + io.setADCDetection(true); + + m_averagePtr = NOAVEPTR; + + m_countdown = 5U; + } + } + + if (m_countdown > 0U) + m_countdown--; + + if (m_countdown == 1U) { + m_minFSWPtr = m_fswPtr + NXDN_FRAME_LENGTH_SAMPLES - 1U; + if (m_minFSWPtr >= NXDN_FRAME_LENGTH_SAMPLES) + m_minFSWPtr -= NXDN_FRAME_LENGTH_SAMPLES; + + m_maxFSWPtr = m_fswPtr + 1U; + if (m_maxFSWPtr >= NXDN_FRAME_LENGTH_SAMPLES) + m_maxFSWPtr -= NXDN_FRAME_LENGTH_SAMPLES; + + m_state = NXDNRXS_DATA; + m_countdown = 0U; + } +} + +void CNXDNRX::processData(q15_t sample) +{ + if (m_minFSWPtr < m_maxFSWPtr) { + if (m_dataPtr >= m_minFSWPtr && m_dataPtr <= m_maxFSWPtr) + correlateFSW(); + } else { + if (m_dataPtr >= m_minFSWPtr || m_dataPtr <= m_maxFSWPtr) + correlateFSW(); + } + + if (m_dataPtr == m_endPtr) { + // Only update the centre and threshold if they are from a good sync + if (m_lostCount == MAX_FSW_FRAMES) { + m_minFSWPtr = m_fswPtr + NXDN_FRAME_LENGTH_SAMPLES - 1U; + if (m_minFSWPtr >= NXDN_FRAME_LENGTH_SAMPLES) + m_minFSWPtr -= NXDN_FRAME_LENGTH_SAMPLES; + + m_maxFSWPtr = m_fswPtr + 1U; + if (m_maxFSWPtr >= NXDN_FRAME_LENGTH_SAMPLES) + m_maxFSWPtr -= NXDN_FRAME_LENGTH_SAMPLES; + } + + calculateLevels(m_startPtr, NXDN_FRAME_LENGTH_SYMBOLS); + + DEBUG4("NXDNRX: sync found pos/centre/threshold", m_fswPtr, m_centreVal, m_thresholdVal); + + uint8_t frame[NXDN_FRAME_LENGTH_BYTES + 3U]; + samplesToBits(m_startPtr, NXDN_FRAME_LENGTH_SYMBOLS, frame, 8U, m_centreVal, m_thresholdVal); + + // We've not seen a data sync for too long, signal RXLOST and change to RX_NONE + m_lostCount--; + if (m_lostCount == 0U) { + DEBUG1("NXDNRX: sync timed out, lost lock"); + + io.setDecode(false); + io.setADCDetection(false); + + serial.writeNXDNLost(); + + m_state = NXDNRXS_NONE; + m_endPtr = NOENDPTR; + m_averagePtr = NOAVEPTR; + m_countdown = 0U; + m_maxCorr = 0; + } else { + frame[0U] = m_lostCount == (MAX_FSW_FRAMES - 1U) ? 0x01U : 0x00U; + writeRSSIData(frame); + m_maxCorr = 0; + } + } +} + +bool CNXDNRX::correlateFSW() +{ + if (countBits32((m_bitBuffer[m_bitPtr] & NXDN_FSW_SYMBOLS_MASK) ^ NXDN_FSW_SYMBOLS) <= MAX_FSW_SYMBOLS_ERRS) { + uint16_t ptr = m_dataPtr + NXDN_FRAME_LENGTH_SAMPLES - NXDN_FSW_LENGTH_SAMPLES + NXDN_RADIO_SYMBOL_LENGTH; + if (ptr >= NXDN_FRAME_LENGTH_SAMPLES) + ptr -= NXDN_FRAME_LENGTH_SAMPLES; + + q31_t corr = 0; + q15_t min = 16000; + q15_t max = -16000; + + for (uint8_t i = 0U; i < NXDN_FSW_LENGTH_SYMBOLS; i++) { + q15_t val = m_buffer[ptr]; + + if (val > max) + max = val; + if (val < min) + min = val; + + switch (NXDN_FSW_SYMBOLS_VALUES[i]) { + case +3: + corr -= (val + val + val); + break; + case +1: + corr -= val; + break; + case -1: + corr += val; + break; + default: // -3 + corr += (val + val + val); + break; + } + + ptr += NXDN_RADIO_SYMBOL_LENGTH; + if (ptr >= NXDN_FRAME_LENGTH_SAMPLES) + ptr -= NXDN_FRAME_LENGTH_SAMPLES; + } + + if (corr > m_maxCorr) { + if (m_averagePtr == NOAVEPTR) { + m_centreVal = (max + min) >> 1; + + q31_t v1 = (max - m_centreVal) * SCALING_FACTOR; + m_thresholdVal = q15_t(v1 >> 15); + } + + uint16_t startPtr = m_dataPtr + NXDN_FRAME_LENGTH_SAMPLES - NXDN_FSW_LENGTH_SAMPLES + NXDN_RADIO_SYMBOL_LENGTH; + if (startPtr >= NXDN_FRAME_LENGTH_SAMPLES) + startPtr -= NXDN_FRAME_LENGTH_SAMPLES; + + uint8_t sync[NXDN_FSW_BYTES_LENGTH]; + samplesToBits(startPtr, NXDN_FSW_LENGTH_SYMBOLS, sync, 0U, m_centreVal, m_thresholdVal); + + uint8_t maxErrs; + if (m_state == NXDNRXS_NONE) + maxErrs = MAX_FSW_BIT_START_ERRS; + else + maxErrs = MAX_FSW_BIT_RUN_ERRS; + + uint8_t errs = 0U; + for (uint8_t i = 0U; i < NXDN_FSW_BYTES_LENGTH; i++) + errs += countBits8((sync[i] & NXDN_FSW_BYTES_MASK[i]) ^ NXDN_FSW_BYTES[i]); + + if (errs <= maxErrs) { + m_maxCorr = corr; + m_lostCount = MAX_FSW_FRAMES; + m_fswPtr = m_dataPtr; + + m_startPtr = startPtr; + + m_endPtr = m_dataPtr + NXDN_FRAME_LENGTH_SAMPLES - NXDN_FSW_LENGTH_SAMPLES - 1U; + if (m_endPtr >= NXDN_FRAME_LENGTH_SAMPLES) + m_endPtr -= NXDN_FRAME_LENGTH_SAMPLES; + + return true; + } + } + } + + return false; +} + +void CNXDNRX::calculateLevels(uint16_t start, uint16_t count) +{ + q15_t maxPos = -16000; + q15_t minPos = 16000; + q15_t maxNeg = 16000; + q15_t minNeg = -16000; + + for (uint16_t i = 0U; i < count; i++) { + q15_t sample = m_buffer[start]; + + if (sample > 0) { + if (sample > maxPos) + maxPos = sample; + if (sample < minPos) + minPos = sample; + } else { + if (sample < maxNeg) + maxNeg = sample; + if (sample > minNeg) + minNeg = sample; + } + + start += NXDN_RADIO_SYMBOL_LENGTH; + if (start >= NXDN_FRAME_LENGTH_SAMPLES) + start -= NXDN_FRAME_LENGTH_SAMPLES; + } + + q15_t posThresh = (maxPos + minPos) >> 1; + q15_t negThresh = (maxNeg + minNeg) >> 1; + + q15_t centre = (posThresh + negThresh) >> 1; + + q15_t threshold = posThresh - centre; + + DEBUG5("NXDNRX: pos/neg/centre/threshold", posThresh, negThresh, centre, threshold); + + if (m_averagePtr == NOAVEPTR) { + for (uint8_t i = 0U; i < 16U; i++) { + m_centre[i] = centre; + m_threshold[i] = threshold; + } + + m_averagePtr = 0U; + } else { + m_centre[m_averagePtr] = centre; + m_threshold[m_averagePtr] = threshold; + + m_averagePtr++; + if (m_averagePtr >= 16U) + m_averagePtr = 0U; + } + + m_centreVal = 0; + m_thresholdVal = 0; + + for (uint8_t i = 0U; i < 16U; i++) { + m_centreVal += m_centre[i]; + m_thresholdVal += m_threshold[i]; + } + + m_centreVal >>= 4; + m_thresholdVal >>= 4; +} + +void CNXDNRX::samplesToBits(uint16_t start, uint16_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold) +{ + for (uint16_t i = 0U; i < count; i++) { + q15_t sample = m_buffer[start] - centre; + + if (sample < -threshold) { + WRITE_BIT1(buffer, offset, false); + offset++; + WRITE_BIT1(buffer, offset, true); + offset++; + } else if (sample < 0) { + WRITE_BIT1(buffer, offset, false); + offset++; + WRITE_BIT1(buffer, offset, false); + offset++; + } else if (sample < threshold) { + WRITE_BIT1(buffer, offset, true); + offset++; + WRITE_BIT1(buffer, offset, false); + offset++; + } else { + WRITE_BIT1(buffer, offset, true); + offset++; + WRITE_BIT1(buffer, offset, true); + offset++; + } + + start += NXDN_RADIO_SYMBOL_LENGTH; + if (start >= NXDN_FRAME_LENGTH_SAMPLES) + start -= NXDN_FRAME_LENGTH_SAMPLES; + } +} + +void CNXDNRX::writeRSSIData(uint8_t* data) +{ +#if defined(SEND_RSSI_DATA) + if (m_rssiCount > 0U) { + uint16_t rssi = m_rssiAccum / m_rssiCount; + + data[49U] = (rssi >> 8) & 0xFFU; + data[50U] = (rssi >> 0) & 0xFFU; + + serial.writeNXDNData(data, NXDN_FRAME_LENGTH_BYTES + 3U); + } else { + serial.writeNXDNData(data, NXDN_FRAME_LENGTH_BYTES + 1U); + } +#else + serial.writeNXDNData(data, NXDN_FRAME_LENGTH_BYTES + 1U); +#endif + + m_rssiAccum = 0U; + m_rssiCount = 0U; +} + diff --git a/NXDNRX.h b/NXDNRX.h new file mode 100644 index 0000000..1bff2ac --- /dev/null +++ b/NXDNRX.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015,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(NXDNRX_H) +#define NXDNRX_H + +#include "Config.h" +#include "NXDNDefines.h" + +enum NXDNRX_STATE { + NXDNRXS_NONE, + NXDNRXS_DATA +}; + +class CNXDNRX { +public: + CNXDNRX(); + + void samples(const q15_t* samples, uint16_t* rssi, uint8_t length); + + void reset(); + +private: + NXDNRX_STATE m_state; + uint16_t m_bitBuffer[NXDN_RADIO_SYMBOL_LENGTH]; + q15_t m_buffer[NXDN_FRAME_LENGTH_SAMPLES]; + uint16_t m_bitPtr; + uint16_t m_dataPtr; + uint16_t m_startPtr; + uint16_t m_endPtr; + uint16_t m_fswPtr; + uint16_t m_minFSWPtr; + uint16_t m_maxFSWPtr; + q31_t m_maxCorr; + uint16_t m_lostCount; + uint8_t m_countdown; + q15_t m_centre[16U]; + q15_t m_centreVal; + q15_t m_threshold[16U]; + q15_t m_thresholdVal; + uint8_t m_averagePtr; + uint32_t m_rssiAccum; + uint16_t m_rssiCount; + + void processNone(q15_t sample); + void processData(q15_t sample); + bool correlateFSW(); + void calculateLevels(uint16_t start, uint16_t count); + void samplesToBits(uint16_t start, uint16_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold); + void writeRSSIData(uint8_t* data); +}; + +#endif + diff --git a/NXDNTX.cpp b/NXDNTX.cpp new file mode 100644 index 0000000..905c502 --- /dev/null +++ b/NXDNTX.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2009-2018 by Jonathan Naylor G4KLX + * Copyright (C) 2017 by Andy Uribe CA6JAU + * + * 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 "Config.h" +#include "Globals.h" +#include "NXDNTX.h" + +#include "NXDNDefines.h" + +// Generated using rcosdesign(0.2, 8, 10, 'sqrt') in MATLAB +static q15_t RRC_0_2_FILTER[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 850, 592, 219, -234, -720, -1179, -1548, -1769, -1795, -1597, -1172, + -544, 237, 1092, 1927, 2637, 3120, 3286, 3073, 2454, 1447, 116, -1431, -3043, -4544, -5739, -6442, + -6483, -5735, -4121, -1633, 1669, 5651, 10118, 14822, 19484, 23810, 27520, 30367, 32156, 32767, + 32156, 30367, 27520, 23810, 19484, 14822, 10118, 5651, 1669, -1633, -4121, -5735, -6483, -6442, + -5739, -4544, -3043, -1431, 116, 1447, 2454, 3073, 3286, 3120, 2637, 1927, 1092, 237, -544, -1172, + -1597, -1795, -1769, -1548, -1179, -720, -234, 219, 592, 850}; // numTaps = 90, L = 10 +const uint16_t RRC_0_2_FILTER_PHASE_LEN = 9U; // phaseLength = numTaps/L + +static q15_t NXDN_SINC_FILTER[] = {572, -1003, -253, 254, 740, 1290, 1902, 2527, 3090, 3517, 3747, 3747, 3517, 3090, 2527, 1902, + 1290, 740, 254, -253, -1003, 572}; +const uint16_t NXDN_SINC_FILTER_LEN = 22U; + +const q15_t NXDN_LEVELA = 735; +const q15_t NXDN_LEVELB = 245; +const q15_t NXDN_LEVELC = -245; +const q15_t NXDN_LEVELD = -735; + +const uint8_t NXDN_PREAMBLE[] = {0x57U, 0x75U, 0xFDU}; +const uint8_t NXDN_SYNC = 0x5FU; + +CNXDNTX::CNXDNTX() : +m_buffer(2000U), +m_modFilter(), +m_sincFilter(), +m_modState(), +m_sincState(), +m_poBuffer(), +m_poLen(0U), +m_poPtr(0U), +m_txDelay(240U) // 200ms +{ + ::memset(m_modState, 0x00U, 16U * sizeof(q15_t)); + ::memset(m_sincState, 0x00U, 70U * sizeof(q15_t)); + + m_modFilter.L = NXDN_RADIO_SYMBOL_LENGTH; + m_modFilter.phaseLength = RRC_0_2_FILTER_PHASE_LEN; + m_modFilter.pCoeffs = RRC_0_2_FILTER; + m_modFilter.pState = m_modState; + + m_sincFilter.numTaps = NXDN_SINC_FILTER_LEN; + m_sincFilter.pState = m_sincState; + m_sincFilter.pCoeffs = NXDN_SINC_FILTER; +} + +void CNXDNTX::process() +{ + if (m_buffer.getData() == 0U && m_poLen == 0U) + return; + + if (m_poLen == 0U) { + if (!m_tx) { + for (uint16_t i = 0U; i < m_txDelay; 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 { + for (uint8_t i = 0U; i < NXDN_FRAME_LENGTH_BYTES; i++) { + uint8_t c = m_buffer.get(); + m_poBuffer[m_poLen++] = c; + } + } + + m_poPtr = 0U; + } + + if (m_poLen > 0U) { + uint16_t space = io.getSpace(); + + while (space > (4U * NXDN_RADIO_SYMBOL_LENGTH)) { + uint8_t c = m_poBuffer[m_poPtr++]; + writeByte(c); + + space -= 4U * NXDN_RADIO_SYMBOL_LENGTH; + + if (m_poPtr >= m_poLen) { + m_poPtr = 0U; + m_poLen = 0U; + return; + } + } + } +} + +uint8_t CNXDNTX::writeData(const uint8_t* data, uint8_t length) +{ + if (length != (NXDN_FRAME_LENGTH_BYTES + 1U)) + return 4U; + + uint16_t space = m_buffer.getSpace(); + if (space < NXDN_FRAME_LENGTH_BYTES) + return 5U; + + for (uint8_t i = 0U; i < NXDN_FRAME_LENGTH_BYTES; i++) + m_buffer.put(data[i + 1U]); + + return 0U; +} + +void CNXDNTX::writeByte(uint8_t c) +{ + q15_t inBuffer[4U]; + q15_t intBuffer[NXDN_RADIO_SYMBOL_LENGTH * 4U]; + q15_t outBuffer[NXDN_RADIO_SYMBOL_LENGTH * 4U]; + + const uint8_t MASK = 0xC0U; + + for (uint8_t i = 0U; i < 4U; i++, c <<= 2) { + switch (c & MASK) { + case 0xC0U: + inBuffer[i] = NXDN_LEVELA; + break; + case 0x80U: + inBuffer[i] = NXDN_LEVELB; + break; + case 0x00U: + inBuffer[i] = NXDN_LEVELC; + break; + default: + inBuffer[i] = NXDN_LEVELD; + break; + } + } + + ::arm_fir_interpolate_q15(&m_modFilter, inBuffer, intBuffer, 4U); + + ::arm_fir_fast_q15(&m_sincFilter, intBuffer, outBuffer, NXDN_RADIO_SYMBOL_LENGTH * 4U); + + io.write(STATE_NXDN, outBuffer, NXDN_RADIO_SYMBOL_LENGTH * 4U); +} + +void CNXDNTX::setTXDelay(uint8_t delay) +{ + m_txDelay = 300U + uint16_t(delay) * 6U; // 500ms + tx delay + + if (m_txDelay > 1200U) + m_txDelay = 1200U; +} + +uint8_t CNXDNTX::getSpace() const +{ + return m_buffer.getSpace() / NXDN_FRAME_LENGTH_BYTES; +} + diff --git a/NXDNTX.h b/NXDNTX.h new file mode 100644 index 0000000..eec74d4 --- /dev/null +++ b/NXDNTX.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015,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(NXDNTX_H) +#define NXDNTX_H + +#include "Config.h" + +#include "SerialRB.h" + +class CNXDNTX { +public: + CNXDNTX(); + + uint8_t writeData(const uint8_t* data, uint8_t length); + + void process(); + + void setTXDelay(uint8_t delay); + + uint8_t getSpace() const; + +private: + CSerialRB m_buffer; + arm_fir_interpolate_instance_q15 m_modFilter; + arm_fir_instance_q15 m_sincFilter; + q15_t m_modState[16U]; // blockSize + phaseLength - 1, 4 + 9 - 1 plus some spare + q15_t m_sincState[70U]; // NoTaps + BlockSize - 1, 22 + 40 - 1 plus some spare + uint8_t m_poBuffer[1200U]; + uint16_t m_poLen; + uint16_t m_poPtr; + uint16_t m_txDelay; + + void writeByte(uint8_t c); +}; + +#endif + diff --git a/P25TX.cpp b/P25TX.cpp index 2901a74..57a8db0 100644 --- a/P25TX.cpp +++ b/P25TX.cpp @@ -30,12 +30,10 @@ static q15_t RC_0_2_FILTER[] = {-897, -1636, -1840, -1278, 0, 1613, 2936, 3310, -3011, 0, 2315, 3310, 2936, 1613, 0, -1278, -1840, -1636, -897, 0}; // numTaps = 40, L = 5 const uint16_t RC_0_2_FILTER_PHASE_LEN = 8U; // phaseLength = numTaps/L -// Generated in MATLAB using the following commands, and then normalised for unity gain -// shape2 = 'Inverse-sinc Lowpass'; -// d2 = fdesign.interpolator(1, shape2); -// h2 = design(d2, 'SystemObject', true); -static q15_t LOWPASS_FILTER[] = {1294, -2251, 4312, -8402, 20999, 20999, -8402, 4312, -2251, 1294}; -const uint16_t LOWPASS_FILTER_LEN = 10U; +static q15_t LOWPASS_FILTER[] = {124, -188, -682, 1262, 556, -621, -1912, -911, 2058, 3855, 1234, -4592, -7692, -2799, + 8556, 18133, 18133, 8556, -2799, -7692, -4592, 1234, 3855, 2058, -911, -1912, -621, + 556, 1262, -682, -188, 124}; +const uint16_t LOWPASS_FILTER_LEN = 32U; const q15_t P25_LEVELA = 1260; const q15_t P25_LEVELB = 420; @@ -56,7 +54,7 @@ m_poPtr(0U), m_txDelay(240U) // 200ms { ::memset(m_modState, 0x00U, 16U * sizeof(q15_t)); - ::memset(m_lpState, 0x00U, 70U * sizeof(q15_t)); + ::memset(m_lpState, 0x00U, 60U * sizeof(q15_t)); m_modFilter.L = P25_RADIO_SYMBOL_LENGTH; m_modFilter.phaseLength = RC_0_2_FILTER_PHASE_LEN; diff --git a/P25TX.h b/P25TX.h index 61f6a87..1d542f9 100644 --- a/P25TX.h +++ b/P25TX.h @@ -40,7 +40,7 @@ private: arm_fir_interpolate_instance_q15 m_modFilter; arm_fir_instance_q15 m_lpFilter; q15_t m_modState[16U]; // blockSize + phaseLength - 1, 4 + 9 - 1 plus some spare - q15_t m_lpState[70U]; // NoTaps + BlockSize - 1, 44 + 20 - 1 plus some spare + q15_t m_lpState[60U]; // NoTaps + BlockSize - 1, 32 + 20 - 1 plus some spare uint8_t m_poBuffer[1200U]; uint16_t m_poLen; uint16_t m_poPtr; diff --git a/SerialPort.cpp b/SerialPort.cpp index c6cdec6..dd78e61 100644 --- a/SerialPort.cpp +++ b/SerialPort.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013,2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2013,2015,2016,2017,2018 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Colin Durbridge G4EML * * This program is free software; you can redistribute it and/or modify @@ -60,6 +60,9 @@ const uint8_t MMDVM_P25_HDR = 0x30U; const uint8_t MMDVM_P25_LDU = 0x31U; const uint8_t MMDVM_P25_LOST = 0x32U; +const uint8_t MMDVM_NXDN_DATA = 0x40U; +const uint8_t MMDVM_NXDN_LOST = 0x41U; + const uint8_t MMDVM_ACK = 0x70U; const uint8_t MMDVM_NAK = 0x7FU; @@ -73,9 +76,9 @@ const uint8_t MMDVM_DEBUG5 = 0xF5U; #if defined(EXTERNAL_OSC) -#define DESCRIPTION "MMDVM 20170501 TCXO (D-Star/DMR/System Fusion/P25/RSSI/CW Id)" +#define DESCRIPTION "MMDVM 20180223 TCXO (D-Star/DMR/System Fusion/P25/NXDN/RSSI/CW Id)" #else -#define DESCRIPTION "MMDVM 20170501 (D-Star/DMR/System Fusion/P25/RSSI/CW Id)" +#define DESCRIPTION "MMDVM 20180223 (D-Star/DMR/System Fusion/P25/NXDN/RSSI/CW Id)" #endif #if defined(GITVERSION) @@ -143,6 +146,8 @@ void CSerialPort::getStatus() reply[3U] |= 0x04U; if (m_p25Enable) reply[3U] |= 0x08U; + if (m_nxdnEnable) + reply[3U] |= 0x10U; reply[4U] = uint8_t(m_modemState); @@ -197,7 +202,12 @@ void CSerialPort::getStatus() else reply[10U] = 0U; - writeInt(1U, reply, 11); + if (m_nxdnEnable) + reply[11U] = nxdnTX.getSpace(); + else + reply[11U] = 0U; + + writeInt(1U, reply, 12); } void CSerialPort::getVersion() @@ -221,7 +231,7 @@ void CSerialPort::getVersion() uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) { - if (length < 15U) + if (length < 16U) return 4U; bool rxInvert = (data[0U] & 0x01U) == 0x01U; @@ -236,6 +246,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) bool dmrEnable = (data[1U] & 0x02U) == 0x02U; bool ysfEnable = (data[1U] & 0x04U) == 0x04U; bool p25Enable = (data[1U] & 0x08U) == 0x08U; + bool nxdnEnable = (data[1U] & 0x10U) == 0x10U; uint8_t txDelay = data[2U]; if (txDelay > 50U) @@ -243,7 +254,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) MMDVM_STATE modemState = MMDVM_STATE(data[3U]); - if (modemState != STATE_IDLE && modemState != STATE_DSTAR && modemState != STATE_DMR && modemState != STATE_YSF && modemState != STATE_P25 && modemState != STATE_DSTARCAL && modemState != STATE_DMRCAL && modemState != STATE_RSSICAL && modemState != STATE_LFCAL && modemState != STATE_DMRCAL1K && modemState != STATE_P25CAL1K) + if (modemState != STATE_IDLE && modemState != STATE_DSTAR && modemState != STATE_DMR && modemState != STATE_YSF && modemState != STATE_P25 && modemState != STATE_NXDN && modemState != STATE_DSTARCAL && modemState != STATE_DMRCAL && modemState != STATE_RSSICAL && modemState != STATE_LFCAL && modemState != STATE_DMRCAL1K && modemState != STATE_P25CAL1K && modemState != STATE_DMRDMO1K && modemState != STATE_NXDNCAL1K) return 4U; if (modemState == STATE_DSTAR && !dstarEnable) return 4U; @@ -253,6 +264,8 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) return 4U; if (modemState == STATE_P25 && !p25Enable) return 4U; + if (modemState == STATE_NXDN && !nxdnEnable) + return 4U; uint8_t rxLevel = data[4U]; @@ -271,18 +284,22 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) int16_t txDCOffset = int16_t(data[13U]) - 128; int16_t rxDCOffset = int16_t(data[14U]) - 128; + uint8_t nxdnTXLevel = data[15U]; + m_modemState = modemState; m_dstarEnable = dstarEnable; m_dmrEnable = dmrEnable; m_ysfEnable = ysfEnable; m_p25Enable = p25Enable; + m_nxdnEnable = nxdnEnable; m_duplex = !simplex; dstarTX.setTXDelay(txDelay); ysfTX.setTXDelay(txDelay); p25TX.setTXDelay(txDelay); dmrDMOTX.setTXDelay(txDelay); + nxdnTX.setTXDelay(txDelay); dmrTX.setColorCode(colorCode); dmrRX.setColorCode(colorCode); @@ -292,7 +309,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) ysfTX.setLoDev(ysfLoDev); - io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, txDCOffset, rxDCOffset); + io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, txDCOffset, rxDCOffset); io.start(); @@ -309,7 +326,7 @@ uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length) if (modemState == m_modemState) return 0U; - if (modemState != STATE_IDLE && modemState != STATE_DSTAR && modemState != STATE_DMR && modemState != STATE_YSF && modemState != STATE_P25 && modemState != STATE_DSTARCAL && modemState != STATE_DMRCAL && modemState != STATE_RSSICAL && modemState != STATE_LFCAL && modemState != STATE_DMRCAL1K && modemState != STATE_P25CAL1K) + if (modemState != STATE_IDLE && modemState != STATE_DSTAR && modemState != STATE_DMR && modemState != STATE_YSF && modemState != STATE_P25 && modemState != STATE_NXDN && modemState != STATE_DSTARCAL && modemState != STATE_DMRCAL && modemState != STATE_RSSICAL && modemState != STATE_LFCAL && modemState != STATE_DMRCAL1K && modemState != STATE_P25CAL1K && modemState != STATE_DMRDMO1K && modemState != STATE_NXDNCAL1K) return 4U; if (modemState == STATE_DSTAR && !m_dstarEnable) return 4U; @@ -319,6 +336,8 @@ uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length) return 4U; if (modemState == STATE_P25 && !m_p25Enable) return 4U; + if (modemState == STATE_NXDN && !m_nxdnEnable) + return 4U; setMode(modemState); @@ -333,6 +352,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dstarRX.reset(); ysfRX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_DSTAR: @@ -342,6 +362,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dmrRX.reset(); ysfRX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_YSF: @@ -351,6 +372,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dmrRX.reset(); dstarRX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_P25: @@ -360,6 +382,17 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dmrRX.reset(); dstarRX.reset(); ysfRX.reset(); + nxdnRX.reset(); + cwIdTX.reset(); + break; + case STATE_NXDN: + DEBUG1("Mode set to NXDN"); + dmrIdleRX.reset(); + dmrDMORX.reset(); + dmrRX.reset(); + dstarRX.reset(); + ysfRX.reset(); + p25RX.reset(); cwIdTX.reset(); break; case STATE_DSTARCAL: @@ -370,6 +403,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dstarRX.reset(); ysfRX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_DMRCAL: @@ -380,6 +414,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dstarRX.reset(); ysfRX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_RSSICAL: @@ -390,6 +425,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dstarRX.reset(); ysfRX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_LFCAL: @@ -400,16 +436,18 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dstarRX.reset(); ysfRX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_DMRCAL1K: - DEBUG1("Mode set to DMR 1031 Hz Calibrate"); + DEBUG1("Mode set to DMR BS 1031 Hz Calibrate"); dmrIdleRX.reset(); dmrDMORX.reset(); dmrRX.reset(); dstarRX.reset(); ysfRX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_P25CAL1K: @@ -420,6 +458,29 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dstarRX.reset(); ysfRX.reset(); p25RX.reset(); + nxdnRX.reset(); + cwIdTX.reset(); + break; + case STATE_DMRDMO1K: + DEBUG1("Mode set to DMR MS 1031 Hz Calibrate"); + dmrIdleRX.reset(); + dmrDMORX.reset(); + dmrRX.reset(); + dstarRX.reset(); + ysfRX.reset(); + p25RX.reset(); + nxdnRX.reset(); + cwIdTX.reset(); + break; + case STATE_NXDNCAL1K: + DEBUG1("Mode set to NXDN 1031 Hz Calibrate"); + dmrIdleRX.reset(); + dmrDMORX.reset(); + dmrRX.reset(); + dstarRX.reset(); + ysfRX.reset(); + p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; default: @@ -502,10 +563,12 @@ void CSerialPort::process() case MMDVM_CAL_DATA: if (m_modemState == STATE_DSTARCAL) err = calDStarTX.write(m_buffer + 3U, m_len - 3U); - if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K) + if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K || m_modemState == STATE_DMRDMO1K) err = calDMR.write(m_buffer + 3U, m_len - 3U); if (m_modemState == STATE_P25CAL1K) err = calP25.write(m_buffer + 3U, m_len - 3U); + if (m_modemState == STATE_NXDNCAL1K) + err = calNXDN.write(m_buffer + 3U, m_len - 3U); if (err == 0U) { sendACK(); } else { @@ -681,6 +744,20 @@ void CSerialPort::process() } break; + case MMDVM_NXDN_DATA: + if (m_nxdnEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_NXDN) + err = nxdnTX.writeData(m_buffer + 3U, m_len - 3U); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_NXDN); + } else { + DEBUG2("Received invalid NXDN data", err); + sendNAK(err); + } + break; + #if defined(SERIAL_REPEATER) case MMDVM_SERIAL: { for (uint8_t i = 3U; i < m_len; i++) @@ -948,6 +1025,46 @@ void CSerialPort::writeP25Lost() writeInt(1U, reply, 3); } +void CSerialPort::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] = MMDVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = MMDVM_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); +} + +void CSerialPort::writeNXDNLost() +{ + if (m_modemState != STATE_NXDN && m_modemState != STATE_IDLE) + return; + + if (!m_nxdnEnable) + return; + + uint8_t reply[3U]; + + reply[0U] = MMDVM_FRAME_START; + reply[1U] = 3U; + reply[2U] = MMDVM_NXDN_LOST; + + writeInt(1U, reply, 3); +} + void CSerialPort::writeCalData(const uint8_t* data, uint8_t length) { if (m_modemState != STATE_DSTARCAL) diff --git a/SerialPort.h b/SerialPort.h index 69ce699..728b6b3 100644 --- a/SerialPort.h +++ b/SerialPort.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,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 @@ -47,6 +47,9 @@ public: void writeP25Ldu(const uint8_t* data, uint8_t length); void writeP25Lost(); + void writeNXDNData(const uint8_t* data, uint8_t length); + void writeNXDNLost(); + void writeCalData(const uint8_t* data, uint8_t length); void writeRSSIData(const uint8_t* data, uint8_t length); @@ -80,3 +83,4 @@ private: }; #endif +