From a6b6219c6d10bcc15e9bbaed4a60d5a03f920ee7 Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Wed, 7 Feb 2018 07:52:18 +0000 Subject: [PATCH] Start work on 4800 baud NXDN support. --- NXDN48Defines.h | 50 +++++ NXDNRX.cpp => NXDN48RX.cpp | 0 NXDNRX.h => NXDN48RX.h | 0 NXDNTX.cpp => NXDN48TX.cpp | 0 NXDNTX.h => NXDN48TX.h | 0 NXDN96Defines.h | 50 +++++ NXDN96RX.cpp | 398 +++++++++++++++++++++++++++++++++++++ NXDN96RX.h | 69 +++++++ NXDN96TX.cpp | 153 ++++++++++++++ NXDN96TX.h | 51 +++++ 10 files changed, 771 insertions(+) create mode 100644 NXDN48Defines.h rename NXDNRX.cpp => NXDN48RX.cpp (100%) rename NXDNRX.h => NXDN48RX.h (100%) rename NXDNTX.cpp => NXDN48TX.cpp (100%) rename NXDNTX.h => NXDN48TX.h (100%) create mode 100644 NXDN96Defines.h create mode 100644 NXDN96RX.cpp create mode 100644 NXDN96RX.h create mode 100644 NXDN96TX.cpp create mode 100644 NXDN96TX.h diff --git a/NXDN48Defines.h b/NXDN48Defines.h new file mode 100644 index 0000000..cbb7731 --- /dev/null +++ b/NXDN48Defines.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 = 5U; // 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/NXDN48RX.cpp similarity index 100% rename from NXDNRX.cpp rename to NXDN48RX.cpp diff --git a/NXDNRX.h b/NXDN48RX.h similarity index 100% rename from NXDNRX.h rename to NXDN48RX.h diff --git a/NXDNTX.cpp b/NXDN48TX.cpp similarity index 100% rename from NXDNTX.cpp rename to NXDN48TX.cpp diff --git a/NXDNTX.h b/NXDN48TX.h similarity index 100% rename from NXDNTX.h rename to NXDN48TX.h diff --git a/NXDN96Defines.h b/NXDN96Defines.h new file mode 100644 index 0000000..cbb7731 --- /dev/null +++ b/NXDN96Defines.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 = 5U; // 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/NXDN96RX.cpp b/NXDN96RX.cpp new file mode 100644 index 0000000..eab67dd --- /dev/null +++ b/NXDN96RX.cpp @@ -0,0 +1,398 @@ +/* + * 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_ERRS = 1U; + +const uint8_t MAX_FSW_SYMBOLS_ERRS = 1U; + +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 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 <= MAX_FSW_BIT_ERRS) { + 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/NXDN96RX.h b/NXDN96RX.h new file mode 100644 index 0000000..1bff2ac --- /dev/null +++ b/NXDN96RX.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/NXDN96TX.cpp b/NXDN96TX.cpp new file mode 100644 index 0000000..16e6d55 --- /dev/null +++ b/NXDN96TX.cpp @@ -0,0 +1,153 @@ +/* + * 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 = 1680; +const q15_t NXDN_LEVELB = 560; +const q15_t NXDN_LEVELC = -560; +const q15_t NXDN_LEVELD = -1680; + +const uint8_t NXDN_PREAMBLE[] = {0x57U, 0x75U, 0xFDU}; +const uint8_t NXDN_SYNC = 0x5FU; + +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_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 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/NXDN96TX.h b/NXDN96TX.h new file mode 100644 index 0000000..f98cf48 --- /dev/null +++ b/NXDN96TX.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 +