Merge branch 'FM' into boxcar_fm

This commit is contained in:
Jonathan Naylor 2020-04-29 21:34:53 +01:00
commit ace9780b1f
36 changed files with 2527 additions and 107 deletions

127
CalFM.cpp Normal file
View File

@ -0,0 +1,127 @@
/*
* Copyright (C) 2009-2015 by Jonathan Naylor G4KLX
* Copyright (C) 2016 by Colin Durbridge G4EML
* Copyright (C) 2020 by Phil Taylor M0VSE
*
* 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 "CalFM.h"
const struct TONE_TABLE {
uint16_t frequency;
uint16_t length;
q31_t increment;
} TONE_TABLE_DATA[] = {
{2495U, 10U, 223248821},
{2079U, 12U, 186025772},
{1633U, 15U, 146118367},
{1247U, 19U, 111579672},
{1039U, 23U, 93012886},
{956U, 25U, 85541432}};
const uint8_t TONE_TABLE_DATA_LEN = 6U;
CCalFM::CCalFM() :
m_tone(NULL),
m_frequency(0),
m_length(0),
m_level(128*32),
m_transmit(false),
m_lastState(STATE_IDLE),
m_audioSeq(0)
{
}
void CCalFM::process()
{
const TONE_TABLE* entry = NULL;
if (m_modemState!=m_lastState)
{
switch (m_modemState) {
case STATE_FMCAL10K:
m_frequency=956U;
break;
case STATE_FMCAL12K:
m_frequency=1039U;
break;
case STATE_FMCAL15K:
m_frequency=1247U;
break;
case STATE_FMCAL20K:
m_frequency=1633U;
break;
case STATE_FMCAL25K:
m_frequency=2079U;
break;
case STATE_FMCAL30K:
m_frequency=2495U;
break;
default:
m_frequency=0;
break;
}
for (uint8_t i = 0U; i < TONE_TABLE_DATA_LEN; i++) {
if (TONE_TABLE_DATA[i].frequency == m_frequency) {
entry = TONE_TABLE_DATA + i;
break;
}
}
if (entry == NULL)
return;
m_length = entry->length;
delete[] m_tone;
m_tone = new q15_t[m_length];
q31_t arg = 0;
for (uint16_t i = 0U; i < m_length; i++) {
q63_t value = ::arm_sin_q31(arg) * q63_t(m_level);
m_tone[i] = q15_t(__SSAT((value >> 31), 16));
arg += entry->increment;
}
m_lastState=m_modemState;
}
if (m_transmit)
{
uint16_t space = io.getSpace();
while (space > m_length)
{
io.write(m_modemState,m_tone,m_length);
space -= m_length;
}
}
}
uint8_t CCalFM::write(const uint8_t* data, uint8_t length)
{
if (length != 1U)
return 4U;
m_transmit = data[0U] == 1U;
return 0U;
}

51
CalFM.h Normal file
View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2009-2015 by Jonathan Naylor G4KLX
* Copyright (C) 2016 by Colin Durbridge G4EML
* Copyright (C) 2020 by Phil Taylor M0VSE
*
* 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(CALFM_H)
#define CALFM_H
#include "Config.h"
class CCalFM {
public:
CCalFM();
void process();
void fm10kcal();
void fm12k5cal();
void fm15kcal();
void fm20kcal();
void fm25kcal();
void fm30kcal();
uint8_t write(const uint8_t* data, uint8_t length);
private:
uint16_t m_frequency;
uint16_t m_length;
q15_t* m_tone;
q15_t m_level;
bool m_transmit;
uint8_t m_audioSeq;
uint8_t m_lastState;
};
#endif

View File

@ -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 * 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 * it under the terms of the GNU General Public License as published by
@ -84,5 +84,7 @@
// Use the D-Star and DMR LEDs for POCSAG // Use the D-Star and DMR LEDs for POCSAG
// #define USE_ALTERNATE_POCSAG_LEDS // #define USE_ALTERNATE_POCSAG_LEDS
#endif // Use the D-Star and YSF LEDs for FM
// #define USE_ALTERNATE_FM_LEDS
#endif

406
FM.cpp Normal file
View File

@ -0,0 +1,406 @@
/*
* Copyright (C) 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 "FM.h"
CFM::CFM() :
m_callsign(),
m_rfAck(),
m_ctcssRX(),
m_ctcssTX(),
m_timeoutTone(),
m_state(FS_LISTENING),
m_callsignAtStart(false),
m_callsignAtEnd(false),
m_callsignTimer(),
m_timeoutTimer(),
m_holdoffTimer(),
m_kerchunkTimer(),
m_ackMinTimer(),
m_ackDelayTimer(),
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_rfAudioBoost(1U),
m_downsampler(1024)//Size might need adjustement
{
}
void CFM::samples(bool cos, q15_t* samples, uint8_t length)
{
if (!m_useCOS)
cos = true;
uint8_t i = 0U;
for (; i < length; i++) {
q15_t currentSample = samples[i];//save to a local variable to avoid indirection on every access
CTCSSState ctcssState = m_ctcssRX.process(currentSample);
if (CTCSS_NOT_READY(ctcssState) && m_modemState != STATE_FM) {
//Not enough samples to determine if you have CTCSS, just carry on
continue;
} else if (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, i + 1U);
if (m_modemState != STATE_FM)
continue;
} else if (CTCSS_READY(ctcssState) && m_modemState == STATE_FM) {
//We had enough samples for CTCSS and we are in FM mode, trigger the state machine
bool validCTCSS = CTCSS_VALID(ctcssState);
stateMachine(validCTCSS && cos, i + 1U);
if (m_modemState != STATE_FM)
break;
} else if (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, i + 1U);
}
// Only let audio through when relaying audio
if (m_state == FS_RELAYING || m_state == FS_KERCHUNK) {
currentSample = m_deemphasis.filter(currentSample);
m_downsampler.addSample(currentSample);
currentSample = m_blanking.process(currentSample);
currentSample *= m_rfAudioBoost;
} else {
currentSample = 0U;
}
if (!m_callsign.isRunning())
currentSample += m_rfAck.getHighAudio();
if (!m_rfAck.isRunning()) {
if (m_state == FS_LISTENING)
currentSample += m_callsign.getHighAudio();
else
currentSample += m_callsign.getLowAudio();
}
if (!m_callsign.isRunning() && !m_rfAck.isRunning())
currentSample += m_timeoutTone.getAudio();
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)
io.write(STATE_FM, samples, i);//only write the actual number of processed samples to IO
}
void CFM::process()
{
}
void CFM::reset()
{
m_ctcssRX.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)
{
m_callsignAtStart = callsignAtStart;
m_callsignAtEnd = callsignAtEnd;
uint16_t holdoffTime = holdoff * 60U;
uint16_t callsignTime = time * 60U;
m_holdoffTimer.setTimeout(holdoffTime, 0U);
m_callsignTimer.setTimeout(callsignTime, 0U);
if (holdoffTime > 0U)
m_holdoffTimer.start();
return m_callsign.setParams(callsign, speed, frequency, highLevel, lowLevel);
}
uint8_t CFM::setAck(const char* rfAck, uint8_t speed, uint16_t frequency, uint8_t minTime, uint16_t delay, uint8_t level)
{
m_ackDelayTimer.setTimeout(0U, delay);
m_ackMinTimer.setTimeout(minTime, 0U);
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, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel)
{
m_useCOS = useCOS;
m_rfAudioBoost = q15_t(rfAudioBoost);
m_timeoutTimer.setTimeout(timeout, 0U);
m_kerchunkTimer.setTimeout(kerchunkTime, 0U);
m_hangTimer.setTimeout(hangTime, 0U);
m_timeoutTone.setParams(timeoutLevel);
m_blanking.setParams(maxDev, timeoutLevel);
uint8_t ret = m_ctcssRX.setParams(ctcssFrequency, ctcssThreshold, rxLevel);
if (ret != 0U)
return ret;
return m_ctcssTX.setParams(ctcssFrequency, ctcssLevel);
}
void CFM::stateMachine(bool validSignal, uint8_t length)
{
m_callsignTimer.clock(length);
m_timeoutTimer.clock(length);
m_holdoffTimer.clock(length);
m_kerchunkTimer.clock(length);
m_ackMinTimer.clock(length);
m_ackDelayTimer.clock(length);
m_hangTimer.clock(length);
switch (m_state) {
case FS_LISTENING:
listeningState(validSignal);
break;
case FS_KERCHUNK:
kerchunkState(validSignal);
break;
case FS_RELAYING:
relayingState(validSignal);
break;
case FS_RELAYING_WAIT:
relayingWaitState(validSignal);
break;
case FS_TIMEOUT:
timeoutState(validSignal);
break;
case FS_TIMEOUT_WAIT:
timeoutWaitState(validSignal);
break;
case FS_HANG:
hangState(validSignal);
break;
default:
break;
}
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();
}
}
}
void CFM::listeningState(bool validSignal)
{
if (validSignal) {
if (m_kerchunkTimer.getTimeout() > 0U) {
DEBUG1("State to KERCHUNK");
m_state = FS_KERCHUNK;
m_kerchunkTimer.start();
} else {
DEBUG1("State to RELAYING");
m_state = FS_RELAYING;
if (m_callsignAtStart)
sendCallsign();
}
beginRelaying();
m_callsignTimer.start();
DEBUG1("Change to STATE_FM");
m_modemState = STATE_FM;
}
}
void CFM::kerchunkState(bool validSignal)
{
if (validSignal) {
if (m_kerchunkTimer.hasExpired()) {
DEBUG1("State to RELAYING");
m_state = FS_RELAYING;
m_kerchunkTimer.stop();
}
} else {
DEBUG1("State to LISTENING");
m_state = FS_LISTENING;
m_kerchunkTimer.stop();
m_timeoutTimer.stop();
m_ackMinTimer.stop();
m_callsignTimer.stop();
}
}
void CFM::relayingState(bool validSignal)
{
if (validSignal) {
if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) {
DEBUG1("State to TIMEOUT");
m_state = FS_TIMEOUT;
m_ackMinTimer.stop();
m_timeoutTimer.stop();
m_timeoutTone.start();
}
} else {
DEBUG1("State to RELAYING_WAIT");
m_state = FS_RELAYING_WAIT;
m_ackDelayTimer.start();
}
if (m_callsignTimer.isRunning() && m_callsignTimer.hasExpired()) {
sendCallsign();
m_callsignTimer.start();
}
}
void CFM::relayingWaitState(bool validSignal)
{
if (validSignal) {
DEBUG1("State to RELAYING");
m_state = FS_RELAYING;
m_ackDelayTimer.stop();
} else {
if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) {
DEBUG1("State to HANG");
m_state = FS_HANG;
if (m_ackMinTimer.isRunning()) {
if (m_ackMinTimer.hasExpired()) {
DEBUG1("Send ack");
m_rfAck.start();
m_ackMinTimer.stop();
}
} else {
DEBUG1("Send ack");
m_rfAck.start();
m_ackMinTimer.stop();
}
m_ackDelayTimer.stop();
m_timeoutTimer.stop();
m_hangTimer.start();
}
}
if (m_callsignTimer.isRunning() && m_callsignTimer.hasExpired()) {
sendCallsign();
m_callsignTimer.start();
}
}
void CFM::hangState(bool validSignal)
{
if (validSignal) {
DEBUG1("State to RELAYING");
m_state = FS_RELAYING;
DEBUG1("Stop ack");
m_rfAck.stop();
beginRelaying();
} else {
if (m_hangTimer.isRunning() && m_hangTimer.hasExpired()) {
DEBUG1("State to LISTENING");
m_state = FS_LISTENING;
m_hangTimer.stop();
if (m_callsignAtEnd)
sendCallsign();
m_callsignTimer.stop();
}
}
if (m_callsignTimer.isRunning() && m_callsignTimer.hasExpired()) {
sendCallsign();
m_callsignTimer.start();
}
}
void CFM::timeoutState(bool validSignal)
{
if (!validSignal) {
DEBUG1("State to TIMEOUT_WAIT");
m_state = FS_TIMEOUT_WAIT;
m_ackDelayTimer.start();
}
if (m_callsignTimer.isRunning() && m_callsignTimer.hasExpired()) {
sendCallsign();
m_callsignTimer.start();
}
}
void CFM::timeoutWaitState(bool validSignal)
{
if (validSignal) {
DEBUG1("State to TIMEOUT");
m_state = FS_TIMEOUT;
m_ackDelayTimer.stop();
} else {
if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) {
DEBUG1("State to HANG");
m_state = FS_HANG;
m_timeoutTone.stop();
DEBUG1("Send ack");
m_rfAck.start();
m_ackDelayTimer.stop();
m_ackMinTimer.stop();
m_timeoutTimer.stop();
m_hangTimer.start();
}
}
if (m_callsignTimer.isRunning() && m_callsignTimer.hasExpired()) {
sendCallsign();
m_callsignTimer.start();
}
}
void CFM::sendCallsign()
{
if (m_holdoffTimer.isRunning()) {
if (m_holdoffTimer.hasExpired()) {
DEBUG1("Send callsign");
m_callsign.start();
m_holdoffTimer.start();
}
} else {
DEBUG1("Send callsign");
m_callsign.start();
}
}
void CFM::beginRelaying()
{
m_timeoutTimer.start();
m_ackMinTimer.start();
}

99
FM.h Normal file
View File

@ -0,0 +1,99 @@
/*
* Copyright (C) 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(FM_H)
#define FM_H
#include "Config.h"
#include "FMBlanking.h"
#include "FMCTCSSRX.h"
#include "FMCTCSSTX.h"
#include "FMTimeout.h"
#include "FMKeyer.h"
#include "FMTimer.h"
#include "FMDirectForm1.h"
#include "FMDownsampler.h"
enum FM_STATE {
FS_LISTENING,
FS_KERCHUNK,
FS_RELAYING,
FS_RELAYING_WAIT,
FS_TIMEOUT,
FS_TIMEOUT_WAIT,
FS_HANG
};
class CFM {
public:
CFM();
void samples(bool cos, q15_t* samples, uint8_t length);
void process();
void reset();
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);
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, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel);
private:
CFMKeyer m_callsign;
CFMKeyer m_rfAck;
CFMCTCSSRX m_ctcssRX;
CFMCTCSSTX m_ctcssTX;
CFMTimeout m_timeoutTone;
FM_STATE m_state;
bool m_callsignAtStart;
bool m_callsignAtEnd;
CFMTimer m_callsignTimer;
CFMTimer m_timeoutTimer;
CFMTimer m_holdoffTimer;
CFMTimer m_kerchunkTimer;
CFMTimer m_ackMinTimer;
CFMTimer m_ackDelayTimer;
CFMTimer m_hangTimer;
CFMDirectFormI m_filterStage1;
CFMDirectFormI m_filterStage2;
CFMDirectFormI m_filterStage3;
CFMDirectFormI m_preemphasis;
CFMDirectFormI m_deemphasis;
CFMBlanking m_blanking;
bool m_useCOS;
q15_t m_rfAudioBoost;
CFMDownsampler m_downsampler;
void stateMachine(bool validSignal, uint8_t length);
void listeningState(bool validSignal);
void kerchunkState(bool validSignal);
void relayingState(bool validSignal);
void relayingWaitState(bool validSignal);
void timeoutState(bool validSignal);
void timeoutWaitState(bool validSignal);
void hangState(bool validSignal);
void sendCallsign();
void beginRelaying();
};
#endif

82
FMBlanking.cpp Normal file
View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 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 "FMBlanking.h"
// 2000 Hz sine wave at 24000 Hz sample rate
const q31_t TONE[] = {0, 16384, 28378, 32767, 28378, 16384, 0, -16383, -28377, -32767, -28377, -16383};
const uint8_t TONE_LEN = 12U;
const uint32_t BLEEP_LEN = 2400U; // 100ms
CFMBlanking::CFMBlanking() :
m_posValue(0),
m_negValue(0),
m_level(128 * 128),
m_running(false),
m_pos(0U),
m_n(0U)
{
}
void CFMBlanking::setParams(uint8_t value, uint8_t level)
{
m_posValue = q15_t(value * 128);
m_negValue = -m_posValue;
m_level = q15_t(level * 128);
}
q15_t CFMBlanking::process(q15_t sample)
{
if (m_posValue == 0)
return sample;
if (!m_running) {
if (sample >= m_posValue) {
m_running = true;
m_pos = 0U;
m_n = 0U;
} else if (sample <= m_negValue) {
m_running = true;
m_pos = 0U;
m_n = 0U;
}
}
if (!m_running)
return sample;
if (m_pos <= BLEEP_LEN) {
q31_t value = TONE[m_n++] * m_level;
sample = q15_t(__SSAT((value >> 15), 16));
if (m_n >= TONE_LEN)
m_n = 0U;
} else {
sample = 0;
}
m_pos++;
if (m_pos >= 12000U)
m_running = false;
return sample;
}

41
FMBlanking.h Normal file
View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 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(FMBlanking_H)
#define FMBlanking_H
#include "Config.h"
class CFMBlanking {
public:
CFMBlanking();
void setParams(uint8_t value, uint8_t level);
q15_t process(q15_t sample);
private:
q15_t m_posValue;
q15_t m_negValue;
q15_t m_level;
bool m_running;
uint32_t m_pos;
uint8_t m_n;
};
#endif

181
FMCTCSSRX.cpp Normal file
View File

@ -0,0 +1,181 @@
/*
* Copyright (C) 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 "FMCTCSSRX.h"
const struct RX_CTCSS_TABLE {
uint8_t frequency;
q63_t coeffDivTwo;
} RX_CTCSS_TABLE_DATA[] = {
{ 67U, 2147153298},
{ 69U, 2147130228},
{ 71U, 2147103212},
{ 74U, 2147076297},
{ 77U, 2147047330},
{ 79U, 2147016195},
{ 82U, 2146982775},
{ 85U, 2146946945},
{ 88U, 2146907275},
{ 91U, 2146867538},
{ 94U, 2146822298},
{ 97U, 2146785526},
{100U, 2146747759},
{103U, 2146695349},
{107U, 2146637984},
{110U, 2146578604},
{114U, 2146513835},
{118U, 2146445080},
{123U, 2146370355},
{127U, 2146291161},
{131U, 2146205372},
{136U, 2146112589},
{141U, 2146014479},
{146U, 2145910829},
{151U, 2145796971},
{156U, 2145676831},
{159U, 2145604646},
{162U, 2145547790},
{165U, 2145468230},
{167U, 2145409363},
{171U, 2145324517},
{173U, 2145261046},
{177U, 2145170643},
{179U, 2145102321},
{183U, 2145006080},
{186U, 2144932648},
{189U, 2144830280},
{192U, 2144748638},
{196U, 2144639788},
{199U, 2144555290},
{203U, 2144436713},
{206U, 2144346237},
{210U, 2144217348},
{218U, 2143983951},
{225U, 2143735870},
{229U, 2143622139},
{233U, 2143469001},
{241U, 2143182299},
{250U, 2142874683},
{254U, 2142733729}};
const uint8_t CTCSS_TABLE_DATA_LEN = 50U;
// 4Hz bandwidth
const uint16_t N = 24000U / 4U;
CFMCTCSSRX::CFMCTCSSRX() :
m_coeffDivTwo(0),
m_threshold(0),
m_count(0U),
m_q0(0),
m_q1(0),
m_result(CTS_NONE),
m_rxLevelInverse(1)
{
}
uint8_t CFMCTCSSRX::setParams(uint8_t frequency, uint8_t threshold, uint8_t level)
{
m_rxLevelInverse = 511 / q15_t(level);
m_coeffDivTwo = 0;
for (uint8_t i = 0U; i < CTCSS_TABLE_DATA_LEN; i++) {
if (RX_CTCSS_TABLE_DATA[i].frequency == frequency) {
m_coeffDivTwo = RX_CTCSS_TABLE_DATA[i].coeffDivTwo;
break;
}
}
if (m_coeffDivTwo == 0)
return 4U;
m_threshold = q31_t(threshold);
return 0U;
}
CTCSSState CFMCTCSSRX::process(q15_t sample)
{
q31_t sample31 = q31_t(sample) * m_rxLevelInverse;
m_result = m_result & (~CTS_READY);
q31_t q2 = m_q1;
m_q1 = m_q0;
// Q31 multiplication, t3 = m_coeffDivTwo * 2 * m_q1
q63_t t1 = m_coeffDivTwo * m_q1;
q31_t t2 = __SSAT((t1 >> 31), 31);
q31_t t3 = t2 * 2;
// m_q0 = m_coeffDivTwo * m_q1 * 2 - q2 + sample
m_q0 = t3 - q2 + sample31;
m_count++;
if (m_count == N) {
// Q31 multiplication, t2 = m_q0 * m_q0
q63_t t1 = q63_t(m_q0) * q63_t(m_q0);
q31_t t2 = __SSAT((t1 >> 31), 31);
// Q31 multiplication, t4 = m_q0 * m_q0
q63_t t3 = q63_t(m_q1) * q63_t(m_q1);
q31_t t4 = __SSAT((t3 >> 31), 31);
// Q31 multiplication, t9 = m_q0 * m_q1 * m_coeffDivTwo * 2
q63_t t5 = q63_t(m_q0) * q63_t(m_q1);
q31_t t6 = __SSAT((t5 >> 31), 31);
q63_t t7 = t6 * m_coeffDivTwo;
q31_t t8 = __SSAT((t7 >> 31), 31);
q31_t t9 = t8 * 2;
// value = m_q0 * m_q0 + m_q1 * m_q1 - m_q0 * m_q1 * m_coeffDivTwo * 2
q31_t value = t2 + t4 - t9;
bool previousCTCSSValid = CTCSS_VALID(m_result);
m_result = m_result | CTS_READY;
if (value >= m_threshold)
m_result = m_result | CTS_VALID;
else
m_result = m_result & ~CTS_VALID;
if(previousCTCSSValid != CTCSS_VALID(m_result))
DEBUG4("CTCSS Value / Threshold / Valid", value, m_threshold, CTCSS_VALID(m_result));
m_count = 0U;
m_q0 = 0;
m_q1 = 0;
}
return m_result;
}
CTCSSState CFMCTCSSRX::getState()
{
return m_result;
}
void CFMCTCSSRX::reset()
{
m_q0 = 0;
m_q1 = 0;
m_result = CTS_NONE;
m_count = 0U;
}

74
FMCTCSSRX.h Normal file
View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 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(FMCTCSSRX_H)
#define FMCTCSSRX_H
#include "Config.h"
enum CTCSSState
{
CTS_NONE = 0,
CTS_READY = 1,
CTS_VALID = 2,
CTS_READY_VALID = CTS_READY | CTS_VALID
};
inline CTCSSState operator|(CTCSSState a, CTCSSState b)
{
return static_cast<CTCSSState>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
}
inline CTCSSState operator&(CTCSSState a, CTCSSState b)
{
return static_cast<CTCSSState>(static_cast<uint8_t>(a) & static_cast<uint8_t>(b));
}
inline CTCSSState operator~(CTCSSState a)
{
return static_cast<CTCSSState>(~(static_cast<uint8_t>(a)));
}
#define CTCSS_READY(a) ((a & CTS_READY) != 0)
#define CTCSS_NOT_READY(a) ((a & CTS_READY) == 0)
#define CTCSS_VALID(a) ((a & CTS_VALID) != 0)
#define CTCSS_NOT_VALID(a) ((a & CTS_VALID) == 0)
class CFMCTCSSRX {
public:
CFMCTCSSRX();
uint8_t setParams(uint8_t frequency, uint8_t threshold, uint8_t level);
CTCSSState process(q15_t sample);
CTCSSState getState();
void reset();
private:
q63_t m_coeffDivTwo;
q31_t m_threshold;
uint16_t m_count;
q31_t m_q0;
q31_t m_q1;
CTCSSState m_result;
q15_t m_rxLevelInverse;
};
#endif

125
FMCTCSSTX.cpp Normal file
View File

@ -0,0 +1,125 @@
/*
* Copyright (C) 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 "FMCTCSSTX.h"
const struct TX_CTCSS_TABLE {
uint8_t frequency;
uint16_t length;
q31_t increment;
} TX_CTCSS_TABLE_DATA[] = {
{ 67U, 358U, 5995059},
{ 69U, 346U, 6200860},
{ 71U, 334U, 6433504},
{ 74U, 323U, 6657200},
{ 77U, 312U, 6889844},
{ 79U, 301U, 7131436},
{ 82U, 291U, 7381976},
{ 85U, 281U, 7641463},
{ 88U, 271U, 7918846},
{ 91U, 262U, 8187282},
{ 94U, 253U, 8482561},
{ 97U, 246U, 8715205},
{100U, 240U, 8947849},
{103U, 232U, 9261024},
{107U, 224U, 9592094},
{110U, 216U, 9923165},
{114U, 209U, 10272131},
{118U, 202U, 10630045},
{123U, 195U, 11005854},
{127U, 189U, 11390612},
{131U, 182U, 11793265},
{136U, 176U, 12213814},
{141U, 170U, 12643310},
{146U, 164U, 13081755},
{151U, 159U, 13547043},
{156U, 153U, 14021279},
{159U, 150U, 14298662},
{162U, 148U, 14513411},
{165U, 145U, 14808690},
{167U, 143U, 15023438},
{171U, 140U, 15327665},
{173U, 138U, 15551361},
{177U, 135U, 15864536},
{179U, 133U, 16097180},
{183U, 131U, 16419303},
{186U, 129U, 16660894},
{189U, 126U, 16991965},
{192U, 124U, 17251452},
{196U, 122U, 17591471},
{199U, 120U, 17850958},
{203U, 118U, 18208872},
{206U, 116U, 18477308},
{210U, 114U, 18853117},
{218U, 110U, 19515258},
{225U, 106U, 20195295},
{229U, 105U, 20499521},
{233U, 103U, 20902175},
{241U, 99U, 21635898},
{250U, 96U, 22396465},
{254U, 94U, 22736484}};
const uint8_t CTCSS_TABLE_DATA_LEN = 50U;
CFMCTCSSTX::CFMCTCSSTX() :
m_values(NULL),
m_length(0U),
m_n(0U)
{
}
uint8_t CFMCTCSSTX::setParams(uint8_t frequency, uint8_t level)
{
const TX_CTCSS_TABLE* entry = NULL;
for (uint8_t i = 0U; i < CTCSS_TABLE_DATA_LEN; i++) {
if (TX_CTCSS_TABLE_DATA[i].frequency == frequency) {
entry = TX_CTCSS_TABLE_DATA + i;
break;
}
}
if (entry == NULL)
return 4U;
m_length = entry->length;
delete[] m_values;
m_values = new q15_t[m_length];
q31_t arg = 0;
for (uint16_t i = 0U; i < m_length; i++) {
q63_t value = ::arm_sin_q31(arg) * q63_t(level * 13);
m_values[i] = q15_t(__SSAT((value >> 31), 16));
arg += entry->increment;
}
return 0U;
}
q15_t CFMCTCSSTX::getAudio()
{
q15_t sample = m_values[m_n++];
if(m_n >= m_length)
m_n = 0U;
return sample;
}

38
FMCTCSSTX.h Normal file
View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 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(FMCTCSSTX_H)
#define FMCTCSSTX_H
#include "Config.h"
class CFMCTCSSTX {
public:
CFMCTCSSTX();
uint8_t setParams(uint8_t frequency, uint8_t level);
q15_t getAudio();
private:
q15_t* m_values;
uint16_t m_length;
uint16_t m_n;
};
#endif

111
FMDirectForm1.h Normal file
View File

@ -0,0 +1,111 @@
/*******************************************************************************
This header file has been taken from:
"A Collection of Useful C++ Classes for Digital Signal Processing"
By Vinnie Falco
Bernd Porr adapted it for Linux and turned it into a filter using
fixed point arithmetic.
--------------------------------------------------------------------------------
License: MIT License (http://www.opensource.org/licenses/mit-license.php)
Copyright (c) 2009 by Vinnie Falco
Copyright (C) 2013-2017, Bernd Porr, mail@berndporr.me.uk
Copyright (C) 2020 , Mario Molitor , mario_molitor@web.de
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*******************************************************************************/
// based on https://raw.githubusercontent.com/berndporr/iir_fixed_point/master/DirectFormI.h
#include "Globals.h"
#ifndef DIRECTFORMI_H_
#define DIRECTFORMI_H_
class CFMDirectFormI
{
public:
// convenience function which takes the a0 argument but ignores it!
CFMDirectFormI(const q31_t b0, const q31_t b1, const q31_t b2,
const q31_t, const q31_t a1, const q31_t a2)
{
// FIR coefficients
c_b0 = b0;
c_b1 = b1;
c_b2 = b2;
// IIR coefficients
c_a1 = a1;
c_a2 = a2;
reset();
}
CFMDirectFormI(const CFMDirectFormI &my)
{
// delay line
m_x2 = my.m_x2; // x[n-2]
m_y2 = my.m_y2; // y[n-2]
m_x1 = my.m_x1; // x[n-1]
m_y1 = my.m_y1; // y[n-1]
// coefficients
c_b0 = my.c_b0;
c_b1 = my.c_b1;
c_b2 = my.c_b2; // FIR
c_a1 = my.c_a1;
c_a2 = my.c_a2; // IIR
}
void reset ()
{
m_x1 = 0;
m_x2 = 0;
m_y1 = 0;
m_y2 = 0;
}
// filtering operation: one sample in and one out
inline q15_t filter(const q15_t in)
{
// calculate the output
register q31_t out_upscaled = c_b0 * in
+ c_b1 * m_x1
+ c_b2 * m_x2
- c_a1 * m_y1
- c_a2 * m_y2;
q15_t out = __SSAT(out_upscaled >> 15, 15);
// update the delay lines
m_x2 = m_x1;
m_y2 = m_y1;
m_x1 = in;
m_y1 = out;
return out;
}
private:
// delay line
q31_t m_x2; // x[n-2]
q31_t m_y2; // y[n-2]
q31_t m_x1; // x[n-1]
q31_t m_y1; // y[n-1]
// coefficients
q31_t c_b0,c_b1,c_b2; // FIR
q31_t c_a1,c_a2; // IIR
};
#endif /* DIRECTFORMI_H */

