From b5ca5e3839d9534a1da01e1f3a3fb84cfecf5422 Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Wed, 10 Jan 2018 20:30:35 +0000 Subject: [PATCH] NXDN first cut, not working yet. --- Globals.h | 9 +- IO.cpp | 50 +++++- IO.h | 6 +- IODue.cpp | 12 +- IOSTM.cpp | 67 +++++++- IOSTM_CMSIS.cpp | 12 +- IOTeensy.cpp | 9 +- MMDVM.cpp | 10 +- MMDVM.ino | 9 +- NXDNDefines.h | 54 +++++++ NXDNRX.cpp | 405 ++++++++++++++++++++++++++++++++++++++++++++++++ NXDNRX.h | 69 +++++++++ NXDNTX.cpp | 150 ++++++++++++++++++ NXDNTX.h | 51 ++++++ SerialPort.cpp | 109 ++++++++++++- SerialPort.h | 6 +- 16 files changed, 1005 insertions(+), 23 deletions(-) create mode 100644 NXDNDefines.h create mode 100644 NXDNRX.cpp create mode 100644 NXDNRX.h create mode 100644 NXDNTX.cpp create mode 100644 NXDNTX.h diff --git a/Globals.h b/Globals.h index d671e7b..4e0f62b 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,6 +48,7 @@ enum MMDVM_STATE { STATE_DMR = 2, STATE_YSF = 3, STATE_P25 = 4, + STATE_NXDN = 5, // Dummy states start at 90 STATE_P25CAL1K = 93, @@ -71,6 +72,8 @@ 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" @@ -95,6 +98,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,6 +124,9 @@ extern CYSFTX ysfTX; extern CP25RX p25RX; extern CP25TX p25TX; +extern CNXDNRX nxdnRX; +extern CNXDNTX nxdnTX; + extern CCalDStarRX calDStarRX; extern CCalDStarTX calDStarTX; extern CCalDMR calDMR; diff --git a/IO.cpp b/IO.cpp index bbea122..aa6745d 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 * @@ -62,6 +62,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), @@ -114,6 +115,7 @@ void CIO::selfTest() setDMRInt(ledValue); setYSFInt(ledValue); setP25Int(ledValue); + setNXDNInt(ledValue); #endif delayInt(250); } @@ -123,6 +125,7 @@ void CIO::selfTest() setDMRInt(false); setYSFInt(false); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -130,6 +133,7 @@ void CIO::selfTest() setDMRInt(true); setYSFInt(false); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -137,6 +141,7 @@ void CIO::selfTest() setDMRInt(true); setYSFInt(true); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -144,6 +149,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); @@ -151,6 +173,7 @@ void CIO::selfTest() setDMRInt(true); setYSFInt(true); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -158,6 +181,7 @@ void CIO::selfTest() setDMRInt(true); setYSFInt(false); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -165,6 +189,7 @@ void CIO::selfTest() setDMRInt(false); setYSFInt(false); setP25Int(false); + setNXDNInt(false); delayInt(250); @@ -172,6 +197,7 @@ void CIO::selfTest() setDMRInt(false); setYSFInt(false); setP25Int(false); + setNXDNInt(false); #endif } @@ -193,7 +219,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; @@ -282,7 +308,7 @@ void CIO::process() } // XXX YSF should use dcSamples, but DMR not - if (m_dmrEnable || m_ysfEnable) { + if (m_dmrEnable || m_ysfEnable || m_nxdnEnable) { q15_t C4FSKVals[RX_BLOCK_SIZE]; ::arm_fir_fast_q15(&m_rrcFilter, samples, C4FSKVals, RX_BLOCK_SIZE); @@ -295,6 +321,9 @@ void CIO::process() if (m_ysfEnable) ysfRX.samples(C4FSKVals, rssi, RX_BLOCK_SIZE); + + if (m_nxdnEnable) + nxdnRX.samples(C4FSKVals, rssi, RX_BLOCK_SIZE); } } else if (m_modemState == STATE_DSTAR) { if (m_dstarEnable) { @@ -332,6 +361,13 @@ void CIO::process() p25RX.samples(P25Vals, rssi, RX_BLOCK_SIZE); } + } else if (m_modemState == STATE_NXDN) { + if (m_nxdnEnable) { + q15_t C4FSKVals[RX_BLOCK_SIZE]; + ::arm_fir_fast_q15(&m_rrcFilter, dcSamples, C4FSKVals, RX_BLOCK_SIZE); + + nxdnRX.samples(C4FSKVals, rssi, RX_BLOCK_SIZE); + } } else if (m_modemState == STATE_DSTARCAL) { q15_t GMSKVals[RX_BLOCK_SIZE]; ::arm_fir_fast_q15(&m_gaussianFilter, samples, GMSKVals, RX_BLOCK_SIZE); @@ -371,6 +407,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; @@ -417,10 +456,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; @@ -430,6 +470,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; @@ -442,6 +483,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 48373ec..0f1f62d 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); @@ -80,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; @@ -110,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..6c326c5 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) 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 +P25 PC10 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_P25 GPIO_Pin_10 +#define PORT_P25 GPIOC +#define RCC_Per_P25 RCC_AHB1Periph_GPIOC + #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 PC10 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_10 +#define PORT_NXDN GPIOC +#define RCC_Per_NXDN RCC_AHB1Periph_GPIOC + #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 PC10 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_10 +#define PORT_NXDN GPIOC +#define RCC_Per_NXDN RCC_AHB1Periph_GPIOC + #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 PB2 output CN10 Pin32 MDSTAR PC4 output CN10 Pin34 MDMR PC5 output CN10 Pin6 MYSF PC2 output CN7 Pin35 MP25 PC3 output CN7 Pin37 +MNXDN PC6 output CN10 Pin?? 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_2 +#define PORT_NXDN GPIOB +#define RCC_Per_NXDN RCC_AHB1Periph_GPIOB + #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_1 +#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 PC? 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_6 +#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 PB2 output CN12 Pin32 MDSTAR PC4 output CN12 Pin34 MDMR PC5 output CN12 Pin6 MYSF PC2 output CN11 Pin35 MP25 PC3 output CN11 Pin37 +MNXDN PC6 output CN11 Pin?? 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_2 +#define PORT_NXDN GPIOB +#define RCC_Per_NXDN RCC_AHB1Periph_GPIOB + #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 } @@ -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..a2e9433 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,6 +53,9 @@ CYSFTX ysfTX; CP25RX p25RX; CP25TX p25TX; +CNXDNRX nxdnRX; +CNXDNTX nxdnTX; + CCalDStarRX calDStarRX; CCalDStarTX calDStarTX; CCalDMR calDMR; @@ -91,6 +95,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_DSTARCAL) calDStarTX.process(); @@ -113,4 +120,3 @@ int main() } #endif - diff --git a/MMDVM.ino b/MMDVM.ino index d132f2a..af74591 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,6 +50,9 @@ CYSFTX ysfTX; CP25RX p25RX; CP25TX p25TX; +CNXDNRX nxdnRX; +CNXDNTX nxdnTX; + CCalDStarRX calDStarRX; CCalDStarTX calDStarTX; CCalDMR calDMR; @@ -88,6 +92,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_DSTARCAL) calDStarTX.process(); diff --git a/NXDNDefines.h b/NXDNDefines.h new file mode 100644 index 0000000..1253b8e --- /dev/null +++ b/NXDNDefines.h @@ -0,0 +1,54 @@ +/* + * 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 = 5U; // At 24 kHz sample rate + +const unsigned int NXDN_FRAME_LENGTH_BYTES = 120U; +const unsigned int NXDN_FRAME_LENGTH_BITS = NXDN_FRAME_LENGTH_BYTES * 8U; +const unsigned int NXDN_FRAME_LENGTH_SYMBOLS = NXDN_FRAME_LENGTH_BYTES * 4U; +const unsigned int NXDN_FRAME_LENGTH_SAMPLES = NXDN_FRAME_LENGTH_SYMBOLS * NXDN_RADIO_SYMBOL_LENGTH; + +const unsigned int NXDN_SYNC_LENGTH_BYTES = 5U; +const unsigned int NXDN_SYNC_LENGTH_BITS = NXDN_SYNC_LENGTH_BYTES * 8U; +const unsigned int NXDN_SYNC_LENGTH_SYMBOLS = NXDN_SYNC_LENGTH_BYTES * 4U; +const unsigned int NXDN_SYNC_LENGTH_SAMPLES = NXDN_SYNC_LENGTH_SYMBOLS * NXDN_RADIO_SYMBOL_LENGTH; + +const unsigned int NXDN_FICH_LENGTH_BITS = 200U; +const unsigned int NXDN_FICH_LENGTH_SYMBOLS = 100U; +const unsigned int NXDN_FICH_LENGTH_SAMPLES = NXDN_FICH_LENGTH_SYMBOLS * NXDN_RADIO_SYMBOL_LENGTH; + +const uint8_t NXDN_SYNC_BYTES[] = {0xD4U, 0x71U, 0xC9U, 0x63U, 0x4DU}; +const uint8_t NXDN_SYNC_BYTES_LENGTH = 5U; + +const uint64_t NXDN_SYNC_BITS = 0x000000D471C9634DU; +const uint64_t NXDN_SYNC_BITS_MASK = 0x000000FFFFFFFFFFU; + +// D 4 7 1 C 9 6 3 4 D +// 11 01 01 00 01 11 00 01 11 00 10 01 01 10 00 11 01 00 11 01 +// -3 +3 +3 +1 +3 -3 +1 +3 -3 +1 -1 +3 +3 -1 +3 -3 +3 +1 -3 +3 + +const int8_t NXDN_SYNC_SYMBOLS_VALUES[] = {-3, +3, +3, +1, +3, -3, +1, +3, -3, +1, -1, +3, +3, -1, +3, -3, +3, +1, -3, +3}; + +const uint32_t NXDN_SYNC_SYMBOLS = 0x0007B5ADU; +const uint32_t NXDN_SYNC_SYMBOLS_MASK = 0x000FFFFFU; + +#endif + diff --git a/NXDNRX.cpp b/NXDNRX.cpp new file mode 100644 index 0000000..360a3a8 --- /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_SYNC_BIT_START_ERRS = 2U; +const uint8_t MAX_SYNC_BIT_RUN_ERRS = 4U; + +const uint8_t MAX_SYNC_SYMBOLS_ERRS = 3U; + +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_SYNC_FRAMES = 4U + 1U; + +CNXDNRX::CNXDNRX() : +m_state(NXDNRXS_NONE), +m_bitBuffer(), +m_buffer(), +m_bitPtr(0U), +m_dataPtr(0U), +m_startPtr(NOENDPTR), +m_endPtr(NOENDPTR), +m_syncPtr(NOENDPTR), +m_minSyncPtr(NOENDPTR), +m_maxSyncPtr(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_syncPtr = NOENDPTR; + m_minSyncPtr = NOENDPTR; + m_maxSyncPtr = 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 = correlateSync(); + 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_minSyncPtr = m_syncPtr + NXDN_FRAME_LENGTH_SAMPLES - 1U; + if (m_minSyncPtr >= NXDN_FRAME_LENGTH_SAMPLES) + m_minSyncPtr -= NXDN_FRAME_LENGTH_SAMPLES; + + m_maxSyncPtr = m_syncPtr + 1U; + if (m_maxSyncPtr >= NXDN_FRAME_LENGTH_SAMPLES) + m_maxSyncPtr -= NXDN_FRAME_LENGTH_SAMPLES; + + m_state = NXDNRXS_DATA; + m_countdown = 0U; + } +} + +void CNXDNRX::processData(q15_t sample) +{ + if (m_minSyncPtr < m_maxSyncPtr) { + if (m_dataPtr >= m_minSyncPtr && m_dataPtr <= m_maxSyncPtr) + correlateSync(); + } else { + if (m_dataPtr >= m_minSyncPtr || m_dataPtr <= m_maxSyncPtr) + correlateSync(); + } + + if (m_dataPtr == m_endPtr) { + // Only update the centre and threshold if they are from a good sync + if (m_lostCount == MAX_SYNC_FRAMES) { + m_minSyncPtr = m_syncPtr + NXDN_FRAME_LENGTH_SAMPLES - 1U; + if (m_minSyncPtr >= NXDN_FRAME_LENGTH_SAMPLES) + m_minSyncPtr -= NXDN_FRAME_LENGTH_SAMPLES; + + m_maxSyncPtr = m_syncPtr + 1U; + if (m_maxSyncPtr >= NXDN_FRAME_LENGTH_SAMPLES) + m_maxSyncPtr -= NXDN_FRAME_LENGTH_SAMPLES; + } + + calculateLevels(m_startPtr, NXDN_FRAME_LENGTH_SYMBOLS); + + DEBUG4("NXDNRX: sync found pos/centre/threshold", m_syncPtr, 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_SYNC_FRAMES - 1U) ? 0x01U : 0x00U; + writeRSSIData(frame); + m_maxCorr = 0; + } + } +} + +bool CNXDNRX::correlateSync() +{ + if (countBits32((m_bitBuffer[m_bitPtr] & NXDN_SYNC_SYMBOLS_MASK) ^ NXDN_SYNC_SYMBOLS) <= MAX_SYNC_SYMBOLS_ERRS) { + uint16_t ptr = m_dataPtr + NXDN_FRAME_LENGTH_SAMPLES - NXDN_SYNC_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_SYNC_LENGTH_SYMBOLS; i++) { + q15_t val = m_buffer[ptr]; + + if (val > max) + max = val; + if (val < min) + min = val; + + switch (NXDN_SYNC_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_SYNC_LENGTH_SAMPLES + NXDN_RADIO_SYMBOL_LENGTH; + if (startPtr >= NXDN_FRAME_LENGTH_SAMPLES) + startPtr -= NXDN_FRAME_LENGTH_SAMPLES; + + uint8_t sync[NXDN_SYNC_BYTES_LENGTH]; + samplesToBits(startPtr, NXDN_SYNC_LENGTH_SYMBOLS, sync, 0U, m_centreVal, m_thresholdVal); + + uint8_t maxErrs; + if (m_state == NXDNRXS_NONE) + maxErrs = MAX_SYNC_BIT_START_ERRS; + else + maxErrs = MAX_SYNC_BIT_RUN_ERRS; + + uint8_t errs = 0U; + for (uint8_t i = 0U; i < NXDN_SYNC_BYTES_LENGTH; i++) + errs += countBits8(sync[i] ^ NXDN_SYNC_BYTES[i]); + + if (errs <= maxErrs) { + m_maxCorr = corr; + m_lostCount = MAX_SYNC_FRAMES; + m_syncPtr = m_dataPtr; + + m_startPtr = startPtr; + + m_endPtr = m_dataPtr + NXDN_FRAME_LENGTH_SAMPLES - NXDN_SYNC_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[121U] = (rssi >> 8) & 0xFFU; + data[122U] = (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..7fc81f6 --- /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; + uint32_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_syncPtr; + uint16_t m_minSyncPtr; + uint16_t m_maxSyncPtr; + 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 correlateSync(); + 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..d450e98 --- /dev/null +++ b/NXDNTX.cpp @@ -0,0 +1,150 @@ +/* + * 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, 5, 'sqrt') in MATLAB +static q15_t RRC_0_2_FILTER[] = {0, 0, 0, 0, 850, 219, -720, -1548, -1795, -1172, 237, 1927, 3120, 3073, 1447, -1431, -4544, -6442, + -5735, -1633, 5651, 14822, 23810, 30367, 32767, 30367, 23810, 14822, 5651, -1633, -5735, -6442, + -4544, -1431, 1447, 3073, 3120, 1927, 237, -1172, -1795, -1548, -720, 219, 850}; // numTaps = 45, L = 5 +const uint16_t RRC_0_2_FILTER_PHASE_LEN = 9U; // phaseLength = numTaps/L + +const q15_t NXDN_LEVELA = 1893; +const q15_t NXDN_LEVELB = 631; +const q15_t NXDN_LEVELC = -631; +const q15_t NXDN_LEVELD = -1893; + +const uint8_t NXDN_START_SYNC = 0x77U; +const uint8_t NXDN_END_SYNC = 0xFFU; + +CNXDNTX::CNXDNTX() : +m_buffer(1500U), +m_modFilter(), +m_modState(), +m_poBuffer(), +m_poLen(0U), +m_poPtr(0U), +m_txDelay(240U) // 200ms +{ + ::memset(m_modState, 0x00U, 16U * 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; +} + +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_START_SYNC; + } 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 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, outBuffer, 4U); + + io.write(STATE_NXDN, outBuffer, NXDN_RADIO_SYMBOL_LENGTH * 4U); +} + +void CNXDNTX::setTXDelay(uint8_t delay) +{ + m_txDelay = 600U + uint16_t(delay) * 12U; // 500ms + tx delay + + if (m_txDelay > 1200U) + m_txDelay = 1200U; +} + +uint8_t CNXDNTX::getSpace() const +{ + return m_buffer.getSpace() / YSF_FRAME_LENGTH_BYTES; +} + diff --git a/NXDNTX.h b/NXDNTX.h new file mode 100644 index 0000000..f98cf48 --- /dev/null +++ b/NXDNTX.h @@ -0,0 +1,51 @@ +/* + * 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; + q15_t m_modState[16U]; // blockSize + phaseLength - 1, 4 + 9 - 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/SerialPort.cpp b/SerialPort.cpp index d734013..231ff30 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 20170501 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 20170501 (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) 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) 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,6 +436,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dstarRX.reset(); ysfRX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_DMRCAL1K: @@ -410,6 +447,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dstarRX.reset(); ysfRX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; case STATE_P25CAL1K: @@ -420,6 +458,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) dstarRX.reset(); ysfRX.reset(); p25RX.reset(); + nxdnRX.reset(); cwIdTX.reset(); break; default: @@ -682,6 +721,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++) @@ -949,6 +1002,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 +