diff --git a/Config.h b/Config.h index dc83577..55a75af 100644 --- a/Config.h +++ b/Config.h @@ -93,3 +93,4 @@ #define USE_ALTERNATE_FM_LEDS #endif + diff --git a/FM.cpp b/FM.cpp index 3ab3386..09b0e9a 100644 --- a/FM.cpp +++ b/FM.cpp @@ -20,12 +20,19 @@ #include "Globals.h" #include "FM.h" - const uint16_t FM_TX_BLOCK_SIZE = 100U; const uint16_t FM_SERIAL_BLOCK_SIZE = 84U;//this is the number of sample pairs to send over serial. One sample pair is 3bytes. //three times this value shall never exceed 252 const uint16_t FM_SERIAL_BLOCK_SIZE_BYTES = FM_SERIAL_BLOCK_SIZE * 3U; +/* + * Access Mode values are: + * 0 - Carrier access with COS + * 1 - CTCSS only access without COS + * 2 - CTCSS only access with COS + * 3 - CTCSS only access with COS to start, then carrier access with COS + */ + CFM::CFM() : m_callsign(), m_rfAck(), @@ -45,11 +52,13 @@ m_ackMinTimer(), m_ackDelayTimer(), m_hangTimer(), m_statusTimer(), +m_reverseTimer(), +m_needReverse(false), m_filterStage1( 724, 1448, 724, 32768, -37895, 21352),//3rd order Cheby Filter 300 to 2700Hz, 0.2dB passband ripple, sampling rate 24kHz m_filterStage2(32768, 0,-32768, 32768, -50339, 19052), m_filterStage3(32768, -65536, 32768, 32768, -64075, 31460), m_blanking(), -m_useCOS(true), +m_accessMode(1U), m_cosInvert(false), m_rfAudioBoost(1U), m_extAudioBoost(1U), @@ -61,18 +70,15 @@ m_outputRFRB(2400U), // 100ms of audio m_inputExtRB() { m_statusTimer.setTimeout(1U, 0U); + m_reverseTimer.setTimeout(0U, 150U); insertDelay(100U); } void CFM::samples(bool cos, q15_t* samples, uint8_t length) { - if (m_useCOS) { - if (m_cosInvert) - cos = !cos; - } else { - cos = true; - } + if (m_cosInvert) + cos = !cos; clock(length); @@ -80,34 +86,84 @@ void CFM::samples(bool cos, q15_t* samples, uint8_t length) for (; i < length; i++) { // ARMv7-M has hardware integer division q15_t currentRFSample = q15_t((q31_t(samples[i]) << 8) / m_rxLevel); - uint8_t ctcssState = m_ctcssRX.process(currentRFSample); - - if (!m_useCOS) { - // Delay the audio by 100ms to better match the CTCSS detector output - m_inputRFRB.put(currentRFSample); - m_inputRFRB.get(currentRFSample); - } q15_t currentExtSample; bool inputExt = m_inputExtRB.getSample(currentExtSample);//always consume the external input data so it does not overflow inputExt = inputExt && m_extEnabled; - if (!inputExt && (CTCSS_NOT_READY(ctcssState)) && m_modemState != STATE_FM) { - //Not enough samples to determine if you have CTCSS, just carry on. But only if we haven't any external data in the queue - continue; - } else if ((inputExt || CTCSS_READY(ctcssState)) && m_modemState != STATE_FM) { - //we had enough samples for CTCSS and we are in some other mode than FM - bool validCTCSS = CTCSS_VALID(ctcssState); - stateMachine(validCTCSS && cos, inputExt); - if (m_modemState != STATE_FM) - continue; - } else if ((inputExt || CTCSS_NOT_READY(ctcssState)) && m_modemState == STATE_FM && i == length - 1) { - //Not enough samples for CTCSS but we already are in FM, trigger the state machine - //but do not trigger the state machine on every single sample, save CPU! - bool validCTCSS = CTCSS_VALID(ctcssState); - stateMachine(validCTCSS && cos, inputExt); + switch (m_accessMode) { + case 0U: + if (!inputExt && !cos && m_modemState != STATE_FM) + continue; + else + stateMachine(cos, inputExt); + break; + + case 1U: { + uint8_t ctcssState = m_ctcssRX.process(currentRFSample); + + // Delay the audio by 100ms to better match the CTCSS detector output + m_inputRFRB.put(currentRFSample); + m_inputRFRB.get(currentRFSample); + + if (!inputExt && CTCSS_NOT_READY(ctcssState) && m_modemState != STATE_FM) { + // Not enough samples to determine if you have CTCSS, just carry on + continue; + } else if ((inputExt || CTCSS_READY(ctcssState)) && m_modemState != STATE_FM) { + // We had enough samples for CTCSS and we are in some other mode than FM + bool validCTCSS = CTCSS_VALID(ctcssState); + stateMachine(validCTCSS, inputExt); + if (m_state == FS_LISTENING) + continue; + } else { + bool validCTCSS = CTCSS_VALID(ctcssState); + stateMachine(validCTCSS, inputExt); + } + } + break; + + case 2U: { + uint8_t ctcssState = m_ctcssRX.process(currentRFSample); + if (!inputExt && CTCSS_NOT_READY(ctcssState) && m_modemState != STATE_FM) { + // Not enough samples to determine if you have CTCSS, just carry on + continue; + } else if ((inputExt || CTCSS_READY(ctcssState)) && m_modemState != STATE_FM) { + // We had enough samples for CTCSS and we are in some other mode than FM + bool validCTCSS = CTCSS_VALID(ctcssState); + stateMachine(validCTCSS && cos, inputExt); + if (m_state == FS_LISTENING) + continue; + } else { + bool validCTCSS = CTCSS_VALID(ctcssState); + stateMachine(validCTCSS && cos, inputExt); + } + } + break; + + default: { + uint8_t ctcssState = m_ctcssRX.process(currentRFSample); + if (!inputExt && CTCSS_NOT_READY(ctcssState) && m_modemState != STATE_FM) { + // Not enough samples to determine if you have CTCSS, just carry on + continue; + } else if ((inputExt || CTCSS_READY(ctcssState)) && m_modemState != STATE_FM) { + // We had enough samples for CTCSS and we are in some other mode than FM + bool validCTCSS = CTCSS_VALID(ctcssState); + stateMachine(validCTCSS && cos, inputExt); + if (m_state == FS_LISTENING) + continue; + } else { + stateMachine(cos, inputExt); + } + } + break; } + if (m_modemState != STATE_FM) + continue; + + if (m_state == FS_LISTENING && !m_rfAck.isWanted() && !m_extAck.isWanted() && !m_callsign.isWanted() && !m_reverseTimer.isRunning()) + continue; + q15_t currentSample = currentRFSample; q15_t currentBoost = m_rfAudioBoost; if (m_state == FS_RELAYING_EXT || m_state == FS_KERCHUNK_EXT) { @@ -127,12 +183,12 @@ void CFM::samples(bool cos, q15_t* samples, uint8_t length) currentSample = 0; } } else { - if (m_state == FS_RELAYING_EXT) { + if (m_state == FS_RELAYING_EXT || m_state == FS_KERCHUNK_EXT) { currentSample *= currentBoost; } else { - if (m_extEnabled && m_state == FS_RELAYING_RF) + if (m_extEnabled && (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF)) m_downSampler.addSample(currentSample); - return; + continue; } } @@ -154,7 +210,7 @@ void CFM::samples(bool cos, q15_t* samples, uint8_t length) if (!m_callsign.isRunning() && !m_rfAck.isRunning() && !m_extAck.isRunning()) currentSample += m_timeoutTone.getAudio(); - currentSample += m_ctcssTX.getAudio(); + currentSample += m_ctcssTX.getAudio(m_reverseTimer.isRunning()); m_outputRFRB.put(currentSample); } @@ -213,6 +269,7 @@ void CFM::reset() m_ackDelayTimer.stop(); m_hangTimer.stop(); m_statusTimer.stop(); + m_reverseTimer.stop(); m_ctcssRX.reset(); m_rfAck.stop(); @@ -224,6 +281,8 @@ void CFM::reset() m_inputExtRB.reset(); m_downSampler.reset(); + + m_needReverse = false; } uint8_t CFM::setCallsign(const char* callsign, uint8_t speed, uint16_t frequency, uint8_t time, uint8_t holdoff, uint8_t highLevel, uint8_t lowLevel, bool callsignAtStart, bool callsignAtEnd, bool callsignAtLatch) @@ -254,10 +313,10 @@ uint8_t CFM::setAck(const char* rfAck, uint8_t speed, uint16_t frequency, uint8_ return m_rfAck.setParams(rfAck, speed, frequency, level, level); } -uint8_t CFM::setMisc(uint16_t timeout, uint8_t timeoutLevel, uint8_t ctcssFrequency, uint8_t ctcssHighThreshold, uint8_t ctcssLowThreshold, uint8_t ctcssLevel, uint8_t kerchunkTime, uint8_t hangTime, bool useCOS, bool cosInvert, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel) +uint8_t CFM::setMisc(uint16_t timeout, uint8_t timeoutLevel, uint8_t ctcssFrequency, uint8_t ctcssHighThreshold, uint8_t ctcssLowThreshold, uint8_t ctcssLevel, uint8_t kerchunkTime, uint8_t hangTime, uint8_t accessMode, bool cosInvert, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel) { - m_useCOS = useCOS; - m_cosInvert = cosInvert; + m_accessMode = accessMode; + m_cosInvert = cosInvert; m_rfAudioBoost = q15_t(rfAudioBoost); @@ -331,8 +390,13 @@ void CFM::simplexStateMachine(bool validRFSignal, bool validExtSignal) } if (m_state == FS_LISTENING) { - m_outputRFRB.reset(); - m_downSampler.reset(); + if (!m_reverseTimer.isRunning() && m_needReverse) + m_reverseTimer.start(); + + if (m_reverseTimer.isRunning() && m_reverseTimer.hasExpired()) { + m_reverseTimer.stop(); + m_needReverse = false; + } } } @@ -380,8 +444,13 @@ void CFM::duplexStateMachine(bool validRFSignal, bool validExtSignal) } if (m_state == FS_LISTENING && !m_rfAck.isWanted() && !m_extAck.isWanted() && !m_callsign.isWanted()) { - m_outputRFRB.reset(); - m_downSampler.reset(); + if (!m_reverseTimer.isRunning() && m_needReverse) + m_reverseTimer.start(); + + if (m_reverseTimer.isRunning() && m_reverseTimer.hasExpired()) { + m_reverseTimer.stop(); + m_needReverse = false; + } } } @@ -395,6 +464,7 @@ void CFM::clock(uint8_t length) m_ackDelayTimer.clock(length); m_hangTimer.clock(length); m_statusTimer.clock(length); + m_reverseTimer.clock(length); if (m_statusTimer.isRunning() && m_statusTimer.hasExpired()) { serial.writeFMStatus(m_state); @@ -424,6 +494,7 @@ void CFM::listeningStateDuplex(bool validRFSignal, bool validExtSignal) beginRelaying(); m_callsignTimer.start(); + m_reverseTimer.stop(); io.setDecode(true); io.setADCDetection(true); @@ -451,6 +522,7 @@ void CFM::listeningStateDuplex(bool validRFSignal, bool validExtSignal) beginRelaying(); m_callsignTimer.start(); + m_reverseTimer.stop(); m_statusTimer.start(); serial.writeFMStatus(m_state); @@ -468,6 +540,7 @@ void CFM::listeningStateSimplex(bool validRFSignal, bool validExtSignal) io.setADCDetection(true); m_timeoutTimer.start(); + m_reverseTimer.stop(); m_statusTimer.start(); serial.writeFMStatus(m_state); @@ -478,6 +551,7 @@ void CFM::listeningStateSimplex(bool validRFSignal, bool validExtSignal) insertSilence(50U); m_timeoutTimer.start(); + m_reverseTimer.stop(); m_statusTimer.start(); serial.writeFMStatus(m_state); @@ -507,7 +581,7 @@ void CFM::kerchunkRFStateDuplex(bool validSignal) m_ackMinTimer.stop(); m_callsignTimer.stop(); m_statusTimer.stop(); - + m_needReverse = true; if (m_extEnabled) serial.writeFMEOT(); } @@ -620,7 +694,6 @@ void CFM::relayingRFWaitStateSimplex(bool validSignal) if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { DEBUG1("State to LISTENING"); m_state = FS_LISTENING; - m_ackDelayTimer.stop(); m_timeoutTimer.stop(); } @@ -647,6 +720,7 @@ void CFM::kerchunkExtStateDuplex(bool validSignal) m_ackMinTimer.stop(); m_callsignTimer.stop(); m_statusTimer.stop(); + m_needReverse = true; } } @@ -733,9 +807,9 @@ void CFM::relayingExtWaitStateSimplex(bool validSignal) if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { DEBUG1("State to LISTENING"); m_state = FS_LISTENING; - m_ackDelayTimer.stop(); m_timeoutTimer.stop(); + m_needReverse = true; } } } @@ -770,7 +844,7 @@ void CFM::hangStateDuplex(bool validRFSignal, bool validExtSignal) sendCallsign(); m_callsignTimer.stop(); - + m_needReverse = true; } } @@ -923,6 +997,7 @@ void CFM::timeoutExtWaitStateSimplex(bool validSignal) m_state = FS_LISTENING; m_ackDelayTimer.stop(); m_timeoutTimer.stop(); + m_needReverse = true; } } } diff --git a/FM.h b/FM.h index 12c7d67..2b269a7 100644 --- a/FM.h +++ b/FM.h @@ -60,7 +60,7 @@ public: uint8_t setCallsign(const char* callsign, uint8_t speed, uint16_t frequency, uint8_t time, uint8_t holdoff, uint8_t highLevel, uint8_t lowLevel, bool callsignAtStart, bool callsignAtEnd, bool callsignAtLatch); uint8_t setAck(const char* rfAck, uint8_t speed, uint16_t frequency, uint8_t minTime, uint16_t delay, uint8_t level); - uint8_t setMisc(uint16_t timeout, uint8_t timeoutLevel, uint8_t ctcssFrequency, uint8_t ctcssHighThreshold, uint8_t ctcssLowThreshold, uint8_t ctcssLevel, uint8_t kerchunkTime, uint8_t hangTime, bool useCOS, bool cosInvert, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel); + uint8_t setMisc(uint16_t timeout, uint8_t timeoutLevel, uint8_t ctcssFrequency, uint8_t ctcssHighThreshold, uint8_t ctcssLowThreshold, uint8_t ctcssLevel, uint8_t kerchunkTime, uint8_t hangTime, uint8_t accessMode, bool cosInvert, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel); uint8_t setExt(const char* ack, uint8_t audioBoost, uint8_t speed, uint16_t frequency, uint8_t level); uint8_t getSpace() const; @@ -86,11 +86,13 @@ private: CFMTimer m_ackDelayTimer; CFMTimer m_hangTimer; CFMTimer m_statusTimer; + CFMTimer m_reverseTimer; + bool m_needReverse; CFMDirectFormI m_filterStage1; CFMDirectFormI m_filterStage2; CFMDirectFormI m_filterStage3; CFMBlanking m_blanking; - bool m_useCOS; + uint8_t m_accessMode; bool m_cosInvert; q15_t m_rfAudioBoost; q15_t m_extAudioBoost; diff --git a/FMCTCSSTX.cpp b/FMCTCSSTX.cpp index fcffbb5..a2253a6 100644 --- a/FMCTCSSTX.cpp +++ b/FMCTCSSTX.cpp @@ -115,11 +115,14 @@ uint8_t CFMCTCSSTX::setParams(uint8_t frequency, uint8_t level) return 0U; } -q15_t CFMCTCSSTX::getAudio() +q15_t CFMCTCSSTX::getAudio(bool reverse) { q15_t sample = m_values[m_n++]; - if(m_n >= m_length) + if (m_n >= m_length) m_n = 0U; - return sample; + if (reverse) + return -sample; + else + return sample; } diff --git a/FMCTCSSTX.h b/FMCTCSSTX.h index 0c3e6e5..b6f9b12 100644 --- a/FMCTCSSTX.h +++ b/FMCTCSSTX.h @@ -27,7 +27,7 @@ public: uint8_t setParams(uint8_t frequency, uint8_t level); - q15_t getAudio(); + q15_t getAudio(bool reverse); private: q15_t* m_values; diff --git a/IO.cpp b/IO.cpp index 1ee5b8a..24cdb6b 100644 --- a/IO.cpp +++ b/IO.cpp @@ -84,6 +84,7 @@ m_fmTXLevel(128 * 128), m_ax25TXLevel(128 * 128), m_rxDCOffset(DC_OFFSET), m_txDCOffset(DC_OFFSET), +m_useCOSAsLockout(false), m_ledCount(0U), m_ledValue(true), m_detect(false), @@ -257,9 +258,8 @@ void CIO::process() return; } -#if defined(USE_COS_AS_LOCKOUT) - m_lockout = getCOSInt(); -#endif + if (m_useCOSAsLockout) + m_lockout = getCOSInt(); // Switch off the transmitter if needed if (m_txBuffer.getData() == 0U && m_tx) { @@ -548,7 +548,7 @@ void CIO::setMode() #endif } -void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, uint8_t ax25TXLevel, int16_t txDCOffset, int16_t rxDCOffset) +void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, uint8_t ax25TXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout) { m_pttInvert = pttInvert; @@ -565,6 +565,8 @@ void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rx m_rxDCOffset = DC_OFFSET + rxDCOffset; m_txDCOffset = DC_OFFSET + txDCOffset; + + m_useCOSAsLockout = useCOSAsLockout; if (rxInvert) m_rxLevel = -m_rxLevel; diff --git a/IO.h b/IO.h index 204f82d..861b321 100644 --- a/IO.h +++ b/IO.h @@ -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 pocsagTXLevel, uint8_t fmTXLevel, uint8_t ax25TXLevel, int16_t txDCOffset, int16_t rxDCOffset); + void setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, uint8_t ax25TXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout); void getOverflow(bool& adcOverflow, bool& dacOverflow); @@ -96,6 +96,8 @@ private: uint16_t m_rxDCOffset; uint16_t m_txDCOffset; + bool m_useCOSAsLockout; + uint32_t m_ledCount; bool m_ledValue; diff --git a/SerialPort.cpp b/SerialPort.cpp index 9661167..b6a7cef 100644 --- a/SerialPort.cpp +++ b/SerialPort.cpp @@ -109,7 +109,7 @@ const uint8_t MMDVM_DEBUG5 = 0xF5U; #define HW_TYPE "MMDVM" #endif -#define DESCRIPTION "20200712 (D-Star/DMR/System Fusion/P25/NXDN/POCSAG/FM/AX.25)" +#define DESCRIPTION "20200714 (D-Star/DMR/System Fusion/P25/NXDN/POCSAG/FM/AX.25)" #if defined(GITVERSION) #define concat(h, a, b, c) h " " a " " b " GitID #" c "" @@ -285,11 +285,12 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) if (length < 26U) return 4U; - bool rxInvert = (data[0U] & 0x01U) == 0x01U; - bool txInvert = (data[0U] & 0x02U) == 0x02U; - bool pttInvert = (data[0U] & 0x04U) == 0x04U; - bool ysfLoDev = (data[0U] & 0x08U) == 0x08U; - bool simplex = (data[0U] & 0x80U) == 0x80U; + bool rxInvert = (data[0U] & 0x01U) == 0x01U; + bool txInvert = (data[0U] & 0x02U) == 0x02U; + bool pttInvert = (data[0U] & 0x04U) == 0x04U; + bool ysfLoDev = (data[0U] & 0x08U) == 0x08U; + bool useCOSAsLockout = (data[0U] & 0x20U) == 0x20U; + bool simplex = (data[0U] & 0x80U) == 0x80U; m_debug = (data[0U] & 0x10U) == 0x10U; @@ -397,7 +398,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) nxdnTX.setParams(nxdnTXHang); ax25RX.setParams(ax25RXTwist, ax25SlotTime, ax25PPersist); - io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, pocsagTXLevel, fmTXLevel, ax25TXLevel, txDCOffset, rxDCOffset); + io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, pocsagTXLevel, fmTXLevel, ax25TXLevel, txDCOffset, rxDCOffset, useCOSAsLockout); io.start(); @@ -465,14 +466,14 @@ uint8_t CSerialPort::setFMParams3(const uint8_t* data, uint16_t length) uint8_t kerchunkTime = data[6U]; uint8_t hangTime = data[7U]; - bool useCOS = (data[8U] & 0x01U) == 0x01U; - bool cosInvert = (data[8U] & 0x02U) == 0x02U; + uint8_t accessMode = data[8U] & 0x7FU; + bool cosInvert = (data[8U] & 0x80U) == 0x80U; uint8_t rfAudioBoost = data[9U]; uint8_t maxDev = data[10U]; uint8_t rxLevel = data[11U]; - return fm.setMisc(timeout, timeoutLevel, ctcssFrequency, ctcssHighThreshold, ctcssLowThreshold, ctcssLevel, kerchunkTime, hangTime, useCOS, cosInvert, rfAudioBoost, maxDev, rxLevel); + return fm.setMisc(timeout, timeoutLevel, ctcssFrequency, ctcssHighThreshold, ctcssLowThreshold, ctcssLevel, kerchunkTime, hangTime, accessMode, cosInvert, rfAudioBoost, maxDev, rxLevel); } uint8_t CSerialPort::setFMParams4(const uint8_t* data, uint16_t length)