diff --git a/Config.h b/Config.h index da79210..ea3a784 100644 --- a/Config.h +++ b/Config.h @@ -78,6 +78,9 @@ // Use the YSF and P25 LEDs for NXDN // #define USE_ALTERNATE_NXDN_LEDS +// Use the D-Star and P25 LEDs for M17 +// #define USE_ALTERNATE_M17_LEDS + // Use the D-Star and DMR LEDs for POCSAG // #define USE_ALTERNATE_POCSAG_LEDS diff --git a/Globals.h b/Globals.h index 5cb147a..760ca32 100644 --- a/Globals.h +++ b/Globals.h @@ -50,6 +50,7 @@ enum MMDVM_STATE { STATE_P25 = 4, STATE_NXDN = 5, STATE_POCSAG = 6, + STATE_M17 = 7, STATE_FM = 10, // Dummy states start at 90 @@ -84,6 +85,8 @@ enum MMDVM_STATE { #include "YSFTX.h" #include "P25RX.h" #include "P25TX.h" +#include "M17RX.h" +#include "M17TX.h" #include "NXDNRX.h" #include "NXDNTX.h" #include "POCSAGTX.h" @@ -123,6 +126,7 @@ extern bool m_ysfEnable; extern bool m_p25Enable; extern bool m_nxdnEnable; extern bool m_pocsagEnable; +extern bool m_m17Enable; extern bool m_fmEnable; extern bool m_duplex; @@ -154,6 +158,9 @@ extern CNXDNTX nxdnTX; extern CPOCSAGTX pocsagTX; +extern CM17RX m17RX; +extern CM17TX m17TX; + extern CFM fm; extern CCalDStarRX calDStarRX; diff --git a/IO.cpp b/IO.cpp index 5e10feb..2aebcbb 100644 --- a/IO.cpp +++ b/IO.cpp @@ -79,6 +79,7 @@ m_dmrTXLevel(128 * 128), m_ysfTXLevel(128 * 128), m_p25TXLevel(128 * 128), m_nxdnTXLevel(128 * 128), +m_m17TXLevel(128 * 128), m_pocsagTXLevel(128 * 128), m_fmTXLevel(128 * 128), m_rxDCOffset(DC_OFFSET), @@ -147,6 +148,9 @@ void CIO::selfTest() #if !defined(USE_ALTERNATE_NXDN_LEDS) setNXDNInt(ledValue); #endif +#if !defined(USE_ALTERNATE_M17_LEDS) + setM17Int(ledValue); +#endif #if !defined(USE_ALTERNATE_POCSAG_LEDS) setPOCSAGInt(ledValue); #endif @@ -165,6 +169,9 @@ void CIO::selfTest() #if !defined(USE_ALTERNATE_NXDN_LEDS) setNXDNInt(false); #endif +#if !defined(USE_ALTERNATE_M17_LEDS) + setM17Int(false); +#endif #if !defined(USE_ALTERNATE_POCSAG_LEDS) setPOCSAGInt(false); #endif @@ -187,6 +194,11 @@ void CIO::selfTest() setNXDNInt(true); #endif +#if !defined(USE_ALTERNATE_M17_LEDS) + delayInt(250); + setM17Int(true); +#endif + #if !defined(USE_ALTERNATE_POCSAG_LEDS) delayInt(250); setPOCSAGInt(true); @@ -205,6 +217,11 @@ void CIO::selfTest() setPOCSAGInt(false); #endif +#if !defined(USE_ALTERNATE_M17_LEDS) + delayInt(250); + setM17Int(false); +#endif + #if !defined(USE_ALTERNATE_NXDN_LEDS) delayInt(250); setNXDNInt(false); @@ -242,7 +259,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_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_POCSAG) { if (m_modemState == STATE_DMR && m_tx) dmrTX.setStart(false); setMode(STATE_IDLE); @@ -353,7 +370,7 @@ void CIO::process() nxdnRX.samples(NXDNVals, rssi, RX_BLOCK_SIZE); } - if (m_dmrEnable || m_ysfEnable) { + if (m_dmrEnable || m_ysfEnable || m_m17Enable) { q15_t RRCVals[RX_BLOCK_SIZE]; ::arm_fir_fast_q15(&m_rrcFilter, samples, RRCVals, RX_BLOCK_SIZE); @@ -366,6 +383,9 @@ void CIO::process() else dmrDMORX.samples(RRCVals, rssi, RX_BLOCK_SIZE); } + + if (m_m17Enable) + m17RX.samples(RRCVals, rssi, RX_BLOCK_SIZE); } if (m_fmEnable) { @@ -434,6 +454,16 @@ void CIO::process() nxdnRX.samples(NXDNVals, rssi, RX_BLOCK_SIZE); } + } else if (m_modemState == STATE_M17) { + if (m_m17Enable) { + q15_t M17Vals[RX_BLOCK_SIZE]; +#if defined(USE_DCBLOCKER) + ::arm_fir_fast_q15(&m_rrcFilter, dcSamples, M17Vals, RX_BLOCK_SIZE); +#else + ::arm_fir_fast_q15(&m_rrcFilter, samples, M17Vals, RX_BLOCK_SIZE); +#endif + m17RX.samples(M17Vals, rssi, RX_BLOCK_SIZE); + } } else if (m_modemState == STATE_FM) { bool cos = getCOSInt(); #if defined(USE_DCBLOCKER) @@ -483,6 +513,9 @@ void CIO::write(MMDVM_STATE mode, q15_t* samples, uint16_t length, const uint8_t case STATE_NXDN: txLevel = m_nxdnTXLevel; break; + case STATE_M17: + txLevel = m_m17TXLevel; + break; case STATE_POCSAG: txLevel = m_pocsagTXLevel; break; @@ -540,6 +573,7 @@ void CIO::setMode(MMDVM_STATE state) case STATE_YSF: setYSFInt(false); break; case STATE_P25: setP25Int(false); break; case STATE_NXDN: setNXDNInt(false); break; + case STATE_M17: setM17Int(false); break; case STATE_POCSAG: setPOCSAGInt(false); break; case STATE_FM: setFMInt(false); break; } @@ -550,6 +584,7 @@ void CIO::setMode(MMDVM_STATE state) case STATE_YSF: setYSFInt(true); break; case STATE_P25: setP25Int(true); break; case STATE_NXDN: setNXDNInt(true); break; + case STATE_M17: setM17Int(true); break; case STATE_POCSAG: setPOCSAGInt(true); break; case STATE_FM: setFMInt(true); break; } @@ -558,7 +593,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 pocsagTXLevel, uint8_t fmTXLevel, 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 pocsagTXLevel, uint8_t fmTXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout) { m_pttInvert = pttInvert; @@ -569,6 +604,7 @@ void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rx m_ysfTXLevel = q15_t(ysfTXLevel * 128); m_p25TXLevel = q15_t(p25TXLevel * 128); m_nxdnTXLevel = q15_t(nxdnTXLevel * 128); + m_m17TXLevel = q15_t(m17TXLevel * 128); m_pocsagTXLevel = q15_t(pocsagTXLevel * 128); m_fmTXLevel = q15_t(fmTXLevel * 128); @@ -586,6 +622,7 @@ void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rx m_ysfTXLevel = -m_ysfTXLevel; m_p25TXLevel = -m_p25TXLevel; m_nxdnTXLevel = -m_nxdnTXLevel; + m_m17TXLevel = -m_m17TXLevel; m_pocsagTXLevel = -m_pocsagTXLevel; } } diff --git a/IO.h b/IO.h index cd4d3d1..4364a63 100644 --- a/IO.h +++ b/IO.h @@ -42,7 +42,7 @@ public: void interrupt(); - void setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, 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 pocsagTXLevel, uint8_t fmTXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout); void getOverflow(bool& adcOverflow, bool& dacOverflow); @@ -85,6 +85,7 @@ private: q15_t m_ysfTXLevel; q15_t m_p25TXLevel; q15_t m_nxdnTXLevel; + q15_t m_m17TXLevel; q15_t m_pocsagTXLevel; q15_t m_fmTXLevel; @@ -121,6 +122,7 @@ private: void setP25Int(bool on); void setNXDNInt(bool on); void setPOCSAGInt(bool on); + void setM17Int(bool on); void setFMInt(bool on); void delayInt(unsigned int dly); diff --git a/M17Defines.h b/M17Defines.h new file mode 100644 index 0000000..47b02c1 --- /dev/null +++ b/M17Defines.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016,2017,2018,2020 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(M17DEFINES_H) +#define M17DEFINES_H + +const unsigned int M17_RADIO_SYMBOL_LENGTH = 5U; // At 24 kHz sample rate + +const unsigned int M17_FRAME_LENGTH_BITS = 384U; +const unsigned int M17_FRAME_LENGTH_BYTES = M17_FRAME_LENGTH_BITS / 8U; +const unsigned int M17_FRAME_LENGTH_SYMBOLS = M17_FRAME_LENGTH_BITS / 2U; +const unsigned int M17_FRAME_LENGTH_SAMPLES = M17_FRAME_LENGTH_SYMBOLS * M17_RADIO_SYMBOL_LENGTH; + +const unsigned int M17_SYNC_LENGTH_BITS = 16U; +const unsigned int M17_SYNC_LENGTH_SYMBOLS = M17_SYNC_LENGTH_BITS / 2U; +const unsigned int M17_SYNC_LENGTH_SAMPLES = M17_SYNC_LENGTH_SYMBOLS * M17_RADIO_SYMBOL_LENGTH; + +const uint8_t M17_SYNC_BYTES[] = {0xCDU, 0xF5U}; // XXX +const uint8_t M17_SYNC_BYTES_LENGTH = 2U; + +const uint16_t M17_SYNC_BITS = 0xCDF5U; // XXX + +// C D F 5 // XXX +// 11 00 11 01 11 11 01 01 // XXX +// -3 +1 -3 +3 -3 -3 +3 +3 // XXX + +const int8_t M17_SYNC_SYMBOLS_VALUES[] = {-3, +1, -3, +3, -3, -3, +3, +3}; // XXX + +const uint16_t M17_SYNC_SYMBOLS = 0x014DU; // XXX + +#endif + diff --git a/M17RX.cpp b/M17RX.cpp new file mode 100644 index 0000000..06e9240 --- /dev/null +++ b/M17RX.cpp @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2009-2017,2020 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 "M17RX.h" +#include "Utils.h" + +const q15_t SCALING_FACTOR = 18750; // Q15(0.55) + +const uint8_t MAX_SYNC_BIT_START_ERRS = 2U; +const uint8_t MAX_SYNC_BIT_RUN_ERRS = 4U; + +const uint8_t MAX_SYNC_SYMBOLS_ERRS = 3U; + +const uint8_t BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U}; + +#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) + +const uint8_t NOAVEPTR = 99U; + +const uint16_t NOENDPTR = 9999U; + +const unsigned int MAX_SYNC_FRAMES = 1U + 1U; + +CM17RX::CM17RX() : +m_state(M17RXS_NONE), +m_bitBuffer(), +m_buffer(), +m_bitPtr(0U), +m_dataPtr(0U), +m_startPtr(NOENDPTR), +m_endPtr(NOENDPTR), +m_syncPtr(NOENDPTR), +m_minSyncPtr(NOENDPTR), +m_maxSyncPtr(NOENDPTR), +m_maxCorr(0), +m_lostCount(0U), +m_countdown(0U), +m_centre(), +m_centreVal(0), +m_threshold(), +m_thresholdVal(0), +m_averagePtr(NOAVEPTR), +m_rssiAccum(0U), +m_rssiCount(0U) +{ +} + +void CM17RX::reset() +{ + m_state = M17RXS_NONE; + m_dataPtr = 0U; + m_bitPtr = 0U; + m_maxCorr = 0; + m_averagePtr = NOAVEPTR; + m_startPtr = NOENDPTR; + m_endPtr = NOENDPTR; + m_syncPtr = NOENDPTR; + m_minSyncPtr = NOENDPTR; + m_maxSyncPtr = NOENDPTR; + m_centreVal = 0; + m_thresholdVal = 0; + m_lostCount = 0U; + m_countdown = 0U; + m_rssiAccum = 0U; + m_rssiCount = 0U; +} + +void CM17RX::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 M17RXS_DATA: + processData(sample); + break; + default: + processNone(sample); + break; + } + + m_dataPtr++; + if (m_dataPtr >= M17_FRAME_LENGTH_SAMPLES) + m_dataPtr = 0U; + + m_bitPtr++; + if (m_bitPtr >= M17_RADIO_SYMBOL_LENGTH) + m_bitPtr = 0U; + } +} + +void CM17RX::processNone(q15_t sample) +{ + bool ret = correlateSync(); + if (ret) { + // On the first sync, start the countdown to the state change + if (m_countdown == 0U) { + m_rssiAccum = 0U; + m_rssiCount = 0U; + + io.setDecode(true); + io.setADCDetection(true); + + m_averagePtr = NOAVEPTR; + + m_countdown = 5U; + } + } + + if (m_countdown > 0U) + m_countdown--; + + if (m_countdown == 1U) { + m_minSyncPtr = m_syncPtr + M17_FRAME_LENGTH_SAMPLES - 1U; + if (m_minSyncPtr >= M17_FRAME_LENGTH_SAMPLES) + m_minSyncPtr -= M17_FRAME_LENGTH_SAMPLES; + + m_maxSyncPtr = m_syncPtr + 1U; + if (m_maxSyncPtr >= M17_FRAME_LENGTH_SAMPLES) + m_maxSyncPtr -= M17_FRAME_LENGTH_SAMPLES; + + m_state = M17RXS_DATA; + m_countdown = 0U; + } +} + +void CM17RX::processData(q15_t sample) +{ + if (m_minSyncPtr < m_maxSyncPtr) { + if (m_dataPtr >= m_minSyncPtr && m_dataPtr <= m_maxSyncPtr) + correlateSync(); + } else { + if (m_dataPtr >= m_minSyncPtr || m_dataPtr <= m_maxSyncPtr) + correlateSync(); + } + + if (m_dataPtr == m_endPtr) { + // Only update the centre and threshold if they are from a good sync + if (m_lostCount == MAX_SYNC_FRAMES) { + m_minSyncPtr = m_syncPtr + M17_FRAME_LENGTH_SAMPLES - 1U; + if (m_minSyncPtr >= M17_FRAME_LENGTH_SAMPLES) + m_minSyncPtr -= M17_FRAME_LENGTH_SAMPLES; + + m_maxSyncPtr = m_syncPtr + 1U; + if (m_maxSyncPtr >= M17_FRAME_LENGTH_SAMPLES) + m_maxSyncPtr -= M17_FRAME_LENGTH_SAMPLES; + } + + calculateLevels(m_startPtr, M17_FRAME_LENGTH_SYMBOLS); + + DEBUG4("M17RX: sync found pos/centre/threshold", m_syncPtr, m_centreVal, m_thresholdVal); + + uint8_t frame[M17_FRAME_LENGTH_BYTES + 3U]; + samplesToBits(m_startPtr, M17_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("M17RX: sync timed out, lost lock"); + + io.setDecode(false); + io.setADCDetection(false); + + serial.writeM17Lost(); + + m_state = M17RXS_NONE; + m_endPtr = NOENDPTR; + m_averagePtr = NOAVEPTR; + m_countdown = 0U; + m_maxCorr = 0; + } else { + frame[0U] = m_lostCount == (MAX_SYNC_FRAMES - 1U) ? 0x01U : 0x00U; + writeRSSIData(frame); + m_maxCorr = 0; + } + } +} + +bool CM17RX::correlateSync() +{ + if (countBits16(m_bitBuffer[m_bitPtr] ^ M17_SYNC_SYMBOLS) <= MAX_SYNC_SYMBOLS_ERRS) { + uint16_t ptr = m_dataPtr + M17_FRAME_LENGTH_SAMPLES - M17_SYNC_LENGTH_SAMPLES + M17_RADIO_SYMBOL_LENGTH; + if (ptr >= M17_FRAME_LENGTH_SAMPLES) + ptr -= M17_FRAME_LENGTH_SAMPLES; + + q31_t corr = 0; + q15_t min = 16000; + q15_t max = -16000; + + for (uint8_t i = 0U; i < M17_SYNC_LENGTH_SYMBOLS; i++) { + q15_t val = m_buffer[ptr]; + + if (val > max) + max = val; + if (val < min) + min = val; + + switch (M17_SYNC_SYMBOLS_VALUES[i]) { + case +3: + corr -= (val + val + val); + break; + case +1: + corr -= val; + break; + case -1: + corr += val; + break; + default: // -3 + corr += (val + val + val); + break; + } + + ptr += M17_RADIO_SYMBOL_LENGTH; + if (ptr >= M17_FRAME_LENGTH_SAMPLES) + ptr -= M17_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 + M17_FRAME_LENGTH_SAMPLES - M17_SYNC_LENGTH_SAMPLES + M17_RADIO_SYMBOL_LENGTH; + if (startPtr >= M17_FRAME_LENGTH_SAMPLES) + startPtr -= M17_FRAME_LENGTH_SAMPLES; + + uint8_t sync[M17_SYNC_BYTES_LENGTH]; + samplesToBits(startPtr, M17_SYNC_LENGTH_SYMBOLS, sync, 0U, m_centreVal, m_thresholdVal); + + uint8_t maxErrs; + if (m_state == M17RXS_NONE) + maxErrs = MAX_SYNC_BIT_START_ERRS; + else + maxErrs = MAX_SYNC_BIT_RUN_ERRS; + + uint8_t errs = 0U; + for (uint8_t i = 0U; i < M17_SYNC_BYTES_LENGTH; i++) + errs += countBits8(sync[i] ^ M17_SYNC_BYTES[i]); + + if (errs <= maxErrs) { + m_maxCorr = corr; + m_lostCount = MAX_SYNC_FRAMES; + m_syncPtr = m_dataPtr; + + m_startPtr = startPtr; + + m_endPtr = m_dataPtr + M17_FRAME_LENGTH_SAMPLES - M17_SYNC_LENGTH_SAMPLES - 1U; + if (m_endPtr >= M17_FRAME_LENGTH_SAMPLES) + m_endPtr -= M17_FRAME_LENGTH_SAMPLES; + + return true; + } + } + } + + return false; +} + +void CM17RX::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 += M17_RADIO_SYMBOL_LENGTH; + if (start >= M17_FRAME_LENGTH_SAMPLES) + start -= M17_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("M17RX: 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 CM17RX::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 += M17_RADIO_SYMBOL_LENGTH; + if (start >= M17_FRAME_LENGTH_SAMPLES) + start -= M17_FRAME_LENGTH_SAMPLES; + } +} + +void CM17RX::writeRSSIData(uint8_t* data) +{ +#if defined(SEND_RSSI_DATA) + if (m_rssiCount > 0U) { + uint16_t rssi = m_rssiAccum / m_rssiCount; + + data[121U] = (rssi >> 8) & 0xFFU; + data[122U] = (rssi >> 0) & 0xFFU; + + serial.writeM17Data(data, M17_FRAME_LENGTH_BYTES + 3U); + } else { + serial.writeM17Data(data, M17_FRAME_LENGTH_BYTES + 1U); + } +#else + serial.writeM17Data(data, M17_FRAME_LENGTH_BYTES + 1U); +#endif + + m_rssiAccum = 0U; + m_rssiCount = 0U; +} diff --git a/M17RX.h b/M17RX.h new file mode 100644 index 0000000..7d6dc6f --- /dev/null +++ b/M17RX.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015,2016,2017,2020 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(M17RX_H) +#define M17RX_H + +#include "Config.h" +#include "M17Defines.h" + +enum M17RX_STATE { + M17RXS_NONE, + M17RXS_DATA +}; + +class CM17RX { +public: + CM17RX(); + + void samples(const q15_t* samples, uint16_t* rssi, uint8_t length); + + void reset(); + +private: + M17RX_STATE m_state; + uint16_t m_bitBuffer[M17_RADIO_SYMBOL_LENGTH]; + q15_t m_buffer[M17_FRAME_LENGTH_SAMPLES]; + uint16_t m_bitPtr; + uint16_t m_dataPtr; + uint16_t m_startPtr; + uint16_t m_endPtr; + uint16_t m_syncPtr; + uint16_t m_minSyncPtr; + uint16_t m_maxSyncPtr; + q31_t m_maxCorr; + uint16_t m_lostCount; + uint8_t m_countdown; + q15_t m_centre[16U]; + q15_t m_centreVal; + q15_t m_threshold[16U]; + q15_t m_thresholdVal; + uint8_t m_averagePtr; + uint32_t m_rssiAccum; + uint16_t m_rssiCount; + + void processNone(q15_t sample); + void processData(q15_t sample); + bool correlateSync(); + void calculateLevels(uint16_t start, uint16_t count); + void samplesToBits(uint16_t start, uint16_t count, uint8_t* buffer, uint16_t offset, q15_t centre, q15_t threshold); + void writeRSSIData(uint8_t* data); +}; + +#endif + diff --git a/M17TX.cpp b/M17TX.cpp new file mode 100644 index 0000000..e38a4db --- /dev/null +++ b/M17TX.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2009-2018,2020 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 "M17TX.h" + +#include "M17Defines.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 M17_LEVELA = 948; +const q15_t M17_LEVELB = 316; +const q15_t M17_LEVELC = -316; +const q15_t M17_LEVELD = -948; + +const uint8_t M17_START_SYNC = 0x77U; +const uint8_t M17_END_SYNC = 0xFFU; +const uint8_t M17_HANG = 0x00U; + +CM17TX::CM17TX() : +m_buffer(TX_BUFFER_LEN), +m_modFilter(), +m_modState(), +m_poBuffer(), +m_poLen(0U), +m_poPtr(0U), +m_txDelay(240U), // 200ms +m_txHang(4800U), // 4s +m_txCount(0U) +{ + ::memset(m_modState, 0x00U, 16U * sizeof(q15_t)); + + m_modFilter.L = M17_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 CM17TX::process() +{ + // If we have M17 data to transmit, do so. + if (m_poLen == 0U && m_buffer.getData() > 0U) { + if (!m_tx) { + for (uint16_t i = 0U; i < m_txDelay; i++) + m_poBuffer[m_poLen++] = M17_START_SYNC; + } else { + for (uint8_t i = 0U; i < M17_FRAME_LENGTH_BYTES; i++) { + uint8_t c = m_buffer.get(); + m_poBuffer[m_poLen++] = c; + } + } + + m_poPtr = 0U; + } + + if (m_poLen > 0U) { + // Transmit M17 data. + uint16_t space = io.getSpace(); + + while (space > (4U * M17_RADIO_SYMBOL_LENGTH)) { + uint8_t c = m_poBuffer[m_poPtr++]; + writeByte(c); + + // Reduce space and reset the hang timer. + space -= 4U * M17_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 * M17_RADIO_SYMBOL_LENGTH)) { + writeSilence(); + + space -= 4U * M17_RADIO_SYMBOL_LENGTH; + m_txCount--; + + if (m_txCount == 0U) + return; + } + } +} + +uint8_t CM17TX::writeData(const uint8_t* data, uint8_t length) +{ + if (length != (M17_FRAME_LENGTH_BYTES + 1U)) + return 4U; + + uint16_t space = m_buffer.getSpace(); + if (space < M17_FRAME_LENGTH_BYTES) + return 5U; + + for (uint8_t i = 0U; i < M17_FRAME_LENGTH_BYTES; i++) + m_buffer.put(data[i + 1U]); + + return 0U; +} + +void CM17TX::writeByte(uint8_t c) +{ + q15_t inBuffer[4U]; + q15_t outBuffer[M17_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] = M17_LEVELA; + break; + case 0x80U: + inBuffer[i] = M17_LEVELB; + break; + case 0x00U: + inBuffer[i] = M17_LEVELC; + break; + default: + inBuffer[i] = M17_LEVELD; + break; + } + } + + ::arm_fir_interpolate_q15(&m_modFilter, inBuffer, outBuffer, 4U); + + io.write(STATE_M17, outBuffer, M17_RADIO_SYMBOL_LENGTH * 4U); +} + +void CM17TX::writeSilence() +{ + q15_t inBuffer[4U] = {0x00U, 0x00U, 0x00U, 0x00U}; + q15_t outBuffer[M17_RADIO_SYMBOL_LENGTH * 4U]; + + ::arm_fir_interpolate_q15(&m_modFilter, inBuffer, outBuffer, 4U); + + io.write(STATE_M17, outBuffer, M17_RADIO_SYMBOL_LENGTH * 4U); +} + +void CM17TX::setTXDelay(uint8_t delay) +{ + m_txDelay = 600U + uint16_t(delay) * 12U; // 500ms + tx delay + + if (m_txDelay > 1200U) + m_txDelay = 1200U; +} + +uint8_t CM17TX::getSpace() const +{ + return m_buffer.getSpace() / M17_FRAME_LENGTH_BYTES; +} + +void CM17TX::setParams(uint8_t txHang) +{ + m_txHang = txHang * 1200U; +} diff --git a/M17TX.h b/M17TX.h new file mode 100644 index 0000000..211d747 --- /dev/null +++ b/M17TX.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015,2016,2017,2020 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(M17TX_H) +#define M17TX_H + +#include "Config.h" + +#include "SerialRB.h" + +class CM17TX { +public: + CM17TX(); + + uint8_t writeData(const uint8_t* data, uint8_t length); + + void process(); + + void setTXDelay(uint8_t delay); + + uint8_t getSpace() const; + + void setParams(uint8_t txHang); + +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; + uint32_t m_txHang; + uint32_t m_txCount; + + void writeByte(uint8_t c); + void writeSilence(); +}; + +#endif + diff --git a/MMDVM.cpp b/MMDVM.cpp index a6d4577..5fadb0a 100644 --- a/MMDVM.cpp +++ b/MMDVM.cpp @@ -31,6 +31,7 @@ bool m_dmrEnable = true; bool m_ysfEnable = true; bool m_p25Enable = true; bool m_nxdnEnable = true; +bool m_m17Enable = true; bool m_pocsagEnable = true; bool m_fmEnable = true; @@ -58,6 +59,9 @@ CP25TX p25TX; CNXDNRX nxdnRX; CNXDNTX nxdnTX; +CM17RX m17RX; +CM17TX m17TX; + CPOCSAGTX pocsagTX; CFM fm; @@ -107,6 +111,9 @@ void loop() if (m_nxdnEnable && m_modemState == STATE_NXDN) nxdnTX.process(); + if (m_m17Enable && m_modemState == STATE_M17) + m17TX.process(); + if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy())) pocsagTX.process(); diff --git a/MMDVM.ino b/MMDVM.ino index 17c4ad8..02bfe62 100644 --- a/MMDVM.ino +++ b/MMDVM.ino @@ -28,6 +28,7 @@ bool m_dmrEnable = true; bool m_ysfEnable = true; bool m_p25Enable = true; bool m_nxdnEnable = true; +bool m_m17Enable = true; bool m_pocsagEnable = true; bool m_fmEnable = true; @@ -55,6 +56,9 @@ CP25TX p25TX; CNXDNRX nxdnRX; CNXDNTX nxdnTX; +CM17RX m17RX; +CM17TX m17TX; + CPOCSAGTX pocsagTX; CFM fm; @@ -104,6 +108,9 @@ void loop() if (m_nxdnEnable && m_modemState == STATE_NXDN) nxdnTX.process(); + if (m_m17Enable && m_modemState == STATE_M17) + m17TX.process(); + if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy())) pocsagTX.process(); diff --git a/SerialPort.cpp b/SerialPort.cpp index 17cb476..20c2766 100644 --- a/SerialPort.cpp +++ b/SerialPort.cpp @@ -63,6 +63,9 @@ const uint8_t MMDVM_P25_LOST = 0x32U; const uint8_t MMDVM_NXDN_DATA = 0x40U; const uint8_t MMDVM_NXDN_LOST = 0x41U; +const uint8_t MMDVM_M17_DATA = 0x45U; +const uint8_t MMDVM_M17_LOST = 0x46U; + const uint8_t MMDVM_POCSAG_DATA = 0x50U; const uint8_t MMDVM_FM_PARAMS1 = 0x60U; @@ -103,7 +106,7 @@ const uint8_t MMDVM_DEBUG5 = 0xF5U; #define HW_TYPE "MMDVM" #endif -#define DESCRIPTION "20200901 (D-Star/DMR/System Fusion/P25/NXDN/POCSAG/FM)" +#define DESCRIPTION "20201014 (D-Star/DMR/System Fusion/P25/NXDN/M17/POCSAG/FM)" #if defined(GITVERSION) #define concat(h, a, b, c) h " " a " " b " GitID #" c "" @@ -158,7 +161,7 @@ void CSerialPort::getStatus() // Send all sorts of interesting internal values reply[0U] = MMDVM_FRAME_START; - reply[1U] = 13U; + reply[1U] = 14U; reply[2U] = MMDVM_GET_STATUS; reply[3U] = 0x00U; @@ -176,6 +179,8 @@ void CSerialPort::getStatus() reply[3U] |= 0x20U; if (m_fmEnable) reply[3U] |= 0x40U; + if (m_m17Enable) + reply[3U] |= 0x80U; reply[4U] = uint8_t(m_modemState); @@ -240,7 +245,12 @@ void CSerialPort::getStatus() else reply[12U] = 0U; - writeInt(1U, reply, 13); + if (m_m17Enable) + reply[13U] = m17TX.getSpace(); + else + reply[13U] = 0U; + + writeInt(1U, reply, 14); } void CSerialPort::getVersion() @@ -264,7 +274,7 @@ void CSerialPort::getVersion() uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) { - if (length < 21U) + if (length < 23U) return 4U; bool rxInvert = (data[0U] & 0x01U) == 0x01U; @@ -283,6 +293,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) bool nxdnEnable = (data[1U] & 0x10U) == 0x10U; bool pocsagEnable = (data[1U] & 0x20U) == 0x20U; bool fmEnable = (data[1U] & 0x40U) == 0x40U; + bool m17Enable = (data[1U] & 0x80U) == 0x80U; uint8_t txDelay = data[2U]; if (txDelay > 50U) @@ -290,7 +301,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) MMDVM_STATE modemState = MMDVM_STATE(data[3U]); - if (modemState != STATE_IDLE && modemState != STATE_DSTAR && modemState != STATE_DMR && modemState != STATE_YSF && modemState != STATE_P25 && modemState != STATE_NXDN && 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_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_DSTARCAL && modemState != STATE_DMRCAL && modemState != STATE_RSSICAL && modemState != STATE_LFCAL && modemState != STATE_DMRCAL1K && modemState != STATE_P25CAL1K && modemState != STATE_DMRDMO1K && modemState != STATE_NXDNCAL1K && modemState != STATE_POCSAGCAL && modemState != STATE_FMCAL10K && modemState != STATE_FMCAL12K && modemState != STATE_FMCAL15K && modemState != STATE_FMCAL20K && modemState != STATE_FMCAL25K && modemState != STATE_FMCAL30K) return 4U; if (modemState == STATE_DSTAR && !dstarEnable) return 4U; @@ -302,6 +313,8 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) return 4U; if (modemState == STATE_NXDN && !nxdnEnable) return 4U; + if (modemState == STATE_M17 && !m17Enable) + return 4U; if (modemState == STATE_POCSAG && !pocsagEnable) return 4U; if (modemState == STATE_FM && !fmEnable) @@ -336,6 +349,9 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) uint8_t nxdnTXHang = data[20U]; + uint8_t m17TXLevel = data[21U]; + uint8_t m17TXHang = data[22U]; + setMode(modemState); m_dstarEnable = dstarEnable; @@ -343,6 +359,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) m_ysfEnable = ysfEnable; m_p25Enable = p25Enable; m_nxdnEnable = nxdnEnable; + m_m17Enable = m17Enable; m_pocsagEnable = pocsagEnable; m_fmEnable = fmEnable; m_duplex = !simplex; @@ -352,6 +369,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) p25TX.setTXDelay(txDelay); dmrDMOTX.setTXDelay(txDelay); nxdnTX.setTXDelay(txDelay); + m17TX.setTXDelay(txDelay); pocsagTX.setTXDelay(txDelay); dmrTX.setColorCode(colorCode); @@ -363,8 +381,9 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) ysfTX.setParams(ysfLoDev, ysfTXHang); p25TX.setParams(p25TXHang); nxdnTX.setParams(nxdnTXHang); + m17TX.setParams(m17TXHang); - io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, pocsagTXLevel, fmTXLevel, txDCOffset, rxDCOffset, useCOSAsLockout); + io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, m17TXLevel, pocsagTXLevel, fmTXLevel, txDCOffset, rxDCOffset, useCOSAsLockout); io.start(); @@ -452,7 +471,7 @@ uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length) if (modemState == m_modemState) return 0U; - if (modemState != STATE_IDLE && modemState != STATE_DSTAR && modemState != STATE_DMR && modemState != STATE_YSF && modemState != STATE_P25 && modemState != STATE_NXDN && 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_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_DSTARCAL && modemState != STATE_DMRCAL && modemState != STATE_RSSICAL && modemState != STATE_LFCAL && modemState != STATE_DMRCAL1K && modemState != STATE_P25CAL1K && modemState != STATE_DMRDMO1K && modemState != STATE_NXDNCAL1K && modemState != STATE_POCSAGCAL && modemState != STATE_FMCAL10K && modemState != STATE_FMCAL12K && modemState != STATE_FMCAL15K && modemState != STATE_FMCAL20K && modemState != STATE_FMCAL25K && modemState != STATE_FMCAL30K) return 4U; if (modemState == STATE_DSTAR && !m_dstarEnable) return 4U; @@ -464,6 +483,8 @@ uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length) return 4U; if (modemState == STATE_NXDN && !m_nxdnEnable) return 4U; + if (modemState == STATE_M17 && !m_m17Enable) + return 4U; if (modemState == STATE_POCSAG && !m_pocsagEnable) return 4U; if (modemState == STATE_FM && !m_fmEnable) @@ -492,6 +513,9 @@ void CSerialPort::setMode(MMDVM_STATE modemState) case STATE_NXDN: DEBUG1("Mode set to NXDN"); break; + case STATE_M17: + DEBUG1("Mode set to M17"); + break; case STATE_POCSAG: DEBUG1("Mode set to POCSAG"); break; @@ -563,6 +587,9 @@ void CSerialPort::setMode(MMDVM_STATE modemState) if (modemState != STATE_NXDN) nxdnRX.reset(); + if (modemState != STATE_M17) + m17RX.reset(); + if (modemState != STATE_FM) fm.reset(); @@ -870,6 +897,20 @@ void CSerialPort::process() } break; + case MMDVM_M17_DATA: + if (m_m17Enable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_M17) + err = m17TX.writeData(m_buffer + 3U, m_len - 3U); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_M17); + } else { + DEBUG2("Received invalid M17 data", err); + sendNAK(err); + } + break; + case MMDVM_POCSAG_DATA: if (m_pocsagEnable) { if (m_modemState == STATE_IDLE || m_modemState == STATE_POCSAG) @@ -1196,6 +1237,46 @@ void CSerialPort::writeNXDNLost() writeInt(1U, reply, 3); } +void CSerialPort::writeM17Data(const uint8_t* data, uint8_t length) +{ + if (m_modemState != STATE_M17 && m_modemState != STATE_IDLE) + return; + + if (!m_m17Enable) + return; + + uint8_t reply[130U]; + + reply[0U] = MMDVM_FRAME_START; + reply[1U] = 0U; + reply[2U] = MMDVM_M17_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::writeM17Lost() +{ + if (m_modemState != STATE_M17 && m_modemState != STATE_IDLE) + return; + + if (!m_m17Enable) + return; + + uint8_t reply[3U]; + + reply[0U] = MMDVM_FRAME_START; + reply[1U] = 3U; + reply[2U] = MMDVM_M17_LOST; + + writeInt(1U, reply, 3); +} + void CSerialPort::writeCalData(const uint8_t* data, uint8_t length) { if (m_modemState != STATE_DSTARCAL) diff --git a/SerialPort.h b/SerialPort.h index cd09bbe..1e120c8 100644 --- a/SerialPort.h +++ b/SerialPort.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2018,2020 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 @@ -50,6 +50,9 @@ public: void writeNXDNData(const uint8_t* data, uint8_t length); void writeNXDNLost(); + void writeM17Data(const uint8_t* data, uint8_t length); + void writeM17Lost(); + void writeCalData(const uint8_t* data, uint8_t length); void writeRSSIData(const uint8_t* data, uint8_t length); diff --git a/Utils.cpp b/Utils.cpp index 6a380f4..a51654a 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -30,6 +30,15 @@ uint8_t countBits8(uint8_t bits) return BITS_TABLE[bits]; } +uint8_t countBits16(uint16_t bits) +{ + uint8_t* p = (uint8_t*)&bits; + uint8_t n = 0U; + n += BITS_TABLE[p[0U]]; + n += BITS_TABLE[p[1U]]; + return n; +} + uint8_t countBits32(uint32_t bits) { uint8_t* p = (uint8_t*)&bits; diff --git a/Utils.h b/Utils.h index 95cb1d5..d8ebdd2 100644 --- a/Utils.h +++ b/Utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2020 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 @@ -32,6 +32,8 @@ uint8_t countBits8(uint8_t bits); +uint8_t countBits16(uint16_t bits); + uint8_t countBits32(uint32_t bits); uint8_t countBits64(uint64_t bits);