From a904b5fb4ed49be5f6e5e0d48f67e9d7a4da68e9 Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Thu, 3 Oct 2024 16:50:54 +0100 Subject: [PATCH] Beginning od dPMR support. --- Config.h | 8 +- DPMRDefines.h | 120 +++++++++ DPMRRX.cpp | 687 +++++++++++++++++++++++++++++++++++++++++++++++++ DPMRRX.h | 82 ++++++ DPMRTX.cpp | 238 +++++++++++++++++ DPMRTX.h | 64 +++++ Globals.h | 13 +- IO.cpp | 89 +++++-- IO.h | 14 +- MMDVM.cpp | 13 +- MMDVM.ino | 13 +- SerialPort.cpp | 204 ++++++++++++++- SerialPort.h | 9 +- Version.h | 4 +- 14 files changed, 1520 insertions(+), 38 deletions(-) create mode 100644 DPMRDefines.h create mode 100644 DPMRRX.cpp create mode 100644 DPMRRX.h create mode 100644 DPMRTX.cpp create mode 100644 DPMRTX.h diff --git a/Config.h b/Config.h index 828e231..c122e49 100644 --- a/Config.h +++ b/Config.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020,2024 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 @@ -38,6 +38,9 @@ #define MODE_NXDN #define USE_NXDN_BOXCAR +// Enable dPMR support. +#define MODE_DPMR + // Enable M17 support. #define MODE_M17 @@ -125,5 +128,8 @@ // Use the D-Star and YSF LEDs for FM #define USE_ALTERNATE_FM_LEDS +// Use the DMR and YSF LEDs for dPMR +#define USE_ALTERNATE_DPMR_LEDS + #endif diff --git a/DPMRDefines.h b/DPMRDefines.h new file mode 100644 index 0000000..2169306 --- /dev/null +++ b/DPMRDefines.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016,2017,2018,2024 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(DPMRDEFINES_H) +#define DPMRDEFINES_H + +const unsigned int DPMR_RADIO_SYMBOL_LENGTH = 10U; // At 24 kHz sample rate + +const unsigned int DPMR_HEADER_FRAME_LENGTH_BITS = 312U; +const unsigned int DPMR_HEADER_FRAME_LENGTH_BYTES = DPMR_HEADER_FRAME_LENGTH_BITS / 8U; +const unsigned int DPMR_HEADER_FRAME_LENGTH_SYMBOLS = DPMR_HEADER_FRAME_LENGTH_BITS / 2U; +const unsigned int DPMR_HEADER_FRAME_LENGTH_SAMPLES = DPMR_HEADER_FRAME_LENGTH_SYMBOLS * DPMR_RADIO_SYMBOL_LENGTH; + +const unsigned int DPMR_DATA_FRAME_LENGTH_BITS = 384U; +const unsigned int DPMR_DATA_FRAME_LENGTH_BYTES = DPMR_DATA_FRAME_LENGTH_BITS / 8U; +const unsigned int DPMR_DATA_FRAME_LENGTH_SYMBOLS = DPMR_DATA_FRAME_LENGTH_BITS / 2U; +const unsigned int DPMR_DATA_FRAME_LENGTH_SAMPLES = DPMR_DATA_FRAME_LENGTH_SYMBOLS * DPMR_RADIO_SYMBOL_LENGTH; + +const unsigned int DPMR_END_FRAME_LENGTH_BITS = 96U; +const unsigned int DPMR_END_FRAME_LENGTH_BYTES = DPMR_END_FRAME_LENGTH_BITS / 8U; +const unsigned int DPMR_END_FRAME_LENGTH_SYMBOLS = DPMR_END_FRAME_LENGTH_BITS / 2U; +const unsigned int DPMR_END_FRAME_LENGTH_SAMPLES = DPMR_END_FRAME_LENGTH_SYMBOLS * DPMR_RADIO_SYMBOL_LENGTH; + +const unsigned int DPMR_FS1_LENGTH_BITS = 48U; +const unsigned int DPMR_FS1_LENGTH_SYMBOLS = DPMR_FS1_LENGTH_BITS / 2U; +const unsigned int DPMR_FS1_LENGTH_SAMPLES = DPMR_FS1_LENGTH_SYMBOLS * DPMR_RADIO_SYMBOL_LENGTH; + +const unsigned int DPMR_FS2_LENGTH_BITS = 24U; +const unsigned int DPMR_FS2_LENGTH_SYMBOLS = DPMR_FS2_LENGTH_BITS / 2U; +const unsigned int DPMR_FS2_LENGTH_SAMPLES = DPMR_FS2_LENGTH_SYMBOLS * DPMR_RADIO_SYMBOL_LENGTH; + +const unsigned int DPMR_FS3_LENGTH_BITS = 24U; +const unsigned int DPMR_FS3_LENGTH_SYMBOLS = DPMR_FS3_LENGTH_BITS / 2U; +const unsigned int DPMR_FS3_LENGTH_SAMPLES = DPMR_FS3_LENGTH_SYMBOLS * DPMR_RADIO_SYMBOL_LENGTH; + +const unsigned int DPMR_FS4_LENGTH_BITS = 48U; +const unsigned int DPMR_FS4_LENGTH_SYMBOLS = DPMR_FS4_LENGTH_BITS / 2U; +const unsigned int DPMR_FS4_LENGTH_SAMPLES = DPMR_FS4_LENGTH_SYMBOLS * DPMR_RADIO_SYMBOL_LENGTH; + +const uint8_t DPMR_FS1_BYTES[] = {0x57U, 0xFFU, 0x5FU, 0x75U, 0xD5U, 0x77U}; +const uint8_t DPMR_FS1_BYTES_LENGTH = 6U; + +const uint8_t DPMR_FS2_BYTES[] = {0x5FU, 0xF7U, 0x7DU}; +const uint8_t DPMR_FS2_BYTES_LENGTH = 3U; + +const uint8_t DPMR_FS3_BYTES[] = {0x7DU, 0xDFU, 0xF5U}; +const uint8_t DPMR_FS3_BYTES_LENGTH = 3U; + +const uint8_t DPMR_FS4_BYTES[] = {0xFDU, 0x55U, 0xF5U, 0xDFU, 0x7FU, 0xDDU}; +const uint8_t DPMR_FS4_BYTES_LENGTH = 6U; + +const uint64_t DPMR_FS1_BITS = 0x000057FF5F75D577U; +const uint64_t DPMR_FS1_BITS_MASK = 0x0000FFFFFFFFFFFFU; + +const uint64_t DPMR_FS2_BITS = 0x00000000005FF77DU; +const uint64_t DPMR_FS2_BITS_MASK = 0x0000000000FFFFFFU; + +const uint64_t DPMR_FS3_BITS = 0x00000000007DDFF5U; +const uint64_t DPMR_FS3_BITS_MASK = 0x0000000000FFFFFFU; + +const uint64_t DPMR_FS4_BITS = 0x0000FD55F5DF7FDDU; +const uint64_t DPMR_FS4_BITS_MASK = 0x0000FFFFFFFFFFFFU; + +// FS1 +// 5 5 F F 5 F 7 5 D 5 7 7 +// 01 01 01 11 11 11 11 11 01 01 11 11 01 11 01 01 11 01 01 01 01 11 01 11 +// +3 +3 +3 -3 -3 -3 -3 -3 +3 +3 -3 -3 +3 -3 +3 +3 -3 +3 +3 +3 +3 -3 +3 -3 + +const int8_t DPMR_FS1_SYMBOLS_VALUES[] = {+3, +3, +3, -3, -3, -3, -3, -3, +3, +3, -3, -3, +3, -3, +3, +3, -3, +3, +3, +3, +3, -3, +3, -3}; + +const uint32_t DPMR_FS1_SYMBOLS = 0x00E0CB7AU; +const uint32_t DPMR_FS1_SYMBOLS_MASK = 0x00FFFFFFU; + +// FS2 +// 5 F F 7 7 D +// 01 01 11 11 11 11 01 11 01 11 11 01 +// +3 +3 -3 -3 -3 -3 +3 -3 +3 -3 -3 +3 + +const int8_t DPMR_FS2_SYMBOLS_VALUES[] = {+3, +3, -3, -3, -3, -3, +3, -3, +3, -3, -3, +3}; + +const uint32_t DPMR_FS2_SYMBOLS = 0x00000C29U; +const uint32_t DPMR_FS2_SYMBOLS_MASK = 0x00000FFFU; + +// FS3 +// 7 D D F F 5 +// 01 11 11 01 11 01 11 11 11 11 01 01 +// +3 -3 -3 +3 -3 +3 -3 -3 -3 -3 +3 +3 + +const int8_t DPMR_FS3_SYMBOLS_VALUES[] = {+3, -3, -3, +3, -3, +3, -3, -3, -3, -3, +3, +3}; + +const uint32_t DPMR_FS3_SYMBOLS = 0x00000943U; +const uint32_t DPMR_FS3_SYMBOLS_MASK = 0x00000FFFU; + +// FS4 +// F D 5 5 F 5 D F 7 F D D +// 11 11 11 01 01 01 01 01 11 11 01 01 11 01 11 11 01 11 11 11 11 01 11 01 +// -3 -3 -3 +3 +3 +3 +3 +3 -3 -3 +3 +3 -3 +3 -3 -3 +3 -3 -3 -3 -3 +3 -3 +3 + +const int8_t DPMR_FS4_SYMBOLS_VALUES[] = {-3, -3, -3, +3, +3, +3, +3, +3, -3, -3, +3, +3, -3, +3, -3, -3, +3, -3, -3, -3, -3, +3, -3, +3}; + +const uint32_t DPMR_FS4_SYMBOLS = 0x001F3485U; +const uint32_t DPMR_FS4_SYMBOLS_MASK = 0x00FFFFFFU; + +#endif + diff --git a/DPMRRX.cpp b/DPMRRX.cpp new file mode 100644 index 0000000..d2560c6 --- /dev/null +++ b/DPMRRX.cpp @@ -0,0 +1,687 @@ +/* + * Copyright (C) 2009-2018,2020,2024 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" + +#if defined(MODE_DPMR) + +#include "Globals.h" +#include "DPMRRX.h" +#include "Utils.h" + +const q15_t SCALING_FACTOR = 18750; // Q15(0.55) + +const uint8_t MAX_FS_BIT_START_ERRS = 1U; +const uint8_t MAX_FS_BIT_RUN_ERRS = 3U; + +const uint8_t MAX_FS_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_FS_FRAMES = 5U + 1U; + +CDPMRRX::CDPMRRX() : +m_state(DPMRRXS_NONE), +m_bitBuffer(), +m_buffer(), +m_bitPtr(0U), +m_dataPtr(0U), +m_startPtr(NOENDPTR), +m_endPtr(NOENDPTR), +m_fsPtr(NOENDPTR), +m_minFSPtr(NOENDPTR), +m_maxFSPtr(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 CDPMRRX::reset() +{ + m_state = DPMRRXS_NONE; + m_dataPtr = 0U; + m_bitPtr = 0U; + m_maxCorr = 0; + m_averagePtr = NOAVEPTR; + m_startPtr = NOENDPTR; + m_endPtr = NOENDPTR; + m_fsPtr = NOENDPTR; + m_minFSPtr = NOENDPTR; + m_maxFSPtr = NOENDPTR; + m_centreVal = 0; + m_thresholdVal = 0; + m_lostCount = 0U; + m_countdown = 0U; + m_rssiAccum = 0U; + m_rssiCount = 0U; +} + +void CDPMRRX::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 DPMRRXS_HEADER: + processHeader(sample); + break; + case DPMRRXS_DATA: + processData(sample); + break; + case DPMRRXS_EOT: + processEOT(sample); + break; + default: + processNone(sample); + break; + } + + m_dataPtr++; + if (m_dataPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_dataPtr = 0U; + + m_bitPtr++; + if (m_bitPtr >= DPMR_RADIO_SYMBOL_LENGTH) + m_bitPtr = 0U; + } +} + +void CDPMRRX::processNone(q15_t sample) +{ + // Process the FS1/Header sync + bool ret = correlateFS1(); + 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_minFSPtr = m_fsPtr + DPMR_HEADER_FRAME_LENGTH_SAMPLES - 1U; + if (m_minFSPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_minFSPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + m_maxFSPtr = m_fsPtr + 1U; + if (m_maxFSPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_maxFSPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + m_state = DPMRRXS_HEADER; + m_countdown = 0U; + } + + // Process the FS2/Data sync + ret = correlateFS2(); + 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_minFSPtr = m_fsPtr + DPMR_DATA_FRAME_LENGTH_SAMPLES - 1U; + if (m_minFSPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_minFSPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + m_maxFSPtr = m_fsPtr + 1U; + if (m_maxFSPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_maxFSPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + m_state = DPMRRXS_DATA; + m_countdown = 0U; + } + + // Don't process FS3/End sync here +} + +void CDPMRRX::processHeader(q15_t sample) +{ + if (m_dataPtr == m_endPtr) { + m_minFSPtr = m_fsPtr + DPMR_HEADER_FRAME_LENGTH_SAMPLES - 1U; + if (m_minFSPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_minFSPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + m_maxFSPtr = m_fsPtr + 1U; + if (m_maxFSPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_maxFSPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + calculateLevels(m_startPtr, DPMR_HEADER_FRAME_LENGTH_SYMBOLS); + + DEBUG4("DPMRRX: FS1 sync found pos/centre/threshold", m_fsPtr, m_centreVal, m_thresholdVal); + + uint8_t frame[DPMR_HEADER_FRAME_LENGTH_BYTES + 3U]; + samplesToBits(m_startPtr, DPMR_HEADER_FRAME_LENGTH_SYMBOLS, frame, 8U, m_centreVal, m_thresholdVal); + + frame[0U] = 0x01U; + + writeRSSIHeader(frame); + + m_state = DPMRRXS_DATA; + m_maxCorr = 0; + } +} + +void CDPMRRX::processData(q15_t sample) +{ + if (m_minFSPtr < m_maxFSPtr) { + if (m_dataPtr >= m_minFSPtr && m_dataPtr <= m_maxFSPtr) { + correlateFS2(); + bool ret = correlateFS3(); + if (ret) + m_state = DPMRRXS_EOT; + } + } else { + if (m_dataPtr >= m_minFSPtr || m_dataPtr <= m_maxFSPtr) { + correlateFS2(); + bool ret = correlateFS3(); + if (ret) + m_state = DPMRRXS_EOT; + } + } + + if (m_dataPtr == m_endPtr) { + // Only update the centre and threshold if they are from a good sync + if (m_lostCount == MAX_FS2_FRAMES) { + m_minFSPtr = m_fsPtr + DPMR_DATA_FRAME_LENGTH_SAMPLES - 1U; + if (m_minFSPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_minFSPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + m_maxFSPtr = m_fsPtr + 1U; + if (m_maxFSPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_maxFSPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + } + + calculateLevels(m_startPtr, DPMR_DATA_FRAME_LENGTH_SYMBOLS); + + DEBUG4("DPMRRX: FS2 sync found pos/centre/threshold", m_fsPtr, m_centreVal, m_thresholdVal); + + uint8_t frame[DPMR_DATA_FRAME_LENGTH_BYTES + 3U]; + samplesToBits(m_startPtr, DPMR_DATA_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("DPMRRX: FS2 sync timed out, lost lock"); + + io.setDecode(false); + io.setADCDetection(false); + + serial.writeDPMRLost(); + + m_state = DPMRRXS_NONE; + m_endPtr = NOENDPTR; + m_averagePtr = NOAVEPTR; + m_countdown = 0U; + m_maxCorr = 0; + } else { + frame[0U] = m_lostCount == (MAX_FS2_FRAMES - 1U) ? 0x01U : 0x00U; + writeRSSIData(frame); + m_maxCorr = 0; + } + } +} + +void CDPMRRX::processEnd(q15_t sample) +{ + if (m_dataPtr == m_endPtr) { + calculateLevels(m_startPtr, DPMR_END_FRAME_LENGTH_SYMBOLS); + + DEBUG4("DPMRRX: FS3 sync found pos/centre/threshold", m_fsPtr, m_centreVal, m_thresholdVal); + + uint8_t frame[DPMR_END_FRAME_LENGTH_BYTES + 3U]; + samplesToBits(m_startPtr, DPMR_END_FRAME_LENGTH_SYMBOLS, frame, 8U, m_centreVal, m_thresholdVal); + + frame[0U] = 0x01U; + + io.setDecode(false); + io.setADCDetection(false); + + writeRSSIEnd(frame); + + m_state = DPMRRXS_NONE; + m_endPtr = NOENDPTR; + m_averagePtr = NOAVEPTR; + m_countdown = 0U; + m_maxCorr = 0; + } +} + +bool CDPMRRX::correlateFS1() +{ + if (countBits32(m_bitBuffer[m_bitPtr] ^ DPMR_FS1_SYMBOLS) <= MAX_FS1_SYMBOLS_ERRS) { + uint16_t ptr = m_dataPtr + DPMR_HEADER_FRAME_LENGTH_SAMPLES - DPMR_FS1_LENGTH_SAMPLES + DPMR_RADIO_SYMBOL_LENGTH; + if (ptr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + ptr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + q31_t corr = 0; + q15_t min = 16000; + q15_t max = -16000; + + for (uint8_t i = 0U; i < DPMR_FS1_LENGTH_SYMBOLS; i++) { + q15_t val = m_buffer[ptr]; + + if (val > max) + max = val; + if (val < min) + min = val; + + switch (DPMR_FS1_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 += DPMR_RADIO_SYMBOL_LENGTH; + if (ptr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + ptr -= DPMR_DATA_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 + DPMR_HEADER_FRAME_LENGTH_SAMPLES - DPMR_FS1_LENGTH_SAMPLES + DPMR_RADIO_SYMBOL_LENGTH; + if (startPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + startPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + uint8_t sync[DPMR_FS1_BYTES_LENGTH]; + samplesToBits(startPtr, DPMR_FS1_LENGTH_SYMBOLS, sync, 0U, m_centreVal, m_thresholdVal); + + uint8_t errs = 0U; + for (uint8_t i = 0U; i < DPMR_FS1_BYTES_LENGTH; i++) + errs += countBits8(sync[i] ^ DPMR_FS1_BYTES[i]); + + if (errs <= MAX_FS1_BIT_START_ERRS) { + m_maxCorr = corr; + m_lostCount = MAX_FS1_FRAMES; + m_fsPtr = m_dataPtr; + + m_startPtr = startPtr; + + m_endPtr = m_dataPtr + DPMR_HEADER_FRAME_LENGTH_SAMPLES - DPMR_FS1_LENGTH_SAMPLES - 1U; + if (m_endPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_endPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + return true; + } + } + } + + return false; +} + +bool CDPMRRX::correlateFS2() +{ + if (countBits32(m_bitBuffer[m_bitPtr] ^ DPMR_FS2_SYMBOLS) <= MAX_FS2_SYMBOLS_ERRS) { + uint16_t ptr = m_dataPtr + DPMR_DATA_FRAME_LENGTH_SAMPLES - DPMR_FS2_LENGTH_SAMPLES + DPMR_RADIO_SYMBOL_LENGTH; + if (ptr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + ptr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + q31_t corr = 0; + q15_t min = 16000; + q15_t max = -16000; + + for (uint8_t i = 0U; i < DPMR_FS2_LENGTH_SYMBOLS; i++) { + q15_t val = m_buffer[ptr]; + + if (val > max) + max = val; + if (val < min) + min = val; + + switch (DPMR_FS2_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 += DPMR_RADIO_SYMBOL_LENGTH; + if (ptr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + ptr -= DPMR_DATA_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 + DPMR_DATA_FRAME_LENGTH_SAMPLES - DPMR_FS2_LENGTH_SAMPLES + DPMR_RADIO_SYMBOL_LENGTH; + if (startPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + startPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + uint8_t sync[DPMR_FS2_BYTES_LENGTH]; + samplesToBits(startPtr, DPMR_FS2_LENGTH_SYMBOLS, sync, 0U, m_centreVal, m_thresholdVal); + + uint8_t maxErrs; + if (m_state == DPMRRXS_NONE) + maxErrs = MAX_FS2_BIT_START_ERRS; + else + maxErrs = MAX_FS2_BIT_RUN_ERRS; + + uint8_t errs = 0U; + for (uint8_t i = 0U; i < DPMR_FS2_BYTES_LENGTH; i++) + errs += countBits8(sync[i] ^ DPMR_FS2_BYTES[i]); + + if (errs <= maxErrs) { + m_maxCorr = corr; + m_lostCount = MAX_FS2_FRAMES; + m_fsPtr = m_dataPtr; + + m_startPtr = startPtr; + + m_endPtr = m_dataPtr + DPMR_DATA_FRAME_LENGTH_SAMPLES - DPMR_FS2_LENGTH_SAMPLES - 1U; + if (m_endPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_endPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + return true; + } + } + } + + return false; +} + +bool CDPMRRX::correlateFS3() +{ + if (countBits32(m_bitBuffer[m_bitPtr] ^ DPMR_FS3_SYMBOLS) <= MAX_FS3_SYMBOLS_ERRS) { + uint16_t ptr = m_dataPtr + DPMR_END_FRAME_LENGTH_SAMPLES - DPMR_FS3_LENGTH_SAMPLES + DPMR_RADIO_SYMBOL_LENGTH; + if (ptr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + ptr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + q31_t corr = 0; + q15_t min = 16000; + q15_t max = -16000; + + for (uint8_t i = 0U; i < DPMR_FS3_LENGTH_SYMBOLS; i++) { + q15_t val = m_buffer[ptr]; + + if (val > max) + max = val; + if (val < min) + min = val; + + switch (DPMR_FS3_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 += DPMR_RADIO_SYMBOL_LENGTH; + if (ptr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + ptr -= DPMR_DATA_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 + DPMR_END_FRAME_LENGTH_SAMPLES - DPMR_FS3_LENGTH_SAMPLES + DPMR_RADIO_SYMBOL_LENGTH; + if (startPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + startPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + uint8_t sync[DPMR_FS1_BYTES_LENGTH]; + samplesToBits(startPtr, DPMR_FS3_LENGTH_SYMBOLS, sync, 0U, m_centreVal, m_thresholdVal); + + uint8_t errs = 0U; + for (uint8_t i = 0U; i < DPMR_FS3_BYTES_LENGTH; i++) + errs += countBits8(sync[i] ^ DPMR_FS3_BYTES[i]); + + if (errs <= MAX_FS3_BIT_RUN_ERRS) { + m_maxCorr = corr; + m_lostCount = MAX_FS3_FRAMES; + m_fsPtr = m_dataPtr; + + m_startPtr = startPtr; + + m_endPtr = m_dataPtr + DPMR_END_FRAME_LENGTH_SAMPLES - DPMR_FS3_LENGTH_SAMPLES - 1U; + if (m_endPtr >= DPMR_DATA_FRAME_LENGTH_SAMPLES) + m_endPtr -= DPMR_DATA_FRAME_LENGTH_SAMPLES; + + return true; + } + } + } + + return false; +} + +void CDPMRRX::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 += DPMR_RADIO_SYMBOL_LENGTH; + if (start >= DPMR_FRAME_LENGTH_SAMPLES) + start -= DPMR_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("DPMRRX: 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 CDPMRRX::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 += DPMR_RADIO_SYMBOL_LENGTH; + if (start >= DPMR_FRAME_LENGTH_SAMPLES) + start -= DPMR_FRAME_LENGTH_SAMPLES; + } +} + +void CDPMRRX::writeRSSIHeader(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.writeDPMRHeader(data, DPMR_HEADER_FRAME_LENGTH_BYTES + 3U); + } else { + serial.writeDPMRHeader(data, DPMR_HEADER_FRAME_LENGTH_BYTES + 1U); + } +#else + serial.writeDPMRHeader(data, DPMR_HEADER_FRAME_LENGTH_BYTES + 1U); +#endif + + m_rssiAccum = 0U; + m_rssiCount = 0U; +} + +void CDPMRRX::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.writeDPMRData(data, DPMR_DATA_FRAME_LENGTH_BYTES + 3U); + } else { + serial.writeDPMRData(data, DPMR_DATA_FRAME_LENGTH_BYTES + 1U); + } +#else + serial.writeDPMRData(data, DPMR_DATA_FRAME_LENGTH_BYTES + 1U); +#endif + + m_rssiAccum = 0U; + m_rssiCount = 0U; +} + +#endif + diff --git a/DPMRRX.h b/DPMRRX.h new file mode 100644 index 0000000..5f3d546 --- /dev/null +++ b/DPMRRX.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015,2016,2017,2018,2020,2024 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" + +#if defined(MODE_DPMR) + +#if !defined(DPMRRX_H) +#define DPMRRX_H + +#include "DPMRDefines.h" + +enum DPMRRX_STATE { + DPMRRXS_NONE, + DPMRRXS_HEADER, + DPMRRXS_DATA, + DPMRRXS_END +}; + +class CDPMRRX { +public: + CDPMRRX(); + + void samples(const q15_t* samples, uint16_t* rssi, uint8_t length); + + void reset(); + +private: + DPMRRX_STATE m_state; + uint32_t m_bitBuffer[DPMR_RADIO_SYMBOL_LENGTH]; + q15_t m_buffer[DPMR_DATA_FRAME_LENGTH_SAMPLES]; + uint16_t m_bitPtr; + uint16_t m_dataPtr; + uint16_t m_startPtr; + uint16_t m_endPtr; + uint16_t m_fsPtr; + uint16_t m_minFSPtr; + uint16_t m_maxFSPtr; + 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 processHeader(q15_t sample); + void processData(q15_t sample); + void processEnd(q15_t sample); + bool correlateFS1(); + bool correlateFS2(); + bool correlateFS3(); + 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 writeRSSIHeader(uint8_t* data); + void writeRSSIData(uint8_t* data); + void writeRSSIEnd(uint8_t* data); +}; + +#endif + +#endif + diff --git a/DPMRTX.cpp b/DPMRTX.cpp new file mode 100644 index 0000000..431900d --- /dev/null +++ b/DPMRTX.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2009-2018,2020,2024 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" + +#if defined(MODE_DPMR) + +#include "Globals.h" +#include "DPMRTX.h" + +#include "DPMRDefines.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 DPMR_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 DPMR_SINC_FILTER_LEN = 22U; + +const q15_t DPMR_LEVELA = 735; +const q15_t DPMR_LEVELB = 245; +const q15_t DPMR_LEVELC = -245; +const q15_t DPMR_LEVELD = -735; + +const uint8_t DPMR_PREAMBLE[] = {0x57U, 0x75U, 0xFDU}; +const uint8_t DPMR_SYNC = 0x5FU; + +CDPMRTX::CDPMRTX() : +m_buffer(TX_BUFFER_LEN), +m_modFilter(), +m_sincFilter(), +m_modState(), +m_sincState(), +m_poBuffer(), +m_poLen(0U), +m_poPtr(0U), +m_txDelay(240U), // 200ms +m_txHang(3000U), // 5s +m_txCount(0U) +{ + ::memset(m_modState, 0x00U, 16U * sizeof(q15_t)); + ::memset(m_sincState, 0x00U, 70U * sizeof(q15_t)); + + m_modFilter.L = DPMR_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 = DPMR_SINC_FILTER_LEN; + m_sincFilter.pState = m_sincState; + m_sincFilter.pCoeffs = DPMR_SINC_FILTER; +} + +void CDPMRTX::process() +{ + if (m_poLen == 0U && m_buffer.getData() > 0U) { + if (!m_tx) { + for (uint16_t i = 0U; i < m_txDelay; i++) + m_poBuffer[m_poLen++] = DPMR_SYNC; + m_poBuffer[m_poLen++] = DPMR_PREAMBLE[0U]; + m_poBuffer[m_poLen++] = DPMR_PREAMBLE[1U]; + m_poBuffer[m_poLen++] = DPMR_PREAMBLE[2U]; + } else { + for (uint8_t i = 0U; i < DPMR_FRAME_LENGTH_BYTES; i++) { + uint8_t c = 0U; + m_buffer.get(c); + m_poBuffer[m_poLen++] = c; + } + } + + m_poPtr = 0U; + } + + if (m_poLen > 0U) { + uint16_t space = io.getSpace(); + + while (space > (4U * DPMR_RADIO_SYMBOL_LENGTH)) { + uint8_t c = m_poBuffer[m_poPtr++]; + writeByte(c); + + space -= 4U * DPMR_RADIO_SYMBOL_LENGTH; + if (m_duplex) + m_txCount = m_txHang; + + if (m_poPtr >= m_poLen) { + m_poPtr = 0U; + m_poLen = 0U; + return; + } + } + } else if (m_txCount > 0U) { + // Transmit silence until the hang timer has expired. + uint16_t space = io.getSpace(); + + while (space > (4U * DPMR_RADIO_SYMBOL_LENGTH)) { + writeSilence(); + + space -= 4U * DPMR_RADIO_SYMBOL_LENGTH; + m_txCount--; + + if (m_txCount == 0U) + return; + } + } +} + +uint8_t CDPMRTX::writeHeader(const uint8_t* data, uint16_t length) +{ + if (length != (DPMR_HEADER_FRAME_LENGTH_BYTES + 1U)) + return 4U; + + uint16_t space = m_buffer.getSpace(); + if (space < DPMR_HEADER_FRAME_LENGTH_BYTES) + return 5U; + + for (uint8_t i = 0U; i < DPMR_HEADER_FRAME_LENGTH_BYTES; i++) + m_buffer.put(data[i + 1U]); + + return 0U; +} + +uint8_t CDPMRTX::writeData(const uint8_t* data, uint16_t length) +{ + if (length != (DPMR_DATA_FRAME_LENGTH_BYTES + 1U)) + return 4U; + + uint16_t space = m_buffer.getSpace(); + if (space < DPMR_DATA_FRAME_LENGTH_BYTES) + return 5U; + + for (uint8_t i = 0U; i < DPMR_DATA_FRAME_LENGTH_BYTES; i++) + m_buffer.put(data[i + 1U]); + + return 0U; +} + +uint8_t CDPMRTX::writeEOT(const uint8_t* data, uint16_t length) +{ + if (length != (DPMR_END_FRAME_LENGTH_BYTES + 1U)) + return 4U; + + uint16_t space = m_buffer.getSpace(); + if (space < DPMR_END_FRAME_LENGTH_BYTES) + return 5U; + + for (uint8_t i = 0U; i < DPMR_END_FRAME_LENGTH_BYTES; i++) + m_buffer.put(data[i + 1U]); + + return 0U; +} + +void CDPMRTX::writeByte(uint8_t c) +{ + q15_t inBuffer[4U]; + q15_t intBuffer[DPMR_RADIO_SYMBOL_LENGTH * 4U]; + q15_t outBuffer[DPMR_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] = DPMR_LEVELA; + break; + case 0x80U: + inBuffer[i] = DPMR_LEVELB; + break; + case 0x00U: + inBuffer[i] = DPMR_LEVELC; + break; + default: + inBuffer[i] = DPMR_LEVELD; + break; + } + } + + ::arm_fir_interpolate_q15(&m_modFilter, inBuffer, intBuffer, 4U); + + ::arm_fir_fast_q15(&m_sincFilter, intBuffer, outBuffer, DPMR_RADIO_SYMBOL_LENGTH * 4U); + + io.write(STATE_DPMR, outBuffer, DPMR_RADIO_SYMBOL_LENGTH * 4U); +} + +void CDPMRTX::writeSilence() +{ + q15_t inBuffer[4U] = {0x00U, 0x00U, 0x00U, 0x00U}; + q15_t intBuffer[DPMR_RADIO_SYMBOL_LENGTH * 4U]; + q15_t outBuffer[DPMR_RADIO_SYMBOL_LENGTH * 4U]; + + ::arm_fir_interpolate_q15(&m_modFilter, inBuffer, intBuffer, 4U); + + ::arm_fir_fast_q15(&m_sincFilter, intBuffer, outBuffer, DPMR_RADIO_SYMBOL_LENGTH * 4U); + + io.write(STATE_DPMR, outBuffer, DPMR_RADIO_SYMBOL_LENGTH * 4U); +} + +void CDPMRTX::setTXDelay(uint8_t delay) +{ + m_txDelay = 300U + uint16_t(delay) * 6U; // 500ms + tx delay + + if (m_txDelay > 1200U) + m_txDelay = 1200U; +} + +uint8_t CDPMRTX::getSpace() const +{ + return m_buffer.getSpace() / DPMR_FRAME_LENGTH_BYTES; +} + +void CDPMRTX::setParams(uint8_t txHang) +{ + m_txHang = txHang * 600U; +} + +#endif + diff --git a/DPMRTX.h b/DPMRTX.h new file mode 100644 index 0000000..70c11cb --- /dev/null +++ b/DPMRTX.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015,2016,2017,2018,2020,2024 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" + +#if defined(MODE_DPMR) + +#if !defined(DPMRTX_H) +#define DPMRTX_H + +#include "RingBuffer.h" + +class CDPMRTX { +public: + CDPMRTX(); + + uint8_t writeHeader(const uint8_t* data, uint16_t length); + uint8_t writeData(const uint8_t* data, uint16_t length); + uint8_t writeEOT(const uint8_t* data, uint16_t length); + + void process(); + + void setTXDelay(uint8_t delay); + + uint8_t getSpace() const; + + void setParams(uint8_t txHang); + +private: + CRingBuffer 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; + uint32_t m_txHang; + uint32_t m_txCount; + + void writeByte(uint8_t c); + void writeSilence(); +}; + +#endif + +#endif + diff --git a/Globals.h b/Globals.h index 33b7334..15c641e 100644 --- a/Globals.h +++ b/Globals.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018,2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020,2021,2024 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 @@ -49,6 +49,7 @@ enum MMDVM_STATE { STATE_NXDN = 5, STATE_POCSAG = 6, STATE_M17 = 7, + STATE_DPMR = 8, STATE_FM = 10, STATE_AX25 = 11, @@ -79,6 +80,8 @@ enum MMDVM_STATE { #include "DMRDMOTX.h" #include "DStarRX.h" #include "DStarTX.h" +#include "DPMRRX.h" +#include "DPMRTX.h" #include "DMRRX.h" #include "DMRTX.h" #include "YSFRX.h" @@ -130,6 +133,7 @@ extern bool m_p25Enable; extern bool m_nxdnEnable; extern bool m_pocsagEnable; extern bool m_m17Enable; +extern bool m_dpmrEnable; extern bool m_fmEnable; extern bool m_ax25Enable; @@ -191,6 +195,13 @@ extern CM17TX m17TX; extern CCalM17 calM17; #endif +#if defined(MODE_DPMR) +extern CDPMRRX dpmrRX; +extern CDPMRTX dpmrTX; + +extern CCalNXDN calNXDN; +#endif + #if defined(MODE_FM) extern CFM fm; extern CCalFM calFM; diff --git a/IO.cpp b/IO.cpp index 4c973e4..19fa194 100644 --- a/IO.cpp +++ b/IO.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018,2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020,2021,2024 by Jonathan Naylor G4KLX * Copyright (C) 2015 by Jim Mclaughlin KI6ZUM * Copyright (C) 2016 by Colin Durbridge G4EML * @@ -44,12 +44,13 @@ static q15_t RRC_0_5_FILTER[] = {-147, -88, 72, 220, 223, 46, -197, -285, -79, 3 const uint16_t RRC_0_5_FILTER_LEN = 42U; #endif -#if defined(MODE_NXDN) -#if defined(USE_NXDN_BOXCAR) +#if defined(MODE_NXDN) && defined(USE_NXDN_BOXCAR) // One symbol boxcar filter static q15_t BOXCAR10_FILTER[] = {6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000}; const uint16_t BOXCAR10_FILTER_LEN = 10U; -#else +#endif + +#if defined(MODE_DPMR) || (defined(MODE_NXDN) && !defined(USE_NXDN_BOXCAR)) // Generated using rcosdesign(0.2, 8, 10, 'sqrt') in MATLAB static q15_t NXDN_0_2_FILTER[] = {284, 198, 73, -78, -240, -393, -517, -590, -599, -533, -391, -181, 79, 364, 643, 880, 1041, 1097, 1026, 819, 483, 39, -477, -1016, -1516, -1915, -2150, -2164, -1914, -1375, -545, 557, 1886, 3376, 4946, 6502, 7946, 9184, @@ -62,7 +63,6 @@ static q15_t NXDN_ISINC_FILTER[] = {790, -1085, -1073, -553, 747, 2341, 3156, 21 12354, 4441, -3102, -7536, -7834, -4915, -893, 2152, 3156, 2341, 747, -553, -1073, -1085, 790}; const uint16_t NXDN_ISINC_FILTER_LEN = 32U; #endif -#endif #if defined(MODE_DSTAR) // Generated using gaussfir(0.5, 4, 5) in MATLAB @@ -103,17 +103,16 @@ m_rrc02State2(), m_boxcar5Filter(), m_boxcar5State(), #endif -#if defined(MODE_NXDN) -#if defined(USE_NXDN_BOXCAR) +#if defined(MODE_NXDN) && defined(USE_NXDN_BOXCAR) m_boxcar10Filter(), m_boxcar10State(), -#else +#endif +#if defined(MODE_DPMR) || (defined(MODE_NXDN) && !defined(USE_NXDN_BOXCAR)) m_nxdnFilter(), m_nxdnISincFilter(), m_nxdnState(), m_nxdnISincState(), #endif -#endif #if defined(MODE_M17) m_rrc05Filter(), m_rrc05State(), @@ -127,6 +126,7 @@ m_ysfTXLevel(128 * 128), m_p25TXLevel(128 * 128), m_nxdnTXLevel(128 * 128), m_m17TXLevel(128 * 128), +m_dpmrTXLevel(128 * 128), m_pocsagTXLevel(128 * 128), m_fmTXLevel(128 * 128), m_ax25TXLevel(128 * 128), @@ -177,13 +177,14 @@ m_lockout(false) m_boxcar5Filter.pCoeffs = BOXCAR5_FILTER; #endif -#if defined(MODE_NXDN) -#if defined(USE_NXDN_BOXCAR) +#if defined(MODE_NXDN) && defined(USE_NXDN_BOXCAR) ::memset(m_boxcar10State, 0x00U, 40U * sizeof(q15_t)); m_boxcar10Filter.numTaps = BOXCAR10_FILTER_LEN; m_boxcar10Filter.pState = m_boxcar10State; m_boxcar10Filter.pCoeffs = BOXCAR10_FILTER; -#else +#endif + +#if defined(MODE_DPMR) || (defined(MODE_NXDN) && !defined(USE_NXDN_BOXCAR)) ::memset(m_nxdnState, 0x00U, 110U * sizeof(q15_t)); ::memset(m_nxdnISincState, 0x00U, 60U * sizeof(q15_t)); @@ -195,7 +196,6 @@ m_lockout(false) m_nxdnISincFilter.pState = m_nxdnISincState; m_nxdnISincFilter.pCoeffs = NXDN_ISINC_FILTER; #endif -#endif #if defined(MODE_M17) ::memset(m_rrc05State, 0x00U, 70U * sizeof(q15_t)); @@ -236,6 +236,9 @@ void CIO::selfTest() #if !defined(USE_ALTERNATE_FM_LEDS) setFMInt(ledValue); #endif +#if !defined(USE_ALTERNATE_DPMR_LEDS) + setDPMRInt(ledValue); +#endif #endif delayInt(250); } @@ -256,6 +259,9 @@ void CIO::selfTest() #endif #if !defined(USE_ALTERNATE_FM_LEDS) setFMInt(false); +#endif +#if !defined(USE_ALTERNATE_DPMR_LEDS) + setDPMRInt(false); #endif setDStarInt(true); @@ -286,9 +292,19 @@ void CIO::selfTest() #if !defined(USE_ALTERNATE_FM_LEDS) delayInt(250); setFMInt(true); +#endif + +#if !defined(USE_ALTERNATE_DPMR_LEDS) + delayInt(250); + setDPMRInt(true); delayInt(250); - setFMInt(false); + setDPMRInt(false); +#endif + +#if !defined(USE_ALTERNATE_FM_LEDS) + delayInt(250); + setFMInt(true); #endif #if !defined(USE_ALTERNATE_POCSAG_LEDS) @@ -338,7 +354,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 || m_modemState == STATE_P25 || m_modemState == STATE_NXDN || m_modemState == STATE_M17 || m_modemState == STATE_POCSAG) { + if (m_modemState == STATE_DSTAR || m_modemState == STATE_DMR || m_modemState == STATE_YSF || m_modemState == STATE_P25 || m_modemState == STATE_NXDN || m_modemState == STATE_M17 || m_modemState == STATE_DPMR || m_modemState == STATE_POCSAG) { #if defined(MODE_DMR) if (m_modemState == STATE_DMR && m_tx) dmrTX.setStart(false); @@ -502,6 +518,22 @@ void CIO::process() } #endif +#if defined(MODE_DPMR) + if (m_dpmrEnable) { + q15_t DPMRValsTmp[RX_BLOCK_SIZE]; +#if defined(USE_DCBLOCKER) + ::arm_fir_fast_q15(&m_nxdnFilter, dcSamples, DPMRValsTmp, RX_BLOCK_SIZE); +#else + ::arm_fir_fast_q15(&m_nxdnFilter, samples, DPMRValsTmp, RX_BLOCK_SIZE); +#endif + + q15_t DPMRVals[RX_BLOCK_SIZE]; + ::arm_fir_fast_q15(&m_nxdnISincFilter, DPMRValsTmp, DPMRVals, RX_BLOCK_SIZE); + + dpmrRX.samples(DPMRVals, rssi, RX_BLOCK_SIZE); + } +#endif + #if defined(MODE_FM) if (m_fmEnable) { bool cos = getCOSInt(); @@ -623,6 +655,24 @@ void CIO::process() } #endif +#if defined(MODE_DPMR) + else if (m_modemState == STATE_DPMR) { + if (m_dpmrEnable) { + q15_t DPMRValsTmp[RX_BLOCK_SIZE]; +#if defined(USE_DCBLOCKER) + ::arm_fir_fast_q15(&m_nxdnFilter, dcSamples, DPMRValsTmp, RX_BLOCK_SIZE); +#else + ::arm_fir_fast_q15(&m_nxdnFilter, samples, DPMRValsTmp, RX_BLOCK_SIZE); +#endif + + q15_t DPMRVals[RX_BLOCK_SIZE]; + ::arm_fir_fast_q15(&m_nxdnISincFilter, DPMRValsTmp, DPMRVals, RX_BLOCK_SIZE); + + dpmrRX.samples(DPMRVals, rssi, RX_BLOCK_SIZE); + } + } +#endif + #if defined(MODE_FM) else if (m_modemState == STATE_FM) { bool cos = getCOSInt(); @@ -694,6 +744,9 @@ void CIO::write(MMDVM_STATE mode, q15_t* samples, uint16_t length, const uint8_t case STATE_M17: txLevel = m_m17TXLevel; break; + case STATE_DPMR: + txLevel = m_dpmrTXLevel; + break; case STATE_POCSAG: txLevel = m_pocsagTXLevel; break; @@ -755,6 +808,7 @@ void CIO::setMode(MMDVM_STATE state) case STATE_P25: setP25Int(false); break; case STATE_NXDN: setNXDNInt(false); break; case STATE_M17: setM17Int(false); break; + case STATE_DPMR: setDPMRInt(false); break; case STATE_POCSAG: setPOCSAGInt(false); break; case STATE_FM: setFMInt(false); break; default: break; @@ -767,6 +821,7 @@ void CIO::setMode(MMDVM_STATE state) case STATE_P25: setP25Int(true); break; case STATE_NXDN: setNXDNInt(true); break; case STATE_M17: setM17Int(true); break; + case STATE_DPMR: setDPMRInt(true); break; case STATE_POCSAG: setPOCSAGInt(true); break; case STATE_FM: setFMInt(true); break; default: break; @@ -776,7 +831,7 @@ void CIO::setMode(MMDVM_STATE state) m_modemState = state; } -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, uint8_t m17TXLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, uint8_t ax25TXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout) +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, uint8_t m17TXLevel, uint8_t dpmrTXLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, uint8_t ax25TXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout) { m_pttInvert = pttInvert; @@ -788,6 +843,7 @@ void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rx m_p25TXLevel = q15_t(p25TXLevel * 128); m_nxdnTXLevel = q15_t(nxdnTXLevel * 128); m_m17TXLevel = q15_t(m17TXLevel * 128); + m_dpmrTXLevel = q15_t(dpmrTXLevel * 128); m_pocsagTXLevel = q15_t(pocsagTXLevel * 128); m_fmTXLevel = q15_t(fmTXLevel * 128); m_ax25TXLevel = q15_t(ax25TXLevel * 128); @@ -807,6 +863,7 @@ void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rx m_p25TXLevel = -m_p25TXLevel; m_nxdnTXLevel = -m_nxdnTXLevel; m_m17TXLevel = -m_m17TXLevel; + m_dpmrTXLevel = -m_dpmrTXLevel; m_pocsagTXLevel = -m_pocsagTXLevel; } } diff --git a/IO.h b/IO.h index b1cf1ab..afe9f71 100644 --- a/IO.h +++ b/IO.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018,2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020,2021,2024 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 @@ -46,7 +46,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, uint8_t nxdnTXLevel, uint8_t m17TXLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, uint8_t ax25TXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout); + 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 nxdnTXLevel, uint8_t m17TXLevel, uint8_t dpmrLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, uint8_t ax25TXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout); void getOverflow(bool& adcOverflow, bool& dacOverflow); @@ -96,17 +96,17 @@ private: q15_t m_boxcar5State[30U]; // NoTaps + BlockSize - 1, 6 + 20 - 1 plus some spare #endif -#if defined(MODE_NXDN) -#if defined(USE_NXDN_BOXCAR) +#if defined(MODE_NXDN) && defined(USE_NXDN_BOXCAR) arm_fir_instance_q15 m_boxcar10Filter; q15_t m_boxcar10State[40U]; // NoTaps + BlockSize - 1, 10 + 20 - 1 plus some spare -#else +#endif + +#if defined(MODE_DPMR) || (defined(MODE_NXDN) && !defined(USE_NXDN_BOXCAR)) arm_fir_instance_q15 m_nxdnFilter; arm_fir_instance_q15 m_nxdnISincFilter; q15_t m_nxdnState[110U]; // NoTaps + BlockSize - 1, 82 + 20 - 1 plus some spare q15_t m_nxdnISincState[60U]; // NoTaps + BlockSize - 1, 32 + 20 - 1 plus some spare #endif -#endif #if defined(MODE_M17) arm_fir_instance_q15 m_rrc05Filter; @@ -122,6 +122,7 @@ private: q15_t m_p25TXLevel; q15_t m_nxdnTXLevel; q15_t m_m17TXLevel; + q15_t m_dpmrTXLevel; q15_t m_pocsagTXLevel; q15_t m_fmTXLevel; q15_t m_ax25TXLevel; @@ -160,6 +161,7 @@ private: void setNXDNInt(bool on); void setPOCSAGInt(bool on); void setM17Int(bool on); + void setDPMRInt(bool on); void setFMInt(bool on); void delayInt(unsigned int dly); diff --git a/MMDVM.cpp b/MMDVM.cpp index abc755b..a14a538 100644 --- a/MMDVM.cpp +++ b/MMDVM.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018,2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020,2021,2024 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Mathis Schmieder DB9MAT * Copyright (C) 2016 by Colin Durbridge G4EML * @@ -32,6 +32,7 @@ bool m_ysfEnable = true; bool m_p25Enable = true; bool m_nxdnEnable = true; bool m_m17Enable = true; +bool m_dpmrEnable = true; bool m_pocsagEnable = true; bool m_fmEnable = true; bool m_ax25Enable = true; @@ -86,6 +87,11 @@ CM17TX m17TX; CCalM17 calM17; #endif +#if defined(MODE_NXDN) +CDPMRRX dpmrRX; +CDPMRTX dpmrTX; +#endif + #if defined(MODE_POCSAG) CPOCSAGTX pocsagTX; CCalPOCSAG calPOCSAG; @@ -154,6 +160,11 @@ void loop() m17TX.process(); #endif +#if defined(MODE_DPMR) + if (m_dpmrEnable && m_modemState == STATE_DPMR) + dpmrTX.process(); +#endif + #if defined(MODE_POCSAG) if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy())) pocsagTX.process(); diff --git a/MMDVM.ino b/MMDVM.ino index b8754a3..207be3a 100644 --- a/MMDVM.ino +++ b/MMDVM.ino @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018,2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020,2021,2024 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Colin Durbridge G4EML * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ bool m_ysfEnable = true; bool m_p25Enable = true; bool m_nxdnEnable = true; bool m_m17Enable = true; +bool m_dpmrEnable = true; bool m_pocsagEnable = true; bool m_fmEnable = true; bool m_ax25Enable = true; @@ -83,6 +84,11 @@ CM17TX m17TX; CCalM17 calM17; #endif +#if defined(MODE_NXDN) +CDPMRRX dpmrRX; +CDPMRTX dpmrTX; +#endif + #if defined(MODE_POCSAG) CPOCSAGTX pocsagTX; CCalPOCSAG calPOCSAG; @@ -151,6 +157,11 @@ void loop() m17TX.process(); #endif +#if defined(MODE_DPMR) + if (m_dpmrEnable && m_modemState == STATE_DPMR) + dpmrTX.process(); +#endif + #if defined(MODE_POCSAG) if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy())) pocsagTX.process(); diff --git a/SerialPort.cpp b/SerialPort.cpp index 755bc67..0e25296 100644 --- a/SerialPort.cpp +++ b/SerialPort.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013,2015-2021 by Jonathan Naylor G4KLX + * Copyright (C) 2013,2015-2021,2024 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,11 @@ 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_DPMR_HEADER = 0x35U; +const uint8_t MMDVM_DPMR_DATA = 0x36U; +const uint8_t MMDVM_DPMR_LOST = 0x37U; +const uint8_t MMDVM_DPMR_EOT = 0x38U; + const uint8_t MMDVM_NXDN_DATA = 0x40U; const uint8_t MMDVM_NXDN_LOST = 0x41U; @@ -300,7 +305,15 @@ void CSerialPort::getStatus() reply[15U] = 0U; #endif - reply[16U] = 0x00U; +#if defined(MODE_DPMR) + if (m_dpmrEnable) + reply[16U] = dpmrTX.getSpace(); + else + reply[16U] = 0U; +#else + reply[16U] = 0U; +#endif + reply[17U] = 0x00U; reply[18U] = 0x00U; reply[19U] = 0x00U; @@ -341,6 +354,9 @@ void CSerialPort::getVersion() #if defined(MODE_FM) reply[4U] |= 0x40U; #endif +#if defined(MODE_DPMR) + reply[4U] |= 0x80U; +#endif reply[5U] = 0x00U; #if defined(MODE_POCSAG) @@ -403,6 +419,9 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) #if defined(MODE_M17) bool m17Enable = (data[1U] & 0x40U) == 0x40U; #endif +#if defined(MODE_DPMR) + bool dpmrEnable = (data[1U] & 0x80U) == 0x80U; +#endif #if defined(MODE_POCSAG) bool pocsagEnable = (data[2U] & 0x01U) == 0x01U; #endif @@ -416,9 +435,13 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) MMDVM_STATE modemState = MMDVM_STATE(data[4U]); - if (modemState != STATE_IDLE && modemState != STATE_DSTAR && modemState != STATE_DMR && modemState != STATE_YSF && modemState != STATE_P25 && modemState != STATE_NXDN && modemState != STATE_M17 && modemState != STATE_POCSAG && modemState != STATE_FM && - modemState != STATE_DSTARCAL && modemState != STATE_DMRCAL && modemState != STATE_RSSICAL && modemState != STATE_LFCAL && modemState != STATE_DMRCAL1K && modemState != STATE_P25CAL1K && modemState != STATE_DMRDMO1K && modemState != STATE_NXDNCAL1K && modemState != STATE_M17CAL && modemState != STATE_POCSAGCAL && - modemState != STATE_FMCAL10K && modemState != STATE_FMCAL12K && modemState != STATE_FMCAL15K && modemState != STATE_FMCAL20K && modemState != STATE_FMCAL25K && modemState != STATE_FMCAL30K) + if (modemState != STATE_IDLE && modemState != STATE_DSTAR && modemState != STATE_DMR && modemState != STATE_YSF && + modemState != STATE_P25 && modemState != STATE_NXDN && modemState != STATE_M17 && modemState != STATE_POCSAG && + modemState != STATE_FM && modemState != STATE_DPMR && modemState != STATE_DSTARCAL && modemState != STATE_DMRCAL && + modemState != STATE_RSSICAL && modemState != STATE_LFCAL && modemState != STATE_DMRCAL1K && modemState != STATE_P25CAL1K && + modemState != STATE_DMRDMO1K && modemState != STATE_NXDNCAL1K && modemState != STATE_M17CAL && modemState != STATE_POCSAGCAL && + modemState != STATE_FMCAL10K && modemState != STATE_FMCAL12K && modemState != STATE_FMCAL15K && modemState != STATE_FMCAL20K && + modemState != STATE_FMCAL25K && modemState != STATE_FMCAL30K) return 4U; #if defined(MODE_DSTAR) @@ -469,6 +492,14 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) return 4U; #endif +#if defined(MODE_DPMR) + if (modemState == STATE_DPMR && !dpmrEnable) + return 4U; +#else + if (modemState == STATE_DPMR) + return 4U; +#endif + #if defined(MODE_POCSAG) if (modemState == STATE_POCSAG && !pocsagEnable) return 4U; @@ -500,6 +531,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) uint8_t pocsagTXLevel = data[15U]; uint8_t fmTXLevel = data[16U]; uint8_t ax25TXLevel = data[17U]; + uint8_t dpmrTXLevel = data[18U]; #if defined(MODE_YSF) uint8_t ysfTXHang = data[20U]; @@ -513,6 +545,9 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) #if defined(MODE_M17) uint8_t m17TXHang = data[23U]; #endif +#if defined(MODE_DPMR) + uint8_t dpmrTXHang = data[24U]; +#endif #if defined(MODE_DMR) uint8_t colorCode = data[26U]; @@ -570,6 +605,11 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) m17TX.setTXDelay(txDelay); m17TX.setParams(m17TXHang); #endif +#if defined(MODE_DPMR) + m_dpmrEnable = dpmrEnable; + dpmrTX.setTXDelay(txDelay); + dpmrTX.setParams(dpmrTXHang); +#endif #if defined(MODE_POCSAG) m_pocsagEnable = pocsagEnable; pocsagTX.setTXDelay(txDelay); @@ -583,7 +623,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) m_fmEnable = fmEnable; #endif - io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, m17TXLevel, pocsagTXLevel, fmTXLevel, ax25TXLevel, txDCOffset, rxDCOffset, useCOSAsLockout); + io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, m17TXLevel, dpmrTXLevel, pocsagTXLevel, fmTXLevel, ax25TXLevel, txDCOffset, rxDCOffset, useCOSAsLockout); io.start(); @@ -697,9 +737,13 @@ uint8_t CSerialPort::setMode(const uint8_t* data, uint16_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_NXDN && modemState != STATE_M17 && modemState != STATE_POCSAG && modemState != STATE_FM && - modemState != STATE_DSTARCAL && modemState != STATE_DMRCAL && modemState != STATE_RSSICAL && modemState != STATE_LFCAL && modemState != STATE_DMRCAL1K && modemState != STATE_P25CAL1K && modemState != STATE_DMRDMO1K && modemState != STATE_NXDNCAL1K && modemState != STATE_M17CAL && modemState != STATE_POCSAGCAL && - modemState != STATE_FMCAL10K && modemState != STATE_FMCAL12K && modemState != STATE_FMCAL15K && modemState != STATE_FMCAL20K && modemState != STATE_FMCAL25K && modemState != STATE_FMCAL30K) + if (modemState != STATE_IDLE && modemState != STATE_DSTAR && modemState != STATE_DMR && modemState != STATE_YSF && + modemState != STATE_P25 && modemState != STATE_NXDN && modemState != STATE_M17 && modemState != STATE_DPMR && + modemState != STATE_POCSAG && modemState != STATE_FM && modemState != STATE_DSTARCAL && modemState != STATE_DMRCAL && + modemState != STATE_RSSICAL && modemState != STATE_LFCAL && modemState != STATE_DMRCAL1K && modemState != STATE_P25CAL1K && + modemState != STATE_DMRDMO1K && modemState != STATE_NXDNCAL1K && modemState != STATE_M17CAL && modemState != STATE_POCSAGCAL && + modemState != STATE_FMCAL10K && modemState != STATE_FMCAL12K && modemState != STATE_FMCAL15K && modemState != STATE_FMCAL20K && + modemState != STATE_FMCAL25K && modemState != STATE_FMCAL30K) return 4U; #if defined(MODE_DSTAR) @@ -750,6 +794,14 @@ uint8_t CSerialPort::setMode(const uint8_t* data, uint16_t length) return 4U; #endif +#if defined(MODE_DPMR) + if (modemState == STATE_DPMR && !m_dpmrEnable) + return 4U; +#else + if (modemState == STATE_DPMR) + return 4U; +#endif + #if defined(MODE_POCSAG) if (modemState == STATE_POCSAG && !m_pocsagEnable) return 4U; @@ -792,6 +844,9 @@ void CSerialPort::setMode(MMDVM_STATE modemState) case STATE_M17: DEBUG1("Mode set to M17"); break; + case STATE_DPMR: + DEBUG1("Mode set to dPMR"); + break; case STATE_POCSAG: DEBUG1("Mode set to POCSAG"); break; @@ -881,6 +936,11 @@ void CSerialPort::setMode(MMDVM_STATE modemState) m17RX.reset(); #endif +#if defined(MODE_DPMR) + if (modemState != STATE_DPMR) + dpmrRX.reset(); +#endif + #if defined(MODE_FM) if (modemState != STATE_FM) fm.reset(); @@ -1361,6 +1421,50 @@ void CSerialPort::processMessage(uint8_t type, const uint8_t* buffer, uint16_t l break; #endif +#if defined(MODE_DPMR) + case MMDVM_DPMR_HEADER: + if (m_dpmrEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_DPMR) + err = dpmrTX.writeHeader(buffer, length); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_DPMR); + } else { + DEBUG2("Received invalid dPMR header", err); + sendNAK(type, err); + } + break; + + case MMDVM_DPMR_DATA: + if (m_dpmrEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_DPMR) + err = dpmrTX.writeData(buffer, length); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_DPMR); + } else { + DEBUG2("Received invalid dPMR data", err); + sendNAK(type, err); + } + break; + + case MMDVM_DPMR_EOT: + if (m_dpmrEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_DPMR) + err = dpmrTX.writeEOT(buffer, length); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_DPMR); + } else { + DEBUG2("Received invalid dPMR EOT", err); + sendNAK(type, err); + } + break; +#endif + #if defined(MODE_POCSAG) case MMDVM_POCSAG_DATA: if (m_pocsagEnable) { @@ -1791,6 +1895,88 @@ void CSerialPort::writeM17Lost() } #endif +#if defined(MODE_DPMR) +void CSerialPort::writeDPMRHeader(const uint8_t* data, uint8_t length) +{ + if (m_modemState != STATE_DPMR && m_modemState != STATE_IDLE) + return; + + if (!m_dpmrEnable) + return; + + uint8_t reply[130U]; + + reply[0U] = MMDVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = MMDVM_DPMR_HEADER; + + 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::writeDPMRData(const uint8_t* data, uint8_t length) +{ + if (m_modemState != STATE_DPMR && m_modemState != STATE_IDLE) + return; + + if (!m_dpmrEnable) + return; + + uint8_t reply[130U]; + + reply[0U] = MMDVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = MMDVM_DPMR_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::writeDPMREOT() +{ + if (m_modemState != STATE_DPMR && m_modemState != STATE_IDLE) + return; + + if (!m_dpmrEnable) + return; + + uint8_t reply[3U]; + + reply[0U] = MMDVM_FRAME_START; + reply[1U] = 3U; + reply[2U] = MMDVM_DPMR_EOT; + + writeInt(1U, reply, 3); +} + +void CSerialPort::writeDPMRLost() +{ + if (m_modemState != STATE_DPMR && m_modemState != STATE_IDLE) + return; + + if (!m_dpmrEnable) + return; + + uint8_t reply[3U]; + + reply[0U] = MMDVM_FRAME_START; + reply[1U] = 3U; + reply[2U] = MMDVM_DPMR_LOST; + + writeInt(1U, reply, 3); +} +#endif + #if defined(MODE_FM) void CSerialPort::writeFMData(const uint8_t* data, uint16_t length) { diff --git a/SerialPort.h b/SerialPort.h index c013a7f..510c531 100644 --- a/SerialPort.h +++ b/SerialPort.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018,2020,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020,2021,2024 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 @@ -71,6 +71,13 @@ public: void writeM17EOT(); #endif +#if defined(MODE_DPMR) + void writeDPMRHeader(const uint8_t* header, uint8_t length); + void writeDPMRData(const uint8_t* data, uint8_t length); + void writeDPMRLost(); + void writeDPMREOT(); +#endif + #if defined(MODE_AX25) void writeAX25Data(const uint8_t* data, uint16_t length); #endif diff --git a/Version.h b/Version.h index c86e21f..fcde880 100644 --- a/Version.h +++ b/Version.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020,2021,2022 by Jonathan Naylor G4KLX + * Copyright (C) 2020,2021,2022,2024 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 @@ -19,7 +19,7 @@ #if !defined(VERSION_H) #define VERSION_H -#define VERSION "20221121" +#define VERSION "20241003" #endif