mirror of https://github.com/g4klx/MMDVM.git
Merge branch 'FM' into dstar_correlator_fm
This commit is contained in:
commit
85e33bc49f
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
6
Config.h
6
Config.h
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 2015,2016,2017,2018,2020 by Jonathan Naylor G4KLX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -84,5 +84,7 @@
|
|||
// Use the D-Star and DMR LEDs for POCSAG
|
||||
// #define USE_ALTERNATE_POCSAG_LEDS
|
||||
|
||||
#endif
|
||||
// Use the D-Star and YSF LEDs for FM
|
||||
// #define USE_ALTERNATE_FM_LEDS
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
18
Globals.h
18
Globals.h
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 2015,2016,2017,2018,2020 by Jonathan Naylor G4KLX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -50,6 +50,7 @@ enum MMDVM_STATE {
|
|||
STATE_P25 = 4,
|
||||
STATE_NXDN = 5,
|
||||
STATE_POCSAG = 6,
|
||||
STATE_FM = 10,
|
||||
|
||||
// Dummy states start at 90
|
||||
STATE_NXDNCAL1K = 91,
|
||||
|
@ -62,7 +63,13 @@ enum MMDVM_STATE {
|
|||
STATE_DMRCAL = 98,
|
||||
STATE_DSTARCAL = 99,
|
||||
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"
|
||||
|
@ -82,6 +89,7 @@ enum MMDVM_STATE {
|
|||
#include "POCSAGTX.h"
|
||||
#include "CalDStarRX.h"
|
||||
#include "CalDStarTX.h"
|
||||
#include "CalFM.h"
|
||||
#include "CalDMR.h"
|
||||
#include "CalP25.h"
|
||||
#include "CalNXDN.h"
|
||||
|
@ -90,6 +98,7 @@ enum MMDVM_STATE {
|
|||
#include "CWIdTX.h"
|
||||
#include "Debug.h"
|
||||
#include "IO.h"
|
||||
#include "FM.h"
|
||||
|
||||
const uint8_t MARK_SLOT1 = 0x08U;
|
||||
const uint8_t MARK_SLOT2 = 0x04U;
|
||||
|
@ -114,6 +123,7 @@ extern bool m_ysfEnable;
|
|||
extern bool m_p25Enable;
|
||||
extern bool m_nxdnEnable;
|
||||
extern bool m_pocsagEnable;
|
||||
extern bool m_fmEnable;
|
||||
|
||||
extern bool m_duplex;
|
||||
|
||||
|
@ -144,9 +154,12 @@ extern CNXDNTX nxdnTX;
|
|||
|
||||
extern CPOCSAGTX pocsagTX;
|
||||
|
||||
extern CFM fm;
|
||||
|
||||
extern CCalDStarRX calDStarRX;
|
||||
extern CCalDStarTX calDStarTX;
|
||||
extern CCalDMR calDMR;
|
||||
extern CCalFM calFM;
|
||||
extern CCalP25 calP25;
|
||||
extern CCalNXDN calNXDN;
|
||||
extern CCalPOCSAG calPOCSAG;
|
||||
|
@ -155,4 +168,3 @@ extern CCalRSSI calRSSI;
|
|||
extern CCWIdTX cwIdTX;
|
||||
|
||||
#endif
|
||||
|
||||
|
|
109
IO.cpp
109
IO.cpp
|
@ -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) 2016 by Colin Durbridge G4EML
|
||||
*
|
||||
|
@ -80,6 +80,7 @@ m_ysfTXLevel(128 * 128),
|
|||
m_p25TXLevel(128 * 128),
|
||||
m_nxdnTXLevel(128 * 128),
|
||||
m_pocsagTXLevel(128 * 128),
|
||||
m_fmTXLevel(128 * 128),
|
||||
m_rxDCOffset(DC_OFFSET),
|
||||
m_txDCOffset(DC_OFFSET),
|
||||
m_ledCount(0U),
|
||||
|
@ -144,6 +145,7 @@ void CIO::selfTest()
|
|||
setP25Int(ledValue);
|
||||
setNXDNInt(ledValue);
|
||||
setPOCSAGInt(ledValue);
|
||||
setFMInt(ledValue);
|
||||
#endif
|
||||
delayInt(250);
|
||||
}
|
||||
|
@ -155,105 +157,56 @@ void CIO::selfTest()
|
|||
setP25Int(false);
|
||||
setNXDNInt(false);
|
||||
setPOCSAGInt(false);
|
||||
setFMInt(false);
|
||||
|
||||
delayInt(250);
|
||||
|
||||
setDStarInt(true);
|
||||
setDMRInt(true);
|
||||
setYSFInt(false);
|
||||
setP25Int(false);
|
||||
setNXDNInt(false);
|
||||
setPOCSAGInt(false);
|
||||
|
||||
delayInt(250);
|
||||
|
||||
setDStarInt(true);
|
||||
setDMRInt(true);
|
||||
setYSFInt(true);
|
||||
setP25Int(false);
|
||||
setNXDNInt(false);
|
||||
setPOCSAGInt(false);
|
||||
|
||||
delayInt(250);
|
||||
|
||||
setDStarInt(true);
|
||||
setDMRInt(true);
|
||||
setYSFInt(true);
|
||||
setP25Int(true);
|
||||
setNXDNInt(false);
|
||||
setPOCSAGInt(false);
|
||||
|
||||
#if !defined(USE_ALTERNATE_NXDN_LEDS)
|
||||
delayInt(250);
|
||||
|
||||
setDStarInt(true);
|
||||
setDMRInt(true);
|
||||
setYSFInt(true);
|
||||
setP25Int(true);
|
||||
setNXDNInt(true);
|
||||
setPOCSAGInt(false);
|
||||
#endif
|
||||
|
||||
#if !defined(USE_ALTERNATE_POCSAG_LEDS)
|
||||
delayInt(250);
|
||||
|
||||
setDStarInt(true);
|
||||
setDMRInt(true);
|
||||
setYSFInt(true);
|
||||
setP25Int(true);
|
||||
setNXDNInt(true);
|
||||
setPOCSAGInt(true);
|
||||
#endif
|
||||
|
||||
#if !defined(USE_ALTERNATE_FM_LEDS)
|
||||
delayInt(250);
|
||||
setFMInt(true);
|
||||
|
||||
delayInt(250);
|
||||
setFMInt(false);
|
||||
#endif
|
||||
|
||||
setDStarInt(true);
|
||||
setDMRInt(true);
|
||||
setYSFInt(true);
|
||||
setP25Int(true);
|
||||
setNXDNInt(true);
|
||||
#if !defined(USE_ALTERNATE_POCSAG_LEDS)
|
||||
delayInt(250);
|
||||
setPOCSAGInt(false);
|
||||
#endif
|
||||
|
||||
#if !defined(USE_ALTERNATE_NXDN_LEDS)
|
||||
delayInt(250);
|
||||
|
||||
setDStarInt(true);
|
||||
setDMRInt(true);
|
||||
setYSFInt(true);
|
||||
setP25Int(true);
|
||||
setNXDNInt(false);
|
||||
setPOCSAGInt(false);
|
||||
#endif
|
||||
|
||||
delayInt(250);
|
||||
|
||||
setDStarInt(true);
|
||||
setDMRInt(true);
|
||||
setYSFInt(true);
|
||||
setP25Int(false);
|
||||
setNXDNInt(false);
|
||||
setPOCSAGInt(false);
|
||||
|
||||
delayInt(250);
|
||||
|
||||
setDStarInt(true);
|
||||
setDMRInt(true);
|
||||
setYSFInt(false);
|
||||
setP25Int(false);
|
||||
setNXDNInt(false);
|
||||
setPOCSAGInt(false);
|
||||
|
||||
delayInt(250);
|
||||
|
||||
setDStarInt(true);
|
||||
setDMRInt(false);
|
||||
setYSFInt(false);
|
||||
setP25Int(false);
|
||||
setNXDNInt(false);
|
||||
setPOCSAGInt(false);
|
||||
|
||||
delayInt(250);
|
||||
|
||||
setDStarInt(false);
|
||||
setDMRInt(false);
|
||||
setYSFInt(false);
|
||||
setP25Int(false);
|
||||
setNXDNInt(false);
|
||||
setPOCSAGInt(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -402,6 +355,15 @@ void CIO::process()
|
|||
dmrDMORX.samples(RRCVals, 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) {
|
||||
if (m_dstarEnable) {
|
||||
q15_t GMSKVals[RX_BLOCK_SIZE];
|
||||
|
@ -460,6 +422,13 @@ void CIO::process()
|
|||
|
||||
nxdnRX.samples(NXDNVals, 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) {
|
||||
q15_t GMSKVals[RX_BLOCK_SIZE];
|
||||
::arm_fir_fast_q15(&m_gaussianFilter, samples, GMSKVals, RX_BLOCK_SIZE);
|
||||
|
@ -505,6 +474,9 @@ void CIO::write(MMDVM_STATE mode, q15_t* samples, uint16_t length, const uint8_t
|
|||
case STATE_POCSAG:
|
||||
txLevel = m_pocsagTXLevel;
|
||||
break;
|
||||
case STATE_FM:
|
||||
txLevel = m_fmTXLevel;
|
||||
break;
|
||||
default:
|
||||
txLevel = m_cwIdTXLevel;
|
||||
break;
|
||||
|
@ -553,10 +525,11 @@ void CIO::setMode()
|
|||
setP25Int(m_modemState == STATE_P25);
|
||||
setNXDNInt(m_modemState == STATE_NXDN);
|
||||
setPOCSAGInt(m_modemState == STATE_POCSAG);
|
||||
setFMInt(m_modemState == STATE_FM);
|
||||
#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;
|
||||
|
||||
|
@ -568,6 +541,7 @@ void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rx
|
|||
m_p25TXLevel = q15_t(p25TXLevel * 128);
|
||||
m_nxdnTXLevel = q15_t(nxdnTXLevel * 128);
|
||||
m_pocsagTXLevel = q15_t(pocsagTXLevel * 128);
|
||||
m_fmTXLevel = q15_t(fmTXLevel * 128);
|
||||
|
||||
m_rxDCOffset = DC_OFFSET + rxDCOffset;
|
||||
m_txDCOffset = DC_OFFSET + txDCOffset;
|
||||
|
@ -618,4 +592,3 @@ bool CIO::hasLockout() const
|
|||
{
|
||||
return m_lockout;
|
||||
}
|
||||
|
||||
|
|
7
IO.h
7
IO.h
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2015,2016,2017,2018 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 2015,2016,2017,2018,2020 by Jonathan Naylor G4KLX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -42,7 +42,7 @@ public:
|
|||
|
||||
void interrupt();
|
||||
|
||||
void setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel, uint8_t pocsagTXLevel, 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);
|
||||
|
||||
|
@ -86,6 +86,7 @@ private:
|
|||
q15_t m_p25TXLevel;
|
||||
q15_t m_nxdnTXLevel;
|
||||
q15_t m_pocsagTXLevel;
|
||||
q15_t m_fmTXLevel;
|
||||
|
||||
uint16_t m_rxDCOffset;
|
||||
uint16_t m_txDCOffset;
|
||||
|
@ -118,9 +119,9 @@ private:
|
|||
void setP25Int(bool on);
|
||||
void setNXDNInt(bool on);
|
||||
void setPOCSAGInt(bool on);
|
||||
void setFMInt(bool on);
|
||||
|
||||
void delayInt(unsigned int dly);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
|
19
IODue.cpp
19
IODue.cpp
|
@ -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) 2016 by Colin Durbridge G4EML
|
||||
*
|
||||
|
@ -35,6 +35,7 @@
|
|||
#define PIN_P25 19
|
||||
#define PIN_NXDN 20
|
||||
#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_ISR_EOC_Chan ADC_ISR_EOC7
|
||||
#define ADC_CDR_Chan 7
|
||||
|
@ -50,6 +51,7 @@
|
|||
#define PIN_P25 6
|
||||
#define PIN_NXDN 5
|
||||
#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_ISR_EOC_Chan ADC_ISR_EOC13
|
||||
#define ADC_CDR_Chan 13
|
||||
|
@ -67,6 +69,7 @@
|
|||
#define PIN_P25 6
|
||||
#define PIN_NXDN 5
|
||||
#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_ISR_EOC_Chan ADC_ISR_EOC7
|
||||
#define ADC_CDR_Chan 7
|
||||
|
@ -107,6 +110,9 @@ void CIO::initInt()
|
|||
#if !defined(USE_ALTERNATE_POCSAG_LEDS)
|
||||
pinMode(PIN_POCSAG, OUTPUT);
|
||||
#endif
|
||||
#if !defined(USE_ALTERNATE_POCSAG_LEDS)
|
||||
pinMode(PIN_FM, OUTPUT);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -258,10 +264,19 @@ void CIO::setPOCSAGInt(bool on)
|
|||
#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)
|
||||
{
|
||||
delay(dly);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
|
19
IOSTM.cpp
19
IOSTM.cpp
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright (C) 2016 by Jim McLaughlin KI6ZUM
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -1438,6 +1438,23 @@ void CIO::setPOCSAGInt(bool on)
|
|||
#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
|
||||
// Example from: http://thehackerworkshop.com/?p=1209
|
||||
void CIO::delayInt(unsigned int dly)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Copyright (C) 2016 by Jim McLaughlin KI6ZUM
|
||||
* 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
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -41,6 +41,7 @@ YSF PB8 output
|
|||
P25 PB9 output
|
||||
NXDN PB10 output
|
||||
POCSAG PB11 output
|
||||
FM PB12 output
|
||||
|
||||
RX PB0 analog input (ADC1_8)
|
||||
RSSI PB1 analog input (ADC2_9)
|
||||
|
@ -84,6 +85,9 @@ USART1_RXD PA10 input (AF)
|
|||
#define PIN_POCSAG 11
|
||||
#define PORT_POCSAG GPIOB
|
||||
#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_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)
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
*foo[0] = 0x01;
|
||||
for(int i=1; i<7; i++){
|
||||
for(int i=1; i<10; i++){
|
||||
delay(SystemCoreClock/1000*10);
|
||||
*foo[i-1] = 0x00;
|
||||
*foo[i] = 0x01;
|
||||
}
|
||||
for(int i=5; i>=0; i--){
|
||||
for(int i=10; i>=0; i--){
|
||||
delay(SystemCoreClock/1000*10);
|
||||
*foo[i+1] = 0x00;
|
||||
*foo[i] = 0x01;
|
||||
|
@ -228,6 +232,9 @@ static inline void GPIOInit()
|
|||
#if !defined(USE_ALTERNATE_POCSAG_LEDS)
|
||||
GPIOConfigPin(PORT_POCSAG, PIN_POCSAG, GPIO_CRL_MODE0_1);
|
||||
#endif
|
||||
#if !defined(USE_ALTERNATE_FM_LEDS)
|
||||
GPIOConfigPin(PORT_FM, PIN_FM, GPIO_CRL_MODE0_1);
|
||||
#endif
|
||||
|
||||
GPIOConfigPin(PORT_RX, PIN_RX, 0);
|
||||
#if defined(SEND_RSSI_DATA)
|
||||
|
@ -460,6 +467,16 @@ void CIO::setPOCSAGInt(bool on)
|
|||
#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)
|
||||
{
|
||||
delay(dly);
|
||||
|
|
17
IOTeensy.cpp
17
IOTeensy.cpp
|
@ -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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -37,9 +37,11 @@
|
|||
#if defined(__MK20DX256__)
|
||||
#define PIN_NXDN 2
|
||||
#define PIN_POCSAG 3
|
||||
#define PIN_FM 4
|
||||
#else
|
||||
#define PIN_NXDN 24
|
||||
#define PIN_POCSAG 25
|
||||
#define PIN_FM 26
|
||||
#endif
|
||||
#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
|
||||
|
@ -76,6 +78,9 @@ void CIO::initInt()
|
|||
#if !defined(USE_ALTERNATE_POCSAG_LEDS)
|
||||
pinMode(PIN_POCSAG, OUTPUT);
|
||||
#endif
|
||||
#if !defined(USE_ALTERNATE_FM_LEDS)
|
||||
pinMode(PIN_FM, OUTPUT);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -243,6 +248,16 @@ void CIO::setPOCSAGInt(bool on)
|
|||
#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)
|
||||
{
|
||||
delay(dly);
|
||||
|
|
12
MMDVM.cpp
12
MMDVM.cpp
|
@ -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 Colin Durbridge G4EML
|
||||
*
|
||||
|
@ -32,6 +32,7 @@ bool m_ysfEnable = true;
|
|||
bool m_p25Enable = true;
|
||||
bool m_nxdnEnable = true;
|
||||
bool m_pocsagEnable = true;
|
||||
bool m_fmEnable = true;
|
||||
|
||||
bool m_duplex = true;
|
||||
|
||||
|
@ -59,11 +60,14 @@ CNXDNTX nxdnTX;
|
|||
|
||||
CPOCSAGTX pocsagTX;
|
||||
|
||||
CFM fm;
|
||||
|
||||
CCalDStarRX calDStarRX;
|
||||
CCalDStarTX calDStarTX;
|
||||
CCalDMR calDMR;
|
||||
CCalP25 calP25;
|
||||
CCalNXDN calNXDN;
|
||||
CCalFM calFM;
|
||||
CCalPOCSAG calPOCSAG;
|
||||
CCalRSSI calRSSI;
|
||||
|
||||
|
@ -106,6 +110,9 @@ void loop()
|
|||
if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy()))
|
||||
pocsagTX.process();
|
||||
|
||||
if (m_fmEnable && m_modemState == STATE_FM)
|
||||
fm.process();
|
||||
|
||||
if (m_modemState == STATE_DSTARCAL)
|
||||
calDStarTX.process();
|
||||
|
||||
|
@ -121,6 +128,9 @@ void loop()
|
|||
if (m_modemState == STATE_POCSAGCAL)
|
||||
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)
|
||||
cwIdTX.process();
|
||||
}
|
||||
|
|
13
MMDVM.ino
13
MMDVM.ino
|
@ -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
|
||||
*
|
||||
* 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_nxdnEnable = true;
|
||||
bool m_pocsagEnable = true;
|
||||
bool m_fmEnable = true;
|
||||
|
||||
bool m_duplex = true;
|
||||
|
||||
|
@ -56,9 +57,12 @@ CNXDNTX nxdnTX;
|
|||
|
||||
CPOCSAGTX pocsagTX;
|
||||
|
||||
CFM fm;
|
||||
|
||||
CCalDStarRX calDStarRX;
|
||||
CCalDStarTX calDStarTX;
|
||||
CCalDMR calDMR;
|
||||
CCalFM calFM;
|
||||
CCalP25 calP25;
|
||||
CCalNXDN calNXDN;
|
||||
CCalPOCSAG calPOCSAG;
|
||||
|
@ -103,12 +107,18 @@ void loop()
|
|||
if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy()))
|
||||
pocsagTX.process();
|
||||
|
||||
if (m_fmEnable && m_modemState == STATE_FM)
|
||||
fm.process();
|
||||
|
||||
if (m_modemState == STATE_DSTARCAL)
|
||||
calDStarTX.process();
|
||||
|
||||
if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K || m_modemState == STATE_DMRDMO1K)
|
||||
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)
|
||||
calP25.process();
|
||||
|
||||
|
@ -121,4 +131,3 @@ void loop()
|
|||
if (m_modemState == STATE_IDLE)
|
||||
cwIdTX.process();
|
||||
}
|
||||
|
||||
|
|
|
@ -273,6 +273,7 @@
|
|||
<File name="CalDStarTX.cpp" path="CalDStarTX.cpp" type="1"/>
|
||||
<File name="STM32F4XX_Lib/STM32F4xx_StdPeriph_Driver/source" path="" type="2"/>
|
||||
<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="IODue.cpp" path="IODue.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="DMRDefines.h" path="DMRDefines.h" 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="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"/>
|
||||
|
|
151
SerialPort.cpp
151
SerialPort.cpp
|
@ -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
|
||||
*
|
||||
* 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_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_NAK = 0x7FU;
|
||||
|
||||
|
@ -97,7 +101,7 @@ const uint8_t MMDVM_DEBUG5 = 0xF5U;
|
|||
#define HW_TYPE "MMDVM"
|
||||
#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)
|
||||
#define concat(h, a, b, c) h " " a " " b " GitID #" c ""
|
||||
|
@ -168,6 +172,8 @@ void CSerialPort::getStatus()
|
|||
reply[3U] |= 0x10U;
|
||||
if (m_pocsagEnable)
|
||||
reply[3U] |= 0x20U;
|
||||
if (m_fmEnable)
|
||||
reply[3U] |= 0x40U;
|
||||
|
||||
reply[4U] = uint8_t(m_modemState);
|
||||
|
||||
|
@ -256,7 +262,7 @@ void CSerialPort::getVersion()
|
|||
|
||||
uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
|
||||
{
|
||||
if (length < 18U)
|
||||
if (length < 19U)
|
||||
return 4U;
|
||||
|
||||
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 nxdnEnable = (data[1U] & 0x10U) == 0x10U;
|
||||
bool pocsagEnable = (data[1U] & 0x20U) == 0x20U;
|
||||
bool fmEnable = (data[1U] & 0x40U) == 0x40U;
|
||||
|
||||
uint8_t txDelay = data[2U];
|
||||
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]);
|
||||
|
||||
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;
|
||||
if (modemState == STATE_DSTAR && !dstarEnable)
|
||||
return 4U;
|
||||
|
@ -294,6 +301,8 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
|
|||
return 4U;
|
||||
if (modemState == STATE_POCSAG && !pocsagEnable)
|
||||
return 4U;
|
||||
if (modemState == STATE_FM && !fmEnable)
|
||||
return 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 fmTXLevel = data[18U];
|
||||
|
||||
m_modemState = modemState;
|
||||
|
||||
m_dstarEnable = dstarEnable;
|
||||
|
@ -326,6 +337,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
|
|||
m_p25Enable = p25Enable;
|
||||
m_nxdnEnable = nxdnEnable;
|
||||
m_pocsagEnable = pocsagEnable;
|
||||
m_fmEnable = fmEnable;
|
||||
m_duplex = !simplex;
|
||||
|
||||
dstarTX.setTXDelay(txDelay);
|
||||
|
@ -343,13 +355,81 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length)
|
|||
|
||||
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();
|
||||
|
||||
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)
|
||||
{
|
||||
if (length < 1U)
|
||||
|
@ -360,7 +440,7 @@ uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length)
|
|||
if (modemState == m_modemState)
|
||||
return 0U;
|
||||
|
||||
if (modemState != STATE_IDLE && modemState != STATE_DSTAR && modemState != STATE_DMR && modemState != STATE_YSF && modemState != STATE_P25 && modemState != STATE_NXDN && modemState != STATE_POCSAG && modemState != STATE_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;
|
||||
if (modemState == STATE_DSTAR && !m_dstarEnable)
|
||||
return 4U;
|
||||
|
@ -374,6 +454,8 @@ uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length)
|
|||
return 4U;
|
||||
if (modemState == STATE_POCSAG && !m_pocsagEnable)
|
||||
return 4U;
|
||||
if (modemState == STATE_FM && !m_fmEnable)
|
||||
return 4U;
|
||||
|
||||
setMode(modemState);
|
||||
|
||||
|
@ -401,6 +483,9 @@ void CSerialPort::setMode(MMDVM_STATE modemState)
|
|||
case STATE_POCSAG:
|
||||
DEBUG1("Mode set to POCSAG");
|
||||
break;
|
||||
case STATE_FM:
|
||||
DEBUG1("Mode set to FM");
|
||||
break;
|
||||
case STATE_DSTARCAL:
|
||||
DEBUG1("Mode set to D-Star Calibrate");
|
||||
break;
|
||||
|
@ -413,8 +498,23 @@ void CSerialPort::setMode(MMDVM_STATE modemState)
|
|||
case STATE_LFCAL:
|
||||
DEBUG1("Mode set to 80 Hz Calibrate");
|
||||
break;
|
||||
case STATE_DMRCAL1K:
|
||||
DEBUG1("Mode set to DMR BS 1031 Hz Calibrate");
|
||||
case STATE_FMCAL10K:
|
||||
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;
|
||||
case STATE_P25CAL1K:
|
||||
DEBUG1("Mode set to P25 1011 Hz Calibrate");
|
||||
|
@ -451,6 +551,9 @@ void CSerialPort::setMode(MMDVM_STATE modemState)
|
|||
if (modemState != STATE_NXDN)
|
||||
nxdnRX.reset();
|
||||
|
||||
if (modemState != STATE_FM)
|
||||
fm.reset();
|
||||
|
||||
cwIdTX.reset();
|
||||
|
||||
m_modemState = modemState;
|
||||
|
@ -524,11 +627,43 @@ void CSerialPort::process()
|
|||
sendACK();
|
||||
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:
|
||||
if (m_modemState == STATE_DSTARCAL)
|
||||
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)
|
||||
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)
|
||||
err = calP25.write(m_buffer + 3U, m_len - 3U);
|
||||
if (m_modemState == STATE_NXDNCAL1K)
|
||||
|
|
|
@ -73,6 +73,9 @@ private:
|
|||
uint8_t setConfig(const uint8_t* data, uint8_t length);
|
||||
uint8_t setMode(const uint8_t* data, uint8_t length);
|
||||
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
|
||||
void beginInt(uint8_t n, int speed);
|
||||
|
@ -83,4 +86,3 @@ private:
|
|||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -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()
|
|
@ -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();
|
Loading…
Reference in New Issue