diff --git a/DMRDMORX.cpp b/DMRDMORX.cpp new file mode 100644 index 0000000..90003f1 --- /dev/null +++ b/DMRDMORX.cpp @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2009-2016 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. + */ + +#define WANT_DEBUG + +#include "Config.h" +#include "Globals.h" +#include "DMRDMORX.h" +#include "DMRSlotType.h" +#include "Utils.h" + +const q15_t SCALING_FACTOR = 19505; // Q15(0.60) + +const uint8_t MAX_SYNC_SYMBOLS_ERRS = 2U; +const uint8_t MAX_SYNC_BYTES_ERRS = 3U; + +const uint8_t MAX_SYNC_LOST_FRAMES = 13U; + +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 uint16_t NOENDPTR = 9999U; + +const uint8_t CONTROL_NONE = 0x00U; +const uint8_t CONTROL_VOICE = 0x20U; +const uint8_t CONTROL_DATA = 0x40U; + +CDMRDMORX::CDMRDMORX() : +m_bitBuffer(), +m_buffer(), +m_bitPtr(0U), +m_dataPtr(0U), +m_syncPtr(0U), +m_startPtr(0U), +m_endPtr(NOENDPTR), +m_maxCorr(0), +m_centre(), +m_threshold(), +m_averagePtr(0U), +m_control(CONTROL_NONE), +m_syncCount(0U), +m_colorCode(0U), +m_state(DMORXS_NONE), +m_n(0U), +m_type(0U), +m_rssiCount(0U), +m_rssi(0U) +{ +} + +void CDMRDMORX::reset() +{ + m_syncPtr = 0U; + m_maxCorr = 0; + m_control = CONTROL_NONE; + m_syncCount = 0U; + m_state = DMORXS_NONE; + m_startPtr = 0U; + m_endPtr = NOENDPTR; + m_rssiCount = 0U; +} + +void CDMRDMORX::samples(const q15_t* samples, uint8_t length) +{ + bool dcd = false; + + for (uint8_t i = 0U; i < length; i++) + dcd = processSample(samples[i]); + + io.setDecode(dcd); +} + +bool CDMRDMORX::processSample(q15_t sample) +{ + m_buffer[m_dataPtr] = sample; + + m_bitBuffer[m_bitPtr] <<= 1; + if (sample < 0) + m_bitBuffer[m_bitPtr] |= 0x01U; + + if (m_state == DMORXS_NONE) { + correlateSync(true); + } else { +#if defined(SEND_RSSI_DATA) + // Grab the RSSI data during the frame + if (m_state == DMORXS_VOICE && m_dataPtr == m_syncPtr && m_rssiCount == 2U) + m_rssi = io.getRSSIValue(); +#endif + uint16_t min = m_syncPtr + DMO_BUFFER_LENGTH_SAMPLES - 1U; + uint16_t max = m_syncPtr + 1U; + + if (min >= DMO_BUFFER_LENGTH_SAMPLES) + min -= DMO_BUFFER_LENGTH_SAMPLES; + if (max >= DMO_BUFFER_LENGTH_SAMPLES) + max -= DMO_BUFFER_LENGTH_SAMPLES; + + if (min < max) { + if (m_dataPtr >= min && m_dataPtr <= max) + correlateSync(false); + } else { + if (m_dataPtr >= min || m_dataPtr <= max) + correlateSync(false); + } + } + + if (m_dataPtr == m_endPtr) { + // Find the average centre and threshold values + q15_t centre = (m_centre[0U] + m_centre[1U] + m_centre[2U] + m_centre[3U]) >> 2; + q15_t threshold = (m_threshold[0U] + m_threshold[1U] + m_threshold[2U] + m_threshold[3U]) >> 2; + + uint8_t frame[DMR_FRAME_LENGTH_BYTES + 3U]; + frame[0U] = m_control; + + uint16_t ptr = m_endPtr + DMO_BUFFER_LENGTH_SAMPLES - DMR_FRAME_LENGTH_SAMPLES + DMR_RADIO_SYMBOL_LENGTH + 1U; + if (ptr >= DMO_BUFFER_LENGTH_SAMPLES) + ptr -= DMO_BUFFER_LENGTH_SAMPLES; + + samplesToBits(ptr, DMR_FRAME_LENGTH_SYMBOLS, frame, 8U, centre, threshold); + + if (m_control == CONTROL_DATA) { + // Data sync + uint8_t colorCode; + uint8_t dataType; + CDMRSlotType slotType; + slotType.decode(frame + 1U, colorCode, dataType); + + if (colorCode == m_colorCode) { + m_syncCount = 0U; + m_n = 0U; + + frame[0U] |= dataType; + + switch (dataType) { + case DT_DATA_HEADER: + DEBUG4("DMRDMORX: data header found pos/centre/threshold", m_syncPtr, centre, threshold); + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); + m_state = DMORXS_DATA; + m_type = 0x00U; + break; + case DT_RATE_12_DATA: + case DT_RATE_34_DATA: + case DT_RATE_1_DATA: + if (m_state == DMORXS_DATA) { + DEBUG4("DMRDMORX: data payload found pos/centre/threshold", m_syncPtr, centre, threshold); + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); + m_type = dataType; + } + break; + case DT_VOICE_LC_HEADER: + DEBUG4("DMRDMORX: voice header found pos/centre/threshold", m_syncPtr, centre, threshold); + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); + m_state = DMORXS_VOICE; + break; + case DT_VOICE_PI_HEADER: + if (m_state == DMORXS_VOICE) { + DEBUG4("DMRDMORX: voice pi header found pos/centre/threshold", m_syncPtr, centre, threshold); + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); + } + m_state = DMORXS_VOICE; + break; + case DT_TERMINATOR_WITH_LC: + if (m_state == DMORXS_VOICE) { + DEBUG4("DMRDMORX: voice terminator found pos/centre/threshold", m_syncPtr, centre, threshold); + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); + reset(); + } + break; + default: // DT_CSBK + DEBUG4("DMRDMORX: csbk found pos/centre/threshold", m_syncPtr, centre, threshold); + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); + reset(); + break; + } + } + } else if (m_control == CONTROL_VOICE) { + // Voice sync + DEBUG4("DMRDMORX: voice sync found pos/centre/threshold", m_syncPtr, centre, threshold); +#if defined(SEND_RSSI_DATA) + // Send RSSI data approximately every second + if (m_rssiCount == 2U) { + frame[34U] = (m_rssi >> 8) & 0xFFU; + frame[35U] = (m_rssi >> 0) & 0xFFU; + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 3U); + } else { + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); + } + + m_rssiCount++; + if (m_rssiCount >= 16U) + m_rssiCount = 0U; +#else + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); +#endif + m_state = DMORXS_VOICE; + m_syncCount = 0U; + m_n = 0U; + } else { + if (m_state != DMORXS_NONE) { + m_syncCount++; + if (m_syncCount >= MAX_SYNC_LOST_FRAMES) { + serial.writeDMRLost(true); + reset(); + } + } + + if (m_state == DMORXS_VOICE) { + if (m_n >= 5U) { + frame[0U] = CONTROL_VOICE; + m_n = 0U; + } else { + frame[0U] = ++m_n; + } +#if defined(SEND_RSSI_DATA) + // Send RSSI data approximately every second + if (m_rssiCount == 2U) { + frame[34U] = (m_rssi >> 8) & 0xFFU; + frame[35U] = (m_rssi >> 0) & 0xFFU; + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 3U); + } else { + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); + } + + m_rssiCount++; + if (m_rssiCount >= 16U) + m_rssiCount = 0U; +#else + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); +#endif + } else if (m_state == DMORXS_DATA) { + if (m_type != 0x00U) { + frame[0U] = CONTROL_DATA | m_type; + serial.writeDMRData(true, frame, DMR_FRAME_LENGTH_BYTES + 1U); + } + } + } + + // End of this slot, reset some items for the next slot. + m_maxCorr = 0; + m_control = CONTROL_NONE; + } + + m_dataPtr++; + if (m_dataPtr >= DMO_BUFFER_LENGTH_SAMPLES) + m_dataPtr = 0U; + + m_bitPtr++; + if (m_bitPtr >= DMR_RADIO_SYMBOL_LENGTH) + m_bitPtr = 0U; + + return m_state != DMORXS_NONE; +} + +void CDMRDMORX::correlateSync(bool first) +{ + uint8_t errs1 = countBits32((m_bitBuffer[m_bitPtr] & DMR_SYNC_SYMBOLS_MASK) ^ DMR_S2_DATA_SYNC_SYMBOLS); + uint8_t errs2 = countBits32((m_bitBuffer[m_bitPtr] & DMR_SYNC_SYMBOLS_MASK) ^ DMR_MS_DATA_SYNC_SYMBOLS); + + // The voice sync is the complement of the data sync + bool data1 = (errs1 <= MAX_SYNC_SYMBOLS_ERRS); + bool data2 = (errs2 <= MAX_SYNC_SYMBOLS_ERRS); + bool voice1 = (errs1 >= (DMR_SYNC_LENGTH_SYMBOLS - MAX_SYNC_SYMBOLS_ERRS)); + bool voice2 = (errs2 >= (DMR_SYNC_LENGTH_SYMBOLS - MAX_SYNC_SYMBOLS_ERRS)); + + if (data1 || data2 || voice1 || voice2) { + uint16_t ptr = m_dataPtr + DMO_BUFFER_LENGTH_SAMPLES - DMR_SYNC_LENGTH_SAMPLES + DMR_RADIO_SYMBOL_LENGTH; + if (ptr >= DMO_BUFFER_LENGTH_SAMPLES) + ptr -= DMO_BUFFER_LENGTH_SAMPLES; + + q31_t corr = 0; + q15_t min = 16000; + q15_t max = -16000; + + uint32_t mask = 0x00800000U; + for (uint8_t i = 0U; i < DMR_SYNC_LENGTH_SYMBOLS; i++, mask >>= 1) { + bool b; + if (data1 || voice1) + b = (DMR_S2_DATA_SYNC_SYMBOLS & mask) == mask; + else + b = (DMR_MS_DATA_SYNC_SYMBOLS & mask) == mask; + + if (m_buffer[ptr] > max) + max = m_buffer[ptr]; + if (m_buffer[ptr] < min) + min = m_buffer[ptr]; + + if (data1 || data2) + corr += b ? -m_buffer[ptr] : m_buffer[ptr]; + else // if (voice) + corr += b ? m_buffer[ptr] : -m_buffer[ptr]; + + ptr += DMR_RADIO_SYMBOL_LENGTH; + if (ptr >= DMO_BUFFER_LENGTH_SAMPLES) + ptr -= DMO_BUFFER_LENGTH_SAMPLES; + } + + if (corr > m_maxCorr) { + q15_t centre = (max + min) >> 1; + + q31_t v1 = (max - centre) * SCALING_FACTOR; + q15_t threshold = q15_t(v1 >> 15); + + uint8_t sync[DMR_SYNC_BYTES_LENGTH]; + uint16_t ptr = m_dataPtr + DMO_BUFFER_LENGTH_SAMPLES - DMR_SYNC_LENGTH_SAMPLES + DMR_RADIO_SYMBOL_LENGTH; + if (ptr >= DMO_BUFFER_LENGTH_SAMPLES) + ptr -= DMO_BUFFER_LENGTH_SAMPLES; + + samplesToBits(ptr, DMR_SYNC_LENGTH_SYMBOLS, sync, 4U, centre, threshold); + + if (data1 || data2) { + uint8_t errs = 0U; + for (uint8_t i = 0U; i < DMR_SYNC_BYTES_LENGTH; i++) { + if (data1) + errs += countBits8((sync[i] & DMR_SYNC_BYTES_MASK[i]) ^ DMR_S2_DATA_SYNC_BYTES[i]); + else + errs += countBits8((sync[i] & DMR_SYNC_BYTES_MASK[i]) ^ DMR_MS_DATA_SYNC_BYTES[i]); + } + + if (errs <= MAX_SYNC_BYTES_ERRS) { + if (first) { + m_threshold[0U] = m_threshold[1U] = m_threshold[2U] = m_threshold[3U] = threshold; + m_centre[0U] = m_centre[1U] = m_centre[2U] = m_centre[3U] = centre; + m_averagePtr = 0U; + m_rssiCount = 0U; + } else { + m_threshold[m_averagePtr] = threshold; + m_centre[m_averagePtr] = centre; + + m_averagePtr++; + if (m_averagePtr >= 4U) + m_averagePtr = 0U; + } + + m_maxCorr = corr; + m_control = CONTROL_DATA; + m_syncPtr = m_dataPtr; + + m_startPtr = m_dataPtr + DMO_BUFFER_LENGTH_SAMPLES - DMR_SLOT_TYPE_LENGTH_SAMPLES / 2U - DMR_INFO_LENGTH_SAMPLES / 2U - DMR_SYNC_LENGTH_SAMPLES; + if (m_startPtr >= DMO_BUFFER_LENGTH_SAMPLES) + m_startPtr -= DMO_BUFFER_LENGTH_SAMPLES; + + m_endPtr = m_dataPtr + DMR_SLOT_TYPE_LENGTH_SAMPLES / 2U + DMR_INFO_LENGTH_SAMPLES / 2U - 1U; + if (m_endPtr >= DMO_BUFFER_LENGTH_SAMPLES) + m_endPtr -= DMO_BUFFER_LENGTH_SAMPLES; + } + } else { // if (voice1 || voice2) + uint8_t errs = 0U; + for (uint8_t i = 0U; i < DMR_SYNC_BYTES_LENGTH; i++) { + if (voice1) + errs += countBits8((sync[i] & DMR_SYNC_BYTES_MASK[i]) ^ DMR_S2_VOICE_SYNC_BYTES[i]); + else + errs += countBits8((sync[i] & DMR_SYNC_BYTES_MASK[i]) ^ DMR_MS_VOICE_SYNC_BYTES[i]); + } + + if (errs <= MAX_SYNC_BYTES_ERRS) { + if (first) { + m_threshold[0U] = m_threshold[1U] = m_threshold[2U] = m_threshold[3U] = threshold; + m_centre[0U] = m_centre[1U] = m_centre[2U] = m_centre[3U] = centre; + m_averagePtr = 0U; + m_rssiCount = 0U; + } else { + m_threshold[m_averagePtr] = threshold; + m_centre[m_averagePtr] = centre; + + m_averagePtr++; + if (m_averagePtr >= 4U) + m_averagePtr = 0U; + } + + m_maxCorr = corr; + m_control = CONTROL_VOICE; + m_syncPtr = m_dataPtr; + + m_startPtr = m_dataPtr + DMO_BUFFER_LENGTH_SAMPLES - DMR_SLOT_TYPE_LENGTH_SAMPLES / 2U - DMR_INFO_LENGTH_SAMPLES / 2U - DMR_SYNC_LENGTH_SAMPLES; + if (m_startPtr >= DMO_BUFFER_LENGTH_SAMPLES) + m_startPtr -= DMO_BUFFER_LENGTH_SAMPLES; + + m_endPtr = m_dataPtr + DMR_SLOT_TYPE_LENGTH_SAMPLES / 2U + DMR_INFO_LENGTH_SAMPLES / 2U - 1U; + if (m_endPtr >= DMO_BUFFER_LENGTH_SAMPLES) + m_endPtr -= DMO_BUFFER_LENGTH_SAMPLES; + } + } + } + } +} + +void CDMRDMORX::samplesToBits(uint16_t start, uint8_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold) +{ + for (uint8_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 += DMR_RADIO_SYMBOL_LENGTH; + if (start >= DMO_BUFFER_LENGTH_SAMPLES) + start -= DMO_BUFFER_LENGTH_SAMPLES; + } +} + +void CDMRDMORX::setColorCode(uint8_t colorCode) +{ + m_colorCode = colorCode; +} + diff --git a/DMRDMORX.h b/DMRDMORX.h new file mode 100644 index 0000000..138333d --- /dev/null +++ b/DMRDMORX.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2015,2016 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(DMRDMORX_H) +#define DMRDMORX_H + +#include "Config.h" +#include "DMRDefines.h" + +const uint16_t DMO_BUFFER_LENGTH_SAMPLES = 1440U; // 60ms at 24 kHz + +enum DMORX_STATE { + DMORXS_NONE, + DMORXS_VOICE, + DMORXS_DATA +}; + +class CDMRDMORX { +public: + CDMRDMORX(); + + void samples(const q15_t* samples, uint8_t length); + + void setColorCode(uint8_t colorCode); + + void reset(); + +private: + uint32_t m_bitBuffer[DMR_RADIO_SYMBOL_LENGTH]; + q15_t m_buffer[DMO_BUFFER_LENGTH_SAMPLES]; + uint16_t m_bitPtr; + uint16_t m_dataPtr; + uint16_t m_syncPtr; + uint16_t m_startPtr; + uint16_t m_endPtr; + q31_t m_maxCorr; + q15_t m_centre[4U]; + q15_t m_threshold[4U]; + uint8_t m_averagePtr; + uint8_t m_control; + uint8_t m_syncCount; + uint8_t m_colorCode; + DMORX_STATE m_state; + uint8_t m_n; + uint8_t m_type; + uint16_t m_rssiCount; + uint16_t m_rssi; + + bool processSample(q15_t sample); + void correlateSync(bool first); + void samplesToBits(uint16_t start, uint8_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold); +}; + +#endif + diff --git a/DMRDMOTX.cpp b/DMRDMOTX.cpp new file mode 100644 index 0000000..63bc78f --- /dev/null +++ b/DMRDMOTX.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2009-2016 by Jonathan Naylor G4KLX + * Copyright (C) 2016 by Colin Durbridge G4EML + * + * 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. + */ + +// #define WANT_DEBUG + +#include "Config.h" +#include "Globals.h" +#include "DMRSlotType.h" + +#if defined(WIDE_C4FSK_FILTERS_TX) +// Generated using rcosdesign(0.2, 4, 5, 'sqrt') in MATLAB +static q15_t DMR_C4FSK_FILTER[] = {688, -680, -2158, -3060, -2724, -775, 2684, 7041, 11310, 14425, 15565, 14425, + 11310, 7041, 2684, -775, -2724, -3060, -2158, -680, 688, 0}; +const uint16_t DMR_C4FSK_FILTER_LEN = 22U; +#else +// Generated using rcosdesign(0.2, 8, 5, 'sqrt') in MATLAB +static q15_t DMR_C4FSK_FILTER[] = {401, 104, -340, -731, -847, -553, 112, 909, 1472, 1450, 683, -675, -2144, -3040, -2706, -770, 2667, 6995, + 11237, 14331, 15464, 14331, 11237, 6995, 2667, -770, -2706, -3040, -2144, -675, 683, 1450, 1472, 909, 112, + -553, -847, -731, -340, 104, 401, 0}; +const uint16_t DMR_C4FSK_FILTER_LEN = 42U; +#endif + +const q15_t DMR_LEVELA[] = { 640, 640 , 640, 640, 640}; +const q15_t DMR_LEVELB[] = { 213, 213, 213, 213, 213}; +const q15_t DMR_LEVELC[] = {-213, -213, -213, -213, -213}; +const q15_t DMR_LEVELD[] = {-640, -640, -640, -640, -640}; + + +CDMRDMOTX::CDMRDMOTX() : +m_fifo(), +m_modFilter(), +m_modState(), +m_poBuffer(), +m_poLen(0U), +m_poPtr(0U), +m_txDelay(240U), // 200ms +m_count(0U) +{ + ::memset(m_modState, 0x00U, 70U * sizeof(q15_t)); + + m_modFilter.numTaps = DMR_C4FSK_FILTER_LEN; + m_modFilter.pState = m_modState; + m_modFilter.pCoeffs = DMR_C4FSK_FILTER; +} + +void CDMRDMOTX::process() +{ + if (m_poLen == 0U && m_fifo.getData() > 0U) { + if (!m_tx) { + for (uint16_t i = 0U; i < m_txDelay; i++) + m_poBuffer[m_poLen++] = 0x00U; + } else { + for (unsigned int i = 0U; i < 72U; i++) + m_poBuffer[m_poLen++] = 0x00U; + + for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) + m_poBuffer[i] = m_fifo.get(); + } + + m_poPtr = 0U; + } + + if (m_poLen > 0U) { + uint16_t space = io.getSpace(); + + while (space > (4U * DMR_RADIO_SYMBOL_LENGTH)) { + uint8_t c = m_poBuffer[m_poPtr++]; + + writeByte(c); + + space -= 4U * DMR_RADIO_SYMBOL_LENGTH; + + if (m_poPtr >= m_poLen) { + m_poPtr = 0U; + m_poLen = 0U; + return; + } + } + } +} + +uint8_t CDMRDMOTX::writeData(const uint8_t* data, uint8_t length) +{ + if (length != (DMR_FRAME_LENGTH_BYTES + 1U)) + return 4U; + + uint16_t space = m_fifo.getSpace(); + if (space < DMR_FRAME_LENGTH_BYTES) + return 5U; + + uint8_t buffer[DMR_FRAME_LENGTH_BYTES]; + ::memcpy(buffer, data + 1U, DMR_FRAME_LENGTH_BYTES); + + // Swap the sync bytes from BS to MS + if (::memcmp(buffer + 14U, DMR_BS_DATA_SYNC_BYTES + 1U, 5U) == 0) { + ::memcpy(buffer + 14U, DMR_MS_DATA_SYNC_BYTES + 1U, 5U); + buffer[13U] &= 0xF0U; buffer[13U] |= DMR_MS_DATA_SYNC_BYTES[0U]; + buffer[19U] &= 0x0FU; buffer[19U] |= DMR_MS_DATA_SYNC_BYTES[6U]; + } else if (::memcmp(buffer + 14U, DMR_BS_VOICE_SYNC_BYTES + 1U, 5U) == 0) { + ::memcpy(buffer + 14U, DMR_MS_VOICE_SYNC_BYTES + 1U, 5U); + buffer[13U] &= 0xF0U; buffer[13U] |= DMR_MS_VOICE_SYNC_BYTES[0U]; + buffer[19U] &= 0x0FU; buffer[19U] |= DMR_MS_VOICE_SYNC_BYTES[6U]; + } + + for (uint8_t i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) + m_fifo.put(buffer[i]); + + return 0U; +} + +void CDMRDMOTX::writeByte(uint8_t c) +{ + q15_t inBuffer[DMR_RADIO_SYMBOL_LENGTH * 4U + 1U]; + q15_t outBuffer[DMR_RADIO_SYMBOL_LENGTH * 4U + 1U]; + + const uint8_t MASK = 0xC0U; + + q15_t* p = inBuffer; + for (uint8_t i = 0U; i < 4U; i++, c <<= 2, p += DMR_RADIO_SYMBOL_LENGTH) { + switch (c & MASK) { + case 0xC0U: + ::memcpy(p, DMR_LEVELA, DMR_RADIO_SYMBOL_LENGTH * sizeof(q15_t)); + break; + case 0x80U: + ::memcpy(p, DMR_LEVELB, DMR_RADIO_SYMBOL_LENGTH * sizeof(q15_t)); + break; + case 0x00U: + ::memcpy(p, DMR_LEVELC, DMR_RADIO_SYMBOL_LENGTH * sizeof(q15_t)); + break; + default: + ::memcpy(p, DMR_LEVELD, DMR_RADIO_SYMBOL_LENGTH * sizeof(q15_t)); + break; + } + } + + uint16_t blockSize = DMR_RADIO_SYMBOL_LENGTH * 4U; + + // Handle the case of the oscillator not being accurate enough + if (m_sampleCount > 0U) { + m_count += DMR_RADIO_SYMBOL_LENGTH * 4U; + + if (m_count >= m_sampleCount) { + if (m_sampleInsert) { + inBuffer[DMR_RADIO_SYMBOL_LENGTH * 4U] = inBuffer[DMR_RADIO_SYMBOL_LENGTH * 4U - 1U]; + blockSize++; + } else { + blockSize--; + } + + m_count -= m_sampleCount; + } + } + + ::arm_fir_fast_q15(&m_modFilter, inBuffer, outBuffer, blockSize); + + io.write(STATE_DMR, outBuffer, blockSize); +} + +uint16_t CDMRDMOTX::getSpace() const +{ + return m_fifo.getSpace() / (DMR_FRAME_LENGTH_BYTES + 2U); +} + +void CDMRDMOTX::setTXDelay(uint8_t delay) +{ + m_txDelay = 600U + uint16_t(delay) * 12U; // 500ms + tx delay +} + diff --git a/DMRDMOTX.h b/DMRDMOTX.h new file mode 100644 index 0000000..3650d0d --- /dev/null +++ b/DMRDMOTX.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * Copyright (C) 2016 by Colin Durbridge G4EML + * + * 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(DMRDMOTX_H) +#define DMRDMOTX_H + +#include "Config.h" +#include "DMRDefines.h" + +#include "SerialRB.h" + +class CDMRDMOTX { +public: + CDMRDMOTX(); + + uint8_t writeData(const uint8_t* data, uint8_t length); + + void process(); + + void setTXDelay(uint8_t delay); + + uint16_t getSpace() const; + +private: + CSerialRB m_fifo; + arm_fir_instance_q15 m_modFilter; + q15_t m_modState[70U]; // NoTaps + BlockSize - 1, 42 + 20 - 1 plus some spare + uint8_t m_poBuffer[80U]; + uint16_t m_poLen; + uint16_t m_poPtr; + uint16_t m_txDelay; + uint32_t m_count; + + void writeByte(uint8_t c); +}; + +#endif + diff --git a/DMRDefines.h b/DMRDefines.h index 23de540..3b2ccba 100644 --- a/DMRDefines.h +++ b/DMRDefines.h @@ -35,6 +35,10 @@ const unsigned int DMR_EMB_LENGTH_BITS = 16U; const unsigned int DMR_EMB_LENGTH_SYMBOLS = 8U; const unsigned int DMR_EMB_LENGTH_SAMPLES = DMR_EMB_LENGTH_SYMBOLS * DMR_RADIO_SYMBOL_LENGTH; +const unsigned int DMR_EMBSIG_LENGTH_BITS = 32U; +const unsigned int DMR_EMBSIG_LENGTH_SYMBOLS = 16U; +const unsigned int DMR_EMBSIG_LENGTH_SAMPLES = DMR_EMBSIG_LENGTH_SYMBOLS * DMR_RADIO_SYMBOL_LENGTH; + const unsigned int DMR_SLOT_TYPE_LENGTH_BITS = 20U; const unsigned int DMR_SLOT_TYPE_LENGTH_SYMBOLS = 10U; const unsigned int DMR_SLOT_TYPE_LENGTH_SAMPLES = DMR_SLOT_TYPE_LENGTH_SYMBOLS * DMR_RADIO_SYMBOL_LENGTH; @@ -57,18 +61,30 @@ const uint8_t DMR_MS_DATA_SYNC_BYTES[] = {0x0DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0 const uint8_t DMR_MS_VOICE_SYNC_BYTES[] = {0x07U, 0xF7U, 0xD5U, 0xDDU, 0x57U, 0xDFU, 0xD0U}; const uint8_t DMR_BS_DATA_SYNC_BYTES[] = {0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U}; const uint8_t DMR_BS_VOICE_SYNC_BYTES[] = {0x07U, 0x55U, 0xFDU, 0x7DU, 0xF7U, 0x5FU, 0x70U}; +const uint8_t DMR_S1_DATA_SYNC_BYTES[] = {0x0FU, 0x7FU, 0xDDU, 0x5DU, 0xDFU, 0xD5U, 0x50U}; +const uint8_t DMR_S1_VOICE_SYNC_BYTES[] = {0x05U, 0xD5U, 0x77U, 0xF7U, 0x75U, 0x7FU, 0xF0U}; +const uint8_t DMR_S2_DATA_SYNC_BYTES[] = {0x0DU, 0x75U, 0x57U, 0xF5U, 0xFFU, 0x7FU, 0x50U}; +const uint8_t DMR_S2_VOICE_SYNC_BYTES[] = {0x07U, 0xDFU, 0xFDU, 0x5FU, 0x55U, 0xD5U, 0xF0U}; const uint8_t DMR_SYNC_BYTES_MASK[] = {0x0FU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xF0U}; const uint64_t DMR_MS_DATA_SYNC_BITS = 0x0000D5D7F77FD757U; const uint64_t DMR_MS_VOICE_SYNC_BITS = 0x00007F7D5DD57DFDU; const uint64_t DMR_BS_DATA_SYNC_BITS = 0x0000DFF57D75DF5DU; const uint64_t DMR_BS_VOICE_SYNC_BITS = 0x0000755FD7DF75F7U; +const uint64_t DMR_S1_DATA_SYNC_BITS = 0x0000F7FDD5DDFD55U; +const uint64_t DMR_S1_VOICE_SYNC_BITS = 0x00005D577F7757FFU; +const uint64_t DMR_S2_DATA_SYNC_BITS = 0x0000D7557F5FF7F5U; +const uint64_t DMR_S2_VOICE_SYNC_BITS = 0x00007DFFD5F55D5FU; const uint64_t DMR_SYNC_BITS_MASK = 0x0000FFFFFFFFFFFFU; const uint32_t DMR_MS_DATA_SYNC_SYMBOLS = 0x0076286EU; const uint32_t DMR_MS_VOICE_SYNC_SYMBOLS = 0x0089D791U; const uint32_t DMR_BS_DATA_SYNC_SYMBOLS = 0x00439B4DU; const uint32_t DMR_BS_VOICE_SYNC_SYMBOLS = 0x00BC64B2U; +const uint32_t DMR_S1_DATA_SYNC_SYMBOLS = 0x0021751FU; +const uint32_t DMR_S1_VOICE_SYNC_SYMBOLS = 0x00DE8AE0U; +const uint32_t DMR_S2_DATA_SYNC_SYMBOLS = 0x006F8C23U; +const uint32_t DMR_S2_VOICE_SYNC_SYMBOLS = 0x009073DCU; const uint32_t DMR_SYNC_SYMBOLS_MASK = 0x00FFFFFFU; const uint8_t DT_VOICE_PI_HEADER = 0U; diff --git a/Globals.h b/Globals.h index 6ea1d22..161641a 100644 --- a/Globals.h +++ b/Globals.h @@ -45,6 +45,8 @@ enum MMDVM_STATE { #include "SerialPort.h" #include "DMRIdleRX.h" +#include "DMRDMORX.h" +#include "DMRDMOTX.h" #include "DStarRX.h" #include "DStarTX.h" #include "DMRRX.h" @@ -90,6 +92,9 @@ extern CDMRIdleRX dmrIdleRX; extern CDMRRX dmrRX; extern CDMRTX dmrTX; +extern CDMRDMORX dmrDMORX; +extern CDMRDMOTX dmrDMOTX; + extern CYSFRX ysfRX; extern CYSFTX ysfTX; diff --git a/IO.cpp b/IO.cpp index 34b4de0..5f55b9f 100644 --- a/IO.cpp +++ b/IO.cpp @@ -352,8 +352,12 @@ void CIO::process() q15_t C4FSKVals[RX_BLOCK_SIZE + 1U]; ::arm_fir_fast_q15(&m_C4FSKFilter, samples, C4FSKVals, blockSize); - if (m_dmrEnable) - dmrIdleRX.samples(C4FSKVals, blockSize); + if (m_dmrEnable) { + if (m_duplex) + dmrIdleRX.samples(C4FSKVals, blockSize); + else + dmrDMORX.samples(C4FSKVals, blockSize); + } if (m_ysfEnable) ysfRX.samples(C4FSKVals, blockSize); @@ -370,11 +374,15 @@ void CIO::process() q15_t C4FSKVals[RX_BLOCK_SIZE + 1U]; ::arm_fir_fast_q15(&m_C4FSKFilter, samples, C4FSKVals, blockSize); - // If the transmitter isn't on, use the DMR idle RX to detect the wakeup CSBKs - if (m_tx) - dmrRX.samples(C4FSKVals, control, blockSize); - else - dmrIdleRX.samples(C4FSKVals, blockSize); + if (m_duplex) { + // If the transmitter isn't on, use the DMR idle RX to detect the wakeup CSBKs + if (m_tx) + dmrRX.samples(C4FSKVals, control, blockSize); + else + dmrIdleRX.samples(C4FSKVals, blockSize); + } else { + dmrDMORX.samples(C4FSKVals, blockSize); + } } } else if (m_modemState == STATE_YSF) { if (m_ysfEnable) { diff --git a/MMDVM.cpp b/MMDVM.cpp index 669506c..49ab79a 100644 --- a/MMDVM.cpp +++ b/MMDVM.cpp @@ -44,6 +44,9 @@ CDMRIdleRX dmrIdleRX; CDMRRX dmrRX; CDMRTX dmrTX; +CDMRDMORX dmrDMORX; +CDMRDMOTX dmrDMOTX; + CYSFRX ysfRX; CYSFTX ysfTX; @@ -71,8 +74,12 @@ void loop() if (m_dstarEnable && m_modemState == STATE_DSTAR) dstarTX.process(); - if (m_dmrEnable && m_modemState == STATE_DMR) - dmrTX.process(); + if (m_dmrEnable && m_modemState == STATE_DMR) { + if (m_duplex) + dmrTX.process(); + else + dmrDMOTX.process(); + } if (m_ysfEnable && m_modemState == STATE_YSF) ysfTX.process(); diff --git a/MMDVM.ino b/MMDVM.ino index 8fa1608..2bfac98 100644 --- a/MMDVM.ino +++ b/MMDVM.ino @@ -41,6 +41,9 @@ CDMRIdleRX dmrIdleRX; CDMRRX dmrRX; CDMRTX dmrTX; +CDMRDMORX dmrDMORX; +CDMRDMOTX dmrDMOTX; + CYSFRX ysfRX; CYSFTX ysfTX; @@ -68,8 +71,12 @@ void loop() if (m_dstarEnable && m_modemState == STATE_DSTAR) dstarTX.process(); - if (m_dmrEnable && m_modemState == STATE_DMR) - dmrTX.process(); + if (m_dmrEnable && m_modemState == STATE_DMR) { + if (m_duplex) + dmrTX.process(); + else + dmrDMOTX.process(); + } if (m_ysfEnable && m_modemState == STATE_YSF) ysfTX.process(); diff --git a/SerialPort.cpp b/SerialPort.cpp index 067df37..f169bbf 100644 --- a/SerialPort.cpp +++ b/SerialPort.cpp @@ -153,8 +153,13 @@ void CSerialPort::getStatus() reply[6U] = 0U; if (m_dmrEnable) { - reply[7U] = dmrTX.getSpace1(); - reply[8U] = dmrTX.getSpace2(); + if (m_duplex) { + reply[7U] = dmrTX.getSpace1(); + reply[8U] = dmrTX.getSpace2(); + } else { + reply[7U] = 10U; + reply[8U] = dmrDMOTX.getSpace(); + } } else { reply[7U] = 0U; reply[8U] = 0U; @@ -195,7 +200,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) bool rxInvert = (data[0U] & 0x01U) == 0x01U; bool txInvert = (data[0U] & 0x02U) == 0x02U; bool pttInvert = (data[0U] & 0x04U) == 0x04U; - bool duplex = (data[0U] & 0x80U) == 0x80U; + bool simplex = (data[0U] & 0x80U) == 0x80U; bool dstarEnable = (data[1U] & 0x01U) == 0x01U; bool dmrEnable = (data[1U] & 0x02U) == 0x02U; @@ -245,14 +250,16 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) m_dstarEnable = dstarEnable; m_dmrEnable = dmrEnable; m_ysfEnable = ysfEnable; - m_duplex = duplex; + m_duplex = !simplex; dstarTX.setTXDelay(txDelay); ysfTX.setTXDelay(txDelay); + dmrDMOTX.setTXDelay(txDelay); dmrTX.setColorCode(colorCode); dmrRX.setColorCode(colorCode); dmrRX.setDelay(dmrDelay); + dmrDMORX.setColorCode(colorCode); dmrIdleRX.setColorCode(colorCode); io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel); @@ -298,6 +305,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) case STATE_DSTAR: DEBUG1("Mode set to D-Star"); dmrIdleRX.reset(); + dmrDMORX.reset(); dmrRX.reset(); ysfRX.reset(); cwIdTX.reset(); @@ -305,6 +313,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) case STATE_YSF: DEBUG1("Mode set to System Fusion"); dmrIdleRX.reset(); + dmrDMORX.reset(); dmrRX.reset(); dstarRX.reset(); cwIdTX.reset(); @@ -312,6 +321,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) case STATE_DSTARCAL: DEBUG1("Mode set to D-Star Calibrate"); dmrIdleRX.reset(); + dmrDMORX.reset(); dmrRX.reset(); dstarRX.reset(); ysfRX.reset(); @@ -320,6 +330,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) case STATE_DMRCAL: DEBUG1("Mode set to DMR Calibrate"); dmrIdleRX.reset(); + dmrDMORX.reset(); dmrRX.reset(); dstarRX.reset(); ysfRX.reset(); @@ -475,8 +486,10 @@ void CSerialPort::process() case MMDVM_DMR_DATA1: if (m_dmrEnable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_DMR) - err = dmrTX.writeData1(m_buffer + 3U, m_len - 3U); + if (m_modemState == STATE_IDLE || m_modemState == STATE_DMR) { + if (m_duplex) + err = dmrTX.writeData1(m_buffer + 3U, m_len - 3U); + } } if (err == 0U) { if (m_modemState == STATE_IDLE) @@ -489,8 +502,12 @@ void CSerialPort::process() case MMDVM_DMR_DATA2: if (m_dmrEnable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_DMR) - err = dmrTX.writeData2(m_buffer + 3U, m_len - 3U); + if (m_modemState == STATE_IDLE || m_modemState == STATE_DMR) { + if (m_duplex) + err = dmrTX.writeData2(m_buffer + 3U, m_len - 3U); + else + err = dmrDMOTX.writeData(m_buffer + 3U, m_len - 3U); + } } if (err == 0U) { if (m_modemState == STATE_IDLE)