diff --git a/CalFM.cpp b/CalFM.cpp index 3f847cc..e89f0be 100644 --- a/CalFM.cpp +++ b/CalFM.cpp @@ -41,7 +41,7 @@ CCalFM::CCalFM() : m_frequency(0), m_length(0), m_tone(NULL), -m_level(128*32), +m_level(128 * 12), m_transmit(false), m_audioSeq(0), m_lastState(STATE_IDLE) @@ -56,25 +56,25 @@ void CCalFM::process() { switch (m_modemState) { case STATE_FMCAL10K: - m_frequency=956U; + m_frequency = 956U; break; case STATE_FMCAL12K: - m_frequency=1039U; + m_frequency = 1039U; break; case STATE_FMCAL15K: - m_frequency=1247U; + m_frequency = 1247U; break; case STATE_FMCAL20K: - m_frequency=1633U; + m_frequency = 1633U; break; case STATE_FMCAL25K: - m_frequency=2079U; + m_frequency = 2079U; break; case STATE_FMCAL30K: - m_frequency=2495U; + m_frequency = 2495U; break; default: - m_frequency=0; + m_frequency = 0U; break; } diff --git a/FM.cpp b/FM.cpp index 70b6b6a..3bc0bbf 100644 --- a/FM.cpp +++ b/FM.cpp @@ -40,17 +40,19 @@ m_hangTimer(), 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_preemphasis(32768, 13967, 0, 32768, -18801, 0),//75µS 24kHz sampling rate -m_deemphasis (32768, -18801, 0, 32768, 13967, 0),//75µS 24kHz sampling rate m_blanking(), m_useCOS(true), m_cosInvert(false), m_rfAudioBoost(1U), -m_downsampler(128)//Size might need adjustement +m_downsampler(128U),//Size might need adjustement +m_rxLevel(1), +m_inputRB(4800U), // 200ms of audio +m_outputRB(2400U) // 100ms of audio { + insertDelay(100U); } -void CFM::samples(bool cos, q15_t* samples, uint8_t length) +void CFM::samples(bool cos, const q15_t* samples, uint8_t length) { if (m_useCOS) { if (m_cosInvert) @@ -63,10 +65,17 @@ void CFM::samples(bool cos, q15_t* samples, uint8_t length) uint8_t i = 0U; for (; i < length; i++) { - q15_t currentSample = samples[i];//save to a local variable to avoid indirection on every access + // ARMv7-M has hardware integer division + q15_t currentSample = q15_t((q31_t(samples[i]) << 8) / m_rxLevel); uint8_t ctcssState = m_ctcssRX.process(currentSample); + if (!m_useCOS) { + // Delay the audio by 100ms to better match the CTCSS detector output + m_inputRB.put(currentSample); + m_inputRB.get(currentSample); + } + if (CTCSS_NOT_READY(ctcssState) && m_modemState != STATE_FM) { //Not enough samples to determine if you have CTCSS, just carry on continue; @@ -90,8 +99,7 @@ void CFM::samples(bool cos, q15_t* samples, uint8_t length) } // Only let audio through when relaying audio - if (m_state == FS_RELAYING || m_state == FS_KERCHUNK) { - // currentSample = m_deemphasis.filter(currentSample); + if (m_state == FS_RELAYING || m_state == FS_KERCHUNK) { // m_downsampler.addSample(currentSample); currentSample = m_blanking.process(currentSample); currentSample *= m_rfAudioBoost; @@ -114,19 +122,18 @@ void CFM::samples(bool cos, q15_t* samples, uint8_t length) currentSample = m_filterStage3.filter(m_filterStage2.filter(m_filterStage1.filter(currentSample))); - // currentSample = m_preemphasis.filter(currentSample); - currentSample += m_ctcssTX.getAudio(); - samples[i] = currentSample; + if (m_modemState == STATE_FM) + m_outputRB.put(currentSample); } - - if (m_modemState == STATE_FM) - io.write(STATE_FM, samples, i);//only write the actual number of processed samples to IO } void CFM::process() { + q15_t sample; + while (io.getSpace() >= 3U && m_outputRB.get(sample)) + io.write(STATE_FM, &sample, 1U); } void CFM::reset() @@ -144,6 +151,8 @@ void CFM::reset() m_rfAck.stop(); m_callsign.stop(); m_timeoutTone.stop(); + + m_outputRB.reset(); } 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) @@ -174,7 +183,7 @@ 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 ctcssThreshold, 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, bool useCOS, bool cosInvert, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel) { m_useCOS = useCOS; m_cosInvert = cosInvert; @@ -188,7 +197,9 @@ uint8_t CFM::setMisc(uint16_t timeout, uint8_t timeoutLevel, uint8_t ctcssFreque m_timeoutTone.setParams(timeoutLevel); m_blanking.setParams(maxDev, timeoutLevel); - uint8_t ret = m_ctcssRX.setParams(ctcssFrequency, ctcssThreshold, rxLevel); + m_rxLevel = rxLevel; //q15_t(255)/q15_t(rxLevel >> 1); + + uint8_t ret = m_ctcssRX.setParams(ctcssFrequency, ctcssHighThreshold, ctcssLowThreshold); if (ret != 0U) return ret; @@ -224,16 +235,14 @@ void CFM::stateMachine(bool validSignal) } if (m_state == FS_LISTENING && m_modemState == STATE_FM) { - if (!m_callsign.isRunning() && !m_rfAck.isRunning()) { - DEBUG1("Change to STATE_IDLE"); - m_modemState = STATE_IDLE; - m_callsignTimer.stop(); - m_timeoutTimer.stop(); - m_kerchunkTimer.stop(); - m_ackMinTimer.stop(); - m_ackDelayTimer.stop(); - m_hangTimer.stop(); - } + DEBUG1("Change to STATE_IDLE"); + m_modemState = STATE_IDLE; + m_callsignTimer.stop(); + m_timeoutTimer.stop(); + m_kerchunkTimer.stop(); + m_ackMinTimer.stop(); + m_ackDelayTimer.stop(); + m_hangTimer.stop(); } } @@ -264,6 +273,8 @@ void CFM::listeningState(bool validSignal) sendCallsign(); } + insertSilence(50U); + beginRelaying(); m_callsignTimer.start(); @@ -438,3 +449,19 @@ void CFM::beginRelaying() m_timeoutTimer.start(); m_ackMinTimer.start(); } + +void CFM::insertDelay(uint16_t ms) +{ + uint32_t nSamples = ms * 24U; + + for (uint32_t i = 0U; i < nSamples; i++) + m_inputRB.put(0); +} + +void CFM::insertSilence(uint16_t ms) +{ + uint32_t nSamples = ms * 24U; + + for (uint32_t i = 0U; i < nSamples; i++) + m_outputRB.put(0); +} diff --git a/FM.h b/FM.h index 9e15c8a..6411585 100644 --- a/FM.h +++ b/FM.h @@ -27,6 +27,7 @@ #include "FMTimeout.h" #include "FMKeyer.h" #include "FMTimer.h" +#include "FMRB.h" #include "FMDirectForm1.h" #include "FMDownsampler.h" @@ -47,7 +48,7 @@ class CFM { public: CFM(); - void samples(bool cos, q15_t* samples, uint8_t length); + void samples(bool cos, const q15_t* samples, uint8_t length); void process(); @@ -55,7 +56,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 ctcssThreshold, 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, bool useCOS, bool cosInvert, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel); private: CFMKeyer m_callsign; @@ -77,13 +78,14 @@ private: CFMDirectFormI m_filterStage1; CFMDirectFormI m_filterStage2; CFMDirectFormI m_filterStage3; - CFMDirectFormI m_preemphasis; - CFMDirectFormI m_deemphasis; CFMBlanking m_blanking; bool m_useCOS; bool m_cosInvert; q15_t m_rfAudioBoost; CFMDownsampler m_downsampler; + q15_t m_rxLevel; + CFMRB m_inputRB; + CFMRB m_outputRB; void stateMachine(bool validSignal); void listeningState(bool validSignal); @@ -98,6 +100,9 @@ private: void sendCallsign(); void beginRelaying(); + + void insertDelay(uint16_t ms); + void insertSilence(uint16_t ms); }; #endif diff --git a/FMCTCSSRX.cpp b/FMCTCSSRX.cpp index ba0c03f..e88ff98 100644 --- a/FMCTCSSRX.cpp +++ b/FMCTCSSRX.cpp @@ -82,19 +82,17 @@ const uint16_t N = 24000U / 4U; CFMCTCSSRX::CFMCTCSSRX() : m_coeffDivTwo(0), -m_threshold(0), +m_highThreshold(0), +m_lowThreshold(0), m_count(0U), m_q0(0), m_q1(0), -m_result(CTS_NONE), -m_rxLevelInverse(1) +m_result(CTS_NONE) { } -uint8_t CFMCTCSSRX::setParams(uint8_t frequency, uint8_t threshold, uint8_t level) +uint8_t CFMCTCSSRX::setParams(uint8_t frequency, uint8_t highThreshold, uint8_t lowThreshold) { - m_rxLevelInverse = q15Division(65535, q15_t(level * 128)); - m_coeffDivTwo = 0; for (uint8_t i = 0U; i < CTCSS_TABLE_DATA_LEN; i++) { @@ -107,14 +105,16 @@ uint8_t CFMCTCSSRX::setParams(uint8_t frequency, uint8_t threshold, uint8_t leve if (m_coeffDivTwo == 0) return 4U; - m_threshold = q31_t(threshold); + m_highThreshold = q31_t(highThreshold); + m_lowThreshold = q31_t(lowThreshold); return 0U; } uint8_t CFMCTCSSRX::process(q15_t sample) { - q31_t sample31 = q31_t(sample) * m_rxLevelInverse; + //get more dynamic into the decoder by multiplying the sample by 1.5 + q31_t sample31 = q31_t(sample) + (q31_t(sample) >> 1); m_result &= ~CTS_READY; @@ -150,14 +150,19 @@ uint8_t CFMCTCSSRX::process(q15_t sample) q31_t value = t2 + t4 - t9; bool previousCTCSSValid = CTCSS_VALID(m_result); + + q31_t threshold = m_highThreshold; + if (previousCTCSSValid) + threshold = m_lowThreshold; + m_result |= CTS_READY; - if (value >= m_threshold) + if (value >= threshold) m_result |= CTS_VALID; else m_result &= ~CTS_VALID; if (previousCTCSSValid != CTCSS_VALID(m_result)) - DEBUG4("CTCSS Value / Threshold / Valid", value, m_threshold, CTCSS_VALID(m_result)); + DEBUG4("CTCSS Value / Threshold / Valid", value, threshold, CTCSS_VALID(m_result)); m_count = 0U; m_q0 = 0; @@ -174,16 +179,3 @@ void CFMCTCSSRX::reset() m_result = CTS_NONE; m_count = 0U; } - -//Taken from https://en.wikipedia.org/wiki/Q_(number_format)#Division -q15_t CFMCTCSSRX::q15Division(q15_t a, q15_t divisor) -{ - q31_t a31 = q31_t(a) << 16; - - if (((a >> 31) & 1) == ((divisor >> 15) & 1)) - a31 += divisor >> 1; - else - a31 -= divisor >> 1; - - return a31 / divisor; -} diff --git a/FMCTCSSRX.h b/FMCTCSSRX.h index 2caa9e6..eed82de 100644 --- a/FMCTCSSRX.h +++ b/FMCTCSSRX.h @@ -34,7 +34,7 @@ class CFMCTCSSRX { public: CFMCTCSSRX(); - uint8_t setParams(uint8_t frequency, uint8_t threshold, uint8_t level); + uint8_t setParams(uint8_t frequency, uint8_t highThreshold, uint8_t lowThreshold); uint8_t process(q15_t sample); @@ -42,14 +42,12 @@ public: private: q63_t m_coeffDivTwo; - q31_t m_threshold; + q31_t m_highThreshold; + q31_t m_lowThreshold; uint16_t m_count; q31_t m_q0; q31_t m_q1; uint8_t m_result; - q15_t m_rxLevelInverse; - - q15_t q15Division(q15_t a, q15_t divisor); }; #endif diff --git a/FMDownsampleRB.h b/FMDownsampleRB.h index 6e8e466..df60daf 100644 --- a/FMDownsampleRB.h +++ b/FMDownsampleRB.h @@ -16,8 +16,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#if !defined(FMRB_H) -#define FMRB_H +#if !defined(FMDOWNSAMPLERB_H) +#define FMDOWNSAMPLERB_H #if defined(STM32F4XX) #include "stm32f4xx.h" diff --git a/FMDownsampler.h b/FMDownsampler.h index fd17e0a..48b0d44 100644 --- a/FMDownsampler.h +++ b/FMDownsampler.h @@ -40,4 +40,4 @@ private: uint8_t m_downSampleIndex; }; -#endif \ No newline at end of file +#endif diff --git a/FMRB.cpp b/FMRB.cpp new file mode 100644 index 0000000..0d8c6ef --- /dev/null +++ b/FMRB.cpp @@ -0,0 +1,110 @@ +/* +TX fifo control - Copyright (C) KI6ZUM 2015 +Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This library 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this library; if not, write to the +Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +Boston, MA 02110-1301, USA. +*/ + +#include "FMRB.h" + +CFMRB::CFMRB(uint16_t length) : +m_length(length), +m_head(0U), +m_tail(0U), +m_full(false), +m_overflow(false) +{ + m_samples = new q15_t[length]; +} + +uint16_t CFMRB::getSpace() const +{ + uint16_t n = 0U; + + if (m_tail == m_head) + n = m_full ? 0U : m_length; + else if (m_tail < m_head) + n = m_length - m_head + m_tail; + else + n = m_tail - m_head; + + if (n > m_length) + n = 0U; + + return n; +} + +uint16_t CFMRB::getData() const +{ + if (m_tail == m_head) + return m_full ? m_length : 0U; + else if (m_tail < m_head) + return m_head - m_tail; + else + return m_length - m_tail + m_head; +} + +bool CFMRB::put(q15_t sample) +{ + if (m_full) { + m_overflow = true; + return false; + } + + m_samples[m_head] = sample; + + m_head++; + if (m_head >= m_length) + m_head = 0U; + + if (m_head == m_tail) + m_full = true; + + return true; +} + +bool CFMRB::get(q15_t& sample) +{ + if (m_head == m_tail && !m_full) + return false; + + sample = m_samples[m_tail]; + + m_full = false; + + m_tail++; + if (m_tail >= m_length) + m_tail = 0U; + + return true; +} + +bool CFMRB::hasOverflowed() +{ + bool overflow = m_overflow; + + m_overflow = false; + + return overflow; +} + +void CFMRB::reset() +{ + m_head = 0U; + m_tail = 0U; + m_full = false; + m_overflow = false; +} diff --git a/FMRB.h b/FMRB.h new file mode 100644 index 0000000..7cf6cf4 --- /dev/null +++ b/FMRB.h @@ -0,0 +1,72 @@ +/* +Serial fifo control - Copyright (C) KI6ZUM 2015 +Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This library 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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with this library; if not, write to the +Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +Boston, MA 02110-1301, USA. +*/ + +#if !defined(FMRB_H) +#define FMRB_H + +#if defined(STM32F4XX) +#include "stm32f4xx.h" +#elif defined(STM32F7XX) +#include "stm32f7xx.h" +#elif defined(STM32F105xC) +#include "stm32f1xx.h" +#include +#else +#include +#endif + +#if defined(__SAM3X8E__) || defined(STM32F105xC) +#define ARM_MATH_CM3 +#elif defined(STM32F7XX) +#define ARM_MATH_CM7 +#elif defined(STM32F4XX) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) +#define ARM_MATH_CM4 +#else +#error "Unknown processor type" +#endif + +#include + +class CFMRB { +public: + CFMRB(uint16_t length); + + uint16_t getSpace() const; + + uint16_t getData() const; + + bool put(q15_t sample); + + bool get(q15_t& sample); + + bool hasOverflowed(); + + void reset(); + +private: + uint16_t m_length; + volatile q15_t* m_samples; + volatile uint16_t m_head; + volatile uint16_t m_tail; + volatile bool m_full; + bool m_overflow; +}; + +#endif diff --git a/IOSTM.cpp b/IOSTM.cpp index 1d565fe..1757237 100644 --- a/IOSTM.cpp +++ b/IOSTM.cpp @@ -547,6 +547,7 @@ YSF PC7 output P25 PC8 output NXDN PC9 output POCSAG PA8 output +FM PA11 output MDSTAR PC1 output MDMR PC2 output @@ -554,6 +555,7 @@ MYSF PC3 output MP25 PC4 output MNXDN PC10 output MPOCSAG PC11 output +MFM PC13 output RX PA0 analog input RSSI PA7 analog input @@ -590,6 +592,10 @@ EXT_CLK PA15 input #define PORT_POCSAG GPIOA #define RCC_Per_POCSAG RCC_AHB1Periph_GPIOA +#define PIN_FM GPIO_Pin_11 +#define PORT_FM GPIOA +#define RCC_Per_FM RCC_AHB1Periph_GPIOA + #define PIN_DSTAR GPIO_Pin_15 #define PORT_DSTAR GPIOB #define RCC_Per_DSTAR RCC_AHB1Periph_GPIOB @@ -626,6 +632,10 @@ EXT_CLK PA15 input #define PIN_MPOCSAG GPIO_Pin_11 #define PORT_MPOCSAG GPIOC #define RCC_Per_MPOCSAG RCC_AHB1Periph_GPIOC + +#define PIN_MFM GPIO_Pin_13 +#define PORT_MFM GPIOC +#define RCC_Per_MFM RCC_AHB1Periph_GPIOC #endif #define PIN_EXT_CLK GPIO_Pin_15 diff --git a/MMDVM_STM32F4xx.coproj b/MMDVM_STM32F4xx.coproj deleted file mode 100644 index 4f1592a..0000000 --- a/MMDVM_STM32F4xx.coproj +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SerialPort.cpp b/SerialPort.cpp index 96fc7d8..36cb3c2 100644 --- a/SerialPort.cpp +++ b/SerialPort.cpp @@ -101,7 +101,7 @@ const uint8_t MMDVM_DEBUG5 = 0xF5U; #define HW_TYPE "MMDVM" #endif -#define DESCRIPTION "20200506 (D-Star/DMR/System Fusion/P25/NXDN/POCSAG/FM)" +#define DESCRIPTION "20200512 (D-Star/DMR/System Fusion/P25/NXDN/POCSAG/FM)" #if defined(GITVERSION) #define concat(h, a, b, c) h " " a " " b " GitID #" c "" @@ -409,27 +409,28 @@ uint8_t CSerialPort::setFMParams2(const uint8_t* data, uint8_t length) uint8_t CSerialPort::setFMParams3(const uint8_t* data, uint8_t length) { - if (length < 11U) + if (length < 12U) return 4U; uint16_t timeout = data[0U] * 5U; uint8_t timeoutLevel = data[1U]; - uint8_t ctcssFrequency = data[2U]; - uint8_t ctcssThreshold = data[3U]; - uint8_t ctcssLevel = data[4U]; + uint8_t ctcssFrequency = data[2U]; + uint8_t ctcssHighThreshold = data[3U]; + uint8_t ctcssLowThreshold = data[4U]; + uint8_t ctcssLevel = data[5U]; - uint8_t kerchunkTime = data[5U]; - uint8_t hangTime = data[6U]; + uint8_t kerchunkTime = data[6U]; + uint8_t hangTime = data[7U]; - bool useCOS = (data[7U] & 0x01U) == 0x01U; - bool cosInvert = (data[7U] & 0x02U) == 0x02U; + bool useCOS = (data[8U] & 0x01U) == 0x01U; + bool cosInvert = (data[8U] & 0x02U) == 0x02U; - uint8_t rfAudioBoost = data[8U]; - uint8_t maxDev = data[9U]; - uint8_t rxLevel = data[10U]; + uint8_t rfAudioBoost = data[9U]; + uint8_t maxDev = data[10U]; + uint8_t rxLevel = data[11U]; - return fm.setMisc(timeout, timeoutLevel, ctcssFrequency, ctcssThreshold, ctcssLevel, kerchunkTime, hangTime, useCOS, cosInvert, rfAudioBoost, maxDev, rxLevel); + return fm.setMisc(timeout, timeoutLevel, ctcssFrequency, ctcssHighThreshold, ctcssLowThreshold, ctcssLevel, kerchunkTime, hangTime, useCOS, cosInvert, rfAudioBoost, maxDev, rxLevel); } uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length)