101
FMDownsampleRB.cpp Normal file
View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 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 "FMDownsampleRB.h"
CFMDownsampleRB::CFMDownsampleRB(uint16_t length) :
m_length(length),
m_head(0U),
m_tail(0U),
m_full(false),
m_overflow(false)
{
m_samples = new uint8_t[length];
}
uint16_t CFMDownsampleRB::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 CFMDownsampleRB::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 CFMDownsampleRB::put(uint8_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 CFMDownsampleRB::get(uint8_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 CFMDownsampleRB::hasOverflowed()
{
bool overflow = m_overflow;
m_overflow = false;
return overflow;
}

68
FMDownsampleRB.h Normal file
View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 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(FMRB_H)
#define FMRB_H
#if defined(STM32F4XX)
#include "stm32f4xx.h"
#elif defined(STM32F7XX)
#include "stm32f7xx.h"
#elif defined(STM32F105xC)
#include "stm32f1xx.h"
#include <cstddef>
#else
#include <Arduino.h>
#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 <arm_math.h>
class CFMDownsampleRB {
public:
CFMDownsampleRB(uint16_t length);
uint16_t getSpace() const;
uint16_t getData() const;
bool put(uint8_t sample);
bool get(uint8_t& sample);
bool hasOverflowed();
private:
uint16_t m_length;
volatile uint8_t* m_samples;
volatile uint16_t m_head;
volatile uint16_t m_tail;
volatile bool m_full;
bool m_overflow;
};
#endif

63
FMDownsampler.cpp Normal file
View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2020 by Jonathan Naylor G4KLX
* Copyright (C) 2020 by Geoffrey Merck F4FXL - KC3FRA
*
* 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 "FMDownsampler.h"
CFMDownsampler::CFMDownsampler(uint16_t length) :
m_ringBuffer(length),//length might need tweaking
m_packIndex(0),
m_downSampleIndex(0)
{
m_samplePack = 0;
}
void CFMDownsampler::addSample(q15_t sample)
{
//only take one of three samples
if(m_downSampleIndex == 0) {
switch(m_packIndex){
case 0:
m_samplePack = int32_t(sample) << 12;
break;
case 1:{
m_samplePack |= int32_t(sample);
//we did not use MSB; skip it
m_ringBuffer.put(m_samplePackBytes[1]);
m_ringBuffer.put(m_samplePackBytes[2]);
m_ringBuffer.put(m_samplePackBytes[3]);
m_samplePack = 0;
}
break;
default:
//should never happen
break;
}
m_packIndex++;
if(m_packIndex >= 2)
m_packIndex = 0;
}
m_downSampleIndex++;
if(m_downSampleIndex >= 3)
m_downSampleIndex = 0;
}

43
FMDownsampler.h Normal file
View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2020 by Jonathan Naylor G4KLX
* Copyright (C) 2020 by Geoffrey Merck F4FXL - KC3FRA
*
* 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(FMDOWNSAMPLER_H)
#define FMDOWNSAMPLER_H
#include "Config.h"
#include "FMDownsampleRB.h"
class CFMDownsampler {
public:
CFMDownsampler(uint16_t length);
void addSample(q15_t sample);
inline bool getPackedData(uint8_t& data){ return m_ringBuffer.get(data); };
inline bool hasOverflowed() { return m_ringBuffer.hasOverflowed(); };
private:
CFMDownsampleRB m_ringBuffer;
union {
int32_t m_samplePack;
int8_t m_samplePackBytes[4];
};
uint8_t m_packIndex;
uint8_t m_downSampleIndex;
};
#endif

210
FMKeyer.cpp Normal file
View File

@ -0,0 +1,210 @@
/*
* Copyright (C) 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 "FMKeyer.h"
const struct {
char c;
uint32_t pattern;
uint8_t length;
} SYMBOL_LIST[] = {
{'A', 0xB8000000U, 8U},
{'B', 0xEA800000U, 12U},
{'C', 0xEBA00000U, 14U},
{'D', 0xEA000000U, 10U},
{'E', 0x80000000U, 4U},
{'F', 0xAE800000U, 12U},
{'G', 0xEE800000U, 12U},
{'H', 0xAA000000U, 10U},
{'I', 0xA0000000U, 6U},
{'J', 0xBBB80000U, 16U},
{'K', 0xEB800000U, 12U},
{'L', 0xBA800000U, 12U},
{'M', 0xEE000000U, 10U},
{'N', 0xE8000000U, 8U},
{'O', 0xEEE00000U, 14U},
{'P', 0xBBA00000U, 14U},
{'Q', 0xEEB80000U, 16U},
{'R', 0xBA000000U, 10U},
{'S', 0xA8000000U, 8U},
{'T', 0xE0000000U, 6U},
{'U', 0xAE000000U, 10U},
{'V', 0xAB800000U, 12U},
{'W', 0xBB800000U, 12U},
{'X', 0xEAE00000U, 14U},
{'Y', 0xEBB80000U, 16U},
{'Z', 0xEEA00000U, 14U},
{'1', 0xBBBB8000U, 20U},
{'2', 0xAEEE0000U, 18U},
{'3', 0xABB80000U, 16U},
{'4', 0xAAE00000U, 14U},
{'5', 0xAA800000U, 12U},
{'6', 0xEAA00000U, 14U},
{'7', 0xEEA80000U, 16U},
{'8', 0xEEEA0000U, 18U},
{'9', 0xEEEE8000U, 20U},
{'0', 0xEEEEE000U, 22U},
{'/', 0xEAE80000U, 16U},
{'?', 0xAEEA0000U, 18U},
{',', 0xEEAEE000U, 22U},
{'-', 0xEAAE0000U, 18U},
{'=', 0xEAB80000U, 16U},
{' ', 0x00000000U, 4U},
{0U, 0x00000000U, 0U}
};
const uint8_t BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U};
#define WRITE_BIT_FM(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7])
#define READ_BIT_FM(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7])
CFMKeyer::CFMKeyer() :
m_wanted(false),
m_poBuffer(),
m_poLen(0U),
m_poPos(0U),
m_dotLen(0U),
m_dotPos(0U),
m_audio(NULL),
m_audioLen(0U),
m_audioPos(0U),
m_highLevel(0U),
m_lowLevel(0)
{
}
uint8_t CFMKeyer::setParams(const char* text, uint8_t speed, uint16_t frequency, uint8_t highLevel, uint8_t lowLevel)
{
m_poLen = 0U;
for (uint8_t i = 0U; text[i] != '\0'; i++) {
for (uint8_t j = 0U; SYMBOL_LIST[j].c != 0U; j++) {
if (SYMBOL_LIST[j].c == text[i]) {
uint32_t MASK = 0x80000000U;
for (uint8_t k = 0U; k < SYMBOL_LIST[j].length; k++, m_poLen++, MASK >>= 1) {
bool b = (SYMBOL_LIST[j].pattern & MASK) == MASK;
WRITE_BIT_FM(m_poBuffer, m_poLen, b);
if (m_poLen >= 995U) {
m_poLen = 0U;
return 4U;
}
}
break;
}
}
}
m_highLevel = q15_t(highLevel);
m_lowLevel = q15_t(lowLevel);
m_dotLen = 24000U / speed; // In samples
m_audioLen = 24000U / frequency; // In samples
delete[] m_audio;
m_audio = new bool[m_audioLen];
for (uint16_t i = 0U; i < m_audioLen; i++) {
if (i < (m_audioLen / 2U))
m_audio[i] = true;
else
m_audio[i] = false;
}
return 0U;
}
q15_t CFMKeyer::getHighAudio()
{
q15_t output = 0U;
if (!m_wanted)
return 0U;
bool b = READ_BIT_FM(m_poBuffer, m_poPos);
if (b)
output = m_audio[m_audioPos] ? m_highLevel : -m_highLevel;
m_audioPos++;
if (m_audioPos >= m_audioLen)
m_audioPos = 0U;
m_dotPos++;
if (m_dotPos >= m_dotLen) {
m_dotPos = 0U;
m_poPos++;
if (m_poPos >= m_poLen) {
stop();
return output;
}
}
return output;
}
q15_t CFMKeyer::getLowAudio()
{
q15_t output = 0U;
if (!m_wanted)
return 0U;
bool b = READ_BIT_FM(m_poBuffer, m_poPos);
if (b)
output = m_audio[m_audioPos] ? m_lowLevel : -m_lowLevel;
m_audioPos++;
if (m_audioPos >= m_audioLen)
m_audioPos = 0U;
m_dotPos++;
if (m_dotPos >= m_dotLen) {
m_dotPos = 0U;
m_poPos++;
if (m_poPos >= m_poLen) {
stop();
return output;
}
}
return output;
}
void CFMKeyer::start()
{
if (isRunning())
return;
m_wanted = true;
m_poPos = 0U;
m_dotPos = 0U;
m_audioPos = 0U;
}
void CFMKeyer::stop()
{
m_wanted = false;
m_poPos = 0U;
m_dotPos = 0U;
m_audioPos = 0U;
}
bool CFMKeyer::isRunning() const
{
return m_poPos > 0U || m_dotPos > 0U || m_audioPos > 0U;
}

52
FMKeyer.h Normal file
View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 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(FMKeyer_H)
#define FMKeyer_H
#include "Config.h"
class CFMKeyer {
public:
CFMKeyer();
uint8_t setParams(const char* text, uint8_t speed, uint16_t frequency, uint8_t highLevel, uint8_t lowLevel);
q15_t getHighAudio();
q15_t getLowAudio();
void start();
void stop();
bool isRunning() const;
private:
bool m_wanted;
uint8_t m_poBuffer[1000U];
uint16_t m_poLen;
uint16_t m_poPos;
uint16_t m_dotLen;
uint16_t m_dotPos;
bool* m_audio;
uint16_t m_audioLen;
uint16_t m_audioPos;
q15_t m_highLevel;
q15_t m_lowLevel;
};
#endif

76
FMTimeout.cpp Normal file
View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 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 "FMTimeout.h"
// 400 Hz sine wave at 24000 Hz sample rate
const q15_t BUSY_AUDIO[] = {0, 3426, 6813, 10126, 13328, 16384, 19261, 21926, 24351, 26510, 28378, 29935, 31164, 32052, 32588, 32767, 32588, 32052, 31164, 29935, 28378, 26510, 24351,
21926, 19261, 16384, 13328, 10126, 6813, 3425, 0, -3425, -6813, -10126, -13328, -16384, -19261, -21926, -24351, -26510, -28378, -29935, -31164, -32052,
-32588, -32768, -32588, -32052, -31164, -29935, -28378, -26510, -24351, -21926, -19261, -16384, -13328, -10126, -6813, -3425};
const uint8_t BUSY_AUDIO_LEN = 60U;
CFMTimeout::CFMTimeout() :
m_level(128 * 128),
m_running(false),
m_pos(0U),
m_n(0U)
{
}
void CFMTimeout::setParams(uint8_t level)
{
m_level = q15_t(level * 128);
}
q15_t CFMTimeout::getAudio()
{
q15_t sample = 0U;
if (!m_running)
return sample;
if (m_pos > 12000U) {
q31_t sample = BUSY_AUDIO[m_n] * m_level;
sample = q15_t(__SSAT((sample >> 15), 16));
m_n++;
if (m_n >= BUSY_AUDIO_LEN)
m_n = 0U;
} else {
sample = 0U;
}
m_pos++;
if (m_pos >= 24000U)
m_pos = 0U;
return sample;
}
void CFMTimeout::start()
{
m_running = true;
m_pos = 0U;
m_n = 0U;
}
void CFMTimeout::stop()
{
m_running = false;
}

42
FMTimeout.h Normal file
View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 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(FMTimeout_H)
#define FMTimeout_H
#include "Config.h"
class CFMTimeout {
public:
CFMTimeout();
void setParams(uint8_t level);
void start();
void stop();
q15_t getAudio();
private:
q15_t m_level;
bool m_running;
uint32_t m_pos;
uint8_t m_n;
};
#endif

70
FMTimer.cpp Normal file
View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2009,2010,2015,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 "FMTimer.h"
CFMTimer::CFMTimer() :
m_timeout(0U),
m_timer(0U)
{
}
void CFMTimer::setTimeout(uint16_t secs, uint32_t msecs)
{
m_timeout = (secs * 24000U) + (msecs * 24U);
}
uint32_t CFMTimer::getTimeout() const
{
return m_timeout / 24U;
}
void CFMTimer::start()
{
if (m_timeout > 0U)
m_timer = 1U;
}
void CFMTimer::stop()
{
m_timer = 0U;
}
void CFMTimer::clock(uint8_t length)
{
if (m_timer > 0U && m_timeout > 0U)
m_timer += length;
}
bool CFMTimer::isRunning() const
{
return m_timer > 0U;
}
bool CFMTimer::hasExpired() const
{
if (m_timeout == 0U || m_timer == 0U)
return false;
if (m_timer > m_timeout)
return true;
return false;
}

47
FMTimer.h Normal file
View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2009,2010,2015,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(FMTimer_H)
#define FMTimer_H
#include "Config.h"
class CFMTimer {
public:
CFMTimer();
void setTimeout(uint16_t secs, uint32_t msecs);
uint32_t getTimeout() const;
void start();
void stop();
void clock(uint8_t length);
bool isRunning() const;
bool hasExpired() const;
private:
uint32_t m_timeout;
uint32_t m_timer;
};
#endif

View File

@ -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 * 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 * it under the terms of the GNU General Public License as published by
@ -50,6 +50,7 @@ enum MMDVM_STATE {
STATE_P25 = 4, STATE_P25 = 4,
STATE_NXDN = 5, STATE_NXDN = 5,
STATE_POCSAG = 6, STATE_POCSAG = 6,
STATE_FM = 10,
// Dummy states start at 90 // Dummy states start at 90
STATE_NXDNCAL1K = 91, STATE_NXDNCAL1K = 91,
@ -62,7 +63,13 @@ enum MMDVM_STATE {
STATE_DMRCAL = 98, STATE_DMRCAL = 98,
STATE_DSTARCAL = 99, STATE_DSTARCAL = 99,
STATE_INTCAL = 100, STATE_INTCAL = 100,
STATE_POCSAGCAL = 101 STATE_POCSAGCAL = 101,
STATE_FMCAL10K = 102,
STATE_FMCAL12K = 103,
STATE_FMCAL15K = 104,
STATE_FMCAL20K = 105,
STATE_FMCAL25K = 106,
STATE_FMCAL30K = 107
}; };
#include "SerialPort.h" #include "SerialPort.h"
@ -82,6 +89,7 @@ enum MMDVM_STATE {
#include "POCSAGTX.h" #include "POCSAGTX.h"
#include "CalDStarRX.h" #include "CalDStarRX.h"
#include "CalDStarTX.h" #include "CalDStarTX.h"
#include "CalFM.h"
#include "CalDMR.h" #include "CalDMR.h"
#include "CalP25.h" #include "CalP25.h"
#include "CalNXDN.h" #include "CalNXDN.h"
@ -90,6 +98,7 @@ enum MMDVM_STATE {
#include "CWIdTX.h" #include "CWIdTX.h"
#include "Debug.h" #include "Debug.h"
#include "IO.h" #include "IO.h"
#include "FM.h"
const uint8_t MARK_SLOT1 = 0x08U; const uint8_t MARK_SLOT1 = 0x08U;
const uint8_t MARK_SLOT2 = 0x04U; const uint8_t MARK_SLOT2 = 0x04U;
@ -114,6 +123,7 @@ extern bool m_ysfEnable;
extern bool m_p25Enable; extern bool m_p25Enable;
extern bool m_nxdnEnable; extern bool m_nxdnEnable;
extern bool m_pocsagEnable; extern bool m_pocsagEnable;
extern bool m_fmEnable;
extern bool m_duplex; extern bool m_duplex;
@ -144,9 +154,12 @@ extern CNXDNTX nxdnTX;
extern CPOCSAGTX pocsagTX; extern CPOCSAGTX pocsagTX;
extern CFM fm;
extern CCalDStarRX calDStarRX; extern CCalDStarRX calDStarRX;
extern CCalDStarTX calDStarTX; extern CCalDStarTX calDStarTX;
extern CCalDMR calDMR; extern CCalDMR calDMR;
extern CCalFM calFM;
extern CCalP25 calP25; extern CCalP25 calP25;
extern CCalNXDN calNXDN; extern CCalNXDN calNXDN;
extern CCalPOCSAG calPOCSAG; extern CCalPOCSAG calPOCSAG;
@ -155,4 +168,3 @@ extern CCalRSSI calRSSI;
extern CCWIdTX cwIdTX; extern CCWIdTX cwIdTX;
#endif #endif

122
IO.cpp
View File

@ -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
* Copyright (C) 2015 by Jim Mclaughlin KI6ZUM * Copyright (C) 2015 by Jim Mclaughlin KI6ZUM
* Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2016 by Colin Durbridge G4EML
* *
@ -55,6 +55,7 @@ m_ysfTXLevel(128 * 128),
m_p25TXLevel(128 * 128), m_p25TXLevel(128 * 128),
m_nxdnTXLevel(128 * 128), m_nxdnTXLevel(128 * 128),
m_pocsagTXLevel(128 * 128), m_pocsagTXLevel(128 * 128),
m_fmTXLevel(128 * 128),
m_rxDCOffset(DC_OFFSET), m_rxDCOffset(DC_OFFSET),
m_txDCOffset(DC_OFFSET), m_txDCOffset(DC_OFFSET),
m_ledCount(0U), m_ledCount(0U),
@ -104,6 +105,7 @@ void CIO::selfTest()
setP25Int(ledValue); setP25Int(ledValue);
setNXDNInt(ledValue); setNXDNInt(ledValue);
setPOCSAGInt(ledValue); setPOCSAGInt(ledValue);
setFMInt(ledValue);
#endif #endif
delayInt(250); delayInt(250);
} }
@ -115,105 +117,56 @@ void CIO::selfTest()
setP25Int(false); setP25Int(false);
setNXDNInt(false); setNXDNInt(false);
setPOCSAGInt(false); setPOCSAGInt(false);
setFMInt(false);
delayInt(250); delayInt(250);
setDStarInt(true);
setDMRInt(true); setDMRInt(true);
setYSFInt(false);
setP25Int(false);
setNXDNInt(false);
setPOCSAGInt(false);
delayInt(250); delayInt(250);
setDStarInt(true);
setDMRInt(true);
setYSFInt(true); setYSFInt(true);
setP25Int(false);
setNXDNInt(false);
setPOCSAGInt(false);
delayInt(250);
setDStarInt(true); delayInt(250);
setDMRInt(true);
setYSFInt(true);
setP25Int(true); setP25Int(true);
setNXDNInt(false);
setPOCSAGInt(false);
delayInt(250);
setDStarInt(true); #if !defined(USE_ALTERNATE_NXDN_LEDS)
setDMRInt(true); delayInt(250);
setYSFInt(true);
setP25Int(true);
setNXDNInt(true); setNXDNInt(true);
setPOCSAGInt(false); #endif
delayInt(250);
setDStarInt(true); #if !defined(USE_ALTERNATE_POCSAG_LEDS)
setDMRInt(true); delayInt(250);
setYSFInt(true);
setP25Int(true);
setNXDNInt(true);
setPOCSAGInt(true); setPOCSAGInt(true);
#endif
delayInt(250);
setDStarInt(true); #if !defined(USE_ALTERNATE_FM_LEDS)
setDMRInt(true); delayInt(250);
setYSFInt(true); setFMInt(true);
setP25Int(true);
setNXDNInt(true); delayInt(250);
setFMInt(false);
#endif
#if !defined(USE_ALTERNATE_POCSAG_LEDS)
delayInt(250);
setPOCSAGInt(false); setPOCSAGInt(false);
#endif
delayInt(250);
setDStarInt(true); #if !defined(USE_ALTERNATE_NXDN_LEDS)
setDMRInt(true); delayInt(250);
setYSFInt(true);
setP25Int(true);
setNXDNInt(false); setNXDNInt(false);
setPOCSAGInt(false); #endif
delayInt(250); delayInt(250);
setDStarInt(true);
setDMRInt(true);
setYSFInt(true);
setP25Int(false); setP25Int(false);
setNXDNInt(false);
setPOCSAGInt(false);
delayInt(250); delayInt(250);
setDStarInt(true);
setDMRInt(true);
setYSFInt(false); setYSFInt(false);
setP25Int(false);
setNXDNInt(false);
setPOCSAGInt(false);
delayInt(250); delayInt(250);
setDStarInt(true);
setDMRInt(false); setDMRInt(false);
setYSFInt(false);
setP25Int(false);
setNXDNInt(false);
setPOCSAGInt(false);
delayInt(250); delayInt(250);
setDStarInt(false); setDStarInt(false);
setDMRInt(false);
setYSFInt(false);
setP25Int(false);
setNXDNInt(false);
setPOCSAGInt(false);
#endif #endif
} }
@ -343,9 +296,17 @@ void CIO::process()
#else #else
::arm_fir_fast_q15(&m_boxcar10Filter, samples, vals, RX_BLOCK_SIZE); ::arm_fir_fast_q15(&m_boxcar10Filter, samples, vals, RX_BLOCK_SIZE);
#endif #endif
nxdnRX.samples(vals, rssi, RX_BLOCK_SIZE); nxdnRX.samples(vals, rssi, RX_BLOCK_SIZE);
} }
if (m_fmEnable) {
bool cos = getCOSInt();
#if defined(USE_DCBLOCKER)
fm.samples(cos, dcSamples, RX_BLOCK_SIZE);
#else
fm.samples(cos, samples, RX_BLOCK_SIZE);
#endif
}
} else if (m_modemState == STATE_DSTAR) { } else if (m_modemState == STATE_DSTAR) {
if (m_dstarEnable) { if (m_dstarEnable) {
q15_t vals[RX_BLOCK_SIZE]; q15_t vals[RX_BLOCK_SIZE];
@ -402,6 +363,13 @@ void CIO::process()
#endif #endif
nxdnRX.samples(vals, rssi, RX_BLOCK_SIZE); nxdnRX.samples(vals, rssi, RX_BLOCK_SIZE);
} }
} else if (m_modemState == STATE_FM) {
bool cos = getCOSInt();
#if defined(USE_DCBLOCKER)
fm.samples(cos, dcSamples, RX_BLOCK_SIZE);
#else
fm.samples(cos, samples, RX_BLOCK_SIZE);
#endif
} else if (m_modemState == STATE_DSTARCAL) { } else if (m_modemState == STATE_DSTARCAL) {
q15_t vals[RX_BLOCK_SIZE]; q15_t vals[RX_BLOCK_SIZE];
#if defined(USE_DCBLOCKER) #if defined(USE_DCBLOCKER)
@ -450,6 +418,9 @@ void CIO::write(MMDVM_STATE mode, q15_t* samples, uint16_t length, const uint8_t
case STATE_POCSAG: case STATE_POCSAG:
txLevel = m_pocsagTXLevel; txLevel = m_pocsagTXLevel;
break; break;
case STATE_FM:
txLevel = m_fmTXLevel;
break;
default: default:
txLevel = m_cwIdTXLevel; txLevel = m_cwIdTXLevel;
break; break;
@ -498,10 +469,11 @@ void CIO::setMode()
setP25Int(m_modemState == STATE_P25); setP25Int(m_modemState == STATE_P25);
setNXDNInt(m_modemState == STATE_NXDN); setNXDNInt(m_modemState == STATE_NXDN);
setPOCSAGInt(m_modemState == STATE_POCSAG); setPOCSAGInt(m_modemState == STATE_POCSAG);
setFMInt(m_modemState == STATE_FM);
#endif #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, 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, int16_t txDCOffset, int16_t rxDCOffset)
{ {
m_pttInvert = pttInvert; m_pttInvert = pttInvert;
@ -513,6 +485,7 @@ void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rx
m_p25TXLevel = q15_t(p25TXLevel * 128); m_p25TXLevel = q15_t(p25TXLevel * 128);
m_nxdnTXLevel = q15_t(nxdnTXLevel * 128); m_nxdnTXLevel = q15_t(nxdnTXLevel * 128);
m_pocsagTXLevel = q15_t(pocsagTXLevel * 128); m_pocsagTXLevel = q15_t(pocsagTXLevel * 128);
m_fmTXLevel = q15_t(fmTXLevel * 128);
m_rxDCOffset = DC_OFFSET + rxDCOffset; m_rxDCOffset = DC_OFFSET + rxDCOffset;
m_txDCOffset = DC_OFFSET + txDCOffset; m_txDCOffset = DC_OFFSET + txDCOffset;
@ -563,4 +536,3 @@ bool CIO::hasLockout() const
{ {
return m_lockout; return m_lockout;
} }

7
IO.h
View File

@ -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 * 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 * it under the terms of the GNU General Public License as published by
@ -42,7 +42,7 @@ public:
void interrupt(); 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, 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, int16_t txDCOffset, int16_t rxDCOffset);
void getOverflow(bool& adcOverflow, bool& dacOverflow); void getOverflow(bool& adcOverflow, bool& dacOverflow);
@ -80,6 +80,7 @@ private:
q15_t m_p25TXLevel; q15_t m_p25TXLevel;
q15_t m_nxdnTXLevel; q15_t m_nxdnTXLevel;
q15_t m_pocsagTXLevel; q15_t m_pocsagTXLevel;
q15_t m_fmTXLevel;
uint16_t m_rxDCOffset; uint16_t m_rxDCOffset;
uint16_t m_txDCOffset; uint16_t m_txDCOffset;
@ -112,9 +113,9 @@ private:
void setP25Int(bool on); void setP25Int(bool on);
void setNXDNInt(bool on); void setNXDNInt(bool on);
void setPOCSAGInt(bool on); void setPOCSAGInt(bool on);
void setFMInt(bool on);
void delayInt(unsigned int dly); void delayInt(unsigned int dly);
}; };
#endif #endif

View File

@ -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
* Copyright (C) 2015 by Jim Mclaughlin KI6ZUM * Copyright (C) 2015 by Jim Mclaughlin KI6ZUM
* Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2016 by Colin Durbridge G4EML
* *
@ -35,6 +35,7 @@
#define PIN_P25 19 #define PIN_P25 19
#define PIN_NXDN 20 #define PIN_NXDN 20
#define PIN_POCSAG 4 #define PIN_POCSAG 4
#define PIN_FM 5
#define ADC_CHER_Chan (1<<7) // ADC on Due pin A0 - Due AD7 - (1 << 7) #define ADC_CHER_Chan (1<<7) // ADC on Due pin A0 - Due AD7 - (1 << 7)
#define ADC_ISR_EOC_Chan ADC_ISR_EOC7 #define ADC_ISR_EOC_Chan ADC_ISR_EOC7
#define ADC_CDR_Chan 7 #define ADC_CDR_Chan 7
@ -50,6 +51,7 @@
#define PIN_P25 6 #define PIN_P25 6
#define PIN_NXDN 5 #define PIN_NXDN 5
#define PIN_POCSAG 4 #define PIN_POCSAG 4
#define PIN_FM 3
#define ADC_CHER_Chan (1<<13) // ADC on Due pin A11 - Due AD13 - (1 << 13) #define ADC_CHER_Chan (1<<13) // ADC on Due pin A11 - Due AD13 - (1 << 13)
#define ADC_ISR_EOC_Chan ADC_ISR_EOC13 #define ADC_ISR_EOC_Chan ADC_ISR_EOC13
#define ADC_CDR_Chan 13 #define ADC_CDR_Chan 13
@ -67,6 +69,7 @@
#define PIN_P25 6 #define PIN_P25 6
#define PIN_NXDN 5 #define PIN_NXDN 5
#define PIN_POCSAG 4 #define PIN_POCSAG 4
#define PIN_FM 3
#define ADC_CHER_Chan (1<<7) // ADC on Due pin A0 - Due AD7 - (1 << 7) #define ADC_CHER_Chan (1<<7) // ADC on Due pin A0 - Due AD7 - (1 << 7)
#define ADC_ISR_EOC_Chan ADC_ISR_EOC7 #define ADC_ISR_EOC_Chan ADC_ISR_EOC7
#define ADC_CDR_Chan 7 #define ADC_CDR_Chan 7
@ -107,6 +110,9 @@ void CIO::initInt()
#if !defined(USE_ALTERNATE_POCSAG_LEDS) #if !defined(USE_ALTERNATE_POCSAG_LEDS)
pinMode(PIN_POCSAG, OUTPUT); pinMode(PIN_POCSAG, OUTPUT);
#endif #endif
#if !defined(USE_ALTERNATE_POCSAG_LEDS)
pinMode(PIN_FM, OUTPUT);
#endif
#endif #endif
} }
@ -258,10 +264,19 @@ void CIO::setPOCSAGInt(bool on)
#endif #endif
} }
void CIO::setFMInt(bool on)
{
#if defined(USE_ALTERNATE_FM_LEDS)
digitalWrite(PIN_DSTAR, on ? HIGH : LOW);
digitalWrite(PIN_YSF, on ? HIGH : LOW);
#else
digitalWrite(PIN_FM, on ? HIGH : LOW);
#endif
}
void CIO::delayInt(unsigned int dly) void CIO::delayInt(unsigned int dly)
{ {
delay(dly); delay(dly);
} }
#endif #endif

View File

@ -1,7 +1,7 @@
/* /*
* Copyright (C) 2016 by Jim McLaughlin KI6ZUM * Copyright (C) 2016 by Jim McLaughlin KI6ZUM
* Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU * Copyright (C) 2016,2017,2018 by Andy Uribe CA6JAU
* Copyright (C) 2017,2018 by Jonathan Naylor G4KLX * Copyright (C) 2017,2018,2020 by Jonathan Naylor G4KLX
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -42,8 +42,8 @@ POCSAG PB12 output CN10 Pin16
MDSTAR PC4 output CN10 Pin34 MDSTAR PC4 output CN10 Pin34
MDMR PC5 output CN10 Pin6 MDMR PC5 output CN10 Pin6
MYSF PC2 output CN7 Pin35 MYSF PC2 output CN7 Pin35
MP25 PC3 output CN7 Pin37 MP25 PC3 output CN7 Pin37
MNXDN PC6 output CN10 Pin4 MNXDN PC6 output CN10 Pin4
MPOCSAG PC8 output CN10 Pin2 MPOCSAG PC8 output CN10 Pin2
@ -1438,6 +1438,23 @@ void CIO::setPOCSAGInt(bool on)
#endif #endif
} }
void CIO::setFMInt(bool on)
{
#if defined(USE_ALTERNATE_FM_LEDS)
GPIO_WriteBit(PORT_DSTAR, PIN_DSTAR, on ? Bit_SET : Bit_RESET);
GPIO_WriteBit(PORT_YSF, PIN_YSF, on ? Bit_SET : Bit_RESET);
#if defined(MODE_PINS) && defined(STM32F4_NUCLEO_MORPHO_HEADER) && (defined(STM32F4_NUCLEO) || defined(STM32F722_RPT_HAT))
GPIO_WriteBit(PORT_MDSTAR, PIN_MDSTAR, on ? Bit_SET : Bit_RESET);
GPIO_WriteBit(PORT_MYSF, PIN_MYSF, on ? Bit_SET : Bit_RESET);
#endif
#else
GPIO_WriteBit(PORT_FM, PIN_FM, on ? Bit_SET : Bit_RESET);
#if defined(MODE_PINS) && defined(STM32F4_NUCLEO_MORPHO_HEADER) && (defined(STM32F4_NUCLEO) || defined(STM32F722_RPT_HAT))
GPIO_WriteBit(PORT_MFM, PIN_MFM, on ? Bit_SET : Bit_RESET);
#endif
#endif
}
// Simple delay function for STM32 // Simple delay function for STM32
// Example from: http://thehackerworkshop.com/?p=1209 // Example from: http://thehackerworkshop.com/?p=1209
void CIO::delayInt(unsigned int dly) void CIO::delayInt(unsigned int dly)

View File

@ -1,7 +1,7 @@
/* /*
* Copyright (C) 2016 by Jim McLaughlin KI6ZUM * Copyright (C) 2016 by Jim McLaughlin KI6ZUM
* Copyright (C) 2016, 2017 by Andy Uribe CA6JAU * Copyright (C) 2016, 2017 by Andy Uribe CA6JAU
* Copyright (C) 2017,2018 by Jonathan Naylor G4KLX * Copyright (C) 2017,2018,2020 by Jonathan Naylor G4KLX
* Copyright (C) 2017 by Wojciech Krutnik N0CALL * Copyright (C) 2017 by Wojciech Krutnik N0CALL
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@ -41,6 +41,7 @@ YSF PB8 output
P25 PB9 output P25 PB9 output
NXDN PB10 output NXDN PB10 output
POCSAG PB11 output POCSAG PB11 output
FM PB12 output
RX PB0 analog input (ADC1_8) RX PB0 analog input (ADC1_8)
RSSI PB1 analog input (ADC2_9) RSSI PB1 analog input (ADC2_9)
@ -84,6 +85,9 @@ USART1_RXD PA10 input (AF)
#define PIN_POCSAG 11 #define PIN_POCSAG 11
#define PORT_POCSAG GPIOB #define PORT_POCSAG GPIOB
#define BB_POCSAG *((bitband_t)BITBAND_PERIPH(&PORT_POCSAG->ODR, PIN_POCSAG)) #define BB_POCSAG *((bitband_t)BITBAND_PERIPH(&PORT_POCSAG->ODR, PIN_POCSAG))
#define PIN_FM 12
#define PORT_FM GPIOB
#define BB_FM *((bitband_t)BITBAND_PERIPH(&PORT_FM->ODR, PIN_FM))
#define PIN_RX 0 #define PIN_RX 0
#define PIN_RX_ADC_CH 8 #define PIN_RX_ADC_CH 8
@ -154,9 +158,9 @@ void GPIOConfigPin(GPIO_TypeDef *port_ptr, uint32_t pin, uint32_t mode_cnf_value
#if defined(STM32F1_POG) #if defined(STM32F1_POG)
void FancyLEDEffect() void FancyLEDEffect()
{ {
bitband_t foo[] = {&BB_LED, &BB_COSLED, &BB_PTT, &BB_DMR, &BB_DSTAR, &BB_YSF, &BB_P25}; bitband_t foo[] = {&BB_LED, &BB_COSLED, &BB_PTT, &BB_DMR, &BB_DSTAR, &BB_YSF, &BB_P25, &BB_NXDN, &BB_POCSAG, &BB_FM};
for(int i=0; i<7; i++){ for(int i=0; i<10; i++){
*foo[i] = 0x01; *foo[i] = 0x01;
} }
GPIOConfigPin(PORT_USART1_TXD, PIN_USART1_TXD, GPIO_CRL_MODE0_1); GPIOConfigPin(PORT_USART1_TXD, PIN_USART1_TXD, GPIO_CRL_MODE0_1);
@ -172,12 +176,12 @@ void FancyLEDEffect()
*((bitband_t)BITBAND_PERIPH(&PORT_USART1_TXD->ODR, PIN_USART1_TXD)) = 0x01; *((bitband_t)BITBAND_PERIPH(&PORT_USART1_TXD->ODR, PIN_USART1_TXD)) = 0x01;
*foo[0] = 0x01; *foo[0] = 0x01;
for(int i=1; i<7; i++){ for(int i=1; i<10; i++){
delay(SystemCoreClock/1000*10); delay(SystemCoreClock/1000*10);
*foo[i-1] = 0x00; *foo[i-1] = 0x00;
*foo[i] = 0x01; *foo[i] = 0x01;
} }
for(int i=5; i>=0; i--){ for(int i=10; i>=0; i--){
delay(SystemCoreClock/1000*10); delay(SystemCoreClock/1000*10);
*foo[i+1] = 0x00; *foo[i+1] = 0x00;
*foo[i] = 0x01; *foo[i] = 0x01;
@ -228,6 +232,9 @@ static inline void GPIOInit()
#if !defined(USE_ALTERNATE_POCSAG_LEDS) #if !defined(USE_ALTERNATE_POCSAG_LEDS)
GPIOConfigPin(PORT_POCSAG, PIN_POCSAG, GPIO_CRL_MODE0_1); GPIOConfigPin(PORT_POCSAG, PIN_POCSAG, GPIO_CRL_MODE0_1);
#endif #endif
#if !defined(USE_ALTERNATE_FM_LEDS)
GPIOConfigPin(PORT_FM, PIN_FM, GPIO_CRL_MODE0_1);
#endif
GPIOConfigPin(PORT_RX, PIN_RX, 0); GPIOConfigPin(PORT_RX, PIN_RX, 0);
#if defined(SEND_RSSI_DATA) #if defined(SEND_RSSI_DATA)
@ -460,6 +467,16 @@ void CIO::setPOCSAGInt(bool on)
#endif #endif
} }
void CIO::setFMInt(bool on)
{
#if defined(USE_ALTERNATE_FM_LEDS)
BB_DSTAR = !!on;
BB_YSF = !!on;
#else
BB_FM = !!on;
#endif
}
void CIO::delayInt(unsigned int dly) void CIO::delayInt(unsigned int dly)
{ {
delay(dly); delay(dly);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2016,2017,2018 by Jonathan Naylor G4KLX * Copyright (C) 2016,2017,2018,2020 by Jonathan Naylor G4KLX
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -37,9 +37,11 @@
#if defined(__MK20DX256__) #if defined(__MK20DX256__)
#define PIN_NXDN 2 #define PIN_NXDN 2
#define PIN_POCSAG 3 #define PIN_POCSAG 3
#define PIN_FM 4
#else #else
#define PIN_NXDN 24 #define PIN_NXDN 24
#define PIN_POCSAG 25 #define PIN_POCSAG 25
#define PIN_FM 26
#endif #endif
#define PIN_ADC 5 // A0, Pin 14 #define PIN_ADC 5 // A0, Pin 14
#define PIN_RSSI 4 // Teensy 3.5/3.6, A16, Pin 35. Teensy 3.1/3.2, A17, Pin 28 #define PIN_RSSI 4 // Teensy 3.5/3.6, A16, Pin 35. Teensy 3.1/3.2, A17, Pin 28
@ -76,6 +78,9 @@ void CIO::initInt()
#if !defined(USE_ALTERNATE_POCSAG_LEDS) #if !defined(USE_ALTERNATE_POCSAG_LEDS)
pinMode(PIN_POCSAG, OUTPUT); pinMode(PIN_POCSAG, OUTPUT);
#endif #endif
#if !defined(USE_ALTERNATE_FM_LEDS)
pinMode(PIN_FM, OUTPUT);
#endif
#endif #endif
} }
@ -243,6 +248,16 @@ void CIO::setPOCSAGInt(bool on)
#endif #endif
} }
void CIO::setFMInt(bool on)
{
#if defined(USE_ALTERNATE_FM_LEDS)
digitalWrite(PIN_DSTAR, on ? HIGH : LOW);
digitalWrite(PIN_YSF, on ? HIGH : LOW);
#else
digitalWrite(PIN_FM, on ? HIGH : LOW);
#endif
}
void CIO::delayInt(unsigned int dly) void CIO::delayInt(unsigned int dly)
{ {
delay(dly); delay(dly);

View File

@ -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
* Copyright (C) 2016 by Mathis Schmieder DB9MAT * Copyright (C) 2016 by Mathis Schmieder DB9MAT
* Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2016 by Colin Durbridge G4EML
* *
@ -32,6 +32,7 @@ bool m_ysfEnable = true;
bool m_p25Enable = true; bool m_p25Enable = true;
bool m_nxdnEnable = true; bool m_nxdnEnable = true;
bool m_pocsagEnable = true; bool m_pocsagEnable = true;
bool m_fmEnable = true;
bool m_duplex = true; bool m_duplex = true;
@ -59,11 +60,14 @@ CNXDNTX nxdnTX;
CPOCSAGTX pocsagTX; CPOCSAGTX pocsagTX;
CFM fm;
CCalDStarRX calDStarRX; CCalDStarRX calDStarRX;
CCalDStarTX calDStarTX; CCalDStarTX calDStarTX;
CCalDMR calDMR; CCalDMR calDMR;
CCalP25 calP25; CCalP25 calP25;
CCalNXDN calNXDN; CCalNXDN calNXDN;
CCalFM calFM;
CCalPOCSAG calPOCSAG; CCalPOCSAG calPOCSAG;
CCalRSSI calRSSI; CCalRSSI calRSSI;
@ -106,6 +110,9 @@ void loop()
if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy())) if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy()))
pocsagTX.process(); pocsagTX.process();
if (m_fmEnable && m_modemState == STATE_FM)
fm.process();
if (m_modemState == STATE_DSTARCAL) if (m_modemState == STATE_DSTARCAL)
calDStarTX.process(); calDStarTX.process();
@ -121,6 +128,9 @@ void loop()
if (m_modemState == STATE_POCSAGCAL) if (m_modemState == STATE_POCSAGCAL)
calPOCSAG.process(); calPOCSAG.process();
if (m_modemState == STATE_FMCAL10K || m_modemState == STATE_FMCAL12K || m_modemState == STATE_FMCAL15K || m_modemState == STATE_FMCAL20K || m_modemState == STATE_FMCAL25K ||m_modemState == STATE_FMCAL30K)
calFM.process();
if (m_modemState == STATE_IDLE) if (m_modemState == STATE_IDLE)
cwIdTX.process(); cwIdTX.process();
} }

View File

@ -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
* Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2016 by Colin Durbridge G4EML
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@ -29,6 +29,7 @@ bool m_ysfEnable = true;
bool m_p25Enable = true; bool m_p25Enable = true;
bool m_nxdnEnable = true; bool m_nxdnEnable = true;
bool m_pocsagEnable = true; bool m_pocsagEnable = true;
bool m_fmEnable = true;
bool m_duplex = true; bool m_duplex = true;
@ -56,9 +57,12 @@ CNXDNTX nxdnTX;
CPOCSAGTX pocsagTX; CPOCSAGTX pocsagTX;
CFM fm;
CCalDStarRX calDStarRX; CCalDStarRX calDStarRX;
CCalDStarTX calDStarTX; CCalDStarTX calDStarTX;
CCalDMR calDMR; CCalDMR calDMR;
CCalFM calFM;
CCalP25 calP25; CCalP25 calP25;
CCalNXDN calNXDN; CCalNXDN calNXDN;
CCalPOCSAG calPOCSAG; CCalPOCSAG calPOCSAG;
@ -103,12 +107,18 @@ void loop()
if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy())) if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy()))
pocsagTX.process(); pocsagTX.process();
if (m_fmEnable && m_modemState == STATE_FM)
fm.process();
if (m_modemState == STATE_DSTARCAL) if (m_modemState == STATE_DSTARCAL)
calDStarTX.process(); calDStarTX.process();
if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K || m_modemState == STATE_DMRDMO1K) if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K || m_modemState == STATE_DMRDMO1K)
calDMR.process(); calDMR.process();
if (m_modemState == STATE_FMCAL10K || m_modemState == STATE_FMCAL12K || m_modemState == STATE_FMCAL15K || m_modemState == STATE_FMCAL20K || m_modemState == STATE_FMCAL25K || m_modemState == STATE_FMCAL30K)
calFM.process();
if (m_modemState == STATE_P25CAL1K) if (m_modemState == STATE_P25CAL1K)
calP25.process(); calP25.process();
@ -121,4 +131,3 @@ void loop()
if (m_modemState == STATE_IDLE) if (m_modemState == STATE_IDLE)
cwIdTX.process(); cwIdTX.process();
} }

View File

@ -273,6 +273,7 @@
<File name="CalDStarTX.cpp" path="CalDStarTX.cpp" type="1"/> <File name="CalDStarTX.cpp" path="CalDStarTX.cpp" type="1"/>
<File name="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver/source" path="" type="2"/> <File name="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver/source" path="" type="2"/>
<File name="CalDMR.h" path="CalDMR.h" type="1"/> <File name="CalDMR.h" path="CalDMR.h" type="1"/>
<File name="CalFM.h" path="CalFM.h" type="1"/>
<File name="DMRDMOTX.h" path="DMRDMOTX.h" type="1"/> <File name="DMRDMOTX.h" path="DMRDMOTX.h" type="1"/>
<File name="IODue.cpp" path="IODue.cpp" type="1"/> <File name="IODue.cpp" path="IODue.cpp" type="1"/>
<File name="DMRTX.cpp" path="DMRTX.cpp" type="1"/> <File name="DMRTX.cpp" path="DMRTX.cpp" type="1"/>
@ -290,6 +291,7 @@
<File name="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver" path="" type="2"/> <File name="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver" path="" type="2"/>
<File name="DMRDefines.h" path="DMRDefines.h" type="1"/> <File name="DMRDefines.h" path="DMRDefines.h" type="1"/>
<File name="CalDMR.cpp" path="CalDMR.cpp" type="1"/> <File name="CalDMR.cpp" path="CalDMR.cpp" type="1"/>
<File name="CalFM.cpp" path="CalFM.cpp" type="1"/>
<File name="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver/include/stm32f4xx_adc.h" path="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver/include/stm32f4xx_adc.h" type="1"/> <File name="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver/include/stm32f4xx_adc.h" path="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver/include/stm32f4xx_adc.h" type="1"/>
<File name="SerialSTM.cpp" path="SerialSTM.cpp" type="1"/> <File name="SerialSTM.cpp" path="SerialSTM.cpp" type="1"/>
<File name="STM32F4XX_Lib/CMSIS/Lib/GCC/libarm_cortexM4lf_math.a" path="STM32F4XX_Lib/CMSIS/Lib/GCC/libarm_cortexM4lf_math.a" type="1"/> <File name="STM32F4XX_Lib/CMSIS/Lib/GCC/libarm_cortexM4lf_math.a" path="STM32F4XX_Lib/CMSIS/Lib/GCC/libarm_cortexM4lf_math.a" type="1"/>
@ -335,4 +337,4 @@
<File name="YSFTX.cpp" path="YSFTX.cpp" type="1"/> <File name="YSFTX.cpp" path="YSFTX.cpp" type="1"/>
<File name="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver/source/stm32f4xx_gpio.c" path="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver/source/stm32f4xx_gpio.c" type="1"/> <File name="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver/source/stm32f4xx_gpio.c" path="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver/source/stm32f4xx_gpio.c" type="1"/>
</Files> </Files>
</Project> </Project>

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013,2015-2019 by Jonathan Naylor G4KLX * Copyright (C) 2013,2015-2020 by Jonathan Naylor G4KLX
* Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2016 by Colin Durbridge G4EML
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@ -65,6 +65,10 @@ const uint8_t MMDVM_NXDN_LOST = 0x41U;
const uint8_t MMDVM_POCSAG_DATA = 0x50U; const uint8_t MMDVM_POCSAG_DATA = 0x50U;
const uint8_t MMDVM_FM_PARAMS1 = 0x60U;
const uint8_t MMDVM_FM_PARAMS2 = 0x61U;
const uint8_t MMDVM_FM_PARAMS3 = 0x62U;
const uint8_t MMDVM_ACK = 0x70U; const uint8_t MMDVM_ACK = 0x70U;
const uint8_t MMDVM_NAK = 0x7FU; const uint8_t MMDVM_NAK = 0x7FU;
@ -97,7 +101,7 @@ const uint8_t MMDVM_DEBUG5 = 0xF5U;
#define HW_TYPE "MMDVM" #define HW_TYPE "MMDVM"
#endif #endif
#define DESCRIPTION "20190130 (D-Star/DMR/System Fusion/P25/NXDN/POCSAG)" #define DESCRIPTION "20200428 (D-Star/DMR/System Fusion/P25/NXDN/POCSAG/FM)"
#if defined(GITVERSION) #if defined(GITVERSION)
#define concat(h, a, b, c) h " " a " " b " GitID #" c "" #define concat(h, a, b, c) h " " a " " b " GitID #" c ""
@ -168,6 +172,8 @@ void CSerialPort::getStatus()
reply[3U] |= 0x10U; reply[3U] |= 0x10U;
if (m_pocsagEnable) if (m_pocsagEnable)
reply[3U] |= 0x20U; reply[3U] |= 0x20U;
if (m_fmEnable)
reply[3U] |= 0x40U;
reply[4U] = uint8_t(m_modemState); reply[4U] = uint8_t(m_modemState);
@ -256,7 +262,7 @@ void CSerialPort::getVersion()
uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
{ {
if (length < 18U) if (length < 19U)
return 4U; return 4U;
bool rxInvert = (data[0U] & 0x01U) == 0x01U; bool rxInvert = (data[0U] & 0x01U) == 0x01U;
@ -273,6 +279,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
bool p25Enable = (data[1U] & 0x08U) == 0x08U; bool p25Enable = (data[1U] & 0x08U) == 0x08U;
bool nxdnEnable = (data[1U] & 0x10U) == 0x10U; bool nxdnEnable = (data[1U] & 0x10U) == 0x10U;
bool pocsagEnable = (data[1U] & 0x20U) == 0x20U; bool pocsagEnable = (data[1U] & 0x20U) == 0x20U;
bool fmEnable = (data[1U] & 0x40U) == 0x40U;
uint8_t txDelay = data[2U]; uint8_t txDelay = data[2U];
if (txDelay > 50U) if (txDelay > 50U)
@ -280,7 +287,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
MMDVM_STATE modemState = MMDVM_STATE(data[3U]); 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_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) 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)
return 4U; return 4U;
if (modemState == STATE_DSTAR && !dstarEnable) if (modemState == STATE_DSTAR && !dstarEnable)
return 4U; return 4U;
@ -294,6 +301,8 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
return 4U; return 4U;
if (modemState == STATE_POCSAG && !pocsagEnable) if (modemState == STATE_POCSAG && !pocsagEnable)
return 4U; return 4U;
if (modemState == STATE_FM && !fmEnable)
return 4U;
uint8_t rxLevel = data[4U]; uint8_t rxLevel = data[4U];
@ -318,6 +327,8 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
uint8_t pocsagTXLevel = data[17U]; uint8_t pocsagTXLevel = data[17U];
uint8_t fmTXLevel = data[18U];
m_modemState = modemState; m_modemState = modemState;
m_dstarEnable = dstarEnable; m_dstarEnable = dstarEnable;
@ -326,6 +337,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
m_p25Enable = p25Enable; m_p25Enable = p25Enable;
m_nxdnEnable = nxdnEnable; m_nxdnEnable = nxdnEnable;
m_pocsagEnable = pocsagEnable; m_pocsagEnable = pocsagEnable;
m_fmEnable = fmEnable;
m_duplex = !simplex; m_duplex = !simplex;
dstarTX.setTXDelay(txDelay); dstarTX.setTXDelay(txDelay);
@ -343,13 +355,81 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
ysfTX.setParams(ysfLoDev, ysfTXHang); ysfTX.setParams(ysfLoDev, ysfTXHang);
io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, pocsagTXLevel, txDCOffset, rxDCOffset); io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, pocsagTXLevel, fmTXLevel, txDCOffset, rxDCOffset);
io.start(); io.start();
return 0U; return 0U;
} }
uint8_t CSerialPort::setFMParams1(const uint8_t* data, uint8_t length)
{
if (length < 8U)
return 4U;
uint8_t speed = data[0U];;
uint16_t frequency = data[1U] * 10U;
uint8_t time = data[2U];
uint8_t holdoff = data[3U];
uint8_t highLevel = data[4U];
uint8_t lowLevel = data[5U];
bool callAtStart = (data[6U] & 0x01U) == 0x01U;
bool callAtEnd = (data[6U] & 0x02U) == 0x02U;
char callsign[50U];
uint8_t n = 0U;
for (uint8_t i = 7U; i < length; i++, n++)
callsign[n] = data[i];
callsign[n] = '\0';
return fm.setCallsign(callsign, speed, frequency, time, holdoff, highLevel, lowLevel, callAtStart, callAtEnd);
}
uint8_t CSerialPort::setFMParams2(const uint8_t* data, uint8_t length)
{
if (length < 6U)
return 4U;
uint8_t speed = data[0U];
uint16_t frequency = data[1U] * 10U;
uint8_t minTime = data[2U];
uint16_t delay = data[3U] * 10U;
uint8_t level = data[4U];
char ack[50U];
uint8_t n = 0U;
for (uint8_t i = 5U; i < length; i++, n++)
ack[n] = data[i];
ack[n] = '\0';
return fm.setAck(ack, speed, frequency, minTime, delay, level);
}
uint8_t CSerialPort::setFMParams3(const uint8_t* data, uint8_t length)
{
if (length < 11U)
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 kerchunkTime = data[5U];
uint8_t hangTime = data[6U];
bool useCOS = (data[7U] & 0x01U) == 0x01U;
uint8_t rfAudioBoost = data[8U];
uint8_t maxDev = data[9U];
uint8_t rxLevel = data[10U];
return fm.setMisc(timeout, timeoutLevel, ctcssFrequency, ctcssThreshold, ctcssLevel, kerchunkTime, hangTime, useCOS, rfAudioBoost, maxDev, rxLevel);
}
uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length) uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length)
{ {
if (length < 1U) if (length < 1U)
@ -360,7 +440,7 @@ uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length)
if (modemState == m_modemState) if (modemState == m_modemState)
return 0U; 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_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) 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)
return 4U; return 4U;
if (modemState == STATE_DSTAR && !m_dstarEnable) if (modemState == STATE_DSTAR && !m_dstarEnable)
return 4U; return 4U;
@ -374,6 +454,8 @@ uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length)
return 4U; return 4U;
if (modemState == STATE_POCSAG && !m_pocsagEnable) if (modemState == STATE_POCSAG && !m_pocsagEnable)
return 4U; return 4U;
if (modemState == STATE_FM && !m_fmEnable)
return 4U;
setMode(modemState); setMode(modemState);
@ -401,6 +483,9 @@ void CSerialPort::setMode(MMDVM_STATE modemState)
case STATE_POCSAG: case STATE_POCSAG:
DEBUG1("Mode set to POCSAG"); DEBUG1("Mode set to POCSAG");
break; break;
case STATE_FM:
DEBUG1("Mode set to FM");
break;
case STATE_DSTARCAL: case STATE_DSTARCAL:
DEBUG1("Mode set to D-Star Calibrate"); DEBUG1("Mode set to D-Star Calibrate");
break; break;
@ -413,8 +498,23 @@ void CSerialPort::setMode(MMDVM_STATE modemState)
case STATE_LFCAL: case STATE_LFCAL:
DEBUG1("Mode set to 80 Hz Calibrate"); DEBUG1("Mode set to 80 Hz Calibrate");
break; break;
case STATE_DMRCAL1K: case STATE_FMCAL10K:
DEBUG1("Mode set to DMR BS 1031 Hz Calibrate"); DEBUG1("Mode set to FM 10Khz Calibrate");
break;
case STATE_FMCAL12K:
DEBUG1("Mode set to FM 12.5Khz Calibrate");
break;
case STATE_FMCAL15K:
DEBUG1("Mode set to FM 15Khz Calibrate");
break;
case STATE_FMCAL20K:
DEBUG1("Mode set to FM 20Khz Calibrate");
break;
case STATE_FMCAL25K:
DEBUG1("Mode set to FM 10Khz Calibrate");
break;
case STATE_FMCAL30K:
DEBUG1("Mode set to FM 30Khz Calibrate");
break; break;
case STATE_P25CAL1K: case STATE_P25CAL1K:
DEBUG1("Mode set to P25 1011 Hz Calibrate"); DEBUG1("Mode set to P25 1011 Hz Calibrate");
@ -451,6 +551,9 @@ void CSerialPort::setMode(MMDVM_STATE modemState)
if (modemState != STATE_NXDN) if (modemState != STATE_NXDN)
nxdnRX.reset(); nxdnRX.reset();
if (modemState != STATE_FM)
fm.reset();
cwIdTX.reset(); cwIdTX.reset();
m_modemState = modemState; m_modemState = modemState;
@ -524,11 +627,43 @@ void CSerialPort::process()
sendACK(); sendACK();
break; break;
case MMDVM_FM_PARAMS1:
err = setFMParams1(m_buffer + 3U, m_len - 3U);
if (err == 0U) {
sendACK();
} else {
DEBUG2("Received invalid FM params 1", err);
sendNAK(err);
}
break;
case MMDVM_FM_PARAMS2:
err = setFMParams2(m_buffer + 3U, m_len - 3U);
if (err == 0U) {
sendACK();
} else {
DEBUG2("Received invalid FM params 2", err);
sendNAK(err);
}
break;
case MMDVM_FM_PARAMS3:
err = setFMParams3(m_buffer + 3U, m_len - 3U);
if (err == 0U) {
sendACK();
} else {
DEBUG2("Received invalid FM params 3", err);
sendNAK(err);
}
break;
case MMDVM_CAL_DATA: case MMDVM_CAL_DATA:
if (m_modemState == STATE_DSTARCAL) if (m_modemState == STATE_DSTARCAL)
err = calDStarTX.write(m_buffer + 3U, m_len - 3U); err = calDStarTX.write(m_buffer + 3U, m_len - 3U);
if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K || m_modemState == STATE_DMRDMO1K) if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K || m_modemState == STATE_DMRDMO1K)
err = calDMR.write(m_buffer + 3U, m_len - 3U); err = calDMR.write(m_buffer + 3U, m_len - 3U);
if (m_modemState == STATE_FMCAL10K || m_modemState == STATE_FMCAL12K || m_modemState == STATE_FMCAL15K || m_modemState == STATE_FMCAL20K || m_modemState == STATE_FMCAL25K || m_modemState == STATE_FMCAL30K)
err = calFM.write(m_buffer + 3U, m_len - 3U);
if (m_modemState == STATE_P25CAL1K) if (m_modemState == STATE_P25CAL1K)
err = calP25.write(m_buffer + 3U, m_len - 3U); err = calP25.write(m_buffer + 3U, m_len - 3U);
if (m_modemState == STATE_NXDNCAL1K) if (m_modemState == STATE_NXDNCAL1K)

View File

@ -73,6 +73,9 @@ private:
uint8_t setConfig(const uint8_t* data, uint8_t length); uint8_t setConfig(const uint8_t* data, uint8_t length);
uint8_t setMode(const uint8_t* data, uint8_t length); uint8_t setMode(const uint8_t* data, uint8_t length);
void setMode(MMDVM_STATE modemState); void setMode(MMDVM_STATE modemState);
uint8_t setFMParams1(const uint8_t* data, uint8_t length);
uint8_t setFMParams2(const uint8_t* data, uint8_t length);
uint8_t setFMParams3(const uint8_t* data, uint8_t length);
// Hardware versions // Hardware versions
void beginInt(uint8_t n, int speed); void beginInt(uint8_t n, int speed);
@ -83,4 +86,3 @@ private:
}; };
#endif #endif

View File

@ -0,0 +1,46 @@
# based on https://github.com/berndporr/iir_fixed_point/blob/master/gen_coeff.py
import numpy as np
import scipy.signal as signal
import pylab as pl
# Calculate the coefficients for a pure fixed point
# integer filter
# sampling rate
fs = 24000
# cutoffs
f1 = 300
f2 = 2700
# ripple
rp = 0.2
# scaling factor in bits, do not change !
q = 0
# scaling factor as facor...
scaling_factor = 2**q
# let's generate a sequence of 2nd order IIR filters
sos = signal.cheby1(3,rp,[f1, f2],'bandpass', output='sos', fs=fs)
#sos = signal.cheby1(1, rp, 2122, 'lowpass', output='sos', fs=fs) #deemphasis filter
#sos = signal.cheby1(1, rp, 2122, 'highpass', output='sos', fs=fs) #deemphasis filter
#sos = np.round((sos) * scaling_factor)
# print coefficients
for biquad in sos:
for coeff in biquad:
#print(int(coeff),",",sep="",end="")
print((coeff),",",sep="",end="")
print("")
# plot the frequency response
b,a = signal.sos2tf(sos)
w,h = signal.freqz(b,a)
pl.plot(w/np.pi/2*fs,20*np.log(np.abs(h)))
pl.xlabel('frequency/Hz');
pl.ylabel('gain/dB');
pl.ylim(top=1,bottom=-20);
pl.xlim(left=250, right=12000);
pl.show()

58
Tools/emphasis.txt Normal file
View File

@ -0,0 +1,58 @@
% GNU Octave script to generate pre and deemphasis filters
% https://dsp.stackexchange.com/questions/34605/biquad-cookbook-formula-for-broadcast-fm-de-emphasis
% PACKAGES
pkg load control
pkg load signal
clear all;
clc;
fs = 24000;
samplingtime = 1/fs;
% analog prototype
A2 = [1];
B2 = [0.000075 1];
% Pre
Ds = tf(B2, A2);
% De
% Ds = tf(A2, B2);
Ds = Ds/dcgain(Ds);
% MZT
T1 = 0.000075; % 75us
z1 = -exp(-1.0/(fs*T1));
p1 = 1+z1;
a0 = 1.0;
a1 = p1;
a2 = 0;
b0 = 1.0;
b1 = z1;
b2 = 0;
% swap between a1, b1 to select pre- or de-emphasis
# Pre
Bmzt = [b0 a1 b2]
Amzt = [a0 b1 a2]
% De
% Bmzt = [b0 b1 b2]
% Amzt = [a0 a1 a2]
DzMZT = tf(Amzt, Bmzt, samplingtime);
DzMZT = DzMZT/dcgain(DzMZT);
%% Plot
wmin = 2 * pi * 20.0; % 20Hz
wmax = 2 * pi * ((fs/2.0) - (fs/2 - 20000)); %20kHz
figure(1);
bode(Ds, 'b', DzMZT, 'c', {wmin, wmax});
legend('Analog prototype', 'MZT 2nd order','location', 'northwest');
grid on;
pause();