diff --git a/AX25Defines.h b/AX25Defines.h new file mode 100644 index 0000000..7087847 --- /dev/null +++ b/AX25Defines.h @@ -0,0 +1,36 @@ +/* + * 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(AX25DEFINES_H) +#define AX25DEFINES_H + +const uint8_t AX25_RADIO_SYMBOL_LENGTH = 20U; // At 24 kHz sample rate + +const uint8_t AX25_FRAME_START = 0x7EU; +const uint8_t AX25_FRAME_END = 0x7EU; +const uint8_t AX25_FRAME_ABORT = 0xFEU; + +const uint8_t AX25_MAX_ONES = 5U; + +const uint16_t AX25_MIN_FRAME_LENGTH = 17U; // Callsign (7) + Callsign (7) + Control (1) + Checksum (2) + +const uint16_t AX25_MAX_FRAME_LENGTH = 330U; // Callsign (7) + Callsign (7) + 8 Digipeaters (56) + + // Control (1) + PID (1) + Data (256) + Checksum (2) + +#endif + diff --git a/AX25Demodulator.cpp b/AX25Demodulator.cpp new file mode 100644 index 0000000..276a8e6 --- /dev/null +++ b/AX25Demodulator.cpp @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2020 by Jonathan Naylor G4KLX + * Copyright 2015-2019 Mobilinkd LLC + * + * 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 "AX25Demodulator.h" +#include "AX25Defines.h" + +const float32_t SAMPLE_RATE = 24000.0F; +const float32_t SYMBOL_RATE = 1200.0F; + +const uint16_t DELAY_LEN = 11U; + +const float32_t SAMPLES_PER_SYMBOL = SAMPLE_RATE / SYMBOL_RATE; +const float32_t PLL_LIMIT = SAMPLES_PER_SYMBOL / 2.0F; + +const uint32_t LPF_FILTER_LEN = 48U; + +q15_t LPF_FILTER_COEFFS[] = { + -2, -8, -17, -28, -40, -47, -47, -34, + -5, 46, 122, 224, 354, 510, 689, 885, + 1092, 1302, 1506, 1693, 1856, 1987, 2077, 2124, + 2124, 2077, 1987, 1856, 1693, 1506, 1302, 1092, + 885, 689, 510, 354, 224, 122, 46, -5, + -34, -47, -47, -40, -28, -17, -8, -2 +}; + +// Lock low-pass filter taps (80Hz Bessel) +// scipy.signal: +// b, a = bessel(4, [80.0/(1200/2)], 'lowpass') +// +const uint8_t PLL_IIR_SIZE = 5U; + +const float32_t PLL_LOCK_B[] = { + 1.077063e-03,4.308253e-03,6.462379e-03,4.308253e-03,1.077063e-03}; + +const float32_t PLL_LOCK_A[] = { + 1.000000e+00,-2.774567e+00,2.962960e+00,-1.437990e+00,2.668296e-01}; + +// 64 Hz loop filter. +// scipy.signal: +// loop_coeffs = firwin(9, [64.0/(1200/2)], width = None, +// pass_zero = True, scale = True, window='hann') +// +const uint32_t PLL_FILTER_LEN = 7U; + +float32_t PLL_FILTER_COEFFS[] = {3.196252e-02F, 1.204223e-01F, 2.176819e-01F, 2.598666e-01F, 2.176819e-01F, 1.204223e-01F, 3.196252e-02F}; + +CAX25Demodulator::CAX25Demodulator(int8_t n) : +m_frame(), +m_twist(n), +m_lpfFilter(), +m_lpfState(), +m_delayLine(NULL), +m_delayPos(0U), +m_nrziState(false), +m_pllFilter(), +m_pllState(), +m_pllLast(false), +m_pllBits(1U), +m_pllCount(0.0F), +m_pllJitter(0.0F), +m_pllDCD(false), +m_iirHistory(), +m_hdlcOnes(0U), +m_hdlcFlag(false), +m_hdlcBuffer(0U), +m_hdlcBits(0U), +m_hdlcState(AX25_IDLE) +{ + m_delayLine = new bool[DELAY_LEN]; + + m_lpfFilter.numTaps = LPF_FILTER_LEN; + m_lpfFilter.pState = m_lpfState; + m_lpfFilter.pCoeffs = LPF_FILTER_COEFFS; + + m_pllFilter.numTaps = PLL_FILTER_LEN; + m_pllFilter.pState = m_pllState; + m_pllFilter.pCoeffs = PLL_FILTER_COEFFS; + + for (uint8_t i = 0U; i < PLL_IIR_SIZE; i++) + m_iirHistory[i] = 0.0F; +} + +bool CAX25Demodulator::process(q15_t* samples, uint8_t length, CAX25Frame& frame) +{ + bool result = false; + + q15_t fa[RX_BLOCK_SIZE]; + m_twist.process(samples, fa, RX_BLOCK_SIZE); + + int16_t buffer[RX_BLOCK_SIZE]; + for (uint8_t i = 0; i < length; i++) { + bool level = (fa[i] >= 0); + bool delayed = delay(level); + buffer[i] = (int16_t(level ^ delayed) << 1) - 1; + } + + q15_t fc[RX_BLOCK_SIZE]; + ::arm_fir_fast_q15(&m_lpfFilter, buffer, fc, RX_BLOCK_SIZE); + + for (uint8_t i = 0; i < length; i++) { + bool bit = fc[i] >= 0; + bool sample = PLL(bit); + + if (sample) { + // We will only ever get one frame because there are + // not enough bits in a block for more than one. + if (result) { + HDLC(NRZI(bit)); + } else { + result = HDLC(NRZI(bit)); + if (result) { + // Copy the frame data. + ::memcpy(frame.m_data, m_frame.m_data, AX25_MAX_PACKET_LEN); + frame.m_length = m_frame.m_length; + frame.m_fcs = m_frame.m_fcs; + m_frame.m_length = 0U; + } + } + } + } + + return result; +} + +bool CAX25Demodulator::delay(bool b) +{ + bool r = m_delayLine[m_delayPos]; + + m_delayLine[m_delayPos++] = b; + + if (m_delayPos >= DELAY_LEN) + m_delayPos = 0U; + + return r; +} + +bool CAX25Demodulator::NRZI(bool b) +{ + bool result = (b == m_nrziState); + + m_nrziState = b; + + return result; +} + +bool CAX25Demodulator::PLL(bool input) +{ + bool sample = false; + + if (input != m_pllLast || m_pllBits > 16U) { + // Record transition. + m_pllLast = input; + + if (m_pllCount > PLL_LIMIT) + m_pllCount -= SAMPLES_PER_SYMBOL; + + float32_t adjust = m_pllBits > 16U ? 5.0F : 0.0F; + float32_t offset = m_pllCount / float32_t(m_pllBits); + float32_t jitter; + ::arm_fir_f32(&m_pllFilter, &offset, &jitter, 1U); + + float32_t absOffset = adjust; + if (offset < 0.0F) + absOffset -= offset; + else + absOffset += offset; + m_pllJitter = iir(absOffset); + + m_pllCount -= jitter / 2.0F; + m_pllBits = 1U; + } else { + if (m_pllCount > PLL_LIMIT) { + sample = true; + m_pllCount -= SAMPLES_PER_SYMBOL; + m_pllBits++; + } + } + + m_pllCount += 1.0F; + + return sample; +} + +bool CAX25Demodulator::HDLC(bool b) +{ + if (m_hdlcOnes == AX25_MAX_ONES) { + if (b) { + // flag byte + m_hdlcFlag = true; + } else { + // bit stuffing... + m_hdlcFlag = false; + m_hdlcOnes = 0U; + return false; + } + } + + m_hdlcBuffer >>= 1; + m_hdlcBuffer |= b ? 128U : 0U; + m_hdlcBits++; // Free-running until Sync byte. + + if (b) + m_hdlcOnes++; + else + m_hdlcOnes = 0U; + + if (m_hdlcFlag) { + bool result = false; + + switch (m_hdlcBuffer) { + case AX25_FRAME_END: + if (m_frame.m_length >= AX25_MIN_FRAME_LENGTH) { + result = m_frame.checkCRC(); + if (!result) + m_frame.m_length = 0U; + } else { + m_frame.m_length = 0U; + } + m_hdlcState = AX25_SYNC; + m_hdlcFlag = false; + m_hdlcBits = 0U; + break; + + case AX25_FRAME_ABORT: + // Frame aborted + m_frame.m_length = 0U; + m_hdlcState = AX25_IDLE; + m_hdlcFlag = false; + m_hdlcBits = 0U; + break; + + default: + break; + } + + return result; + } + + switch (m_hdlcState) { + case AX25_IDLE: + break; + + case AX25_SYNC: + if (m_hdlcBits == 8U) { // 8th bit. + // Start of frame data. + m_hdlcState = AX25_RECEIVE; + m_frame.append(m_hdlcBuffer); + m_hdlcBits = 0U; + } + break; + + case AX25_RECEIVE: + if (m_hdlcBits == 8U) { // 8th bit. + m_frame.append(m_hdlcBuffer); + m_hdlcBits = 0U; + } + break; + + default: + break; + } + + return false; +} + +void CAX25Demodulator::setTwist(int8_t n) +{ + m_twist.setTwist(n); +} + +bool CAX25Demodulator::isDCD() +{ + if (m_pllJitter <= (SAMPLES_PER_SYMBOL * 0.03F)) + m_pllDCD = true; + else if (m_pllJitter >= (SAMPLES_PER_SYMBOL * 0.15F)) + m_pllDCD = false; + + return m_pllDCD; +} + +float32_t CAX25Demodulator::iir(float32_t input) +{ + for (int8_t i = int8_t(PLL_IIR_SIZE) - 1; i != 0; i--) + m_iirHistory[i] = m_iirHistory[i - 1]; + + m_iirHistory[0] = input; + for (uint8_t i = 1U; i < PLL_IIR_SIZE; i++) + m_iirHistory[0] -= PLL_LOCK_A[i] * m_iirHistory[i]; + + float32_t result = 0.0F; + for (uint8_t i = 0U; i < PLL_IIR_SIZE; i++) + result += PLL_LOCK_B[i] * m_iirHistory[i]; + + return result; +} + diff --git a/AX25Demodulator.h b/AX25Demodulator.h new file mode 100644 index 0000000..d1baff8 --- /dev/null +++ b/AX25Demodulator.h @@ -0,0 +1,73 @@ +/* + * 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(AX25Demodulator_H) +#define AX25Demodulator_H + +#include "Config.h" + +#include "AX25Frame.h" +#include "AX25Twist.h" + +enum AX25_STATE { + AX25_IDLE, + AX25_SYNC, + AX25_RECEIVE +}; + +class CAX25Demodulator { +public: + CAX25Demodulator(int8_t n); + + bool process(q15_t* samples, uint8_t length, CAX25Frame& frame); + + void setTwist(int8_t n); + + bool isDCD(); + +private: + CAX25Frame m_frame; + CAX25Twist m_twist; + arm_fir_instance_q15 m_lpfFilter; + q15_t m_lpfState[70U]; // NoTaps + BlockSize - 1, 48 + 20 - 1 plus some spare + bool* m_delayLine; + uint16_t m_delayPos; + bool m_nrziState; + arm_fir_instance_f32 m_pllFilter; + float32_t m_pllState[20U]; // NoTaps + BlockSize - 1, 7 + 1 - 1 plus some spare + bool m_pllLast; + uint8_t m_pllBits; + float32_t m_pllCount; + float32_t m_pllJitter; + bool m_pllDCD; + float32_t m_iirHistory[5U]; + uint16_t m_hdlcOnes; + bool m_hdlcFlag; + uint16_t m_hdlcBuffer; + uint16_t m_hdlcBits; + AX25_STATE m_hdlcState; + + bool delay(bool b); + bool NRZI(bool b); + bool PLL(bool b); + bool HDLC(bool b); + float32_t iir(float32_t input); +}; + +#endif + diff --git a/AX25Frame.cpp b/AX25Frame.cpp new file mode 100644 index 0000000..d666f33 --- /dev/null +++ b/AX25Frame.cpp @@ -0,0 +1,122 @@ +/* + * 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 "AX25Frame.h" + +const uint16_t CCITT_TABLE[] = { + 0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf, + 0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7, + 0x1081,0x0108,0x3393,0x221a,0x56a5,0x472c,0x75b7,0x643e, + 0x9cc9,0x8d40,0xbfdb,0xae52,0xdaed,0xcb64,0xf9ff,0xe876, + 0x2102,0x308b,0x0210,0x1399,0x6726,0x76af,0x4434,0x55bd, + 0xad4a,0xbcc3,0x8e58,0x9fd1,0xeb6e,0xfae7,0xc87c,0xd9f5, + 0x3183,0x200a,0x1291,0x0318,0x77a7,0x662e,0x54b5,0x453c, + 0xbdcb,0xac42,0x9ed9,0x8f50,0xfbef,0xea66,0xd8fd,0xc974, + 0x4204,0x538d,0x6116,0x709f,0x0420,0x15a9,0x2732,0x36bb, + 0xce4c,0xdfc5,0xed5e,0xfcd7,0x8868,0x99e1,0xab7a,0xbaf3, + 0x5285,0x430c,0x7197,0x601e,0x14a1,0x0528,0x37b3,0x263a, + 0xdecd,0xcf44,0xfddf,0xec56,0x98e9,0x8960,0xbbfb,0xaa72, + 0x6306,0x728f,0x4014,0x519d,0x2522,0x34ab,0x0630,0x17b9, + 0xef4e,0xfec7,0xcc5c,0xddd5,0xa96a,0xb8e3,0x8a78,0x9bf1, + 0x7387,0x620e,0x5095,0x411c,0x35a3,0x242a,0x16b1,0x0738, + 0xffcf,0xee46,0xdcdd,0xcd54,0xb9eb,0xa862,0x9af9,0x8b70, + 0x8408,0x9581,0xa71a,0xb693,0xc22c,0xd3a5,0xe13e,0xf0b7, + 0x0840,0x19c9,0x2b52,0x3adb,0x4e64,0x5fed,0x6d76,0x7cff, + 0x9489,0x8500,0xb79b,0xa612,0xd2ad,0xc324,0xf1bf,0xe036, + 0x18c1,0x0948,0x3bd3,0x2a5a,0x5ee5,0x4f6c,0x7df7,0x6c7e, + 0xa50a,0xb483,0x8618,0x9791,0xe32e,0xf2a7,0xc03c,0xd1b5, + 0x2942,0x38cb,0x0a50,0x1bd9,0x6f66,0x7eef,0x4c74,0x5dfd, + 0xb58b,0xa402,0x9699,0x8710,0xf3af,0xe226,0xd0bd,0xc134, + 0x39c3,0x284a,0x1ad1,0x0b58,0x7fe7,0x6e6e,0x5cf5,0x4d7c, + 0xc60c,0xd785,0xe51e,0xf497,0x8028,0x91a1,0xa33a,0xb2b3, + 0x4a44,0x5bcd,0x6956,0x78df,0x0c60,0x1de9,0x2f72,0x3efb, + 0xd68d,0xc704,0xf59f,0xe416,0x90a9,0x8120,0xb3bb,0xa232, + 0x5ac5,0x4b4c,0x79d7,0x685e,0x1ce1,0x0d68,0x3ff3,0x2e7a, + 0xe70e,0xf687,0xc41c,0xd595,0xa12a,0xb0a3,0x8238,0x93b1, + 0x6b46,0x7acf,0x4854,0x59dd,0x2d62,0x3ceb,0x0e70,0x1ff9, + 0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330, + 0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78 }; + +CAX25Frame::CAX25Frame(const uint8_t* data, uint16_t length) : +m_data(), +m_length(0U), +m_fcs(0U) +{ + for (uint16_t i = 0U; i < length && i < (AX25_MAX_PACKET_LEN - 2U); i++) + m_data[m_length++] = data[i]; +} + +CAX25Frame::CAX25Frame() : +m_data(), +m_length(0U), +m_fcs(0U) +{ +} + +bool CAX25Frame::append(uint16_t c) +{ + if (m_length == AX25_MAX_PACKET_LEN) + return false; + + m_data[m_length++] = uint8_t(c); + + return true; +} + +bool CAX25Frame::checkCRC() +{ + union { + uint16_t crc16; + uint8_t crc8[2U]; + }; + + crc16 = 0xFFFFU; + for (uint16_t i = 0U; i < (m_length - 2U); i++) + crc16 = uint16_t(crc8[1U]) ^ CCITT_TABLE[crc8[0U] ^ m_data[i]]; + + crc16 = ~crc16; + + if (crc8[0U] == m_data[m_length - 2U] && crc8[1U] == m_data[m_length - 1U]) { + m_fcs = crc16; + return true; + } else { + return false; + } +} + +void CAX25Frame::addCRC() +{ + union { + uint16_t crc16; + uint8_t crc8[2U]; + }; + + crc16 = 0xFFFFU; + for (uint16_t i = 0U; i < m_length; i++) + crc16 = uint16_t(crc8[1U]) ^ CCITT_TABLE[crc8[0U] ^ m_data[i]]; + + crc16 = ~crc16; + + m_fcs = crc16; + + m_data[m_length++] = crc8[0U]; + m_data[m_length++] = crc8[1U]; +} + diff --git a/AX25Frame.h b/AX25Frame.h new file mode 100644 index 0000000..7f79c28 --- /dev/null +++ b/AX25Frame.h @@ -0,0 +1,43 @@ +/* + * 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(AX25Frame_H) +#define AX25Frame_H + +#include "Config.h" + +const uint16_t AX25_MAX_PACKET_LEN = 300U; + +class CAX25Frame { +public: + CAX25Frame(const uint8_t* data, uint16_t length); + CAX25Frame(); + + bool append(uint16_t c); + + bool checkCRC(); + + void addCRC(); + + uint8_t m_data[AX25_MAX_PACKET_LEN]; + uint16_t m_length; + uint16_t m_fcs; +}; + +#endif + diff --git a/AX25RX.cpp b/AX25RX.cpp new file mode 100644 index 0000000..093316b --- /dev/null +++ b/AX25RX.cpp @@ -0,0 +1,205 @@ +/* + * 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 "AX25RX.h" + +/* + * Generated with Scipy Filter, 152 coefficients, 1100-2300Hz bandpass, + * Hann window, starting and ending 0 value coefficients removed. + * + * np.array( + * firwin2(152, + * [ + * 0.0, + * 1000.0/(sample_rate/2), + * 1100.0/(sample_rate/2), + * 2350.0/(sample_rate/2), + * 2500.0/(sample_rate/2), + * 1.0 + * ], + * [0,0,1,1,0,0], + * antisymmetric = False, + * window='hann') * 32768, + * dtype=int)[10:-10] + */ + +const uint32_t FILTER_LEN = 130U; + +q15_t FILTER_COEFFS[] = { + 5, 12, 18, 21, 19, 11, -2, -15, -25, -27, + -21, -11, -3, -5, -19, -43, -69, -83, -73, -35, + 27, 98, 155, 180, 163, 109, 39, -20, -45, -26, + 23, 74, 89, 39, -81, -247, -407, -501, -480, -334, + -92, 175, 388, 479, 429, 275, 99, 5, 68, 298, + 626, 913, 994, 740, 115, -791, -1770, -2544, -2847, -2509, + -1527, -76, 1518, 2875, 3653, 3653, 2875, 1518, -76, -1527, + -2509, -2847, -2544, -1770, -791, 115, 740, 994, 913, 626, + 298, 68, 5, 99, 275, 429, 479, 388, 175, -92, + -334, -480, -501, -407, -247, -81, 39, 89, 74, 23, + -26, -45, -20, 39, 109, 163, 180, 155, 98, 27, + -35, -73, -83, -69, -43, -19, -5, -3, -11, -21, + -27, -25, -15, -2, 11, 19, 21, 18, 12, 5 +}; + +CAX25RX::CAX25RX() : +m_filter(), +m_state(), +m_demod1(3), +m_demod2(6), +m_demod3(9), +m_lastFCS(0U), +m_count(0U), +m_slotTime(30U), +m_slotCount(0U), +m_pPersist(128U), +m_dcd(false), +m_canTX(false), +m_x(1U), +m_a(0xB7U), +m_b(0x73U), +m_c(0xF6U) +{ + m_filter.numTaps = FILTER_LEN; + m_filter.pState = m_state; + m_filter.pCoeffs = FILTER_COEFFS; + + initRand(); +} + +void CAX25RX::samples(q15_t* samples, uint8_t length) +{ + q15_t output[RX_BLOCK_SIZE]; + ::arm_fir_fast_q15(&m_filter, samples, output, RX_BLOCK_SIZE); + + m_count++; + + CAX25Frame frame; + + bool ret = m_demod1.process(output, length, frame); + if (ret) { + if (frame.m_fcs != m_lastFCS || m_count > 2U) { + m_lastFCS = frame.m_fcs; + m_count = 0U; + serial.writeAX25Data(frame.m_data, frame.m_length - 2U); + } + DEBUG1("Decoder 1 reported"); + } + + ret = m_demod2.process(output, length, frame); + if (ret) { + if (frame.m_fcs != m_lastFCS || m_count > 2U) { + m_lastFCS = frame.m_fcs; + m_count = 0U; + serial.writeAX25Data(frame.m_data, frame.m_length - 2U); + } + DEBUG1("Decoder 2 reported"); + } + + ret = m_demod3.process(output, length, frame); + if (ret) { + if (frame.m_fcs != m_lastFCS || m_count > 2U) { + m_lastFCS = frame.m_fcs; + m_count = 0U; + serial.writeAX25Data(frame.m_data, frame.m_length - 2U); + } + DEBUG1("Decoder 3 reported"); + } + + m_slotCount += RX_BLOCK_SIZE; + if (m_slotCount >= m_slotTime) { + m_slotCount = 0U; + + bool dcd1 = m_demod1.isDCD(); + bool dcd2 = m_demod2.isDCD(); + bool dcd3 = m_demod3.isDCD(); + + if (dcd1 || dcd2 || dcd3) { + if (!m_dcd) { + io.setDecode(true); + io.setADCDetection(true); + m_dcd = true; + } + + m_canTX = false; + } else { + if (m_dcd) { + io.setDecode(false); + io.setADCDetection(false); + m_dcd = false; + } + + m_canTX = m_pPersist >= rand(); + } + } +} + +bool CAX25RX::canTX() const +{ + return m_canTX; +} + +void CAX25RX::setParams(int8_t twist, uint8_t slotTime, uint8_t pPersist) +{ + m_demod1.setTwist(twist - 3); + m_demod2.setTwist(twist); + m_demod3.setTwist(twist + 3); + + m_slotTime = slotTime * 240U; // Slot time in samples + m_pPersist = pPersist; +} + +// Taken from https://www.electro-tech-online.com/threads/ultra-fast-pseudorandom-number-generator-for-8-bit.124249/ +//X ABC Algorithm Random Number Generator for 8-Bit Devices: +//This is a small PRNG, experimentally verified to have at least a 50 million byte period +//by generating 50 million bytes and observing that there were no overapping sequences and repeats. +//This generator passes serial correlation, entropy , Monte Carlo Pi value, arithmetic mean, +//And many other statistical tests. This generator may have a period of up to 2^32, but this has +//not been verified. +// +// By XORing 3 bytes into the a,b, and c registers, you can add in entropy from +//an external source easily. +// +//This generator is free to use, but is not suitable for cryptography due to its short period(by //cryptographic standards) and simple construction. No attempt was made to make this generator +// suitable for cryptographic use. +// +//Due to the use of a constant counter, the generator should be resistant to latching up. +//A significant performance gain is had in that the x variable is only ever incremented. +// +//Only 4 bytes of ram are needed for the internal state, and generating a byte requires 3 XORs , //2 ADDs, one bit shift right , and one increment. Difficult or slow operations like multiply, etc +//were avoided for maximum speed on ultra low power devices. + + +void CAX25RX::initRand() //Can also be used to seed the rng with more entropy during use. +{ + m_a = (m_a ^ m_c ^ m_x); + m_b = (m_b + m_a); + m_c = (m_c + (m_b >> 1) ^ m_a); +} + +uint8_t CAX25RX::rand() +{ + m_x++; //x is incremented every round and is not affected by any other variable + + m_a = (m_a ^ m_c ^ m_x); //note the mix of addition and XOR + m_b = (m_b + m_a); //And the use of very few instructions + m_c = (m_c + (m_b >> 1) ^ m_a); //the right shift is to ensure that high-order bits from b can affect + + return uint8_t(m_c); //low order bits of other variables +} diff --git a/AX25RX.h b/AX25RX.h new file mode 100644 index 0000000..cbafe91 --- /dev/null +++ b/AX25RX.h @@ -0,0 +1,59 @@ +/* + * 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(AX25RX_H) +#define AX25RX_H + +#include "Config.h" + +#include "AX25Demodulator.h" + +class CAX25RX { +public: + CAX25RX(); + + void samples(q15_t* samples, uint8_t length); + + void setParams(int8_t twist, uint8_t slotTime, uint8_t pPersist); + + bool canTX() const; + +private: + arm_fir_instance_q15 m_filter; + q15_t m_state[160U]; // NoTaps + BlockSize - 1, 130 + 20 - 1 plus some spare + CAX25Demodulator m_demod1; + CAX25Demodulator m_demod2; + CAX25Demodulator m_demod3; + uint16_t m_lastFCS; + uint32_t m_count; + uint32_t m_slotTime; + uint32_t m_slotCount; + uint8_t m_pPersist; + bool m_dcd; + bool m_canTX; + uint8_t m_x; + uint8_t m_a; + uint8_t m_b; + uint8_t m_c; + + void initRand(); + uint8_t rand(); +}; + +#endif + diff --git a/AX25TX.cpp b/AX25TX.cpp new file mode 100644 index 0000000..a5b6c95 --- /dev/null +++ b/AX25TX.cpp @@ -0,0 +1,186 @@ +/* + * 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 "AX25TX.h" + +#include "AX25Defines.h" +#include "AX25Frame.h" + + +const uint8_t START_FLAG[] = { AX25_FRAME_START }; +const uint8_t END_FLAG[] = { AX25_FRAME_END }; + +const uint8_t BIT_MASK_TABLE1[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; + +#define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE1[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE1[(i)&7]) +#define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE1[(i)&7]) + +const uint8_t BIT_MASK_TABLE2[] = { 0x01U, 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U }; + +#define WRITE_BIT2(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE2[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE2[(i)&7]) +#define READ_BIT2(p,i) (p[(i)>>3] & BIT_MASK_TABLE2[(i)&7]) + +const uint16_t AUDIO_TABLE_LEN = 120U; + +const q15_t AUDIO_TABLE_DATA[] = { + 0, 214, 428, 641, 851, 1060, 1265, 1468, 1666, 1859, 2048, 2230, 2407, 2577, 2740, 2896, 3043, 3182, 3313, 3434, 3546, 3649, + 3741, 3823, 3895, 3955, 4006, 4045, 4073, 4089, 4095, 4089, 4073, 4045, 4006, 3955, 3895, 3823, 3741, 3649, 3546, 3434, 3313, + 3182, 3043, 2896, 2740, 2577, 2407, 2230, 2048, 1859, 1666, 1468, 1265, 1060, 851, 641, 428, 214, 0, -214, -428, -641, -851, + -1060, -1265, -1468, -1666, -1859, -2047, -2230, -2407, -2577, -2740, -2896, -3043, -3182, -3313, -3434, -3546, -3649, -3741, + -3823, -3895, -3955, -4006, -4045, -4073, -4089, -4095, -4089, -4073, -4045, -4006, -3955, -3895, -3823, -3741, -3649, -3546, + -3434, -3313, -3182, -3043, -2896, -2740, -2577, -2407, -2230, -2047, -1859, -1666, -1468, -1265, -1060, -851, -641, -428, -214 +}; + +CAX25TX::CAX25TX() : +m_poBuffer(), +m_poLen(0U), +m_poPtr(0U), +m_txDelay(360U), +m_tablePtr(0U), +m_nrzi(false) +{ +} + +void CAX25TX::process() +{ + if (m_poLen == 0U) + return; + + if (!m_duplex) { + if (m_poPtr == 0U) { + bool tx = ax25RX.canTX(); + if (!tx) + return; + } + } + + uint16_t space = io.getSpace(); + + while (space > AX25_RADIO_SYMBOL_LENGTH) { + bool b = READ_BIT1(m_poBuffer, m_poPtr) != 0U; + m_poPtr++; + + writeBit(b); + + space -= AX25_RADIO_SYMBOL_LENGTH; + + if (m_poPtr >= m_poLen) { + m_poPtr = 0U; + m_poLen = 0U; + return; + } + } +} + +uint8_t CAX25TX::writeData(const uint8_t* data, uint16_t length) +{ + CAX25Frame frame(data, length); + frame.addCRC(); + + m_poLen = 0U; + m_poPtr = 0U; + m_nrzi = false; + m_tablePtr = 0U; + + // Add TX delay + for (uint16_t i = 0U; i < m_txDelay; i++, m_poLen++) { + bool preamble = NRZI(false); + WRITE_BIT1(m_poBuffer, m_poLen, preamble); + } + + // Add the Start Flag + for (uint16_t i = 0U; i < 8U; i++, m_poLen++) { + bool b1 = READ_BIT1(START_FLAG, i) != 0U; + bool b2 = NRZI(b1); + WRITE_BIT1(m_poBuffer, m_poLen, b2); + } + + uint8_t ones = 0U; + for (uint16_t i = 0U; i < (frame.m_length * 8U); i++) { + bool b1 = READ_BIT2(frame.m_data, i) != 0U; + bool b2 = NRZI(b1); + WRITE_BIT1(m_poBuffer, m_poLen, b2); + m_poLen++; + + if (b1) { + ones++; + if (ones == AX25_MAX_ONES) { + // Bit stuffing + bool b = NRZI(false); + WRITE_BIT1(m_poBuffer, m_poLen, b); + m_poLen++; + ones = 0U; + } + } else { + ones = 0U; + } + } + + // Add the End Flag + for (uint16_t i = 0U; i < 8U; i++, m_poLen++) { + bool b1 = READ_BIT1(END_FLAG, i) != 0U; + bool b2 = NRZI(b1); + WRITE_BIT1(m_poBuffer, m_poLen, b2); + } + + return 0U; +} + +void CAX25TX::writeBit(bool b) +{ + q15_t buffer[AX25_RADIO_SYMBOL_LENGTH]; + for (uint8_t i = 0U; i < AX25_RADIO_SYMBOL_LENGTH; i++) { + q15_t value = AUDIO_TABLE_DATA[m_tablePtr]; + + if (b) { + // De-emphasise the lower frequency by 6dB + value >>= 2; + m_tablePtr += 6U; + } else { + m_tablePtr += 11U; + } + + buffer[i] = value; + + if (m_tablePtr >= AUDIO_TABLE_LEN) + m_tablePtr -= AUDIO_TABLE_LEN; + } + + io.write(STATE_AX25, buffer, AX25_RADIO_SYMBOL_LENGTH); +} + +void CAX25TX::setTXDelay(uint8_t delay) +{ + m_txDelay = delay * 12U; +} + +uint8_t CAX25TX::getSpace() const +{ + return m_poLen == 0U ? 255U : 0U; +} + +bool CAX25TX::NRZI(bool b) +{ + if (!b) + m_nrzi = !m_nrzi; + + return m_nrzi; +} + diff --git a/AX25TX.h b/AX25TX.h new file mode 100644 index 0000000..0a41757 --- /dev/null +++ b/AX25TX.h @@ -0,0 +1,49 @@ +/* + * 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(AX25TX_H) +#define AX25TX_H + +#include "Config.h" + +class CAX25TX { +public: + CAX25TX(); + + uint8_t writeData(const uint8_t* data, uint16_t length); + + void process(); + + void setTXDelay(uint8_t delay); + + uint8_t getSpace() const; + +private: + uint8_t m_poBuffer[600U]; + uint16_t m_poLen; + uint16_t m_poPtr; + uint16_t m_txDelay; + uint16_t m_tablePtr; + bool m_nrzi; + + void writeBit(bool b); + bool NRZI(bool b); +}; + +#endif + diff --git a/AX25Twist.cpp b/AX25Twist.cpp new file mode 100644 index 0000000..bf0c534 --- /dev/null +++ b/AX25Twist.cpp @@ -0,0 +1,311 @@ +/* + * 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 "AX25Twist.h" + +// 1200Hz = -12dB, 2200Hz = 0dB; 3381Hz cutoff; cosine. +q15_t dB12[] = { + 176, + -812, + -3916, + -7586, + 23536, + -7586, + -3916, + -812, + 176 +}; + +// 1200Hz = -11dB, 2200Hz = 0dB; 3258Hz cutoff; cosine. +q15_t dB11[] = { + 121, + -957, + -3959, + -7383, + 23871, + -7383, + -3959, + -957, + 121 +}; + +// 1200Hz = -10dB, 2200Hz = 0dB; 3118Hz cutoff; cosine. +q15_t dB10[] = { + 56, + -1110, + -3987, + -7141, + 24254, + -7141, + -3987, + -1110, + 56 +}; + +// 1200Hz = -9dB, 2200Hz = 0dB; 2959Hz cutoff; cosine. +q15_t dB9[] = { + -19, + -1268, + -3994, + -6856, + 24688, + -6856, + -3994, + -1268, + -19 +}; + +// 1200Hz = -8dB, 2200Hz = 0dB; 2778Hz cutoff; cosine. +q15_t dB8[] = { + -104, + -1424, + -3968, + -6516, + 25182, + -6516, + -3968, + -1424, + -104 +}; + +// 1200Hz = -7dB, 2200Hz = 0dB; 2573Hz cutoff; cosine. +q15_t dB7[] = { + -196, + -1565, + -3896, + -6114, + 25742, + -6114, + -3896, + -1565, + -196 +}; + +// 1200Hz = -6dB, 2200Hz = 0dB; 2343Hz cutoff; cosine. +q15_t dB6[] = { + -288, + -1676, + -3761, + -5642, + 26370, + -5642, + -3761, + -1676, + -288 +}; + +// 1200Hz = -5dB, 2200Hz = 0dB; 2085Hz cutoff; cosine. +q15_t dB5[] = { + -370, + -1735, + -3545, + -5088, + 27075, + -5088, + -3545, + -1735, + -370 +}; + +// 1200Hz = -4dB, 2200Hz = 0dB; 1790Hz cutoff; cosine. +q15_t dB4[] = { + -432, + -1715, + -3220, + -4427, + 27880, + -4427, + -3220, + -1715, + -432 +}; + +// 1200Hz = -3dB, 2200Hz = 0dB; 1456Hz cutoff; cosine. +q15_t dB3[] = { + -452, + -1582, + -2759, + -3646, + 28792, + -3646, + -2759, + -1582, + -452 +}; + +// 1200Hz = -2dB, 2200Hz = 0dB; 1070Hz cutoff; cosine. +q15_t dB2[] = { + -408, + -1295, + -2123, + -2710, + 29846, + -2710, + -2123, + -1295, + -408 +}; + +// 1200Hz = -1dB, 2200Hz = 0dB; 605Hz cutoff; cosine. +q15_t dB1[] = { + -268, + -795, + -1244, + -1546, + 31116, + -1546, + -1244, + -795, + -268 +}; + +q15_t dB0[] = { + 0, + 0, + 0, + 0, + 32767, + 0, + 0, + 0, + 0, +}; + +// 1200Hz = 0dB, 2200Hz = -1dB; 4130Hz cutoff; cosine. +q15_t dB_1[] = { + -419, + -177, + 3316, + 8650, + 11278, + 8650, + 3316, + -177, + -419 +}; + +// 1200Hz = 0dB, 2200Hz = -2dB; 3190Hz cutoff; cosine. +q15_t dB_2[] = { + -90, + 1033, + 3975, + 7267, + 8711, + 7267, + 3975, + 1033, + -90 +}; + +// 1200Hz = 0dB, 2200Hz = -3dB; 2330Hz cutoff; cosine. +q15_t dB_3[] = { + 292, + 1680, + 3752, + 5615, + 6362, + 5615, + 3752, + 1680, + 292 +}; + +// 1200Hz = 0dB, 2200Hz = -4dB; 2657Hz cutoff; boxcar. +q15_t dB_4[] = { + 917, + 3024, + 5131, + 6684, + 7255, + 6684, + 5131, + 3024, + 917 +}; + +// 1200Hz = 0dB, 2200Hz = -5dB; 2360Hz cutoff; boxcar. +q15_t dB_5[] = { + 1620, + 3339, + 4925, + 6042, + 6444, + 6042, + 4925, + 3339, + 1620 +}; + +// 1200Hz = 0dB, 2200Hz = -6dB; 2067Hz cutoff; boxcar. +q15_t dB_6[] = { + 2161, + 3472, + 4605, + 5373, + 5644, + 5373, + 4605, + 3472, + 2161 +}; + +q15_t* coeffs[] = { + dB12, + dB11, + dB10, + dB9, + dB8, + dB7, + dB6, + dB5, + dB4, + dB3, + dB2, + dB1, + dB0, + dB_1, + dB_2, + dB_3, + dB_4, + dB_5, + dB_6 +}; + +CAX25Twist::CAX25Twist(int8_t n) : +m_filter(), +m_state() +{ + setTwist(n); +} + +void CAX25Twist::process(q15_t* in, q15_t* out, uint8_t length) +{ + ::arm_fir_fast_q15(&m_filter, in, out, length); +} + +void CAX25Twist::setTwist(int8_t n) +{ + uint8_t twist = uint8_t(n + 6); + + m_filter.numTaps = 9; + m_filter.pState = m_state; + m_filter.pCoeffs = coeffs[twist]; +} + diff --git a/AX25Twist.h b/AX25Twist.h new file mode 100644 index 0000000..ea1c6de --- /dev/null +++ b/AX25Twist.h @@ -0,0 +1,39 @@ +/* + * 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(AX25Twist_H) +#define AX25Twist_H + +#include "Config.h" + + +class CAX25Twist { +public: + CAX25Twist(int8_t n); + + void process(q15_t* in, q15_t* out, uint8_t length); + + void setTwist(int8_t n); + +private: + arm_fir_instance_q15 m_filter; + q15_t m_state[40U]; // NoTaps + BlockSize - 1, 9 + 20 - 1 plus some spare +}; + +#endif + diff --git a/CWIdTX.cpp b/CWIdTX.cpp index 641b374..aef04b0 100644 --- a/CWIdTX.cpp +++ b/CWIdTX.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2017 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2017,2020 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Colin Durbridge G4EML * * This program is free software; you can redistribute it and/or modify @@ -125,7 +125,7 @@ void CCWIdTX::process() } } -uint8_t CCWIdTX::write(const uint8_t* data, uint8_t length) +uint8_t CCWIdTX::write(const uint8_t* data, uint16_t length) { ::memset(m_poBuffer, 0x00U, 1000U * sizeof(uint8_t)); diff --git a/CWIdTX.h b/CWIdTX.h index 70f67ea..a0480e0 100644 --- a/CWIdTX.h +++ b/CWIdTX.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2009-2015 by Jonathan Naylor G4KLX - * Copyright (C) 2016 by Colin Durbridge G4EML + * Copyright (C) 2016,2020 by Colin Durbridge G4EML * * 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 @@ -28,7 +28,7 @@ public: void process(); - uint8_t write(const uint8_t* data, uint8_t length); + uint8_t write(const uint8_t* data, uint16_t length); void reset(); diff --git a/CalDMR.cpp b/CalDMR.cpp index 5a92fb0..949a8e9 100644 --- a/CalDMR.cpp +++ b/CalDMR.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 by Jonathan Naylor G4KLX + * Copyright (C) 2009-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 @@ -208,7 +208,7 @@ void CCalDMR::dmrdmo1k() } } -uint8_t CCalDMR::write(const uint8_t* data, uint8_t length) +uint8_t CCalDMR::write(const uint8_t* data, uint16_t length) { if (length != 1U) return 4U; diff --git a/CalDMR.h b/CalDMR.h index 06ff6e6..9ba5f2c 100644 --- a/CalDMR.h +++ b/CalDMR.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 by Jonathan Naylor G4KLX + * Copyright (C) 2009-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 @@ -41,7 +41,7 @@ public: void createData1k(uint8_t n); void createDataDMO1k(uint8_t n); - uint8_t write(const uint8_t* data, uint8_t length); + uint8_t write(const uint8_t* data, uint16_t length); private: bool m_transmit; diff --git a/CalDStarTX.cpp b/CalDStarTX.cpp index 1c1a3a4..9680cdc 100644 --- a/CalDStarTX.cpp +++ b/CalDStarTX.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2016 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2016,2020 by Jonathan Naylor G4KLX * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -162,7 +162,7 @@ void CCalDStarTX::process() m_count = (m_count + 1U) % (30U * 21U); } -uint8_t CCalDStarTX::write(const uint8_t* data, uint8_t length) +uint8_t CCalDStarTX::write(const uint8_t* data, uint16_t length) { if (length != 1U) return 4U; diff --git a/CalDStarTX.h b/CalDStarTX.h index c411317..86f6625 100644 --- a/CalDStarTX.h +++ b/CalDStarTX.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,7 +26,7 @@ class CCalDStarTX { public: CCalDStarTX(); - uint8_t write(const uint8_t* data, uint8_t length); + uint8_t write(const uint8_t* data, uint16_t length); void process(); diff --git a/CalFM.cpp b/CalFM.cpp index e89f0be..22c3044 100644 --- a/CalFM.cpp +++ b/CalFM.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2015,2020 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2020 by Phil Taylor M0VSE * @@ -52,7 +52,7 @@ void CCalFM::process() { const TONE_TABLE* entry = NULL; - if (m_modemState!=m_lastState) + if (m_modemState != m_lastState) { switch (m_modemState) { case STATE_FMCAL10K: @@ -116,7 +116,7 @@ void CCalFM::process() } -uint8_t CCalFM::write(const uint8_t* data, uint8_t length) +uint8_t CCalFM::write(const uint8_t* data, uint16_t length) { if (length != 1U) return 4U; diff --git a/CalFM.h b/CalFM.h index 0dd8887..9665bcc 100644 --- a/CalFM.h +++ b/CalFM.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2015 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2015,2020 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2020 by Phil Taylor M0VSE * @@ -35,7 +35,7 @@ public: void fm25kcal(); void fm30kcal(); - uint8_t write(const uint8_t* data, uint8_t length); + uint8_t write(const uint8_t* data, uint16_t length); private: uint16_t m_frequency; diff --git a/CalNXDN.cpp b/CalNXDN.cpp index c223f59..3e46f3e 100644 --- a/CalNXDN.cpp +++ b/CalNXDN.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2018 by Andy Uribe CA6JAU + * 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 @@ -79,7 +80,7 @@ void CCalNXDN::process() } } -uint8_t CCalNXDN::write(const uint8_t* data, uint8_t length) +uint8_t CCalNXDN::write(const uint8_t* data, uint16_t length) { if (length != 1U) return 4U; diff --git a/CalNXDN.h b/CalNXDN.h index f0051ef..cad88a4 100644 --- a/CalNXDN.h +++ b/CalNXDN.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2018 by Andy Uribe CA6JAU + * 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 @@ -32,7 +33,7 @@ public: void process(); - uint8_t write(const uint8_t* data, uint8_t length); + uint8_t write(const uint8_t* data, uint16_t length); private: bool m_transmit; diff --git a/CalP25.cpp b/CalP25.cpp index b1b9b6d..6f3d4c0 100644 --- a/CalP25.cpp +++ b/CalP25.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2018 by Andy Uribe CA6JAU + * 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 @@ -82,7 +83,7 @@ void CCalP25::process() } } -uint8_t CCalP25::write(const uint8_t* data, uint8_t length) +uint8_t CCalP25::write(const uint8_t* data, uint16_t length) { if (length != 1U) return 4U; diff --git a/CalP25.h b/CalP25.h index 6374ed2..75bfe0d 100644 --- a/CalP25.h +++ b/CalP25.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2018 by Andy Uribe CA6JAU + * 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 @@ -34,7 +35,7 @@ public: void process(); - uint8_t write(const uint8_t* data, uint8_t length); + uint8_t write(const uint8_t* data, uint16_t length); private: bool m_transmit; diff --git a/CalPOCSAG.cpp b/CalPOCSAG.cpp index bf88a51..b66eb69 100644 --- a/CalPOCSAG.cpp +++ b/CalPOCSAG.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2019 by Florian Wolters DF2ET + * 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 @@ -38,7 +39,7 @@ void CCalPOCSAG::process() pocsagTX.writeByte(0xAAU); } -uint8_t CCalPOCSAG::write(const uint8_t* data, uint8_t length) +uint8_t CCalPOCSAG::write(const uint8_t* data, uint16_t length) { if (length != 1U) return 4U; diff --git a/CalPOCSAG.h b/CalPOCSAG.h index 291e360..d49eb81 100644 --- a/CalPOCSAG.h +++ b/CalPOCSAG.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2019 by Florian Wolters DF2ET + * 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 @@ -32,7 +33,7 @@ public: void process(); - uint8_t write(const uint8_t* data, uint8_t length); + uint8_t write(const uint8_t* data, uint16_t length); private: POCSAGCAL m_state; diff --git a/Config.h b/Config.h index ea3a784..32b07c5 100644 --- a/Config.h +++ b/Config.h @@ -35,8 +35,16 @@ // For 19.2 MHz // #define EXTERNAL_OSC 19200000 +// Use a higher baudrate for host communication. Required for FM network ! +#define SERIAL_SPEED 115200 //suitable for most older boards (Arduino, Due STM32F1_POG etc). External FM will NOT work with this ! +// #define SERIAL_SPEED 230400 // Only works on newer board M4, M7, Teensy. External FM might work with this +// #define SERIAL_SPEED 460800 // Only works on newer board M4, M7, Teensy. External FM should work with this + +// Allow the use of the COS line to lockout the modem +// #define USE_COS_AS_LOCKOUT + // Use pins to output the current mode via LEDs -// #define MODE_LEDS +#define MODE_LEDS // For the original Arduino Due pin layout // #define ARDUINO_DUE_PAPA @@ -79,12 +87,13 @@ // #define USE_ALTERNATE_NXDN_LEDS // Use the D-Star and P25 LEDs for M17 -// #define USE_ALTERNATE_M17_LEDS +#define USE_ALTERNATE_M17_LEDS // Use the D-Star and DMR LEDs for POCSAG -// #define USE_ALTERNATE_POCSAG_LEDS +#define USE_ALTERNATE_POCSAG_LEDS // Use the D-Star and YSF LEDs for FM #define USE_ALTERNATE_FM_LEDS #endif + diff --git a/DMRDMOTX.cpp b/DMRDMOTX.cpp index 141ed38..4932aaf 100644 --- a/DMRDMOTX.cpp +++ b/DMRDMOTX.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2017 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2017,2020 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2017 by Andy Uribe CA6JAU * @@ -74,7 +74,7 @@ void CDMRDMOTX::process() m_poLen = m_txDelay; } else { for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) - m_poBuffer[i] = m_fifo.get(); + m_fifo.get(m_poBuffer[i]); for (unsigned int i = 0U; i < 39U; i++) m_poBuffer[i + DMR_FRAME_LENGTH_BYTES] = PR_FILL[i]; @@ -104,7 +104,7 @@ void CDMRDMOTX::process() } } -uint8_t CDMRDMOTX::writeData(const uint8_t* data, uint8_t length) +uint8_t CDMRDMOTX::writeData(const uint8_t* data, uint16_t length) { if (length != (DMR_FRAME_LENGTH_BYTES + 1U)) return 4U; diff --git a/DMRDMOTX.h b/DMRDMOTX.h index 62ef2c1..3ef53eb 100644 --- a/DMRDMOTX.h +++ b/DMRDMOTX.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2020 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Colin Durbridge G4EML * * This program is free software; you can redistribute it and/or modify @@ -23,13 +23,13 @@ #include "Config.h" #include "DMRDefines.h" -#include "SerialRB.h" +#include "RingBuffer.h" class CDMRDMOTX { public: CDMRDMOTX(); - uint8_t writeData(const uint8_t* data, uint8_t length); + uint8_t writeData(const uint8_t* data, uint16_t length); void process(); @@ -38,7 +38,7 @@ public: uint8_t getSpace() const; private: - CSerialRB m_fifo; + CRingBuffer m_fifo; arm_fir_interpolate_instance_q15 m_modFilter; q15_t m_modState[16U]; // blockSize + phaseLength - 1, 4 + 9 - 1 plus some spare uint8_t m_poBuffer[1200U]; diff --git a/DMRTX.cpp b/DMRTX.cpp index 81437ec..3342639 100644 --- a/DMRTX.cpp +++ b/DMRTX.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2017 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2017,2020 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Colin Durbridge G4EML * Copyright (C) 2017 by Andy Uribe CA6JAU * @@ -144,7 +144,7 @@ void CDMRTX::process() } } -uint8_t CDMRTX::writeData1(const uint8_t* data, uint8_t length) +uint8_t CDMRTX::writeData1(const uint8_t* data, uint16_t length) { if (length != (DMR_FRAME_LENGTH_BYTES + 1U)) return 4U; @@ -168,7 +168,7 @@ uint8_t CDMRTX::writeData1(const uint8_t* data, uint8_t length) return 0U; } -uint8_t CDMRTX::writeData2(const uint8_t* data, uint8_t length) +uint8_t CDMRTX::writeData2(const uint8_t* data, uint16_t length) { if (length != (DMR_FRAME_LENGTH_BYTES + 1U)) return 4U; @@ -192,7 +192,7 @@ uint8_t CDMRTX::writeData2(const uint8_t* data, uint8_t length) return 0U; } -uint8_t CDMRTX::writeShortLC(const uint8_t* data, uint8_t length) +uint8_t CDMRTX::writeShortLC(const uint8_t* data, uint16_t length) { if (length != 9U) return 4U; @@ -208,7 +208,7 @@ uint8_t CDMRTX::writeShortLC(const uint8_t* data, uint8_t length) return 0U; } -uint8_t CDMRTX::writeAbort(const uint8_t* data, uint8_t length) +uint8_t CDMRTX::writeAbort(const uint8_t* data, uint16_t length) { if (length != 1U) return 4U; @@ -293,7 +293,7 @@ void CDMRTX::createData(uint8_t slotIndex) { if (m_fifo[slotIndex].getData() >= DMR_FRAME_LENGTH_BYTES && m_frameCount >= STARTUP_COUNT && m_abortCount[slotIndex] >= ABORT_COUNT) { for (unsigned int i = 0U; i < DMR_FRAME_LENGTH_BYTES; i++) { - m_poBuffer[i] = m_fifo[slotIndex].get(); + m_fifo[slotIndex].get(m_poBuffer[i]); m_markBuffer[i] = MARK_NONE; } } else { diff --git a/DMRTX.h b/DMRTX.h index 1e2f617..292b01b 100644 --- a/DMRTX.h +++ b/DMRTX.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2020 by Jonathan Naylor G4KLX * Copyright (C) 2016 by Colin Durbridge G4EML * * This program is free software; you can redistribute it and/or modify @@ -23,7 +23,7 @@ #include "Config.h" #include "DMRDefines.h" -#include "SerialRB.h" +#include "RingBuffer.h" enum DMRTXSTATE { DMRTXSTATE_IDLE, @@ -38,11 +38,11 @@ class CDMRTX { public: CDMRTX(); - uint8_t writeData1(const uint8_t* data, uint8_t length); - uint8_t writeData2(const uint8_t* data, uint8_t length); + uint8_t writeData1(const uint8_t* data, uint16_t length); + uint8_t writeData2(const uint8_t* data, uint16_t length); - uint8_t writeShortLC(const uint8_t* data, uint8_t length); - uint8_t writeAbort(const uint8_t* data, uint8_t length); + uint8_t writeShortLC(const uint8_t* data, uint16_t length); + uint8_t writeAbort(const uint8_t* data, uint16_t length); void setStart(bool start); void setCal(bool start); @@ -59,7 +59,7 @@ public: void setColorCode(uint8_t colorCode); private: - CSerialRB m_fifo[2U]; + CRingBuffer m_fifo[2U]; arm_fir_interpolate_instance_q15 m_modFilter; q15_t m_modState[16U]; // blockSize + phaseLength - 1, 4 + 9 - 1 plus some spare DMRTXSTATE m_state; diff --git a/DStarTX.cpp b/DStarTX.cpp index 63eb910..e88b27e 100644 --- a/DStarTX.cpp +++ b/DStarTX.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2017 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2017,2020 by Jonathan Naylor G4KLX * Copyright (C) 2017 by Andy Uribe CA6JAU * * This program is free software; you can redistribute it and/or modify @@ -216,12 +216,13 @@ void CDStarTX::process() for (uint16_t i = 0U; i < m_txDelay; i++) m_poBuffer[m_poLen++] = BIT_SYNC; } else { + uint8_t dummy; // Pop the type byte off - m_buffer.get(); + m_buffer.get(dummy); uint8_t header[DSTAR_HEADER_LENGTH_BYTES]; for (uint8_t i = 0U; i < DSTAR_HEADER_LENGTH_BYTES; i++) - header[i] = m_buffer.get(); + m_buffer.get(header[i]); uint8_t buffer[86U]; txHeader(header, buffer + 2U); @@ -239,17 +240,19 @@ void CDStarTX::process() if (type == DSTAR_DATA && m_poLen == 0U) { // Pop the type byte off - m_buffer.get(); + uint8_t dummy; + m_buffer.get(dummy); for (uint8_t i = 0U; i < DSTAR_DATA_LENGTH_BYTES; i++) - m_poBuffer[m_poLen++] = m_buffer.get(); + m_buffer.get(m_poBuffer[m_poLen++]); m_poPtr = 0U; } if (type == DSTAR_EOT && m_poLen == 0U) { // Pop the type byte off - m_buffer.get(); + uint8_t dummy; + m_buffer.get(dummy); for (uint8_t j = 0U; j < 3U; j++) { for (uint8_t i = 0U; i < DSTAR_EOT_LENGTH_BYTES; i++) @@ -277,7 +280,7 @@ void CDStarTX::process() } } -uint8_t CDStarTX::writeHeader(const uint8_t* header, uint8_t length) +uint8_t CDStarTX::writeHeader(const uint8_t* header, uint16_t length) { if (length != DSTAR_HEADER_LENGTH_BYTES) return 4U; @@ -296,7 +299,7 @@ uint8_t CDStarTX::writeHeader(const uint8_t* header, uint8_t length) return 0U; } -uint8_t CDStarTX::writeData(const uint8_t* data, uint8_t length) +uint8_t CDStarTX::writeData(const uint8_t* data, uint16_t length) { if (length != DSTAR_DATA_LENGTH_BYTES) return 4U; diff --git a/DStarTX.h b/DStarTX.h index 24876d7..0a82846 100644 --- a/DStarTX.h +++ b/DStarTX.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2020 by Jonathan Naylor G4KLX * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,14 +21,14 @@ #include "Config.h" -#include "SerialRB.h" +#include "RingBuffer.h" class CDStarTX { public: CDStarTX(); - uint8_t writeHeader(const uint8_t* header, uint8_t length); - uint8_t writeData(const uint8_t* data, uint8_t length); + uint8_t writeHeader(const uint8_t* header, uint16_t length); + uint8_t writeData(const uint8_t* data, uint16_t length); uint8_t writeEOT(); void process(); @@ -38,7 +38,7 @@ public: uint8_t getSpace() const; private: - CSerialRB m_buffer; + CRingBuffer m_buffer; arm_fir_interpolate_instance_q15 m_modFilter; q15_t m_modState[20U]; // blockSize + phaseLength - 1, 8 + 9 - 1 plus some spare uint8_t m_poBuffer[600U]; diff --git a/FM.cpp b/FM.cpp index 40934bf..787206b 100644 --- a/FM.cpp +++ b/FM.cpp @@ -20,6 +20,11 @@ #include "Globals.h" #include "FM.h" +const uint16_t FM_TX_BLOCK_SIZE = 100U; +const uint16_t FM_SERIAL_BLOCK_SIZE = 84U;//this is the number of sample pairs to send over serial. One sample pair is 3bytes. + //three times this value shall never exceed 252 +const uint16_t FM_SERIAL_BLOCK_SIZE_BYTES = FM_SERIAL_BLOCK_SIZE * 3U; + /* * Access Mode values are: * 0 - Carrier access with COS @@ -31,8 +36,10 @@ CFM::CFM() : m_callsign(), m_rfAck(), +m_extAck(), m_ctcssRX(), m_ctcssTX(), +m_squelch(), m_timeoutTone(), m_state(FS_LISTENING), m_callsignAtStart(false), @@ -45,25 +52,32 @@ m_kerchunkTimer(), m_ackMinTimer(), m_ackDelayTimer(), m_hangTimer(), +m_statusTimer(), m_reverseTimer(), +m_needReverse(false), m_filterStage1( 724, 1448, 724, 32768, -37895, 21352),//3rd order Cheby Filter 300 to 2700Hz, 0.2dB passband ripple, sampling rate 24kHz m_filterStage2(32768, 0,-32768, 32768, -50339, 19052), m_filterStage3(32768, -65536, 32768, 32768, -64075, 31460), m_blanking(), m_accessMode(1U), m_cosInvert(false), +m_noiseSquelch(false), m_rfAudioBoost(1U), -m_downsampler(128U),//Size might need adjustement +m_extAudioBoost(1U), +m_downSampler(400U),// 100 ms of audio +m_extEnabled(false), m_rxLevel(1), -m_inputRB(4800U), // 200ms of audio -m_outputRB(2400U) // 100ms of audio +m_inputRFRB(2401U), // 100ms of audio + 1 sample +m_outputRFRB(2400U), // 100ms of audio +m_inputExtRB() { + m_statusTimer.setTimeout(1U, 0U); m_reverseTimer.setTimeout(0U, 150U); insertDelay(100U); } -void CFM::samples(bool cos, const q15_t* samples, uint8_t length) +void CFM::samples(bool cos, q15_t* samples, uint8_t length) { if (m_cosInvert) cos = !cos; @@ -73,50 +87,72 @@ void CFM::samples(bool cos, const q15_t* samples, uint8_t length) uint8_t i = 0U; for (; i < length; i++) { // ARMv7-M has hardware integer division - q15_t currentSample = q15_t((q31_t(samples[i]) << 8) / m_rxLevel); + q15_t currentRFSample = q15_t((q31_t(samples[i]) << 8) / m_rxLevel); + + if (m_noiseSquelch) + cos = m_squelch.process(currentRFSample); + + q15_t currentExtSample; + bool inputExt = m_inputExtRB.getSample(currentExtSample);//always consume the external input data so it does not overflow + inputExt = inputExt && m_extEnabled; switch (m_accessMode) { case 0U: - stateMachine(cos); + if (!inputExt && !cos && m_modemState != STATE_FM) + continue; + else + stateMachine(cos, inputExt); break; case 1U: { - uint8_t ctcssState = m_ctcssRX.process(currentSample); + bool ctcss = m_ctcssRX.process(currentRFSample); // Delay the audio by 100ms to better match the CTCSS detector output - m_inputRB.put(currentSample); - m_inputRB.get(currentSample); + m_inputRFRB.put(currentRFSample); + m_inputRFRB.get(currentRFSample); - if (CTCSS_NOT_READY(ctcssState) && m_modemState != STATE_FM) { - // Not enough samples to determine if you have CTCSS, just carry on + if (!inputExt && !ctcss && m_modemState != STATE_FM) { + // No CTCSS detected, just carry on + continue; + } else if ((inputExt || ctcss) && m_modemState != STATE_FM) { + // We had CTCSS or external input + stateMachine(ctcss, inputExt); + if (m_state == FS_LISTENING) + continue; } else { - bool validCTCSS = CTCSS_VALID(ctcssState); - stateMachine(validCTCSS); + stateMachine(ctcss, inputExt); } } break; case 2U: { - uint8_t 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 + bool ctcss = m_ctcssRX.process(currentRFSample); + if (!inputExt && !ctcss && m_modemState != STATE_FM) { + // No CTCSS detected, just carry on + continue; + } else if ((inputExt || (ctcss && cos)) && m_modemState != STATE_FM) { + // We had CTCSS or external input + stateMachine(ctcss && cos, inputExt); + if (m_state == FS_LISTENING) + continue; } else { - bool validCTCSS = CTCSS_VALID(ctcssState); - stateMachine(validCTCSS && cos); + stateMachine(ctcss && cos, inputExt); } } break; default: { - uint8_t 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 - } 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); + bool ctcss = m_ctcssRX.process(currentRFSample); + if (!inputExt && !ctcss && m_modemState != STATE_FM) { + // No CTCSS detected, just carry on + continue; + } else if ((inputExt || (ctcss && cos)) && m_modemState != STATE_FM) { + // We had CTCSS or external input + stateMachine(ctcss && cos, inputExt); + if (m_state == FS_LISTENING) + continue; } else { - stateMachine(cos); + stateMachine(cos, inputExt); } } break; @@ -125,19 +161,44 @@ void CFM::samples(bool cos, const q15_t* samples, uint8_t length) if (m_modemState != STATE_FM) continue; - // Only let audio through when relaying audio - if (m_state == FS_RELAYING || m_state == FS_KERCHUNK) { - // m_downsampler.addSample(currentSample); - currentSample = m_blanking.process(currentSample); - currentSample *= m_rfAudioBoost; - } else { - currentSample = 0; + if (m_state == FS_LISTENING && !m_rfAck.isWanted() && !m_extAck.isWanted() && !m_callsign.isWanted() && !m_reverseTimer.isRunning()) + continue; + + q15_t currentSample = currentRFSample; + q15_t currentBoost = m_rfAudioBoost; + if (m_state == FS_RELAYING_EXT || m_state == FS_KERCHUNK_EXT) { + currentSample = currentExtSample; + currentBoost = m_extAudioBoost; } - if (!m_callsign.isRunning()) + // Only let RF audio through when relaying RF audio + if (m_duplex) { + if (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF || m_state == FS_RELAYING_EXT || m_state == FS_KERCHUNK_EXT) { + currentSample = m_blanking.process(currentSample); + if (m_extEnabled && (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF)) + m_downSampler.addSample(currentSample); + + currentSample *= currentBoost; + } else { + currentSample = 0; + } + } else { + if (m_state == FS_RELAYING_EXT || m_state == FS_KERCHUNK_EXT) { + currentSample *= currentBoost; + } else { + if (m_extEnabled && (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF)) + m_downSampler.addSample(currentSample); + continue; + } + } + + if (!m_callsign.isRunning() && !m_extAck.isRunning()) currentSample += m_rfAck.getHighAudio(); - - if (!m_rfAck.isRunning()) { + + if (!m_callsign.isRunning() && !m_rfAck.isRunning()) + currentSample += m_extAck.getHighAudio(); + + if (!m_rfAck.isRunning() && !m_extAck.isRunning()) { if (m_state == FS_LISTENING) currentSample += m_callsign.getHighAudio(); else @@ -146,20 +207,55 @@ void CFM::samples(bool cos, const q15_t* samples, uint8_t length) currentSample = m_filterStage3.filter(m_filterStage2.filter(m_filterStage1.filter(currentSample))); - if (!m_callsign.isRunning() && !m_rfAck.isRunning()) + if (!m_callsign.isRunning() && !m_rfAck.isRunning() && !m_extAck.isRunning()) currentSample += m_timeoutTone.getAudio(); currentSample += m_ctcssTX.getAudio(m_reverseTimer.isRunning()); - m_outputRB.put(currentSample); + m_outputRFRB.put(currentSample); } } void CFM::process() { - q15_t sample; - while (io.getSpace() >= 3U && m_outputRB.get(sample)) - io.write(STATE_FM, &sample, 1U); + uint16_t space = io.getSpace(); + uint16_t length = m_outputRFRB.getData(); + + if (space > 10U && length >= FM_TX_BLOCK_SIZE ) { + space -= 2U; + if (length > FM_TX_BLOCK_SIZE) + length = FM_TX_BLOCK_SIZE; + if (space > FM_TX_BLOCK_SIZE) + space = FM_TX_BLOCK_SIZE; + if (length > space) + length = space; + + q15_t samples[FM_TX_BLOCK_SIZE]; + + for (uint16_t i = 0U; i < length; i++) { + q15_t sample = 0; + m_outputRFRB.get(sample); + samples[i] = sample; + } + + io.write(STATE_FM, samples, length); + } + + if (m_extEnabled) { + uint16_t length = m_downSampler.getData(); + + if (length >= FM_SERIAL_BLOCK_SIZE) { + if (length > FM_SERIAL_BLOCK_SIZE) + length = FM_SERIAL_BLOCK_SIZE; + + TSamplePairPack serialSamples[FM_SERIAL_BLOCK_SIZE]; + + for (uint16_t j = 0U; j < length; j++) + m_downSampler.getPackedData(serialSamples[j]); + + serial.writeFMData((uint8_t*)serialSamples, length * sizeof(TSamplePairPack)); + } + } } void CFM::reset() @@ -172,14 +268,22 @@ void CFM::reset() m_ackMinTimer.stop(); m_ackDelayTimer.stop(); m_hangTimer.stop(); + m_statusTimer.stop(); m_reverseTimer.stop(); m_ctcssRX.reset(); m_rfAck.stop(); + m_extAck.stop(); m_callsign.stop(); m_timeoutTone.stop(); - m_outputRB.reset(); + m_outputRFRB.reset(); + m_inputExtRB.reset(); + + m_downSampler.reset(); + m_squelch.reset(); + + m_needReverse = false; } uint8_t CFM::setCallsign(const char* callsign, uint8_t speed, uint16_t frequency, uint8_t time, uint8_t holdoff, uint8_t highLevel, uint8_t lowLevel, bool callsignAtStart, bool callsignAtEnd, bool callsignAtLatch) @@ -210,15 +314,18 @@ uint8_t CFM::setAck(const char* rfAck, uint8_t speed, uint16_t frequency, uint8_ return m_rfAck.setParams(rfAck, speed, frequency, level, level); } -uint8_t CFM::setMisc(uint16_t timeout, uint8_t timeoutLevel, uint8_t ctcssFrequency, uint8_t ctcssHighThreshold, uint8_t ctcssLowThreshold, uint8_t ctcssLevel, uint8_t kerchunkTime, uint8_t hangTime, uint8_t accessMode, bool cosInvert, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel) +uint8_t CFM::setMisc(uint16_t timeout, uint8_t timeoutLevel, uint8_t ctcssFrequency, uint8_t ctcssHighThreshold, uint8_t ctcssLowThreshold, uint8_t ctcssLevel, uint8_t kerchunkTime, uint8_t hangTime, uint8_t accessMode, bool cosInvert, bool noiseSquelch, uint8_t squelchHighThreshold, uint8_t squelchLowThreshold, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel) { - m_accessMode = accessMode; - m_cosInvert = cosInvert; + m_accessMode = accessMode; + m_cosInvert = cosInvert; + m_noiseSquelch = noiseSquelch; m_rfAudioBoost = q15_t(rfAudioBoost); m_timeoutTimer.setTimeout(timeout, 0U); + m_kerchunkTimer.setTimeout(kerchunkTime, 0U); + m_hangTimer.setTimeout(hangTime, 0U); m_timeoutTone.setParams(timeoutLevel); @@ -226,6 +333,8 @@ uint8_t CFM::setMisc(uint16_t timeout, uint8_t timeoutLevel, uint8_t ctcssFreque m_rxLevel = rxLevel; //q15_t(255)/q15_t(rxLevel >> 1); + m_squelch.setParams(squelchHighThreshold, squelchLowThreshold); + uint8_t ret = m_ctcssRX.setParams(ctcssFrequency, ctcssHighThreshold, ctcssLowThreshold); if (ret != 0U) return ret; @@ -233,48 +342,118 @@ uint8_t CFM::setMisc(uint16_t timeout, uint8_t timeoutLevel, uint8_t ctcssFreque return m_ctcssTX.setParams(ctcssFrequency, ctcssLevel); } -void CFM::stateMachine(bool validSignal) +uint8_t CFM::setExt(const char* ack, uint8_t audioBoost, uint8_t speed, uint16_t frequency, uint8_t level) +{ + m_extEnabled = true; + + m_extAudioBoost = q15_t(audioBoost); + + return m_extAck.setParams(ack, speed, frequency, level, level); +} + +void CFM::stateMachine(bool validRFSignal, bool validExtSignal) +{ + if (m_duplex) + duplexStateMachine(validRFSignal, validExtSignal); + else + simplexStateMachine(validRFSignal, validExtSignal); +} + +void CFM::simplexStateMachine(bool validRFSignal, bool validExtSignal) { switch (m_state) { case FS_LISTENING: - listeningState(validSignal); + listeningStateSimplex(validRFSignal, validExtSignal); break; - case FS_KERCHUNK: - kerchunkState(validSignal); + case FS_RELAYING_RF: + relayingRFStateSimplex(validRFSignal); break; - case FS_RELAYING: - relayingState(validSignal); + case FS_RELAYING_WAIT_RF: + relayingRFWaitStateSimplex(validRFSignal); break; - case FS_RELAYING_WAIT: - relayingWaitState(validSignal); + case FS_TIMEOUT_RF: + timeoutRFStateSimplex(validRFSignal); break; - case FS_TIMEOUT: - timeoutState(validSignal); + case FS_TIMEOUT_WAIT_RF: + timeoutRFStateSimplex(validRFSignal); break; - case FS_TIMEOUT_WAIT: - timeoutWaitState(validSignal); + case FS_RELAYING_EXT: + relayingExtStateSimplex(validExtSignal); break; - case FS_HANG: - hangState(validSignal); + case FS_RELAYING_WAIT_EXT: + relayingExtWaitStateSimplex(validExtSignal); + break; + case FS_TIMEOUT_EXT: + timeoutExtStateSimplex(validExtSignal); + break; + case FS_TIMEOUT_WAIT_EXT: + timeoutExtWaitStateSimplex(validExtSignal); break; default: break; } - if (m_state == FS_LISTENING && m_modemState == STATE_FM) { - if (!m_callsign.isWanted() && !m_rfAck.isWanted() && !m_reverseTimer.isRunning()) + if (m_state == FS_LISTENING) { + if (!m_reverseTimer.isRunning() && m_needReverse) m_reverseTimer.start(); - if (!m_callsign.isWanted() && !m_rfAck.isWanted() && m_reverseTimer.isRunning() && m_reverseTimer.hasExpired()) { - 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(); + if (m_reverseTimer.isRunning() && m_reverseTimer.hasExpired()) { m_reverseTimer.stop(); + m_needReverse = false; + } + } +} + +void CFM::duplexStateMachine(bool validRFSignal, bool validExtSignal) +{ + switch (m_state) { + case FS_LISTENING: + listeningStateDuplex(validRFSignal, validExtSignal); + break; + case FS_KERCHUNK_RF: + kerchunkRFStateDuplex(validRFSignal); + break; + case FS_RELAYING_RF: + relayingRFStateDuplex(validRFSignal); + break; + case FS_RELAYING_WAIT_RF: + relayingRFWaitStateDuplex(validRFSignal); + break; + case FS_TIMEOUT_RF: + timeoutRFStateDuplex(validRFSignal); + break; + case FS_TIMEOUT_WAIT_RF: + timeoutRFWaitStateDuplex(validRFSignal); + break; + case FS_KERCHUNK_EXT: + kerchunkExtStateDuplex(validExtSignal); + break; + case FS_RELAYING_EXT: + relayingExtStateDuplex(validExtSignal); + break; + case FS_RELAYING_WAIT_EXT: + relayingExtWaitStateDuplex(validExtSignal); + break; + case FS_TIMEOUT_EXT: + timeoutExtStateDuplex(validExtSignal); + break; + case FS_TIMEOUT_WAIT_EXT: + timeoutExtWaitStateDuplex(validExtSignal); + break; + case FS_HANG: + hangStateDuplex(validRFSignal, validExtSignal); + break; + default: + break; + } + + if (m_state == FS_LISTENING && !m_rfAck.isWanted() && !m_extAck.isWanted() && !m_callsign.isWanted()) { + if (!m_reverseTimer.isRunning() && m_needReverse) + m_reverseTimer.start(); + + if (m_reverseTimer.isRunning() && m_reverseTimer.hasExpired()) { + m_reverseTimer.stop(); + m_needReverse = false; } } } @@ -288,46 +467,107 @@ void CFM::clock(uint8_t length) m_ackMinTimer.clock(length); m_ackDelayTimer.clock(length); m_hangTimer.clock(length); + m_statusTimer.clock(length); m_reverseTimer.clock(length); + + if (m_statusTimer.isRunning() && m_statusTimer.hasExpired()) { + serial.writeFMStatus(m_state); + m_statusTimer.start(); + } } -void CFM::listeningState(bool validSignal) +void CFM::listeningStateDuplex(bool validRFSignal, bool validExtSignal) { - if (validSignal) { + if (validRFSignal) { if (m_kerchunkTimer.getTimeout() > 0U) { - DEBUG1("State to KERCHUNK"); - m_state = FS_KERCHUNK; + DEBUG1("State to KERCHUNK_RF"); + m_state = FS_KERCHUNK_RF; m_kerchunkTimer.start(); if (m_callsignAtStart && !m_callsignAtLatch) sendCallsign(); } else { - DEBUG1("State to RELAYING"); - m_state = FS_RELAYING; + DEBUG1("State to RELAYING_RF"); + m_state = FS_RELAYING_RF; if (m_callsignAtStart) sendCallsign(); } - insertSilence(50U); + if (m_state == FS_RELAYING_RF || m_state == FS_KERCHUNK_RF) { + insertSilence(50U); - beginRelaying(); + beginRelaying(); - m_callsignTimer.start(); - m_reverseTimer.stop(); + m_callsignTimer.start(); + m_reverseTimer.stop(); + + io.setDecode(true); + io.setADCDetection(true); + + m_statusTimer.start(); + serial.writeFMStatus(m_state); + } + } else if (validExtSignal) { + if (m_kerchunkTimer.getTimeout() > 0U) { + DEBUG1("State to KERCHUNK_EXT"); + m_state = FS_KERCHUNK_EXT; + m_kerchunkTimer.start(); + if (m_callsignAtStart && !m_callsignAtLatch) + sendCallsign(); + } else { + DEBUG1("State to RELAYING_EXT"); + m_state = FS_RELAYING_EXT; + if (m_callsignAtStart) + sendCallsign(); + } + + if (m_state == FS_RELAYING_EXT || m_state == FS_KERCHUNK_EXT) { + insertSilence(50U); + + beginRelaying(); + + m_callsignTimer.start(); + m_reverseTimer.stop(); + + m_statusTimer.start(); + serial.writeFMStatus(m_state); + } + } +} + +void CFM::listeningStateSimplex(bool validRFSignal, bool validExtSignal) +{ + if (validRFSignal) { + DEBUG1("State to RELAYING_RF"); + m_state = FS_RELAYING_RF; io.setDecode(true); io.setADCDetection(true); - DEBUG1("Change to STATE_FM"); - m_modemState = STATE_FM; + m_timeoutTimer.start(); + m_reverseTimer.stop(); + + m_statusTimer.start(); + serial.writeFMStatus(m_state); + } else if (validExtSignal) { + DEBUG1("State to RELAYING_EXT"); + m_state = FS_RELAYING_EXT; + + insertSilence(50U); + + m_timeoutTimer.start(); + m_reverseTimer.stop(); + + m_statusTimer.start(); + serial.writeFMStatus(m_state); } } -void CFM::kerchunkState(bool validSignal) +void CFM::kerchunkRFStateDuplex(bool validSignal) { if (validSignal) { if (m_kerchunkTimer.hasExpired()) { - DEBUG1("State to RELAYING"); - m_state = FS_RELAYING; + DEBUG1("State to RELAYING_RF"); + m_state = FS_RELAYING_RF; m_kerchunkTimer.stop(); if (m_callsignAtStart && m_callsignAtLatch) { sendCallsign(); @@ -344,26 +584,36 @@ void CFM::kerchunkState(bool validSignal) m_timeoutTimer.stop(); m_ackMinTimer.stop(); m_callsignTimer.stop(); + m_statusTimer.stop(); + m_needReverse = true; + if (m_extEnabled) + serial.writeFMEOT(); } } -void CFM::relayingState(bool validSignal) +void CFM::relayingRFStateDuplex(bool validSignal) { if (validSignal) { if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { - DEBUG1("State to TIMEOUT"); - m_state = FS_TIMEOUT; + DEBUG1("State to TIMEOUT_RF"); + m_state = FS_TIMEOUT_RF; m_ackMinTimer.stop(); m_timeoutTimer.stop(); m_timeoutTone.start(); + + if (m_extEnabled) + serial.writeFMEOT(); } } else { io.setDecode(false); io.setADCDetection(false); - DEBUG1("State to RELAYING_WAIT"); - m_state = FS_RELAYING_WAIT; + DEBUG1("State to RELAYING_WAIT_RF"); + m_state = FS_RELAYING_WAIT_RF; m_ackDelayTimer.start(); + + if (m_extEnabled) + serial.writeFMEOT(); } if (m_callsignTimer.isRunning() && m_callsignTimer.hasExpired()) { @@ -372,14 +622,39 @@ void CFM::relayingState(bool validSignal) } } -void CFM::relayingWaitState(bool validSignal) +void CFM::relayingRFStateSimplex(bool validSignal) +{ + if (validSignal) { + if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { + DEBUG1("State to TIMEOUT_RF"); + m_state = FS_TIMEOUT_RF; + + m_timeoutTimer.stop(); + + if (m_extEnabled) + serial.writeFMEOT(); + } + } else { + io.setDecode(false); + io.setADCDetection(false); + + DEBUG1("State to RELAYING_WAIT_RF"); + m_state = FS_RELAYING_WAIT_RF; + m_ackDelayTimer.start(); + + if (m_extEnabled) + serial.writeFMEOT(); + } +} + +void CFM::relayingRFWaitStateDuplex(bool validSignal) { if (validSignal) { io.setDecode(true); io.setADCDetection(true); - DEBUG1("State to RELAYING"); - m_state = FS_RELAYING; + DEBUG1("State to RELAYING_RF"); + m_state = FS_RELAYING_RF; m_ackDelayTimer.stop(); } else { if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { @@ -388,12 +663,12 @@ void CFM::relayingWaitState(bool validSignal) if (m_ackMinTimer.isRunning()) { if (m_ackMinTimer.hasExpired()) { - DEBUG1("Send ack"); + DEBUG1("Send RF ack"); m_rfAck.start(); m_ackMinTimer.stop(); } } else { - DEBUG1("Send ack"); + DEBUG1("Send RF ack"); m_rfAck.start(); m_ackMinTimer.stop(); } @@ -410,44 +685,62 @@ void CFM::relayingWaitState(bool validSignal) } } -void CFM::hangState(bool validSignal) +void CFM::relayingRFWaitStateSimplex(bool validSignal) { if (validSignal) { io.setDecode(true); io.setADCDetection(true); - DEBUG1("State to RELAYING"); - m_state = FS_RELAYING; - DEBUG1("Stop ack"); - m_rfAck.stop(); - beginRelaying(); + DEBUG1("State to RELAYING_RF"); + m_state = FS_RELAYING_RF; + m_ackDelayTimer.stop(); } else { - if (m_hangTimer.isRunning() && m_hangTimer.hasExpired()) { + if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { DEBUG1("State to LISTENING"); m_state = FS_LISTENING; - m_hangTimer.stop(); - - if (m_callsignAtEnd) - sendCallsign(); - - m_callsignTimer.stop(); + m_ackDelayTimer.stop(); + m_timeoutTimer.stop(); } } - - if (m_callsignTimer.isRunning() && m_callsignTimer.hasExpired()) { - sendCallsign(); - m_callsignTimer.start(); - } } -void CFM::timeoutState(bool validSignal) +void CFM::kerchunkExtStateDuplex(bool validSignal) { - if (!validSignal) { - io.setDecode(false); - io.setADCDetection(false); + if (validSignal) { + if (m_kerchunkTimer.hasExpired()) { + DEBUG1("State to RELAYING_EXT"); + m_state = FS_RELAYING_EXT; + m_kerchunkTimer.stop(); + if (m_callsignAtStart && m_callsignAtLatch) { + sendCallsign(); + m_callsignTimer.start(); + } + } + } else { + DEBUG1("State to LISTENING"); + m_state = FS_LISTENING; + m_kerchunkTimer.stop(); + m_timeoutTimer.stop(); + m_ackMinTimer.stop(); + m_callsignTimer.stop(); + m_statusTimer.stop(); + m_needReverse = true; + } +} - DEBUG1("State to TIMEOUT_WAIT"); - m_state = FS_TIMEOUT_WAIT; +void CFM::relayingExtStateDuplex(bool validSignal) +{ + if (validSignal) { + if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { + DEBUG1("State to TIMEOUT_EXT"); + m_state = FS_TIMEOUT_EXT; + m_ackMinTimer.stop(); + m_timeoutTimer.stop(); + m_timeoutTone.start(); + } + } else { + DEBUG1("State to RELAYING_WAIT_EXT"); + m_state = FS_RELAYING_WAIT_EXT; m_ackDelayTimer.start(); } @@ -457,21 +750,163 @@ void CFM::timeoutState(bool validSignal) } } -void CFM::timeoutWaitState(bool validSignal) +void CFM::relayingExtStateSimplex(bool validSignal) +{ + if (validSignal) { + if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { + DEBUG1("State to TIMEOUT_EXT"); + m_state = FS_TIMEOUT_EXT; + + m_timeoutTimer.stop(); + } + } else { + DEBUG1("State to RELAYING_WAIT_EXT"); + m_state = FS_RELAYING_WAIT_EXT; + m_ackDelayTimer.start(); + } +} + +void CFM::relayingExtWaitStateDuplex(bool validSignal) +{ + if (validSignal) { + DEBUG1("State to RELAYING_EXT"); + m_state = FS_RELAYING_EXT; + 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 Ext ack"); + m_extAck.start(); + m_ackMinTimer.stop(); + } + } else { + DEBUG1("Send Ext ack"); + m_extAck.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::relayingExtWaitStateSimplex(bool validSignal) +{ + if (validSignal) { + DEBUG1("State to RELAYING_EXT"); + m_state = FS_RELAYING_EXT; + m_ackDelayTimer.stop(); + } else { + if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { + DEBUG1("State to LISTENING"); + m_state = FS_LISTENING; + m_ackDelayTimer.stop(); + m_timeoutTimer.stop(); + m_needReverse = true; + } + } +} + +void CFM::hangStateDuplex(bool validRFSignal, bool validExtSignal) +{ + if (validRFSignal) { + io.setDecode(true); + io.setADCDetection(true); + + DEBUG1("State to RELAYING_RF"); + m_state = FS_RELAYING_RF; + DEBUG1("Stop ack"); + m_rfAck.stop(); + m_extAck.stop(); + beginRelaying(); + } else if (validExtSignal) { + DEBUG1("State to RELAYING_EXT"); + m_state = FS_RELAYING_EXT; + DEBUG1("Stop ack"); + m_rfAck.stop(); + m_extAck.stop(); + beginRelaying(); + } else { + if (m_hangTimer.isRunning() && m_hangTimer.hasExpired()) { + DEBUG1("State to LISTENING"); + m_state = FS_LISTENING; + m_hangTimer.stop(); + m_statusTimer.stop(); + + if (m_callsignAtEnd) + sendCallsign(); + + m_callsignTimer.stop(); + m_needReverse = true; + } + } + + if (m_callsignTimer.isRunning() && m_callsignTimer.hasExpired()) { + sendCallsign(); + m_callsignTimer.start(); + } +} + +void CFM::timeoutRFStateDuplex(bool validSignal) +{ + if (!validSignal) { + io.setDecode(false); + io.setADCDetection(false); + + DEBUG1("State to TIMEOUT_WAIT_RF"); + m_state = FS_TIMEOUT_WAIT_RF; + + if (m_callsignAtEnd) + sendCallsign(); + + m_ackDelayTimer.start(); + } + + if (m_callsignTimer.isRunning() && m_callsignTimer.hasExpired()) { + sendCallsign(); + m_callsignTimer.start(); + } +} + +void CFM::timeoutRFStateSimplex(bool validSignal) +{ + if (!validSignal) { + io.setDecode(false); + io.setADCDetection(false); + + DEBUG1("State to TIMEOUT_WAIT_RF"); + m_state = FS_TIMEOUT_WAIT_RF; + + m_ackDelayTimer.start(); + } +} + +void CFM::timeoutRFWaitStateDuplex(bool validSignal) { if (validSignal) { io.setDecode(true); io.setADCDetection(true); - DEBUG1("State to TIMEOUT"); - m_state = FS_TIMEOUT; + DEBUG1("State to TIMEOUT_RF"); + m_state = FS_TIMEOUT_RF; 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"); + DEBUG1("Send RF ack"); m_rfAck.start(); m_ackDelayTimer.stop(); m_ackMinTimer.stop(); @@ -486,6 +921,91 @@ void CFM::timeoutWaitState(bool validSignal) } } +void CFM::timeoutRFWaitStateSimplex(bool validSignal) +{ + if (validSignal) { + io.setDecode(true); + io.setADCDetection(true); + + DEBUG1("State to TIMEOUT_RF"); + m_state = FS_TIMEOUT_RF; + m_ackDelayTimer.stop(); + } else { + if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { + DEBUG1("State to LISTENING"); + m_state = FS_LISTENING; + m_ackDelayTimer.stop(); + m_timeoutTimer.stop(); + } + } +} + +void CFM::timeoutExtStateDuplex(bool validSignal) +{ + if (!validSignal) { + DEBUG1("State to TIMEOUT_WAIT_EXT"); + m_state = FS_TIMEOUT_WAIT_EXT; + m_ackDelayTimer.start(); + } + + if (m_callsignTimer.isRunning() && m_callsignTimer.hasExpired()) { + sendCallsign(); + m_callsignTimer.start(); + } +} + +void CFM::timeoutExtStateSimplex(bool validSignal) +{ + if (!validSignal) { + DEBUG1("State to TIMEOUT_WAIT_EXT"); + m_state = FS_TIMEOUT_WAIT_EXT; + m_ackDelayTimer.start(); + } +} + +void CFM::timeoutExtWaitStateDuplex(bool validSignal) +{ + if (validSignal) { + DEBUG1("State to TIMEOUT_EXT"); + m_state = FS_TIMEOUT_EXT; + m_ackDelayTimer.stop(); + } else { + if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { + DEBUG1("State to HANG"); + m_state = FS_HANG; + m_timeoutTone.stop(); + DEBUG1("Send Ext ack"); + m_extAck.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::timeoutExtWaitStateSimplex(bool validSignal) +{ + if (validSignal) { + DEBUG1("State to TIMEOUT_EXT"); + m_state = FS_TIMEOUT_EXT; + m_ackDelayTimer.stop(); + } else { + if (m_ackDelayTimer.isRunning() && m_ackDelayTimer.hasExpired()) { + DEBUG1("State to LISTENING"); + m_state = FS_LISTENING; + m_ackDelayTimer.stop(); + m_timeoutTimer.stop(); + m_needReverse = true; + } + } +} + void CFM::sendCallsign() { if (m_holdoffTimer.isRunning()) { @@ -506,12 +1026,25 @@ void CFM::beginRelaying() m_ackMinTimer.start(); } +uint8_t CFM::getSpace() const +{ + // The amount of free space for receiving external audio, in frames. + return m_inputExtRB.getSpace() / FM_SERIAL_BLOCK_SIZE_BYTES; +} + +uint8_t CFM::writeData(const uint8_t* data, uint8_t length) +{ + //todo check if length is a multiple of 3 + m_inputExtRB.addData(data, length); + return 0U; +} + void CFM::insertDelay(uint16_t ms) { uint32_t nSamples = ms * 24U; for (uint32_t i = 0U; i < nSamples; i++) - m_inputRB.put(0); + m_inputRFRB.put(0); } void CFM::insertSilence(uint16_t ms) @@ -519,5 +1052,5 @@ void CFM::insertSilence(uint16_t ms) uint32_t nSamples = ms * 24U; for (uint32_t i = 0U; i < nSamples; i++) - m_outputRB.put(0); + m_outputRFRB.put(0); } diff --git a/FM.h b/FM.h index 6843f86..2ead4a3 100644 --- a/FM.h +++ b/FM.h @@ -27,28 +27,33 @@ #include "FMTimeout.h" #include "FMKeyer.h" #include "FMTimer.h" -#include "FMRB.h" +#include "RingBuffer.h" #include "FMDirectForm1.h" -#include "FMDownsampler.h" +#include "FMDownSampler.h" +#include "FMUpSampler.h" +#include "FMNoiseSquelch.h" enum FM_STATE { FS_LISTENING, - FS_KERCHUNK, - FS_RELAYING, - FS_RELAYING_WAIT, - FS_TIMEOUT, - FS_TIMEOUT_WAIT, + FS_KERCHUNK_RF, + FS_RELAYING_RF, + FS_RELAYING_WAIT_RF, + FS_TIMEOUT_RF, + FS_TIMEOUT_WAIT_RF, + FS_KERCHUNK_EXT, + FS_RELAYING_EXT, + FS_RELAYING_WAIT_EXT, + FS_TIMEOUT_EXT, + FS_TIMEOUT_WAIT_EXT, FS_HANG }; - - class CFM { public: CFM(); - void samples(bool cos, const q15_t* samples, uint8_t length); + void samples(bool cos, q15_t* samples, uint8_t length); void process(); @@ -56,13 +61,20 @@ public: uint8_t setCallsign(const char* callsign, uint8_t speed, uint16_t frequency, uint8_t time, uint8_t holdoff, uint8_t highLevel, uint8_t lowLevel, bool callsignAtStart, bool callsignAtEnd, bool callsignAtLatch); uint8_t setAck(const char* rfAck, uint8_t speed, uint16_t frequency, uint8_t minTime, uint16_t delay, uint8_t level); - uint8_t setMisc(uint16_t timeout, uint8_t timeoutLevel, uint8_t ctcssFrequency, uint8_t ctcssHighThreshold, uint8_t ctcssLowThreshold, uint8_t ctcssLevel, uint8_t kerchunkTime, uint8_t hangTime, uint8_t accessMode, bool cosInvert, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel); + uint8_t setMisc(uint16_t timeout, uint8_t timeoutLevel, uint8_t ctcssFrequency, uint8_t ctcssHighThreshold, uint8_t ctcssLowThreshold, uint8_t ctcssLevel, uint8_t kerchunkTime, uint8_t hangTime, uint8_t accessMode, bool cosInvert, bool noiseSquelch, uint8_t squelchHighThreshold, uint8_t squelchLowThreshold, uint8_t rfAudioBoost, uint8_t maxDev, uint8_t rxLevel); + uint8_t setExt(const char* ack, uint8_t audioBoost, uint8_t speed, uint16_t frequency, uint8_t level); + + uint8_t getSpace() const; + + uint8_t writeData(const uint8_t* data, uint8_t length); private: CFMKeyer m_callsign; CFMKeyer m_rfAck; + CFMKeyer m_extAck; CFMCTCSSRX m_ctcssRX; CFMCTCSSTX m_ctcssTX; + CFMNoiseSquelch m_squelch; CFMTimeout m_timeoutTone; FM_STATE m_state; bool m_callsignAtStart; @@ -75,27 +87,51 @@ private: CFMTimer m_ackMinTimer; CFMTimer m_ackDelayTimer; CFMTimer m_hangTimer; + CFMTimer m_statusTimer; CFMTimer m_reverseTimer; + bool m_needReverse; CFMDirectFormI m_filterStage1; CFMDirectFormI m_filterStage2; CFMDirectFormI m_filterStage3; CFMBlanking m_blanking; uint8_t m_accessMode; bool m_cosInvert; + bool m_noiseSquelch; q15_t m_rfAudioBoost; - CFMDownsampler m_downsampler; + q15_t m_extAudioBoost; + CFMDownSampler m_downSampler; + bool m_extEnabled; q15_t m_rxLevel; - CFMRB m_inputRB; - CFMRB m_outputRB; + CRingBuffer m_inputRFRB; + CRingBuffer m_outputRFRB; + CFMUpSampler m_inputExtRB; - void stateMachine(bool validSignal); - 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 stateMachine(bool validRFSignal, bool validExtSignal); + + void duplexStateMachine(bool validRFSignal, bool validExtSignal); + void listeningStateDuplex(bool validRFSignal, bool validExtSignal); + void kerchunkRFStateDuplex(bool validSignal); + void relayingRFStateDuplex(bool validSignal); + void relayingRFWaitStateDuplex(bool validSignal); + void timeoutRFStateDuplex(bool validSignal); + void timeoutRFWaitStateDuplex(bool validSignal); + void kerchunkExtStateDuplex(bool validSignal); + void relayingExtStateDuplex(bool validSignal); + void relayingExtWaitStateDuplex(bool validSignal); + void timeoutExtStateDuplex(bool validSignal); + void timeoutExtWaitStateDuplex(bool validSignal); + void hangStateDuplex(bool validRFSignal, bool validExtSignal); + + void simplexStateMachine(bool validRFSignal, bool validExtSignal); + void listeningStateSimplex(bool validRFSignal, bool validExtSignal); + void relayingRFStateSimplex(bool validSignal); + void relayingRFWaitStateSimplex(bool validSignal); + void timeoutRFStateSimplex(bool validSignal); + void timeoutRFWaitStateSimplex(bool validSignal); + void relayingExtStateSimplex(bool validSignal); + void relayingExtWaitStateSimplex(bool validSignal); + void timeoutExtStateSimplex(bool validSignal); + void timeoutExtWaitStateSimplex(bool validSignal); void clock(uint8_t length); diff --git a/FMCTCSSRX.cpp b/FMCTCSSRX.cpp index e88ff98..b2ecb43 100644 --- a/FMCTCSSRX.cpp +++ b/FMCTCSSRX.cpp @@ -87,7 +87,7 @@ m_lowThreshold(0), m_count(0U), m_q0(0), m_q1(0), -m_result(CTS_NONE) +m_state(false) { } @@ -111,13 +111,11 @@ uint8_t CFMCTCSSRX::setParams(uint8_t frequency, uint8_t highThreshold, uint8_t return 0U; } -uint8_t CFMCTCSSRX::process(q15_t sample) +bool CFMCTCSSRX::process(q15_t sample) { //get more dynamic into the decoder by multiplying the sample by 1.5 q31_t sample31 = q31_t(sample) + (q31_t(sample) >> 1); - m_result &= ~CTS_READY; - q31_t q2 = m_q1; m_q1 = m_q0; @@ -149,33 +147,29 @@ uint8_t CFMCTCSSRX::process(q15_t sample) // 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); + bool previousState = m_state; q31_t threshold = m_highThreshold; - if (previousCTCSSValid) + if (previousState) threshold = m_lowThreshold; - m_result |= CTS_READY; - if (value >= threshold) - m_result |= CTS_VALID; - else - m_result &= ~CTS_VALID; + m_state = value >= threshold; - if (previousCTCSSValid != CTCSS_VALID(m_result)) - DEBUG4("CTCSS Value / Threshold / Valid", value, threshold, CTCSS_VALID(m_result)); + if (previousState != m_state) + DEBUG4("CTCSS Value / Threshold / Valid", value, threshold, m_state); m_count = 0U; m_q0 = 0; m_q1 = 0; } - return m_result; + return m_state; } void CFMCTCSSRX::reset() { m_q0 = 0; m_q1 = 0; - m_result = CTS_NONE; - m_count = 0U; + m_state = false; + m_count = 0U; } diff --git a/FMCTCSSRX.h b/FMCTCSSRX.h index eed82de..0980c4c 100644 --- a/FMCTCSSRX.h +++ b/FMCTCSSRX.h @@ -21,22 +21,13 @@ #include "Config.h" -const uint8_t CTS_NONE = 0U; -const uint8_t CTS_READY = 1U; -const uint8_t CTS_VALID = 2U; - -#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 highThreshold, uint8_t lowThreshold); - uint8_t process(q15_t sample); + bool process(q15_t sample); void reset(); @@ -47,7 +38,7 @@ private: uint16_t m_count; q31_t m_q0; q31_t m_q1; - uint8_t m_result; + bool m_state; }; #endif diff --git a/FMDirectForm1.h b/FMDirectForm1.h index 450274d..b3dccb7 100644 --- a/FMDirectForm1.h +++ b/FMDirectForm1.h @@ -29,7 +29,7 @@ THE SOFTWARE. // based on https://raw.githubusercontent.com/berndporr/iir_fixed_point/master/DirectFormI.h -#include "Globals.h" +#include #ifndef DIRECTFORMI_H_ #define DIRECTFORMI_H_ diff --git a/FMDownSampler.cpp b/FMDownSampler.cpp new file mode 100644 index 0000000..8ed95bc --- /dev/null +++ b/FMDownSampler.cpp @@ -0,0 +1,74 @@ +/* + * 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), +m_samplePack(0U), +m_samplePackPointer(NULL), +m_sampleIndex(0U) +{ + m_samplePackPointer = (uint8_t*)&m_samplePack; +} + +void CFMDownSampler::addSample(q15_t sample) +{ + uint32_t usample = uint32_t(int32_t(sample) + 2048); + //only take one of three samples + switch(m_sampleIndex){ + case 0: + m_samplePack = usample << 12; + break; + case 3:{ + m_samplePack |= usample; + + //we did not use MSB; skip it + TSamplePairPack pair{m_samplePackPointer[0U], m_samplePackPointer[1U], m_samplePackPointer[2U]}; + + m_ringBuffer.put(pair); + + m_samplePack = 0U;//reset the sample pack + } + break; + default: + //Just skip this sample + break; + } + + m_sampleIndex++; + if (m_sampleIndex >= 6U)//did we pack two samples ? + m_sampleIndex = 0U; +} + +bool CFMDownSampler::getPackedData(TSamplePairPack& data) +{ + return m_ringBuffer.get(data); +} + +uint16_t CFMDownSampler::getData() +{ + return m_ringBuffer.getData(); +} + +void CFMDownSampler::reset() +{ + m_sampleIndex = 0U; +} diff --git a/FMDownsampler.h b/FMDownSampler.h similarity index 71% rename from FMDownsampler.h rename to FMDownSampler.h index 48b0d44..9f26acf 100644 --- a/FMDownsampler.h +++ b/FMDownSampler.h @@ -21,23 +21,27 @@ #define FMDOWNSAMPLER_H #include "Config.h" -#include "FMDownsampleRB.h" +#include "RingBuffer.h" +#include "FMSamplePairPack.h" -class CFMDownsampler { +class CFMDownSampler { public: - CFMDownsampler(uint16_t length); + 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(); }; + + bool getPackedData(TSamplePairPack& data); + + uint16_t getData(); + + void reset(); private: - CFMDownsampleRB m_ringBuffer; - union { - int32_t m_samplePack; - int8_t m_samplePackBytes[4]; - }; - uint8_t m_packIndex; - uint8_t m_downSampleIndex; + CRingBuffer m_ringBuffer; + uint32_t m_samplePack; + uint8_t* m_samplePackPointer; + uint8_t m_sampleIndex; }; #endif + diff --git a/FMDownsampler.cpp b/FMDownsampler.cpp deleted file mode 100644 index 51e9580..0000000 --- a/FMDownsampler.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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; -} \ No newline at end of file diff --git a/FMNoiseSquelch.cpp b/FMNoiseSquelch.cpp new file mode 100644 index 0000000..6becf99 --- /dev/null +++ b/FMNoiseSquelch.cpp @@ -0,0 +1,127 @@ +/* + * 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 "FMNoiseSquelch.h" + +// 4500Hz centre frequency +const q31_t COEFF_DIV_TWO = 821806413; + +// 400Hz bandwidth +const uint16_t N = 24000U / 400U; + +CFMNoiseSquelch::CFMNoiseSquelch() : +m_highThreshold(0), +m_lowThreshold(0), +m_count(0U), +m_q0(0), +m_q1(0), +m_state(false), +m_validCount(0U) +{ + +} + +void CFMNoiseSquelch::setParams(uint8_t highThreshold, uint8_t lowThreshold) +{ + m_highThreshold = q31_t(highThreshold); + m_lowThreshold = q31_t(lowThreshold); +} + +bool CFMNoiseSquelch::process(q15_t sample) +{ + //get more dynamic into the decoder by multiplying the sample by 64 + q31_t sample31 = q31_t(sample) << 6; //+ (q31_t(sample) >> 1); + + q31_t q2 = m_q1; + m_q1 = m_q0; + + // Q31 multiplication, t3 = m_coeffDivTwo * 2 * m_q1 + q63_t t1 = COEFF_DIV_TWO * 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 * COEFF_DIV_TWO; + 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 previousState = m_state; + + q31_t threshold = m_highThreshold; + if (previousState) + threshold = m_lowThreshold; + + if (!m_state) { + if (value < threshold) + m_validCount++; + else + m_validCount = 0U; + } + + if (m_state) { + if (value >= threshold) + m_invalidCount++; + else + m_invalidCount = 0U; + } + + m_state = m_validCount >= 10U && m_invalidCount < 10U; + + if(previousState && !m_state) + m_invalidCount = 0U; + + if (previousState != m_state) { + DEBUG4("Noise Squelch Value / Threshold / Valid", value, threshold, m_state); + DEBUG3("Valid Count / Invalid Count", m_validCount, m_invalidCount); + } + + m_count = 0U; + m_q0 = 0; + m_q1 = 0; + } + + return m_state; +} + +void CFMNoiseSquelch::reset() +{ + m_q0 = 0; + m_q1 = 0; + m_state = false; + m_count = 0U; +} diff --git a/FMNoiseSquelch.h b/FMNoiseSquelch.h new file mode 100644 index 0000000..c4f5a62 --- /dev/null +++ b/FMNoiseSquelch.h @@ -0,0 +1,45 @@ +/* + * 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(FMNOISESQUELCH_H) +#define FMNOISESQUELCH_H + +#include "Config.h" + +class CFMNoiseSquelch { +public: + CFMNoiseSquelch(); + + void setParams(uint8_t highThreshold, uint8_t lowThreshold); + + bool process(q15_t sample); + + void reset(); + +private: + q31_t m_highThreshold; + q31_t m_lowThreshold; + uint16_t m_count; + q31_t m_q0; + q31_t m_q1; + bool m_state; + uint8_t m_validCount; + uint8_t m_invalidCount; +}; + +#endif diff --git a/FMRB.cpp b/FMRB.cpp deleted file mode 100644 index 0d8c6ef..0000000 --- a/FMRB.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* -TX fifo control - Copyright (C) KI6ZUM 2015 -Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this library; if not, write to the -Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -Boston, MA 02110-1301, USA. -*/ - -#include "FMRB.h" - -CFMRB::CFMRB(uint16_t length) : -m_length(length), -m_head(0U), -m_tail(0U), -m_full(false), -m_overflow(false) -{ - m_samples = new q15_t[length]; -} - -uint16_t CFMRB::getSpace() const -{ - uint16_t n = 0U; - - if (m_tail == m_head) - n = m_full ? 0U : m_length; - else if (m_tail < m_head) - n = m_length - m_head + m_tail; - else - n = m_tail - m_head; - - if (n > m_length) - n = 0U; - - return n; -} - -uint16_t CFMRB::getData() const -{ - if (m_tail == m_head) - return m_full ? m_length : 0U; - else if (m_tail < m_head) - return m_head - m_tail; - else - return m_length - m_tail + m_head; -} - -bool CFMRB::put(q15_t sample) -{ - if (m_full) { - m_overflow = true; - return false; - } - - m_samples[m_head] = sample; - - m_head++; - if (m_head >= m_length) - m_head = 0U; - - if (m_head == m_tail) - m_full = true; - - return true; -} - -bool CFMRB::get(q15_t& sample) -{ - if (m_head == m_tail && !m_full) - return false; - - sample = m_samples[m_tail]; - - m_full = false; - - m_tail++; - if (m_tail >= m_length) - m_tail = 0U; - - return true; -} - -bool CFMRB::hasOverflowed() -{ - bool overflow = m_overflow; - - m_overflow = false; - - return overflow; -} - -void CFMRB::reset() -{ - m_head = 0U; - m_tail = 0U; - m_full = false; - m_overflow = false; -} diff --git a/FMRB.h b/FMRB.h deleted file mode 100644 index 7cf6cf4..0000000 --- a/FMRB.h +++ /dev/null @@ -1,72 +0,0 @@ -/* -Serial fifo control - Copyright (C) KI6ZUM 2015 -Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this library; if not, write to the -Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -Boston, MA 02110-1301, USA. -*/ - -#if !defined(FMRB_H) -#define FMRB_H - -#if defined(STM32F4XX) -#include "stm32f4xx.h" -#elif defined(STM32F7XX) -#include "stm32f7xx.h" -#elif defined(STM32F105xC) -#include "stm32f1xx.h" -#include -#else -#include -#endif - -#if defined(__SAM3X8E__) || defined(STM32F105xC) -#define ARM_MATH_CM3 -#elif defined(STM32F7XX) -#define ARM_MATH_CM7 -#elif defined(STM32F4XX) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__) -#define ARM_MATH_CM4 -#else -#error "Unknown processor type" -#endif - -#include - -class CFMRB { -public: - CFMRB(uint16_t length); - - uint16_t getSpace() const; - - uint16_t getData() const; - - bool put(q15_t sample); - - bool get(q15_t& sample); - - bool hasOverflowed(); - - void reset(); - -private: - uint16_t m_length; - volatile q15_t* m_samples; - volatile uint16_t m_head; - volatile uint16_t m_tail; - volatile bool m_full; - bool m_overflow; -}; - -#endif diff --git a/FMSamplePairPack.h b/FMSamplePairPack.h new file mode 100644 index 0000000..1301173 --- /dev/null +++ b/FMSamplePairPack.h @@ -0,0 +1,29 @@ +/* + * 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(TSAMPLEPACK_H) +#define TSAMPLEPACK_H + +struct TSamplePairPack { + uint8_t byte0; + uint8_t byte1; + uint8_t byte2; +}; + +#endif \ No newline at end of file diff --git a/FMUpSampler.cpp b/FMUpSampler.cpp new file mode 100644 index 0000000..80cba37 --- /dev/null +++ b/FMUpSampler.cpp @@ -0,0 +1,97 @@ +/* + * 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 "FMUpSampler.h" + +const uint32_t FM_UPSAMPLE_MASK = 0x00000FFFU; + +CFMUpSampler::CFMUpSampler() : +m_upSampleIndex(0), +m_pack(0U), +m_packPointer(NULL), +m_samples(3600U), //300ms of 12 bit 8kHz audio +m_running(false) +{ + m_packPointer = (uint8_t*)&m_pack; +} + +void CFMUpSampler::reset() +{ + m_upSampleIndex = 0U; + m_pack = 0U; + m_samples.reset(); + m_running = false; +} + +void CFMUpSampler::addData(const uint8_t* data, uint16_t length) +{ + TSamplePairPack* packPointer = (TSamplePairPack*)data; + TSamplePairPack* packPointerEnd = packPointer + (length / 3U); + while(packPointer != packPointerEnd) { + m_samples.put(*packPointer); + packPointer++; + } + if(!m_running) + m_running = m_samples.getData() > 300U;//75ms of audio +} + +bool CFMUpSampler::getSample(q15_t& sample) +{ + if(!m_running) + return false; + + switch (m_upSampleIndex) + { + case 0: { + TSamplePairPack pairPack; + if(!m_samples.get(pairPack)) { + m_running = false; + return false; + } + + m_pack = 0U; + m_packPointer = (uint8_t*)&m_pack; + + m_packPointer[0U] = pairPack.byte0; + m_packPointer[1U] = pairPack.byte1; + m_packPointer[2U] = pairPack.byte2; + + sample = q15_t(m_pack >> 12) - 2048; + break; + } + case 3: + sample = q15_t(m_pack & FM_UPSAMPLE_MASK) - 2048; + break; + default: + sample = 0; + break; + } + + m_upSampleIndex++; + if(m_upSampleIndex >= 6U) + m_upSampleIndex = 0U; + + return true; +} + +uint16_t CFMUpSampler::getSpace() const +{ + //return available space in bytes + return m_samples.getSpace() * sizeof(TSamplePairPack); +} \ No newline at end of file diff --git a/FMUpSampler.h b/FMUpSampler.h new file mode 100644 index 0000000..b76982f --- /dev/null +++ b/FMUpSampler.h @@ -0,0 +1,48 @@ +/* + * 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(FMUPSAMPLER_H) +#define FMUPSAMPLER_H + +#include "Config.h" +#include "RingBuffer.h" +#include "FMSamplePairPack.h" + +class CFMUpSampler { +public: + CFMUpSampler(); + + void reset(); + + void addData(const uint8_t* data, uint16_t length); + + bool getSample(q15_t& sample); + + uint16_t getSpace() const; + +private: + uint8_t m_upSampleIndex; + uint32_t m_pack; + uint8_t * m_packPointer; + CRingBuffer m_samples; + bool m_running; +}; + +#endif \ No newline at end of file diff --git a/Globals.h b/Globals.h index 760ca32..3b003e6 100644 --- a/Globals.h +++ b/Globals.h @@ -28,6 +28,7 @@ #include "STM32Utils.h" #else #include +#undef PI //Undefine PI to get rid of annoying warning as it is also defined in arm_math.h. #endif #if defined(__SAM3X8E__) || defined(STM32F105xC) @@ -52,6 +53,7 @@ enum MMDVM_STATE { STATE_POCSAG = 6, STATE_M17 = 7, STATE_FM = 10, + STATE_AX25 = 11, // Dummy states start at 90 STATE_NXDNCAL1K = 91, @@ -99,6 +101,8 @@ enum MMDVM_STATE { #include "CalPOCSAG.h" #include "CalRSSI.h" #include "CWIdTX.h" +#include "AX25RX.h" +#include "AX25TX.h" #include "Debug.h" #include "IO.h" #include "FM.h" @@ -128,6 +132,7 @@ extern bool m_nxdnEnable; extern bool m_pocsagEnable; extern bool m_m17Enable; extern bool m_fmEnable; +extern bool m_ax25Enable; extern bool m_duplex; @@ -161,7 +166,10 @@ extern CPOCSAGTX pocsagTX; extern CM17RX m17RX; extern CM17TX m17TX; -extern CFM fm; +extern CFM fm; + +extern CAX25RX ax25RX; +extern CAX25TX ax25TX; extern CCalDStarRX calDStarRX; extern CCalDStarTX calDStarTX; @@ -175,3 +183,4 @@ extern CCalRSSI calRSSI; extern CCWIdTX cwIdTX; #endif + diff --git a/IO.cpp b/IO.cpp index f98e991..1ca14ca 100644 --- a/IO.cpp +++ b/IO.cpp @@ -90,6 +90,7 @@ m_nxdnTXLevel(128 * 128), m_m17TXLevel(128 * 128), m_pocsagTXLevel(128 * 128), m_fmTXLevel(128 * 128), +m_ax25TXLevel(128 * 128), m_rxDCOffset(DC_OFFSET), m_txDCOffset(DC_OFFSET), m_useCOSAsLockout(false), @@ -306,6 +307,7 @@ void CIO::process() if (m_txBuffer.getData() == 0U && m_tx) { m_tx = false; setPTTInt(m_pttInvert ? true : false); + DEBUG1("TX OFF"); } if (m_rxBuffer.getData() >= RX_BLOCK_SIZE) { @@ -314,15 +316,16 @@ void CIO::process() uint16_t rssi[RX_BLOCK_SIZE]; for (uint16_t i = 0U; i < RX_BLOCK_SIZE; i++) { - uint16_t sample; - m_rxBuffer.get(sample, control[i]); + TSample sample; + m_rxBuffer.get(sample); + control[i] = sample.control; m_rssiBuffer.get(rssi[i]); // Detect ADC overflow - if (m_detect && (sample == 0U || sample == 4095U)) + if (m_detect && (sample.sample == 0U || sample.sample == 4095U)) m_adcOverflow++; - q15_t res1 = q15_t(sample) - m_rxDCOffset; + q15_t res1 = q15_t(sample.sample) - m_rxDCOffset; q31_t res2 = res1 * m_rxLevel; samples[i] = q15_t(__SSAT((res2 >> 15), 16)); } @@ -419,6 +422,14 @@ void CIO::process() fm.samples(cos, dcSamples, RX_BLOCK_SIZE); #else fm.samples(cos, samples, RX_BLOCK_SIZE); +#endif + } + + if (m_ax25Enable) { +#if defined(USE_DCBLOCKER) + ax25RX.samples(dcSamples, RX_BLOCK_SIZE); +#else + ax25RX.samples(samples, RX_BLOCK_SIZE); #endif } } else if (m_modemState == STATE_DSTAR) { @@ -493,8 +504,12 @@ void CIO::process() bool cos = getCOSInt(); #if defined(USE_DCBLOCKER) fm.samples(cos, dcSamples, RX_BLOCK_SIZE); + if (m_ax25Enable) + ax25RX.samples(dcSamples, RX_BLOCK_SIZE); #else fm.samples(cos, samples, RX_BLOCK_SIZE); + if (m_ax25Enable) + ax25RX.samples(samples, RX_BLOCK_SIZE); #endif } else if (m_modemState == STATE_DSTARCAL) { q15_t GMSKVals[RX_BLOCK_SIZE]; @@ -519,6 +534,7 @@ void CIO::write(MMDVM_STATE mode, q15_t* samples, uint16_t length, const uint8_t if (!m_tx) { m_tx = true; setPTTInt(m_pttInvert ? false : true); + DEBUG1("TX ON"); } q15_t txLevel = 0; @@ -547,6 +563,9 @@ void CIO::write(MMDVM_STATE mode, q15_t* samples, uint16_t length, const uint8_t case STATE_FM: txLevel = m_fmTXLevel; break; + case STATE_AX25: + txLevel = m_ax25TXLevel; + break; default: txLevel = m_cwIdTXLevel; break; @@ -562,9 +581,9 @@ void CIO::write(MMDVM_STATE mode, q15_t* samples, uint16_t length, const uint8_t m_dacOverflow++; if (control == NULL) - m_txBuffer.put(res3, MARK_NONE); + m_txBuffer.put({res3, MARK_NONE}); else - m_txBuffer.put(res3, control[i]); + m_txBuffer.put({res3, control[i]}); } } @@ -618,7 +637,7 @@ void CIO::setMode(MMDVM_STATE state) m_modemState = state; } -void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel, uint8_t m17TXLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout) +void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel, uint8_t m17TXLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, uint8_t ax25TXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout) { m_pttInvert = pttInvert; @@ -632,6 +651,7 @@ void CIO::setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rx m_m17TXLevel = q15_t(m17TXLevel * 128); m_pocsagTXLevel = q15_t(pocsagTXLevel * 128); m_fmTXLevel = q15_t(fmTXLevel * 128); + m_ax25TXLevel = q15_t(ax25TXLevel * 128); m_rxDCOffset = DC_OFFSET + rxDCOffset; m_txDCOffset = DC_OFFSET + txDCOffset; diff --git a/IO.h b/IO.h index 842de1e..6a920e6 100644 --- a/IO.h +++ b/IO.h @@ -21,8 +21,12 @@ #include "Globals.h" -#include "SampleRB.h" -#include "RSSIRB.h" +#include "RingBuffer.h" + +struct TSample { + volatile uint16_t sample; + volatile uint8_t control; +}; class CIO { public: @@ -42,7 +46,7 @@ public: void interrupt(); - void setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel, uint8_t m17TXLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout); + void setParameters(bool rxInvert, bool txInvert, bool pttInvert, uint8_t rxLevel, uint8_t cwIdTXLevel, uint8_t dstarTXLevel, uint8_t dmrTXLevel, uint8_t ysfTXLevel, uint8_t p25TXLevel, uint8_t nxdnTXLevel, uint8_t m17TXLevel, uint8_t pocsagTXLevel, uint8_t fmTXLevel, uint8_t ax25TXLevel, int16_t txDCOffset, int16_t rxDCOffset, bool useCOSAsLockout); void getOverflow(bool& adcOverflow, bool& dacOverflow); @@ -57,11 +61,11 @@ public: void selfTest(); private: - bool m_started; + bool m_started; - CSampleRB m_rxBuffer; - CSampleRB m_txBuffer; - CRSSIRB m_rssiBuffer; + CRingBuffer m_rxBuffer; + CRingBuffer m_txBuffer; + CRingBuffer m_rssiBuffer; arm_biquad_casd_df1_inst_q31 m_dcFilter; q31_t m_dcState[4]; @@ -90,6 +94,7 @@ private: q15_t m_m17TXLevel; q15_t m_pocsagTXLevel; q15_t m_fmTXLevel; + q15_t m_ax25TXLevel; uint16_t m_rxDCOffset; uint16_t m_txDCOffset; diff --git a/IODue.cpp b/IODue.cpp index 6dd01f3..8921d5b 100644 --- a/IODue.cpp +++ b/IODue.cpp @@ -188,14 +188,13 @@ void CIO::startInt() void CIO::interrupt() { if ((ADC->ADC_ISR & ADC_ISR_EOC_Chan) == ADC_ISR_EOC_Chan) { // Ensure there was an End-of-Conversion and we read the ISR reg - uint8_t control = MARK_NONE; - uint16_t sample = DC_OFFSET; + TSample sample = {DC_OFFSET, MARK_NONE}; - m_txBuffer.get(sample, control); - DACC->DACC_CDR = sample; + m_txBuffer.get(sample); + DACC->DACC_CDR = sample.sample; - sample = ADC->ADC_CDR[ADC_CDR_Chan]; - m_rxBuffer.put(sample, control); + sample.sample = ADC->ADC_CDR[ADC_CDR_Chan]; + m_rxBuffer.put(sample); #if defined(SEND_RSSI_DATA) m_rssiBuffer.put(ADC->ADC_CDR[RSSI_CDR_Chan]); diff --git a/IOSTM.cpp b/IOSTM.cpp index 2e8f635..ecf4f75 100644 --- a/IOSTM.cpp +++ b/IOSTM.cpp @@ -342,25 +342,24 @@ void CIO::startInt() void CIO::interrupt() { - uint8_t control = MARK_NONE; - uint16_t sample = DC_OFFSET; + TSample sample = {DC_OFFSET, MARK_NONE}; uint16_t rawRSSI = 0U; - m_txBuffer.get(sample, control); + m_txBuffer.get(sample); // Send the value to the DAC #if defined(STM32F4_NUCLEO) && defined(STM32F4_NUCLEO_ARDUINO_HEADER) - DAC_SetChannel2Data(DAC_Align_12b_R, sample); + DAC_SetChannel2Data(DAC_Align_12b_R, sample.sample); #else - DAC_SetChannel1Data(DAC_Align_12b_R, sample); + DAC_SetChannel1Data(DAC_Align_12b_R, sample.sample); #endif // Read value from ADC1 and ADC2 if ((ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET)) { // shouldn't be still in reset at this point so null the sample value? - sample = 0U; + sample.sample = 0U; } else { - sample = ADC_GetConversionValue(ADC1); + sample.sample = ADC_GetConversionValue(ADC1); #if defined(SEND_RSSI_DATA) rawRSSI = ADC_GetConversionValue(ADC2); #endif @@ -370,7 +369,7 @@ void CIO::interrupt() ADC_ClearFlag(ADC1, ADC_FLAG_EOC); ADC_SoftwareStartConv(ADC1); - m_rxBuffer.put(sample, control); + m_rxBuffer.put(sample); m_rssiBuffer.put(rawRSSI); m_watchdog++; diff --git a/IOSTM_CMSIS.cpp b/IOSTM_CMSIS.cpp index 8e4b39f..165c03d 100644 --- a/IOSTM_CMSIS.cpp +++ b/IOSTM_CMSIS.cpp @@ -370,8 +370,7 @@ void CIO::startInt() void CIO::interrupt() { - uint8_t control = MARK_NONE; - uint16_t sample = DC_OFFSET; + TSample sample = {DC_OFFSET, MARK_NONE}; #if defined(SEND_RSSI_DATA) uint16_t rawRSSI = 0U; #endif @@ -388,12 +387,12 @@ void CIO::interrupt() *rssi_adon = 1; #endif - m_txBuffer.get(sample, control); - DAC->DHR12R1 = sample; // Send the value to the DAC + m_txBuffer.get(sample); + DAC->DHR12R1 = sample.sample; // Send the value to the DAC // Read value from ADC1 and ADC2 - sample = ADC1->DR; // read conversion result; EOC is cleared by this read - m_rxBuffer.put(sample, control); + sample.sample = ADC1->DR; // read conversion result; EOC is cleared by this read + m_rxBuffer.put(sample); #if defined(SEND_RSSI_DATA) rawRSSI = ADC2->DR; m_rssiBuffer.put(rawRSSI); diff --git a/IOTeensy.cpp b/IOTeensy.cpp index 7e1fbaf..0ee4fad 100644 --- a/IOTeensy.cpp +++ b/IOTeensy.cpp @@ -164,15 +164,14 @@ void CIO::startInt() void CIO::interrupt() { - uint8_t control = MARK_NONE; - uint16_t sample = DC_OFFSET; + TSample sample = {DC_OFFSET, MARK_NONE}; - m_txBuffer.get(sample, control); - *(int16_t *)&(DAC0_DAT0L) = sample; + m_txBuffer.get(sample); + *(int16_t *)&(DAC0_DAT0L) = sample.sample; if ((ADC0_SC1A & ADC_SC1_COCO) == ADC_SC1_COCO) { - sample = ADC0_RA; - m_rxBuffer.put(sample, control); + sample.sample = ADC0_RA; + m_rxBuffer.put(sample); } #if defined(SEND_RSSI_DATA) diff --git a/M17TX.cpp b/M17TX.cpp index d478c9f..870d4ad 100644 --- a/M17TX.cpp +++ b/M17TX.cpp @@ -67,7 +67,8 @@ void CM17TX::process() m_poBuffer[m_poLen++] = M17_START_SYNC; } else { for (uint8_t i = 0U; i < M17_FRAME_LENGTH_BYTES; i++) { - uint8_t c = m_buffer.get(); + uint8_t c = 0U; + m_buffer.get(c); m_poBuffer[m_poLen++] = c; } } diff --git a/M17TX.h b/M17TX.h index 211d747..159b601 100644 --- a/M17TX.h +++ b/M17TX.h @@ -21,7 +21,7 @@ #include "Config.h" -#include "SerialRB.h" +#include "RingBuffer.h" class CM17TX { public: @@ -38,7 +38,7 @@ public: void setParams(uint8_t txHang); private: - CSerialRB m_buffer; + CRingBuffer m_buffer; arm_fir_interpolate_instance_q15 m_modFilter; q15_t m_modState[16U]; // blockSize + phaseLength - 1, 4 + 9 - 1 plus some spare uint8_t m_poBuffer[1200U]; diff --git a/MMDVM.cpp b/MMDVM.cpp index 5fadb0a..2674030 100644 --- a/MMDVM.cpp +++ b/MMDVM.cpp @@ -34,6 +34,7 @@ bool m_nxdnEnable = true; bool m_m17Enable = true; bool m_pocsagEnable = true; bool m_fmEnable = true; +bool m_ax25Enable = true; bool m_duplex = true; @@ -65,6 +66,8 @@ CM17TX m17TX; CPOCSAGTX pocsagTX; CFM fm; +CAX25RX ax25RX; +CAX25TX ax25TX; CCalDStarRX calDStarRX; CCalDStarTX calDStarTX; @@ -117,6 +120,9 @@ void loop() if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy())) pocsagTX.process(); + if (m_ax25Enable && (m_modemState == STATE_IDLE || m_modemState == STATE_FM)) + ax25TX.process(); + if (m_fmEnable && m_modemState == STATE_FM) fm.process(); diff --git a/MMDVM.ino b/MMDVM.ino index 02bfe62..e242737 100644 --- a/MMDVM.ino +++ b/MMDVM.ino @@ -31,6 +31,7 @@ bool m_nxdnEnable = true; bool m_m17Enable = true; bool m_pocsagEnable = true; bool m_fmEnable = true; +bool m_ax25Enable = true; bool m_duplex = true; @@ -62,6 +63,8 @@ CM17TX m17TX; CPOCSAGTX pocsagTX; CFM fm; +CAX25RX ax25RX; +CAX25TX ax25TX; CCalDStarRX calDStarRX; CCalDStarTX calDStarTX; @@ -114,6 +117,9 @@ void loop() if (m_pocsagEnable && (m_modemState == STATE_POCSAG || pocsagTX.busy())) pocsagTX.process(); + if (m_ax25Enable && (m_modemState == STATE_IDLE || m_modemState == STATE_FM)) + ax25TX.process(); + if (m_fmEnable && m_modemState == STATE_FM) fm.process(); diff --git a/Makefile.Arduino b/Makefile.Arduino index 6046810..0afa96a 100644 --- a/Makefile.Arduino +++ b/Makefile.Arduino @@ -1,5 +1,5 @@ #!/usr/bin/make -# makefile for the arduino due (works with arduino IDE 1.6.11) +# makefile for the arduino due (works with arduino IDE 1.6.12) # # The original file can be found at https://github.com/pauldreik/arduino-due-makefile # @@ -34,7 +34,7 @@ OBJCOPY:=$(ADIR)/packages/arduino/tools/arm-none-eabi-gcc/4.8.3-2014q1/bin/arm-n C:=$(CC) #SAM:=arduino/sam/ -SAM:=$(ADIR)/packages/arduino/hardware/sam/1.6.11 +SAM:=$(ADIR)/packages/arduino/hardware/sam/1.6.12 #CMSIS:=arduino/sam/system/CMSIS/ #LIBSAM:=arduino/sam/system/libsam TMPDIR:=$(PWD)/build diff --git a/NXDNTX.cpp b/NXDNTX.cpp index b6ef616..44c243e 100644 --- a/NXDNTX.cpp +++ b/NXDNTX.cpp @@ -81,7 +81,8 @@ void CNXDNTX::process() m_poBuffer[m_poLen++] = NXDN_PREAMBLE[2U]; } else { for (uint8_t i = 0U; i < NXDN_FRAME_LENGTH_BYTES; i++) { - uint8_t c = m_buffer.get(); + uint8_t c = 0U; + m_buffer.get(c); m_poBuffer[m_poLen++] = c; } } @@ -122,7 +123,7 @@ void CNXDNTX::process() } } -uint8_t CNXDNTX::writeData(const uint8_t* data, uint8_t length) +uint8_t CNXDNTX::writeData(const uint8_t* data, uint16_t length) { if (length != (NXDN_FRAME_LENGTH_BYTES + 1U)) return 4U; diff --git a/NXDNTX.h b/NXDNTX.h index bd65818..0efdf01 100644 --- a/NXDNTX.h +++ b/NXDNTX.h @@ -21,13 +21,13 @@ #include "Config.h" -#include "SerialRB.h" +#include "RingBuffer.h" class CNXDNTX { public: CNXDNTX(); - uint8_t writeData(const uint8_t* data, uint8_t length); + uint8_t writeData(const uint8_t* data, uint16_t length); void process(); @@ -38,7 +38,7 @@ public: void setParams(uint8_t txHang); private: - CSerialRB m_buffer; + CRingBuffer m_buffer; arm_fir_interpolate_instance_q15 m_modFilter; arm_fir_instance_q15 m_sincFilter; q15_t m_modState[16U]; // blockSize + phaseLength - 1, 4 + 9 - 1 plus some spare diff --git a/P25TX.cpp b/P25TX.cpp index 009a0f2..23aef3d 100644 --- a/P25TX.cpp +++ b/P25TX.cpp @@ -75,9 +75,11 @@ void CP25TX::process() for (uint16_t i = 0U; i < m_txDelay; i++) m_poBuffer[m_poLen++] = P25_START_SYNC; } else { - uint8_t length = m_buffer.get(); + uint8_t length; + m_buffer.get(length); for (uint8_t i = 0U; i < length; i++) { - uint8_t c = m_buffer.get(); + uint8_t c = 0U; + m_buffer.get(c); m_poBuffer[m_poLen++] = c; } } @@ -118,7 +120,7 @@ void CP25TX::process() } } -uint8_t CP25TX::writeData(const uint8_t* data, uint8_t length) +uint8_t CP25TX::writeData(const uint8_t* data, uint16_t length) { if (length < (P25_TERM_FRAME_LENGTH_BYTES + 1U)) return 4U; diff --git a/P25TX.h b/P25TX.h index a7cd27d..d6ac3cc 100644 --- a/P25TX.h +++ b/P25TX.h @@ -21,13 +21,13 @@ #include "Config.h" -#include "SerialRB.h" +#include "RingBuffer.h" class CP25TX { public: CP25TX(); - uint8_t writeData(const uint8_t* data, uint8_t length); + uint8_t writeData(const uint8_t* data, uint16_t length); void process(); @@ -38,7 +38,7 @@ public: void setParams(uint8_t txHang); private: - CSerialRB m_buffer; + CRingBuffer m_buffer; arm_fir_interpolate_instance_q15 m_modFilter; arm_fir_instance_q15 m_lpFilter; q15_t m_modState[16U]; // blockSize + phaseLength - 1, 4 + 9 - 1 plus some spare diff --git a/POCSAGTX.cpp b/POCSAGTX.cpp index d53f9b7..f8fb935 100644 --- a/POCSAGTX.cpp +++ b/POCSAGTX.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2018 by Jonathan Naylor G4KLX + * Copyright (C) 2009-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 @@ -61,7 +61,8 @@ void CPOCSAGTX::process() m_poBuffer[m_poLen++] = POCSAG_SYNC; } else { for (uint8_t i = 0U; i < POCSAG_FRAME_LENGTH_BYTES; i++) { - uint8_t c = m_buffer.get(); + uint8_t c = 0U; + m_buffer.get(c); m_poBuffer[m_poLen++] = c; } } @@ -95,7 +96,7 @@ bool CPOCSAGTX::busy() return false; } -uint8_t CPOCSAGTX::writeData(const uint8_t* data, uint8_t length) +uint8_t CPOCSAGTX::writeData(const uint8_t* data, uint16_t length) { if (length != POCSAG_FRAME_LENGTH_BYTES) return 4U; @@ -146,4 +147,3 @@ uint8_t CPOCSAGTX::getSpace() const { return m_buffer.getSpace() / POCSAG_FRAME_LENGTH_BYTES; } - diff --git a/POCSAGTX.h b/POCSAGTX.h index d640862..5619ed0 100644 --- a/POCSAGTX.h +++ b/POCSAGTX.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2019 by Jonathan Naylor G4KLX + * Copyright (C) 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 @@ -21,13 +21,13 @@ #include "Config.h" -#include "SerialRB.h" +#include "RingBuffer.h" class CPOCSAGTX { public: CPOCSAGTX(); - uint8_t writeData(const uint8_t* data, uint8_t length); + uint8_t writeData(const uint8_t* data, uint16_t length); void writeByte(uint8_t c); @@ -40,7 +40,7 @@ public: bool busy(); private: - CSerialRB m_buffer; + CRingBuffer m_buffer; arm_fir_instance_q15 m_modFilter; q15_t m_modState[170U]; // NoTaps + BlockSize - 1, 6 + 160 - 1 plus some spare uint8_t m_poBuffer[200U]; diff --git a/RSSIRB.cpp b/RSSIRB.cpp deleted file mode 100644 index 7607773..0000000 --- a/RSSIRB.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* -TX fifo control - Copyright (C) KI6ZUM 2015 -Copyright (C) 2015,2016 by Jonathan Naylor G4KLX - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this library; if not, write to the -Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -Boston, MA 02110-1301, USA. -*/ - -#include "RSSIRB.h" - -CRSSIRB::CRSSIRB(uint16_t length) : -m_length(length), -m_head(0U), -m_tail(0U), -m_full(false), -m_overflow(false) -{ - m_rssi = new uint16_t[length]; -} - -uint16_t CRSSIRB::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 CRSSIRB::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 CRSSIRB::put(uint16_t rssi) -{ - if (m_full) { - m_overflow = true; - return false; - } - - m_rssi[m_head] = rssi; - - m_head++; - if (m_head >= m_length) - m_head = 0U; - - if (m_head == m_tail) - m_full = true; - - return true; -} - -bool CRSSIRB::get(uint16_t& rssi) -{ - if (m_head == m_tail && !m_full) - return false; - - rssi = m_rssi[m_tail]; - - m_full = false; - - m_tail++; - if (m_tail >= m_length) - m_tail = 0U; - - return true; -} - -bool CRSSIRB::hasOverflowed() -{ - bool overflow = m_overflow; - - m_overflow = false; - - return overflow; -} diff --git a/RSSIRB.h b/RSSIRB.h deleted file mode 100644 index f3db3de..0000000 --- a/RSSIRB.h +++ /dev/null @@ -1,59 +0,0 @@ -/* -Serial fifo control - Copyright (C) KI6ZUM 2015 -Copyright (C) 2015,2016 by Jonathan Naylor G4KLX - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this library; if not, write to the -Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -Boston, MA 02110-1301, USA. -*/ - -#if !defined(RSSIRB_H) -#define RSSIRB_H - -#if defined(STM32F4XX) -#include "stm32f4xx.h" -#elif defined(STM32F7XX) -#include "stm32f7xx.h" -#elif defined(STM32F105xC) -#include "stm32f1xx.h" -#include -#else -#include -#endif - -class CRSSIRB { -public: - CRSSIRB(uint16_t length); - - uint16_t getSpace() const; - - uint16_t getData() const; - - bool put(uint16_t rssi); - - bool get(uint16_t& rssi); - - bool hasOverflowed(); - -private: - uint16_t m_length; - volatile uint16_t* m_rssi; - volatile uint16_t m_head; - volatile uint16_t m_tail; - volatile bool m_full; - bool m_overflow; -}; - -#endif - diff --git a/FMDownsampleRB.h b/RingBuffer.h similarity index 71% rename from FMDownsampleRB.h rename to RingBuffer.h index df60daf..79f7814 100644 --- a/FMDownsampleRB.h +++ b/RingBuffer.h @@ -1,5 +1,6 @@ /* * 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 @@ -16,8 +17,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#if !defined(FMDOWNSAMPLERB_H) -#define FMDOWNSAMPLERB_H +#if !defined(RINGBUFFER_H) +#define RINGBUFFER_H #if defined(STM32F4XX) #include "stm32f4xx.h" @@ -28,6 +29,7 @@ #include #else #include +#undef PI #endif #if defined(__SAM3X8E__) || defined(STM32F105xC) @@ -42,27 +44,34 @@ #include -class CFMDownsampleRB { +template +class CRingBuffer { public: - CFMDownsampleRB(uint16_t length); + CRingBuffer(uint16_t length = 370U); uint16_t getSpace() const; uint16_t getData() const; - bool put(uint8_t sample); + bool put(TDATATYPE item) volatile; - bool get(uint8_t& sample); + bool get(TDATATYPE& item) volatile; + + TDATATYPE peek() const; bool hasOverflowed(); + void reset(); + 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; + uint16_t m_length; + TDATATYPE* m_buffer; + volatile uint16_t m_head; + volatile uint16_t m_tail; + volatile bool m_full; + bool m_overflow; }; -#endif +#include "RingBuffer.impl.h" + +#endif \ No newline at end of file diff --git a/FMDownsampleRB.cpp b/RingBuffer.impl.h similarity index 64% rename from FMDownsampleRB.cpp rename to RingBuffer.impl.h index 7d2cc2a..ddcb21c 100644 --- a/FMDownsampleRB.cpp +++ b/RingBuffer.impl.h @@ -1,5 +1,6 @@ /* * 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 @@ -16,20 +17,19 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include "RingBuffer.h" -#include "FMDownsampleRB.h" - -CFMDownsampleRB::CFMDownsampleRB(uint16_t length) : +template CRingBuffer::CRingBuffer(uint16_t length) : m_length(length), m_head(0U), m_tail(0U), m_full(false), m_overflow(false) { - m_samples = new uint8_t[length]; + m_buffer = new TDATATYPE[length]; } -uint16_t CFMDownsampleRB::getSpace() const +template uint16_t CRingBuffer::getSpace() const { uint16_t n = 0U; @@ -46,7 +46,7 @@ uint16_t CFMDownsampleRB::getSpace() const return n; } -uint16_t CFMDownsampleRB::getData() const +template uint16_t CRingBuffer::getData() const { if (m_tail == m_head) return m_full ? m_length : 0U; @@ -56,14 +56,14 @@ uint16_t CFMDownsampleRB::getData() const return m_length - m_tail + m_head; } -bool CFMDownsampleRB::put(uint8_t sample) +template bool CRingBuffer::put(TDATATYPE item) volatile { if (m_full) { m_overflow = true; return false; } - m_samples[m_head] = sample; + m_buffer[m_head] = item; m_head++; if (m_head >= m_length) @@ -75,12 +75,17 @@ bool CFMDownsampleRB::put(uint8_t sample) return true; } -bool CFMDownsampleRB::get(uint8_t& sample) +template TDATATYPE CRingBuffer::peek() const +{ + return m_buffer[m_tail]; +} + +template bool CRingBuffer::get(TDATATYPE& item) volatile { if (m_head == m_tail && !m_full) return false; - sample = m_samples[m_tail]; + item = m_buffer[m_tail]; m_full = false; @@ -91,7 +96,7 @@ bool CFMDownsampleRB::get(uint8_t& sample) return true; } -bool CFMDownsampleRB::hasOverflowed() +template bool CRingBuffer::hasOverflowed() { bool overflow = m_overflow; @@ -99,3 +104,11 @@ bool CFMDownsampleRB::hasOverflowed() return overflow; } + +template void CRingBuffer::reset() +{ + m_head = 0U; + m_tail = 0U; + m_full = false; + m_overflow = false; +} diff --git a/STMUART.cpp b/STMUART.cpp new file mode 100644 index 0000000..c2e779d --- /dev/null +++ b/STMUART.cpp @@ -0,0 +1,100 @@ +/* + * 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(STM32F4XX) || defined(STM32F7XX) + +#include "STMUART.h" + +CSTMUART::CSTMUART() : +m_usart(NULL) +{ + +} + +void CSTMUART::init(USART_TypeDef* usart) +{ + m_usart = usart; +} + +void CSTMUART::write(const uint8_t * data, uint16_t length) +{ + if(length == 0U || m_usart == NULL) + return; + + + m_txFifo.put(data[0]); + USART_ITConfig(m_usart, USART_IT_TXE, ENABLE);//switch TX IRQ is on + + for(uint16_t i = 1U; i < length; i++) { + m_txFifo.put(data[i]); + } + + USART_ITConfig(m_usart, USART_IT_TXE, ENABLE);//make sure TX IRQ is on +} + +uint8_t CSTMUART::read() +{ + return m_rxFifo.get(); +} + +void CSTMUART::handleIRQ() +{ + if(m_usart == NULL) + return; + + if (USART_GetITStatus(m_usart, USART_IT_RXNE)) { + if(!m_rxFifo.isFull()) + m_rxFifo.put((uint8_t) USART_ReceiveData(m_usart)); + USART_ClearITPendingBit(USART1, USART_IT_RXNE); + } + + if (USART_GetITStatus(m_usart, USART_IT_TXE)) { + if(!m_txFifo.isEmpty()) + USART_SendData(m_usart, m_txFifo.get()); + + USART_ClearITPendingBit(m_usart, USART_IT_TXE); + + if (m_txFifo.isEmpty()) // if there's no more data to transmit then turn off TX interrupts + USART_ITConfig(m_usart, USART_IT_TXE, DISABLE); + } +} + + // Flushes the transmit shift register +// warning: this call is blocking +void CSTMUART::flush() +{ + if(m_usart == NULL) + return; + + // wait until the TXE shows the shift register is empty + while (USART_GetITStatus(m_usart, USART_FLAG_TXE)) + ; +} + +uint16_t CSTMUART::available() +{ + return m_rxFifo.isEmpty() ? 0U : 1U; +} + +uint16_t CSTMUART::availableForWrite() +{ + return m_txFifo.isFull() ? 0U : 1U; +} + +#endif \ No newline at end of file diff --git a/STMUART.h b/STMUART.h new file mode 100644 index 0000000..8451aa2 --- /dev/null +++ b/STMUART.h @@ -0,0 +1,91 @@ +/* + * 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(STM32F4XX) || defined(STM32F7XX) +#if !defined(STMUART_H) +#define STMUART_H + +#if defined(STM32F4XX) +#include "stm32f4xx.h" +#elif defined(STM32F7XX) +#include "stm32f7xx.h" +#endif + +const uint16_t BUFFER_SIZE = 2048U; //needs to be a power of 2 ! +const uint16_t BUFFER_MASK = BUFFER_SIZE - 1; + +class CSTMUARTFIFO { +public: + CSTMUARTFIFO() : + m_head(0U), + m_tail(0U) + { + } + + void put(uint8_t data) + { + m_buffer[BUFFER_MASK & (m_head++)] = data; + } + + uint8_t get() + { + return m_buffer[BUFFER_MASK & (m_tail++)]; + } + + void reset() + { + m_tail = 0U; + m_head = 0U; + } + + bool isEmpty() + { + return m_tail == m_head; + } + + bool isFull() + { + return ((m_head + 1U) & BUFFER_MASK) == (m_tail & BUFFER_MASK); + } + +private: + volatile uint8_t m_buffer[BUFFER_SIZE]; + volatile uint16_t m_head; + volatile uint16_t m_tail; +}; + +class CSTMUART { +public: + CSTMUART(); + void init(USART_TypeDef* usart); + void write(const uint8_t * data, uint16_t length); + uint8_t read(); + void handleIRQ(); + void flush(); + uint16_t available(); + uint16_t availableForWrite(); + +private: + USART_TypeDef * m_usart; + CSTMUARTFIFO m_rxFifo; + CSTMUARTFIFO m_txFifo; +}; + +#endif +#endif \ No newline at end of file diff --git a/SampleRB.cpp b/SampleRB.cpp deleted file mode 100644 index 5fa2ef6..0000000 --- a/SampleRB.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* -TX fifo control - Copyright (C) KI6ZUM 2015 -Copyright (C) 2015,2016 by Jonathan Naylor G4KLX - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this library; if not, write to the -Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -Boston, MA 02110-1301, USA. -*/ - -#include "SampleRB.h" - -CSampleRB::CSampleRB(uint16_t length) : -m_length(length), -m_head(0U), -m_tail(0U), -m_full(false), -m_overflow(false) -{ - m_samples = new uint16_t[length]; - m_control = new uint8_t[length]; -} - -uint16_t CSampleRB::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 CSampleRB::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 CSampleRB::put(uint16_t sample, uint8_t control) -{ - if (m_full) { - m_overflow = true; - return false; - } - - m_samples[m_head] = sample; - m_control[m_head] = control; - - m_head++; - if (m_head >= m_length) - m_head = 0U; - - if (m_head == m_tail) - m_full = true; - - return true; -} - -bool CSampleRB::get(uint16_t& sample, uint8_t& control) -{ - if (m_head == m_tail && !m_full) - return false; - - sample = m_samples[m_tail]; - control = m_control[m_tail]; - - m_full = false; - - m_tail++; - if (m_tail >= m_length) - m_tail = 0U; - - return true; -} - -bool CSampleRB::hasOverflowed() -{ - bool overflow = m_overflow; - - m_overflow = false; - - return overflow; -} - diff --git a/SampleRB.h b/SampleRB.h deleted file mode 100644 index e0b6269..0000000 --- a/SampleRB.h +++ /dev/null @@ -1,60 +0,0 @@ -/* -Serial fifo control - Copyright (C) KI6ZUM 2015 -Copyright (C) 2015,2016 by Jonathan Naylor G4KLX - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this library; if not, write to the -Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -Boston, MA 02110-1301, USA. -*/ - -#if !defined(SAMPLERB_H) -#define SAMPLERB_H - -#if defined(STM32F4XX) -#include "stm32f4xx.h" -#elif defined(STM32F7XX) -#include "stm32f7xx.h" -#elif defined(STM32F105xC) -#include "stm32f1xx.h" -#include -#else -#include -#endif - -class CSampleRB { -public: - CSampleRB(uint16_t length); - - uint16_t getSpace() const; - - uint16_t getData() const; - - bool put(uint16_t sample, uint8_t control); - - bool get(uint16_t& sample, uint8_t& control); - - bool hasOverflowed(); - -private: - uint16_t m_length; - volatile uint16_t* m_samples; - volatile uint8_t* m_control; - volatile uint16_t m_head; - volatile uint16_t m_tail; - volatile bool m_full; - bool m_overflow; -}; - -#endif - diff --git a/SerialPort.cpp b/SerialPort.cpp index c72d244..9828310 100644 --- a/SerialPort.cpp +++ b/SerialPort.cpp @@ -68,9 +68,15 @@ const uint8_t MMDVM_M17_LOST = 0x46U; const uint8_t MMDVM_POCSAG_DATA = 0x50U; +const uint8_t MMDVM_AX25_DATA = 0x55U; + 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_FM_PARAMS4 = 0x63U; +const uint8_t MMDVM_FM_DATA = 0x65U; +const uint8_t MMDVM_FM_STATUS = 0x66U; +const uint8_t MMDVM_FM_EOT = 0x67U; const uint8_t MMDVM_ACK = 0x70U; const uint8_t MMDVM_NAK = 0x7FU; @@ -106,7 +112,7 @@ const uint8_t MMDVM_DEBUG5 = 0xF5U; #define HW_TYPE "MMDVM" #endif -#define DESCRIPTION "20201015 (D-Star/DMR/System Fusion/P25/NXDN/M17/POCSAG/FM)" +#define DESCRIPTION "20201106 (D-Star/DMR/System Fusion/P25/NXDN/M17/POCSAG/FM/AX.25)" #if defined(GITVERSION) #define concat(h, a, b, c) h " " a " " b " GitID #" c "" @@ -161,82 +167,69 @@ void CSerialPort::getStatus() // Send all sorts of interesting internal values reply[0U] = MMDVM_FRAME_START; - reply[1U] = 14U; + reply[1U] = 15U; reply[2U] = MMDVM_GET_STATUS; - reply[3U] = 0x00U; - if (m_dstarEnable) - reply[3U] |= 0x01U; - if (m_dmrEnable) - reply[3U] |= 0x02U; - if (m_ysfEnable) - reply[3U] |= 0x04U; - if (m_p25Enable) - reply[3U] |= 0x08U; - if (m_nxdnEnable) - reply[3U] |= 0x10U; - if (m_pocsagEnable) - reply[3U] |= 0x20U; - if (m_fmEnable) - reply[3U] |= 0x40U; - if (m_m17Enable) - reply[3U] |= 0x80U; + reply[3U] = uint8_t(m_modemState); - reply[4U] = uint8_t(m_modemState); - - reply[5U] = m_tx ? 0x01U : 0x00U; + reply[4U] = m_tx ? 0x01U : 0x00U; bool adcOverflow; bool dacOverflow; io.getOverflow(adcOverflow, dacOverflow); if (adcOverflow) - reply[5U] |= 0x02U; + reply[4U] |= 0x02U; if (io.hasRXOverflow()) - reply[5U] |= 0x04U; + reply[4U] |= 0x04U; if (io.hasTXOverflow()) - reply[5U] |= 0x08U; + reply[4U] |= 0x08U; if (io.hasLockout()) - reply[5U] |= 0x10U; + reply[4U] |= 0x10U; if (dacOverflow) - reply[5U] |= 0x20U; + reply[4U] |= 0x20U; - reply[5U] |= m_dcd ? 0x40U : 0x00U; + reply[4U] |= m_dcd ? 0x40U : 0x00U; if (m_dstarEnable) - reply[6U] = dstarTX.getSpace(); + reply[5U] = dstarTX.getSpace(); else - reply[6U] = 0U; + reply[5U] = 0U; if (m_dmrEnable) { if (m_duplex) { - reply[7U] = dmrTX.getSpace1(); - reply[8U] = dmrTX.getSpace2(); + reply[6U] = dmrTX.getSpace1(); + reply[7U] = dmrTX.getSpace2(); } else { - reply[7U] = 10U; - reply[8U] = dmrDMOTX.getSpace(); + reply[6U] = 10U; + reply[7U] = dmrDMOTX.getSpace(); } } else { + reply[6U] = 0U; reply[7U] = 0U; - reply[8U] = 0U; } if (m_ysfEnable) - reply[9U] = ysfTX.getSpace(); + reply[8U] = ysfTX.getSpace(); + else + reply[8U] = 0U; + + if (m_p25Enable) + reply[9U] = p25TX.getSpace(); else reply[9U] = 0U; - if (m_p25Enable) - reply[10U] = p25TX.getSpace(); + if (m_nxdnEnable) + reply[10U] = nxdnTX.getSpace(); else reply[10U] = 0U; - if (m_nxdnEnable) - reply[11U] = nxdnTX.getSpace(); + if (m_m17Enable) + reply[11U] = m17TX.getSpace(); else reply[11U] = 0U; @@ -245,12 +238,17 @@ void CSerialPort::getStatus() else reply[12U] = 0U; - if (m_m17Enable) - reply[13U] = m17TX.getSpace(); + if (m_fmEnable) + reply[13U] = fm.getSpace(); else reply[13U] = 0U; - writeInt(1U, reply, 14); + if (m_ax25Enable) + reply[14U] = ax25TX.getSpace(); + else + reply[14U] = 0U; + + writeInt(1U, reply, 15); } void CSerialPort::getVersion() @@ -272,9 +270,9 @@ void CSerialPort::getVersion() writeInt(1U, reply, count); } -uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) +uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) { - if (length < 23U) + if (length < 28U) return 4U; bool rxInvert = (data[0U] & 0x01U) == 0x01U; @@ -291,15 +289,17 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) bool ysfEnable = (data[1U] & 0x04U) == 0x04U; bool p25Enable = (data[1U] & 0x08U) == 0x08U; bool nxdnEnable = (data[1U] & 0x10U) == 0x10U; - bool pocsagEnable = (data[1U] & 0x20U) == 0x20U; - bool fmEnable = (data[1U] & 0x40U) == 0x40U; - bool m17Enable = (data[1U] & 0x80U) == 0x80U; + bool fmEnable = (data[1U] & 0x20U) == 0x20U; + bool m17Enable = (data[1U] & 0x40U) == 0x40U; - uint8_t txDelay = data[2U]; + bool pocsagEnable = (data[2U] & 0x01U) == 0x01U; + bool ax25Enable = (data[2U] & 0x02U) == 0x02U; + + uint8_t txDelay = data[3U]; if (txDelay > 50U) return 4U; - MMDVM_STATE modemState = MMDVM_STATE(data[3U]); + MMDVM_STATE modemState = MMDVM_STATE(data[4U]); if (modemState != STATE_IDLE && modemState != STATE_DSTAR && modemState != STATE_DMR && modemState != STATE_YSF && modemState != STATE_P25 && modemState != STATE_NXDN && modemState != STATE_M17 && modemState != STATE_POCSAG && modemState != STATE_FM && modemState != STATE_DSTARCAL && modemState != STATE_DMRCAL && modemState != STATE_RSSICAL && modemState != STATE_LFCAL && modemState != STATE_DMRCAL1K && modemState != STATE_P25CAL1K && modemState != STATE_DMRDMO1K && modemState != STATE_NXDNCAL1K && modemState != STATE_POCSAGCAL && modemState != STATE_FMCAL10K && modemState != STATE_FMCAL12K && modemState != STATE_FMCAL15K && modemState != STATE_FMCAL20K && modemState != STATE_FMCAL25K && modemState != STATE_FMCAL30K) return 4U; @@ -320,37 +320,40 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) if (modemState == STATE_FM && !fmEnable) return 4U; - uint8_t rxLevel = data[4U]; + int16_t txDCOffset = int16_t(data[5U]) - 128; + int16_t rxDCOffset = int16_t(data[6U]) - 128; - uint8_t colorCode = data[6U]; + uint8_t rxLevel = data[7U]; + + uint8_t cwIdTXLevel = data[8U]; + uint8_t dstarTXLevel = data[9U]; + uint8_t dmrTXLevel = data[10U]; + uint8_t ysfTXLevel = data[11U]; + uint8_t p25TXLevel = data[12U]; + uint8_t nxdnTXLevel = data[13U]; + uint8_t m17TXLevel = data[14U]; + uint8_t pocsagTXLevel = data[15U]; + uint8_t fmTXLevel = data[16U]; + uint8_t ax25TXLevel = data[17U]; + + uint8_t ysfTXHang = data[18U]; + uint8_t p25TXHang = data[19U]; + uint8_t nxdnTXHang = data[20U]; + uint8_t m17TXHang = data[21U]; + + uint8_t colorCode = data[22U]; if (colorCode > 15U) return 4U; - uint8_t dmrDelay = data[7U]; + uint8_t dmrDelay = data[23U]; - uint8_t cwIdTXLevel = data[5U]; - uint8_t dstarTXLevel = data[9U]; - uint8_t dmrTXLevel = data[10U]; - uint8_t ysfTXLevel = data[11U]; - uint8_t p25TXLevel = data[12U]; + int8_t ax25RXTwist = int8_t(data[24U]) - 128; + if (ax25RXTwist < -4 || ax25RXTwist > 10) + return 4U; - int16_t txDCOffset = int16_t(data[13U]) - 128; - int16_t rxDCOffset = int16_t(data[14U]) - 128; - - uint8_t nxdnTXLevel = data[15U]; - - uint8_t ysfTXHang = data[16U]; - - uint8_t pocsagTXLevel = data[17U]; - - uint8_t fmTXLevel = data[18U]; - - uint8_t p25TXHang = data[19U]; - - uint8_t nxdnTXHang = data[20U]; - - uint8_t m17TXLevel = data[21U]; - uint8_t m17TXHang = data[22U]; + uint8_t ax25TXDelay = data[25U]; + uint8_t ax25SlotTime = data[26U]; + uint8_t ax25PPersist = data[27U]; setMode(modemState); @@ -362,6 +365,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) m_m17Enable = m17Enable; m_pocsagEnable = pocsagEnable; m_fmEnable = fmEnable; + m_ax25Enable = ax25Enable; m_duplex = !simplex; dstarTX.setTXDelay(txDelay); @@ -371,6 +375,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) nxdnTX.setTXDelay(txDelay); m17TX.setTXDelay(txDelay); pocsagTX.setTXDelay(txDelay); + ax25TX.setTXDelay(ax25TXDelay); dmrTX.setColorCode(colorCode); dmrRX.setColorCode(colorCode); @@ -382,15 +387,16 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint8_t length) p25TX.setParams(p25TXHang); nxdnTX.setParams(nxdnTXHang); m17TX.setParams(m17TXHang); + ax25RX.setParams(ax25RXTwist, ax25SlotTime, ax25PPersist); - io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, m17TXLevel, pocsagTXLevel, fmTXLevel, txDCOffset, rxDCOffset, useCOSAsLockout); + io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, m17TXLevel, pocsagTXLevel, fmTXLevel, ax25TXLevel, txDCOffset, rxDCOffset, useCOSAsLockout); io.start(); return 0U; } -uint8_t CSerialPort::setFMParams1(const uint8_t* data, uint8_t length) +uint8_t CSerialPort::setFMParams1(const uint8_t* data, uint16_t length) { if (length < 8U) return 4U; @@ -415,7 +421,7 @@ uint8_t CSerialPort::setFMParams1(const uint8_t* data, uint8_t length) return fm.setCallsign(callsign, speed, frequency, time, holdoff, highLevel, lowLevel, callAtStart, callAtEnd, callAtLatch); } -uint8_t CSerialPort::setFMParams2(const uint8_t* data, uint8_t length) +uint8_t CSerialPort::setFMParams2(const uint8_t* data, uint16_t length) { if (length < 6U) return 4U; @@ -435,9 +441,9 @@ uint8_t CSerialPort::setFMParams2(const uint8_t* data, uint8_t length) return fm.setAck(ack, speed, frequency, minTime, delay, level); } -uint8_t CSerialPort::setFMParams3(const uint8_t* data, uint8_t length) +uint8_t CSerialPort::setFMParams3(const uint8_t* data, uint16_t length) { - if (length < 12U) + if (length < 14U) return 4U; uint16_t timeout = data[0U] * 5U; @@ -451,17 +457,40 @@ uint8_t CSerialPort::setFMParams3(const uint8_t* data, uint8_t length) uint8_t kerchunkTime = data[6U]; uint8_t hangTime = data[7U]; - uint8_t accessMode = data[8U] & 0x7FU; + uint8_t accessMode = data[8U] & 0x0FU; + bool noiseSquelch = (data[8U] & 0x40U) == 0x40U; bool cosInvert = (data[8U] & 0x80U) == 0x80U; uint8_t rfAudioBoost = data[9U]; uint8_t maxDev = data[10U]; uint8_t rxLevel = data[11U]; - return fm.setMisc(timeout, timeoutLevel, ctcssFrequency, ctcssHighThreshold, ctcssLowThreshold, ctcssLevel, kerchunkTime, hangTime, accessMode, cosInvert, rfAudioBoost, maxDev, rxLevel); + uint8_t squelchHighThreshold = data[12U]; + uint8_t squelchLowThreshold = data[13U]; + + return fm.setMisc(timeout, timeoutLevel, ctcssFrequency, ctcssHighThreshold, ctcssLowThreshold, ctcssLevel, kerchunkTime, hangTime, accessMode, cosInvert, noiseSquelch, squelchHighThreshold, squelchLowThreshold, rfAudioBoost, maxDev, rxLevel); } -uint8_t CSerialPort::setMode(const uint8_t* data, uint8_t length) +uint8_t CSerialPort::setFMParams4(const uint8_t* data, uint16_t length) +{ + if (length < 4U) + return 4U; + + uint8_t audioBoost = data[0U]; + uint8_t speed = data[1U]; + uint16_t frequency = data[2U] * 10U; + uint8_t level = data[3U]; + + char ack[50U]; + uint8_t n = 0U; + for (uint8_t i = 4U; i < length; i++, n++) + ack[n] = data[i]; + ack[n] = '\0'; + + return fm.setExt(ack, audioBoost, speed, frequency, level); +} + +uint8_t CSerialPort::setMode(const uint8_t* data, uint16_t length) { if (length < 1U) return 4U; @@ -600,7 +629,7 @@ void CSerialPort::setMode(MMDVM_STATE modemState) void CSerialPort::start() { - beginInt(1U, 115200); + beginInt(1U, SERIAL_SPEED); #if defined(SERIAL_REPEATER) beginInt(3U, 9600); @@ -618,15 +647,25 @@ void CSerialPort::process() m_buffer[0U] = c; m_ptr = 1U; m_len = 0U; - } - else { + } else { m_ptr = 0U; m_len = 0U; } } else if (m_ptr == 1U) { - // Handle the frame length + // Handle the frame length, 1/2 m_len = m_buffer[m_ptr] = c; m_ptr = 2U; + } else if (m_ptr == 2U) { + // Handle the frame length, 2/2 + m_buffer[m_ptr] = c; + m_ptr = 3U; + + if (m_len == 0U) + m_len = c + 255U; + + // The full packet has been received, process it + if (m_ptr == m_len) + processMessage(m_buffer + 3U, m_len - 3U); } else { // Any other bytes are added to the buffer m_buffer[m_ptr] = c; @@ -634,318 +673,10 @@ void CSerialPort::process() // The full packet has been received, process it if (m_ptr == m_len) { - uint8_t err = 2U; - - switch (m_buffer[2U]) { - case MMDVM_GET_STATUS: - getStatus(); - break; - - case MMDVM_GET_VERSION: - getVersion(); - break; - - case MMDVM_SET_CONFIG: - err = setConfig(m_buffer + 3U, m_len - 3U); - if (err == 0U) - sendACK(); - else - sendNAK(err); - break; - - case MMDVM_SET_MODE: - err = setMode(m_buffer + 3U, m_len - 3U); - if (err == 0U) - sendACK(); - else - sendNAK(err); - break; - - case MMDVM_SET_FREQ: - 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) - err = calNXDN.write(m_buffer + 3U, m_len - 3U); - if (m_modemState == STATE_POCSAGCAL) - err = calPOCSAG.write(m_buffer + 3U, m_len - 3U); - if (err == 0U) { - sendACK(); - } else { - DEBUG2("Received invalid calibration data", err); - sendNAK(err); - } - break; - - case MMDVM_SEND_CWID: - err = 5U; - if (m_modemState == STATE_IDLE) - err = cwIdTX.write(m_buffer + 3U, m_len - 3U); - if (err != 0U) { - DEBUG2("Invalid CW Id data", err); - sendNAK(err); - } - break; - - case MMDVM_DSTAR_HEADER: - if (m_dstarEnable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_DSTAR) - err = dstarTX.writeHeader(m_buffer + 3U, m_len - 3U); - } - if (err == 0U) { - if (m_modemState == STATE_IDLE) - setMode(STATE_DSTAR); - } else { - DEBUG2("Received invalid D-Star header", err); - sendNAK(err); - } - break; - - case MMDVM_DSTAR_DATA: - if (m_dstarEnable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_DSTAR) - err = dstarTX.writeData(m_buffer + 3U, m_len - 3U); - } - if (err == 0U) { - if (m_modemState == STATE_IDLE) - setMode(STATE_DSTAR); - } else { - DEBUG2("Received invalid D-Star data", err); - sendNAK(err); - } - break; - - case MMDVM_DSTAR_EOT: - if (m_dstarEnable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_DSTAR) - err = dstarTX.writeEOT(); - } - if (err == 0U) { - if (m_modemState == STATE_IDLE) - setMode(STATE_DSTAR); - } else { - DEBUG2("Received invalid D-Star EOT", err); - sendNAK(err); - } - break; - - case MMDVM_DMR_DATA1: - if (m_dmrEnable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_DMR) { - if (m_duplex) - err = dmrTX.writeData1(m_buffer + 3U, m_len - 3U); - } - } - if (err == 0U) { - if (m_modemState == STATE_IDLE) - setMode(STATE_DMR); - } else { - DEBUG2("Received invalid DMR data", err); - sendNAK(err); - } - break; - - case MMDVM_DMR_DATA2: - if (m_dmrEnable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_DMR) { - if (m_duplex) - err = dmrTX.writeData2(m_buffer + 3U, m_len - 3U); - else - err = dmrDMOTX.writeData(m_buffer + 3U, m_len - 3U); - } - } - if (err == 0U) { - if (m_modemState == STATE_IDLE) - setMode(STATE_DMR); - } else { - DEBUG2("Received invalid DMR data", err); - sendNAK(err); - } - break; - - case MMDVM_DMR_START: - if (m_dmrEnable) { - err = 4U; - if (m_len == 4U) { - if (m_buffer[3U] == 0x01U && m_modemState == STATE_DMR) { - if (!m_tx) - dmrTX.setStart(true); - err = 0U; - } else if (m_buffer[3U] == 0x00U && m_modemState == STATE_DMR) { - if (m_tx) - dmrTX.setStart(false); - err = 0U; - } - } - } - if (err != 0U) { - DEBUG2("Received invalid DMR start", err); - sendNAK(err); - } - break; - - case MMDVM_DMR_SHORTLC: - if (m_dmrEnable) - err = dmrTX.writeShortLC(m_buffer + 3U, m_len - 3U); - if (err != 0U) { - DEBUG2("Received invalid DMR Short LC", err); - sendNAK(err); - } - break; - - case MMDVM_DMR_ABORT: - if (m_dmrEnable) - err = dmrTX.writeAbort(m_buffer + 3U, m_len - 3U); - if (err != 0U) { - DEBUG2("Received invalid DMR Abort", err); - sendNAK(err); - } - break; - - case MMDVM_YSF_DATA: - if (m_ysfEnable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_YSF) - err = ysfTX.writeData(m_buffer + 3U, m_len - 3U); - } - if (err == 0U) { - if (m_modemState == STATE_IDLE) - setMode(STATE_YSF); - } else { - DEBUG2("Received invalid System Fusion data", err); - sendNAK(err); - } - break; - - case MMDVM_P25_HDR: - if (m_p25Enable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_P25) - err = p25TX.writeData(m_buffer + 3U, m_len - 3U); - } - if (err == 0U) { - if (m_modemState == STATE_IDLE) - setMode(STATE_P25); - } else { - DEBUG2("Received invalid P25 header", err); - sendNAK(err); - } - break; - - case MMDVM_P25_LDU: - if (m_p25Enable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_P25) - err = p25TX.writeData(m_buffer + 3U, m_len - 3U); - } - if (err == 0U) { - if (m_modemState == STATE_IDLE) - setMode(STATE_P25); - } else { - DEBUG2("Received invalid P25 LDU", err); - sendNAK(err); - } - break; - - case MMDVM_NXDN_DATA: - if (m_nxdnEnable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_NXDN) - err = nxdnTX.writeData(m_buffer + 3U, m_len - 3U); - } - if (err == 0U) { - if (m_modemState == STATE_IDLE) - setMode(STATE_NXDN); - } else { - DEBUG2("Received invalid NXDN data", err); - sendNAK(err); - } - break; - - case MMDVM_M17_DATA: - if (m_m17Enable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_M17) - err = m17TX.writeData(m_buffer + 3U, m_len - 3U); - } - if (err == 0U) { - if (m_modemState == STATE_IDLE) - setMode(STATE_M17); - } else { - DEBUG2("Received invalid M17 data", err); - sendNAK(err); - } - break; - - case MMDVM_POCSAG_DATA: - if (m_pocsagEnable) { - if (m_modemState == STATE_IDLE || m_modemState == STATE_POCSAG) - err = pocsagTX.writeData(m_buffer + 3U, m_len - 3U); - } - if (err == 0U) { - if (m_modemState == STATE_IDLE) - setMode(STATE_POCSAG); - } else { - DEBUG2("Received invalid POCSAG data", err); - sendNAK(err); - } - break; - - case MMDVM_TRANSPARENT: - case MMDVM_QSO_INFO: - // Do nothing on the MMDVM. - break; - -#if defined(SERIAL_REPEATER) - case MMDVM_SERIAL: { - for (uint8_t i = 3U; i < m_len; i++) - m_repeat.put(m_buffer[i]); - } - break; -#endif - - default: - // Handle this, send a NAK back - sendNAK(1U); - break; - } - - m_ptr = 0U; - m_len = 0U; + if (m_len > 255U) + processMessage(m_buffer + 4U, m_len - 4U); + else + processMessage(m_buffer + 3U, m_len - 3U); } } } @@ -975,6 +706,357 @@ void CSerialPort::process() #endif } +void CSerialPort::processMessage(const uint8_t* buffer, uint16_t length) +{ + uint8_t err = 2U; + + switch (m_buffer[2U]) { + case MMDVM_GET_STATUS: + getStatus(); + break; + + case MMDVM_GET_VERSION: + getVersion(); + break; + + case MMDVM_SET_CONFIG: + err = setConfig(buffer, length); + if (err == 0U) + sendACK(); + else + sendNAK(err); + break; + + case MMDVM_SET_MODE: + err = setMode(buffer, length); + if (err == 0U) + sendACK(); + else + sendNAK(err); + break; + + case MMDVM_SET_FREQ: + sendACK(); + break; + + case MMDVM_FM_PARAMS1: + err = setFMParams1(buffer, length); + if (err == 0U) { + sendACK(); + } else { + DEBUG2("Received invalid FM params 1", err); + sendNAK(err); + } + break; + + case MMDVM_FM_PARAMS2: + err = setFMParams2(buffer, length); + if (err == 0U) { + sendACK(); + } else { + DEBUG2("Received invalid FM params 2", err); + sendNAK(err); + } + break; + + case MMDVM_FM_PARAMS3: + err = setFMParams3(buffer, length); + if (err == 0U) { + sendACK(); + } else { + DEBUG2("Received invalid FM params 3", err); + sendNAK(err); + } + break; + + case MMDVM_FM_PARAMS4: + err = setFMParams4(buffer, length); + if (err == 0U) { + sendACK(); + } else { + DEBUG2("Received invalid FM params 4", err); + sendNAK(err); + } + break; + + case MMDVM_CAL_DATA: + if (m_modemState == STATE_DSTARCAL) + err = calDStarTX.write(buffer, length); + if (m_modemState == STATE_DMRCAL || m_modemState == STATE_LFCAL || m_modemState == STATE_DMRCAL1K || m_modemState == STATE_DMRDMO1K) + err = calDMR.write(buffer, length); + 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(buffer, length); + if (m_modemState == STATE_P25CAL1K) + err = calP25.write(buffer, length); + if (m_modemState == STATE_NXDNCAL1K) + err = calNXDN.write(buffer, length); + if (m_modemState == STATE_POCSAGCAL) + err = calPOCSAG.write(buffer, length); + if (err == 0U) { + sendACK(); + } else { + DEBUG2("Received invalid calibration data", err); + sendNAK(err); + } + break; + + case MMDVM_SEND_CWID: + err = 5U; + if (m_modemState == STATE_IDLE) + err = cwIdTX.write(buffer, length); + if (err != 0U) { + DEBUG2("Invalid CW Id data", err); + sendNAK(err); + } + break; + + case MMDVM_DSTAR_HEADER: + if (m_dstarEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_DSTAR) + err = dstarTX.writeHeader(buffer, length); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_DSTAR); + } else { + DEBUG2("Received invalid D-Star header", err); + sendNAK(err); + } + break; + + case MMDVM_DSTAR_DATA: + if (m_dstarEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_DSTAR) + err = dstarTX.writeData(buffer, length); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_DSTAR); + } else { + DEBUG2("Received invalid D-Star data", err); + sendNAK(err); + } + break; + + case MMDVM_DSTAR_EOT: + if (m_dstarEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_DSTAR) + err = dstarTX.writeEOT(); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_DSTAR); + } else { + DEBUG2("Received invalid D-Star EOT", err); + sendNAK(err); + } + break; + + case MMDVM_DMR_DATA1: + if (m_dmrEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_DMR) { + if (m_duplex) + err = dmrTX.writeData1(buffer, length); + } + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_DMR); + } else { + DEBUG2("Received invalid DMR data", err); + sendNAK(err); + } + break; + + case MMDVM_DMR_DATA2: + if (m_dmrEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_DMR) { + if (m_duplex) + err = dmrTX.writeData2(buffer, length); + else + err = dmrDMOTX.writeData(buffer, length); + } + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_DMR); + } else { + DEBUG2("Received invalid DMR data", err); + sendNAK(err); + } + break; + + case MMDVM_DMR_START: + if (m_dmrEnable) { + err = 4U; + if (m_len == 4U) { + if (m_buffer[3U] == 0x01U && m_modemState == STATE_DMR) { + if (!m_tx) + dmrTX.setStart(true); + err = 0U; + } else if (m_buffer[3U] == 0x00U && m_modemState == STATE_DMR) { + if (m_tx) + dmrTX.setStart(false); + err = 0U; + } + } + } + if (err != 0U) { + DEBUG2("Received invalid DMR start", err); + sendNAK(err); + } + break; + + case MMDVM_DMR_SHORTLC: + if (m_dmrEnable) + err = dmrTX.writeShortLC(buffer, length); + if (err != 0U) { + DEBUG2("Received invalid DMR Short LC", err); + sendNAK(err); + } + break; + + case MMDVM_DMR_ABORT: + if (m_dmrEnable) + err = dmrTX.writeAbort(buffer, length); + if (err != 0U) { + DEBUG2("Received invalid DMR Abort", err); + sendNAK(err); + } + break; + + case MMDVM_YSF_DATA: + if (m_ysfEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_YSF) + err = ysfTX.writeData(buffer, length); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_YSF); + } else { + DEBUG2("Received invalid System Fusion data", err); + sendNAK(err); + } + break; + + case MMDVM_P25_HDR: + if (m_p25Enable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_P25) + err = p25TX.writeData(buffer, length); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_P25); + } else { + DEBUG2("Received invalid P25 header", err); + sendNAK(err); + } + break; + + case MMDVM_P25_LDU: + if (m_p25Enable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_P25) + err = p25TX.writeData(buffer, length); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_P25); + } else { + DEBUG2("Received invalid P25 LDU", err); + sendNAK(err); + } + break; + + case MMDVM_NXDN_DATA: + if (m_nxdnEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_NXDN) + err = nxdnTX.writeData(buffer, length); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_NXDN); + } else { + DEBUG2("Received invalid NXDN data", err); + sendNAK(err); + } + break; + + case MMDVM_M17_DATA: + if (m_m17Enable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_M17) + err = m17TX.writeData(buffer, length); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_M17); + } else { + DEBUG2("Received invalid M17 data", err); + sendNAK(err); + } + break; + + case MMDVM_POCSAG_DATA: + if (m_pocsagEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_POCSAG) + err = pocsagTX.writeData(buffer, length); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_POCSAG); + } else { + DEBUG2("Received invalid POCSAG data", err); + sendNAK(err); + } + break; + + case MMDVM_FM_DATA: + if (m_fmEnable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_FM) + err = fm.writeData(m_buffer + 3U, m_len - 3U); + } + if (err == 0U) { + if (m_modemState == STATE_IDLE) + setMode(STATE_FM); + } else { + DEBUG2("Received invalid FM data", err); + sendNAK(err); + } + break; + + case MMDVM_AX25_DATA: + if (m_ax25Enable) { + if (m_modemState == STATE_IDLE || m_modemState == STATE_FM) + err = ax25TX.writeData(buffer, length); + } + if (err != 0U) { + DEBUG2("Received invalid AX.25 data", err); + sendNAK(err); + } + break; + + case MMDVM_TRANSPARENT: + case MMDVM_QSO_INFO: + // Do nothing on the MMDVM. + break; + +#if defined(SERIAL_REPEATER) + case MMDVM_SERIAL: { + for (uint8_t i = 3U; i < m_len; i++) + m_repeat.put(m_buffer[i]); + } + break; +#endif + + default: + // Handle this, send a NAK back + sendNAK(1U); + break; + } + + m_ptr = 0U; + m_len = 0U; +} + void CSerialPort::writeDStarHeader(const uint8_t* header, uint8_t length) { if (m_modemState != STATE_DSTAR && m_modemState != STATE_IDLE) @@ -1277,6 +1359,105 @@ void CSerialPort::writeM17Lost() writeInt(1U, reply, 3); } +void CSerialPort::writeFMData(const uint8_t* data, uint16_t length) +{ + if (m_modemState != STATE_FM && m_modemState != STATE_IDLE) + return; + + if (!m_fmEnable) + return; + + uint8_t reply[512U]; + + reply[0U] = MMDVM_FRAME_START; + + if (length > 252U) { + reply[1U] = 0U; + reply[2U] = (length + 4U) - 255U; + reply[3U] = MMDVM_FM_DATA; + + for (uint8_t i = 0U; i < length; i++) + reply[i + 4U] = data[i]; + + writeInt(1U, reply, length + 4U); + } else { + reply[1U] = length + 3U; + reply[2U] = MMDVM_FM_DATA; + + for (uint8_t i = 0U; i < length; i++) + reply[i + 3U] = data[i]; + + writeInt(1U, reply, length + 3U); + } +} + +void CSerialPort::writeFMStatus(uint8_t status) +{ + if (m_modemState != STATE_FM && m_modemState != STATE_IDLE) + return; + + if (!m_fmEnable) + return; + + uint8_t reply[10U]; + + reply[0U] = MMDVM_FRAME_START; + reply[1U] = 4U; + reply[2U] = MMDVM_FM_STATUS; + reply[3U] = status; + + writeInt(1U, reply, 4U); +} + +void CSerialPort::writeFMEOT() +{ + if (m_modemState != STATE_FM && m_modemState != STATE_IDLE) + return; + + if (!m_fmEnable) + return; + + uint8_t reply[10U]; + + reply[0U] = MMDVM_FRAME_START; + reply[1U] = 3U; + reply[2U] = MMDVM_FM_EOT; + + writeInt(1U, reply, 3U); +} + +void CSerialPort::writeAX25Data(const uint8_t* data, uint16_t length) +{ + if (m_modemState != STATE_FM && m_modemState != STATE_IDLE) + return; + + if (!m_ax25Enable) + return; + + uint8_t reply[512U]; + + reply[0U] = MMDVM_FRAME_START; + + if (length > 252U) { + reply[1U] = 0U; + reply[2U] = (length + 4U) - 255U; + reply[3U] = MMDVM_AX25_DATA; + + for (uint8_t i = 0U; i < length; i++) + reply[i + 4U] = data[i]; + + writeInt(1U, reply, length + 4U); + } else { + reply[1U] = length + 3U; + reply[2U] = MMDVM_AX25_DATA; + + for (uint8_t i = 0U; i < length; i++) + reply[i + 3U] = data[i]; + + writeInt(1U, reply, length + 3U); + } +} + void CSerialPort::writeCalData(const uint8_t* data, uint8_t length) { if (m_modemState != STATE_DSTARCAL) @@ -1446,3 +1627,4 @@ void CSerialPort::writeDebug(const char* text, int16_t n1, int16_t n2, int16_t n writeInt(1U, reply, count, true); } + diff --git a/SerialPort.h b/SerialPort.h index 1e120c8..08766f8 100644 --- a/SerialPort.h +++ b/SerialPort.h @@ -21,7 +21,11 @@ #include "Config.h" #include "Globals.h" -#include "SerialRB.h" +#include "RingBuffer.h" + +#if !defined(SERIAL_SPEED) +#define SERIAL_SPEED 115200 +#endif class CSerialPort { @@ -53,6 +57,12 @@ public: void writeM17Data(const uint8_t* data, uint8_t length); void writeM17Lost(); + void writeAX25Data(const uint8_t* data, uint16_t length); + + void writeFMData(const uint8_t* data, uint16_t length); + void writeFMStatus(uint8_t status); + void writeFMEOT(); + void writeCalData(const uint8_t* data, uint8_t length); void writeRSSIData(const uint8_t* data, uint8_t length); @@ -63,22 +73,24 @@ public: void writeDebug(const char* text, int16_t n1, int16_t n2, int16_t n3, int16_t n4); private: - uint8_t m_buffer[256U]; - uint8_t m_ptr; - uint8_t m_len; + uint8_t m_buffer[512U]; + uint16_t m_ptr; + uint16_t m_len; bool m_debug; - CSerialRB m_repeat; + CRingBuffer m_repeat; void sendACK(); void sendNAK(uint8_t err); void getStatus(); void getVersion(); - uint8_t setConfig(const uint8_t* data, uint8_t length); - uint8_t setMode(const uint8_t* data, uint8_t length); + uint8_t setConfig(const uint8_t* data, uint16_t length); + uint8_t setMode(const uint8_t* data, uint16_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); + uint8_t setFMParams1(const uint8_t* data, uint16_t length); + uint8_t setFMParams2(const uint8_t* data, uint16_t length); + uint8_t setFMParams3(const uint8_t* data, uint16_t length); + uint8_t setFMParams4(const uint8_t* data, uint16_t length); + void processMessage(const uint8_t* data, uint16_t length); // Hardware versions void beginInt(uint8_t n, int speed); diff --git a/SerialRB.cpp b/SerialRB.cpp deleted file mode 100644 index f6b1578..0000000 --- a/SerialRB.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* -Serial RB control - Copyright (C) KI6ZUM 2015 -Copyright (C) 2015,2016 by Jonathan Naylor G4KLX - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this library; if not, write to the -Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -Boston, MA 02110-1301, USA. -*/ - -#include "SerialRB.h" - -CSerialRB::CSerialRB(uint16_t length) : -m_length(length), -m_head(0U), -m_tail(0U), -m_full(false) -{ - m_buffer = new uint8_t[length]; -} - -void CSerialRB::reset() -{ - m_head = 0U; - m_tail = 0U; - m_full = false; -} - -uint16_t CSerialRB::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 CSerialRB::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 CSerialRB::put(uint8_t c) -{ - if (m_full) - return false; - - m_buffer[m_head] = c; - - m_head++; - if (m_head >= m_length) - m_head = 0U; - - if (m_head == m_tail) - m_full = true; - - return true; -} - -uint8_t CSerialRB::peek() const -{ - return m_buffer[m_tail]; -} - -uint8_t CSerialRB::get() -{ - uint8_t value = m_buffer[m_tail]; - - m_full = false; - - m_tail++; - if (m_tail >= m_length) - m_tail = 0U; - - return value; -} - diff --git a/SerialRB.h b/SerialRB.h deleted file mode 100644 index 7b6b7b9..0000000 --- a/SerialRB.h +++ /dev/null @@ -1,62 +0,0 @@ -/* -Serial fifo control - Copyright (C) KI6ZUM 2015 -Copyright (C) 2015,2016 by Jonathan Naylor G4KLX - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this library; if not, write to the -Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -Boston, MA 02110-1301, USA. -*/ - -#if !defined(SERIALRB_H) -#define SERIALRB_H - -#if defined(STM32F4XX) -#include "stm32f4xx.h" -#elif defined(STM32F7XX) -#include "stm32f7xx.h" -#elif defined(STM32F105xC) -#include "stm32f1xx.h" -#include -#else -#include -#endif - -const uint16_t SERIAL_RINGBUFFER_SIZE = 370U; - -class CSerialRB { -public: - CSerialRB(uint16_t length = SERIAL_RINGBUFFER_SIZE); - - uint16_t getSpace() const; - - uint16_t getData() const; - - void reset(); - - bool put(uint8_t c); - - uint8_t peek() const; - - uint8_t get(); - -private: - uint16_t m_length; - volatile uint8_t* m_buffer; - volatile uint16_t m_head; - volatile uint16_t m_tail; - volatile bool m_full; -}; - -#endif - diff --git a/SerialSTM.cpp b/SerialSTM.cpp index b1bd27d..a2e8490 100644 --- a/SerialSTM.cpp +++ b/SerialSTM.cpp @@ -39,9 +39,7 @@ UART5 - TXD PC12 - RXD PD2 (Discovery, MMDVM-Pi, MMDVM-Pi F722 board, MMDVM-F4M #if defined(STM32F4XX) || defined(STM32F7XX) -#define TX_SERIAL_FIFO_SIZE 512U -#define RX_SERIAL_FIFO_SIZE 512U - +#include "STMUART.h" extern "C" { void USART1_IRQHandler(); void USART2_IRQHandler(); @@ -52,111 +50,12 @@ extern "C" { /* ************* USART1 ***************** */ #if defined(STM32F4_PI) || defined(STM32F4_F4M) || defined(STM32F722_F7M) || defined(STM32F722_PI) || defined(STM32F722_RPT_HAT) || defined(STM32F4_DVM) || (defined(STM32F4_NUCLEO) && defined(STM32F4_NUCLEO_ARDUINO_HEADER)) || defined(DRCC_DVM) || defined(STM32F4_EDA_405) || defined(STM32F4_EDA_446) -volatile uint8_t TXSerialfifo1[TX_SERIAL_FIFO_SIZE]; -volatile uint8_t RXSerialfifo1[RX_SERIAL_FIFO_SIZE]; -volatile uint16_t TXSerialfifohead1, TXSerialfifotail1; -volatile uint16_t RXSerialfifohead1, RXSerialfifotail1; -// Init queues -void TXSerialfifoinit1() -{ - TXSerialfifohead1 = 0U; - TXSerialfifotail1 = 0U; -} - -void RXSerialfifoinit1() -{ - RXSerialfifohead1 = 0U; - RXSerialfifotail1 = 0U; -} - -// How full is queue -// TODO decide if how full or how empty is preferred info to return -uint16_t TXSerialfifolevel1() -{ - uint32_t tail = TXSerialfifotail1; - uint32_t head = TXSerialfifohead1; - - if (tail > head) - return TX_SERIAL_FIFO_SIZE + head - tail; - else - return head - tail; -} - -uint16_t RXSerialfifolevel1() -{ - uint32_t tail = RXSerialfifotail1; - uint32_t head = RXSerialfifohead1; - - if (tail > head) - return RX_SERIAL_FIFO_SIZE + head - tail; - else - return head - tail; -} - -// Flushes the transmit shift register -// warning: this call is blocking -void TXSerialFlush1() -{ - // wait until the TXE shows the shift register is empty - while (USART_GetITStatus(USART1, USART_FLAG_TXE)) - ; -} - -uint8_t TXSerialfifoput1(uint8_t next) -{ - if (TXSerialfifolevel1() < TX_SERIAL_FIFO_SIZE) { - TXSerialfifo1[TXSerialfifohead1] = next; - - TXSerialfifohead1++; - if (TXSerialfifohead1 >= TX_SERIAL_FIFO_SIZE) - TXSerialfifohead1 = 0U; - - // make sure transmit interrupts are enabled as long as there is data to send - USART_ITConfig(USART1, USART_IT_TXE, ENABLE); - return 1U; - } else { - return 0U; // signal an overflow occurred by returning a zero count - } -} +static CSTMUART m_USART1; void USART1_IRQHandler() { - uint8_t c; - - if (USART_GetITStatus(USART1, USART_IT_RXNE)) { - c = (uint8_t) USART_ReceiveData(USART1); - - if (RXSerialfifolevel1() < RX_SERIAL_FIFO_SIZE) { - RXSerialfifo1[RXSerialfifohead1] = c; - - RXSerialfifohead1++; - if (RXSerialfifohead1 >= RX_SERIAL_FIFO_SIZE) - RXSerialfifohead1 = 0U; - } else { - // TODO - do something if rx fifo is full? - } - - USART_ClearITPendingBit(USART1, USART_IT_RXNE); - } - - if (USART_GetITStatus(USART1, USART_IT_TXE)) { - c = 0U; - - if (TXSerialfifohead1 != TXSerialfifotail1) { // if the fifo is not empty - c = TXSerialfifo1[TXSerialfifotail1]; - - TXSerialfifotail1++; - if (TXSerialfifotail1 >= TX_SERIAL_FIFO_SIZE) - TXSerialfifotail1 = 0U; - - USART_SendData(USART1, c); - } else { // if there's no more data to transmit then turn off TX interrupts - USART_ITConfig(USART1, USART_IT_TXE, DISABLE); - } - - USART_ClearITPendingBit(USART1, USART_IT_TXE); - } + m_USART1.handleIRQ(); } void InitUSART1(int speed) @@ -201,41 +100,7 @@ void InitUSART1(int speed) USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); - // initialize the fifos - TXSerialfifoinit1(); - RXSerialfifoinit1(); -} - -uint8_t AvailUSART1() -{ - if (RXSerialfifolevel1() > 0U) - return 1U; - else - return 0U; -} - -int AvailForWriteUSART1() -{ - return TX_SERIAL_FIFO_SIZE - TXSerialfifolevel1(); -} - -uint8_t ReadUSART1() -{ - uint8_t data_c = RXSerialfifo1[RXSerialfifotail1]; - - RXSerialfifotail1++; - if (RXSerialfifotail1 >= RX_SERIAL_FIFO_SIZE) - RXSerialfifotail1 = 0U; - - return data_c; -} - -void WriteUSART1(const uint8_t* data, uint16_t length) -{ - for (uint16_t i = 0U; i < length; i++) - TXSerialfifoput1(data[i]); - - USART_ITConfig(USART1, USART_IT_TXE, ENABLE); + m_USART1.init(USART1); } #endif @@ -243,111 +108,12 @@ void WriteUSART1(const uint8_t* data, uint16_t length) /* ************* USART2 ***************** */ #if defined(STM32F4_NUCLEO) || defined(STM32F4_RPT_HAT_TGO) || defined(DRCC_DVM) -volatile uint8_t TXSerialfifo2[TX_SERIAL_FIFO_SIZE]; -volatile uint8_t RXSerialfifo2[RX_SERIAL_FIFO_SIZE]; -volatile uint16_t TXSerialfifohead2, TXSerialfifotail2; -volatile uint16_t RXSerialfifohead2, RXSerialfifotail2; +static CSTMUART m_USART2; -// Init queues -void TXSerialfifoinit2() -{ - TXSerialfifohead2 = 0U; - TXSerialfifotail2 = 0U; -} - -void RXSerialfifoinit2() -{ - RXSerialfifohead2 = 0U; - RXSerialfifotail2 = 0U; -} - -// How full is queue -// TODO decide if how full or how empty is preferred info to return -uint16_t TXSerialfifolevel2() -{ - uint32_t tail = TXSerialfifotail2; - uint32_t head = TXSerialfifohead2; - - if (tail > head) - return TX_SERIAL_FIFO_SIZE + head - tail; - else - return head - tail; -} - -uint16_t RXSerialfifolevel2() -{ - uint32_t tail = RXSerialfifotail2; - uint32_t head = RXSerialfifohead2; - - if (tail > head) - return RX_SERIAL_FIFO_SIZE + head - tail; - else - return head - tail; -} - -// Flushes the transmit shift register -// warning: this call is blocking -void TXSerialFlush2() -{ - // wait until the TXE shows the shift register is empty - while (USART_GetITStatus(USART2, USART_FLAG_TXE)) - ; -} - -uint8_t TXSerialfifoput2(uint8_t next) -{ - if (TXSerialfifolevel2() < TX_SERIAL_FIFO_SIZE) { - TXSerialfifo2[TXSerialfifohead2] = next; - - TXSerialfifohead2++; - if (TXSerialfifohead2 >= TX_SERIAL_FIFO_SIZE) - TXSerialfifohead2 = 0U; - - // make sure transmit interrupts are enabled as long as there is data to send - USART_ITConfig(USART2, USART_IT_TXE, ENABLE); - return 1U; - } else { - return 0U; // signal an overflow occurred by returning a zero count - } -} void USART2_IRQHandler() { - uint8_t c; - - if (USART_GetITStatus(USART2, USART_IT_RXNE)) { - c = (uint8_t) USART_ReceiveData(USART2); - - if (RXSerialfifolevel2() < RX_SERIAL_FIFO_SIZE) { - RXSerialfifo2[RXSerialfifohead2] = c; - - RXSerialfifohead2++; - if (RXSerialfifohead2 >= RX_SERIAL_FIFO_SIZE) - RXSerialfifohead2 = 0U; - } else { - // TODO - do something if rx fifo is full? - } - - USART_ClearITPendingBit(USART2, USART_IT_RXNE); - } - - if (USART_GetITStatus(USART2, USART_IT_TXE)) { - c = 0U; - - if (TXSerialfifohead2 != TXSerialfifotail2) { // if the fifo is not empty - c = TXSerialfifo2[TXSerialfifotail2]; - - TXSerialfifotail2++; - if (TXSerialfifotail2 >= TX_SERIAL_FIFO_SIZE) - TXSerialfifotail2 = 0U; - - USART_SendData(USART2, c); - } else { // if there's no more data to transmit then turn off TX interrupts - USART_ITConfig(USART2, USART_IT_TXE, DISABLE); - } - - USART_ClearITPendingBit(USART2, USART_IT_TXE); - } + m_USART2.handleIRQ(); } void InitUSART2(int speed) @@ -392,41 +158,7 @@ void InitUSART2(int speed) USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); - // initialize the fifos - TXSerialfifoinit2(); - RXSerialfifoinit2(); -} - -uint8_t AvailUSART2() -{ - if (RXSerialfifolevel2() > 0U) - return 1U; - else - return 0U; -} - -int AvailForWriteUSART2() -{ - return TX_SERIAL_FIFO_SIZE - TXSerialfifolevel2(); -} - -uint8_t ReadUSART2() -{ - uint8_t data_c = RXSerialfifo2[RXSerialfifotail2]; - - RXSerialfifotail2++; - if (RXSerialfifotail2 >= RX_SERIAL_FIFO_SIZE) - RXSerialfifotail2 = 0U; - - return data_c; -} - -void WriteUSART2(const uint8_t* data, uint16_t length) -{ - for (uint16_t i = 0U; i < length; i++) - TXSerialfifoput2(data[i]); - - USART_ITConfig(USART2, USART_IT_TXE, ENABLE); + m_USART2.init(USART2); } #endif @@ -434,111 +166,11 @@ void WriteUSART2(const uint8_t* data, uint16_t length) /* ************* USART3 ***************** */ #if defined(STM32F4_DISCOVERY) || defined(STM32F7_NUCLEO) -volatile uint8_t TXSerialfifo3[TX_SERIAL_FIFO_SIZE]; -volatile uint8_t RXSerialfifo3[RX_SERIAL_FIFO_SIZE]; -volatile uint16_t TXSerialfifohead3, TXSerialfifotail3; -volatile uint16_t RXSerialfifohead3, RXSerialfifotail3; - -// Init queues -void TXSerialfifoinit3() -{ - TXSerialfifohead3 = 0U; - TXSerialfifotail3 = 0U; -} - -void RXSerialfifoinit3() -{ - RXSerialfifohead3 = 0U; - RXSerialfifotail3 = 0U; -} - -// How full is queue -// TODO decide if how full or how empty is preferred info to return -uint16_t TXSerialfifolevel3() -{ - uint32_t tail = TXSerialfifotail3; - uint32_t head = TXSerialfifohead3; - - if (tail > head) - return TX_SERIAL_FIFO_SIZE + head - tail; - else - return head - tail; -} - -uint16_t RXSerialfifolevel3() -{ - uint32_t tail = RXSerialfifotail3; - uint32_t head = RXSerialfifohead3; - - if (tail > head) - return RX_SERIAL_FIFO_SIZE + head - tail; - else - return head - tail; -} - -// Flushes the transmit shift register -// warning: this call is blocking -void TXSerialFlush3() -{ - // wait until the TXE shows the shift register is empty - while (USART_GetITStatus(USART3, USART_FLAG_TXE)) - ; -} - -uint8_t TXSerialfifoput3(uint8_t next) -{ - if (TXSerialfifolevel3() < TX_SERIAL_FIFO_SIZE) { - TXSerialfifo3[TXSerialfifohead3] = next; - - TXSerialfifohead3++; - if (TXSerialfifohead3 >= TX_SERIAL_FIFO_SIZE) - TXSerialfifohead3 = 0U; - - // make sure transmit interrupts are enabled as long as there is data to send - USART_ITConfig(USART3, USART_IT_TXE, ENABLE); - return 1U; - } else { - return 0U; // signal an overflow occurred by returning a zero count - } -} +static CSTMUART m_USART3; void USART3_IRQHandler() { - uint8_t c; - - if (USART_GetITStatus(USART3, USART_IT_RXNE)) { - c = (uint8_t) USART_ReceiveData(USART3); - - if (RXSerialfifolevel3() < RX_SERIAL_FIFO_SIZE) { - RXSerialfifo3[RXSerialfifohead3] = c; - - RXSerialfifohead3++; - if (RXSerialfifohead3 >= RX_SERIAL_FIFO_SIZE) - RXSerialfifohead3 = 0U; - } else { - // TODO - do something if rx fifo is full? - } - - USART_ClearITPendingBit(USART3, USART_IT_RXNE); - } - - if (USART_GetITStatus(USART3, USART_IT_TXE)) { - c = 0U; - - if (TXSerialfifohead3 != TXSerialfifotail3) { // if the fifo is not empty - c = TXSerialfifo3[TXSerialfifotail3]; - - TXSerialfifotail3++; - if (TXSerialfifotail3 >= TX_SERIAL_FIFO_SIZE) - TXSerialfifotail3 = 0U; - - USART_SendData(USART3, c); - } else { // if there's no more data to transmit then turn off TX interrupts - USART_ITConfig(USART3, USART_IT_TXE, DISABLE); - } - - USART_ClearITPendingBit(USART3, USART_IT_TXE); - } + m_USART3.handleIRQ(); } #if defined(STM32F7_NUCLEO) @@ -600,41 +232,7 @@ void InitUSART3(int speed) USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); - // initialize the fifos - TXSerialfifoinit3(); - RXSerialfifoinit3(); -} - -uint8_t AvailUSART3() -{ - if (RXSerialfifolevel3() > 0U) - return 1U; - else - return 0U; -} - -int AvailForWriteUSART3() -{ - return TX_SERIAL_FIFO_SIZE - TXSerialfifolevel3(); -} - -uint8_t ReadUSART3() -{ - uint8_t data_c = RXSerialfifo3[RXSerialfifotail3]; - - RXSerialfifotail3++; - if (RXSerialfifotail3 >= RX_SERIAL_FIFO_SIZE) - RXSerialfifotail3 = 0U; - - return data_c; -} - -void WriteUSART3(const uint8_t* data, uint16_t length) -{ - for (uint16_t i = 0U; i < length; i++) - TXSerialfifoput3(data[i]); - - USART_ITConfig(USART3, USART_IT_TXE, ENABLE); + m_USART3.init(USART3); } #endif @@ -642,111 +240,11 @@ void WriteUSART3(const uint8_t* data, uint16_t length) /* ************* UART5 ***************** */ #if !(defined(STM32F4_NUCLEO) && defined(STM32F4_NUCLEO_ARDUINO_HEADER)) -volatile uint8_t TXSerialfifo5[TX_SERIAL_FIFO_SIZE]; -volatile uint8_t RXSerialfifo5[RX_SERIAL_FIFO_SIZE]; -volatile uint16_t TXSerialfifohead5, TXSerialfifotail5; -volatile uint16_t RXSerialfifohead5, RXSerialfifotail5; - -// Init queues -void TXSerialfifoinit5() -{ - TXSerialfifohead5 = 0U; - TXSerialfifotail5 = 0U; -} - -void RXSerialfifoinit5() -{ - RXSerialfifohead5 = 0U; - RXSerialfifotail5 = 0U; -} - -// How full is queue -// TODO decide if how full or how empty is preferred info to return -uint16_t TXSerialfifolevel5() -{ - uint32_t tail = TXSerialfifotail5; - uint32_t head = TXSerialfifohead5; - - if (tail > head) - return TX_SERIAL_FIFO_SIZE + head - tail; - else - return head - tail; -} - -uint16_t RXSerialfifolevel5() -{ - uint32_t tail = RXSerialfifotail5; - uint32_t head = RXSerialfifohead5; - - if (tail > head) - return RX_SERIAL_FIFO_SIZE + head - tail; - else - return head - tail; -} - -// Flushes the transmit shift register -// warning: this call is blocking -void TXSerialFlush5() -{ - // wait until the TXE shows the shift register is empty - while (USART_GetITStatus(UART5, USART_FLAG_TXE)) - ; -} - -uint8_t TXSerialfifoput5(uint8_t next) -{ - if (TXSerialfifolevel5() < TX_SERIAL_FIFO_SIZE) { - TXSerialfifo5[TXSerialfifohead5] = next; - - TXSerialfifohead5++; - if (TXSerialfifohead5 >= TX_SERIAL_FIFO_SIZE) - TXSerialfifohead5 = 0U; - - // make sure transmit interrupts are enabled as long as there is data to send - USART_ITConfig(UART5, USART_IT_TXE, ENABLE); - return 1U; - } else { - return 0U; // signal an overflow occurred by returning a zero count - } -} +static CSTMUART m_UART5; void UART5_IRQHandler() { - uint8_t c; - - if (USART_GetITStatus(UART5, USART_IT_RXNE)) { - c = (uint8_t) USART_ReceiveData(UART5); - - if (RXSerialfifolevel5() < RX_SERIAL_FIFO_SIZE) { - RXSerialfifo5[RXSerialfifohead5] = c; - - RXSerialfifohead5++; - if (RXSerialfifohead5 >= RX_SERIAL_FIFO_SIZE) - RXSerialfifohead5 = 0U; - } else { - // TODO - do something if rx fifo is full? - } - - USART_ClearITPendingBit(UART5, USART_IT_RXNE); - } - - if (USART_GetITStatus(UART5, USART_IT_TXE)) { - c = 0U; - - if (TXSerialfifohead5 != TXSerialfifotail5) { // if the fifo is not empty - c = TXSerialfifo5[TXSerialfifotail5]; - - TXSerialfifotail5++; - if (TXSerialfifotail5 >= TX_SERIAL_FIFO_SIZE) - TXSerialfifotail5 = 0U; - - USART_SendData(UART5, c); - } else { // if there's no more data to transmit then turn off TX interrupts - USART_ITConfig(UART5, USART_IT_TXE, DISABLE); - } - - USART_ClearITPendingBit(UART5, USART_IT_TXE); - } + m_UART5.handleIRQ(); } void InitUART5(int speed) @@ -795,41 +293,7 @@ void InitUART5(int speed) USART_ITConfig(UART5, USART_IT_RXNE, ENABLE); - // initialize the fifos - TXSerialfifoinit5(); - RXSerialfifoinit5(); -} - -uint8_t AvailUART5() -{ - if (RXSerialfifolevel5() > 0U) - return 1U; - else - return 0U; -} - -int AvailForWriteUART5() -{ - return TX_SERIAL_FIFO_SIZE - TXSerialfifolevel5(); -} - -uint8_t ReadUART5() -{ - uint8_t data_c = RXSerialfifo5[RXSerialfifotail5]; - - RXSerialfifotail5++; - if (RXSerialfifotail5 >= RX_SERIAL_FIFO_SIZE) - RXSerialfifotail5 = 0U; - - return data_c; -} - -void WriteUART5(const uint8_t* data, uint16_t length) -{ - for (uint16_t i = 0U; i < length; i++) - TXSerialfifoput5(data[i]); - - USART_ITConfig(UART5, USART_IT_TXE, ENABLE); + m_UART5.init(UART5); } #endif @@ -868,21 +332,21 @@ int CSerialPort::availableInt(uint8_t n) switch (n) { case 1U: #if defined(STM32F4_DISCOVERY) || defined(STM32F7_NUCLEO) - return AvailUSART3(); + return m_USART3.availble();//AvailUSART3(); #elif defined(STM32F4_PI) || defined(STM32F4_F4M) || defined(STM32F722_PI) || defined(STM32F722_F7M) || defined(STM32F722_RPT_HAT) || defined(STM32F4_DVM) || defined(STM32F4_EDA_405) || defined(STM32F4_EDA_446) - return AvailUSART1(); + return m_USART1.available();//AvailUSART1(); #elif defined(STM32F4_NUCLEO) || defined(STM32F4_RPT_HAT_TGO) - return AvailUSART2(); + return m_USART2.available();//AvailUSART2(); #elif defined(DRCC_DVM) - return AvailUSART1(); + return m_USART1.available();//AvailUSART1(); #endif case 3U: #if defined(STM32F4_NUCLEO) && defined(STM32F4_NUCLEO_ARDUINO_HEADER) - return AvailUSART1(); + return m_USART1.available(); //AvailUSART1(); #elif defined(DRCC_DVM) - return AvailUSART2(); + return m_USART2.available(); //AvailUSART2(); #else - return AvailUART5(); + return m_UART5.available();//AvailUART5(); #endif default: return 0; @@ -894,21 +358,21 @@ int CSerialPort::availableForWriteInt(uint8_t n) switch (n) { case 1U: #if defined(STM32F4_DISCOVERY) || defined(STM32F7_NUCLEO) - return AvailForWriteUSART3(); + return m_USART3.availableForWrite(); //AvailForWriteUSART3(); #elif defined(STM32F4_PI) || defined(STM32F4_F4M) || defined(STM32F722_PI) || defined(STM32F722_F7M) || defined(STM32F722_RPT_HAT) || defined(STM32F4_DVM) || defined(STM32F4_EDA_405) || defined(STM32F4_EDA_446) - return AvailForWriteUSART1(); + return m_USART1.availableForWrite(); //AvailForWriteUSART1(); #elif defined(STM32F4_NUCLEO) || defined(STM32F4_RPT_HAT_TGO) - return AvailForWriteUSART2(); + return m_USART2.availableForWrite();//AvailForWriteUSART2(); #elif defined(DRCC_DVM) - return AvailForWriteUSART1(); + return m_USART1.availableForWrite();//AvailForWriteUSART1(); #endif case 3U: #if defined(STM32F4_NUCLEO) && defined(STM32F4_NUCLEO_ARDUINO_HEADER) - return AvailForWriteUSART1(); + return m_USART1.availableForWrite(); //AvailForWriteUSART1(); #elif defined(DRCC_DVM) - return AvailForWriteUSART2(); + return m_USART2.availableForWrite();//AvailForWriteUSART2(); #else - return AvailForWriteUART5(); + return m_UART5.availableForWrite();//AvailForWriteUART5(); #endif default: return 0; @@ -920,21 +384,21 @@ uint8_t CSerialPort::readInt(uint8_t n) switch (n) { case 1U: #if defined(STM32F4_DISCOVERY) || defined(STM32F7_NUCLEO) - return ReadUSART3(); + return m_USART3.read();//ReadUSART3(); #elif defined(STM32F4_PI) || defined(STM32F4_F4M) || defined(STM32F722_PI) || defined(STM32F722_F7M) || defined(STM32F722_RPT_HAT) || defined(STM32F4_DVM) || defined(STM32F4_EDA_405) || defined(STM32F4_EDA_446) - return ReadUSART1(); + return m_USART1.read();//ReadUSART1(); #elif defined(STM32F4_NUCLEO) || defined(STM32F4_RPT_HAT_TGO) - return ReadUSART2(); + return m_USART2.read();//ReadUSART2(); #elif defined(DRCC_DVM) - return ReadUSART1(); + return m_USART1.read();//ReadUSART1(); #endif case 3U: #if defined(STM32F4_NUCLEO) && defined(STM32F4_NUCLEO_ARDUINO_HEADER) - return ReadUSART1(); + return m_USART1.read();//ReadUSART1(); #elif defined(DRCC_DVM) - return ReadUSART2(); + return m_USART2.read();//ReadUSART2(); #else - return ReadUART5(); + return m_UART5.read();//ReadUART5(); #endif default: return 0U; @@ -946,36 +410,36 @@ void CSerialPort::writeInt(uint8_t n, const uint8_t* data, uint16_t length, bool switch (n) { case 1U: #if defined(STM32F4_DISCOVERY) || defined(STM32F7_NUCLEO) - WriteUSART3(data, length); + m_USART3.write(data, length); //WriteUSART3(data, length); if (flush) - TXSerialFlush3(); + m_USART3.flush();//TXSerialFlush3(); #elif defined(STM32F4_PI) || defined(STM32F4_F4M) || defined(STM32F722_PI) || defined(STM32F722_F7M) || defined(STM32F722_RPT_HAT) || defined(STM32F4_DVM) || defined(STM32F4_EDA_405) || defined(STM32F4_EDA_446) - WriteUSART1(data, length); + m_USART1.write(data, length);//WriteUSART1(data, length); if (flush) - TXSerialFlush1(); + m_USART1.flush();//TXSerialFlush1(); #elif defined(STM32F4_NUCLEO) || defined(STM32F4_RPT_HAT_TGO) - WriteUSART2(data, length); + m_USART2.write(data, length);//WriteUSART2(data, length); if (flush) - TXSerialFlush2(); + m_USART2.flush();//TXSerialFlush2(); #elif defined(DRCC_DVM) - WriteUSART1(data, length); + m_USART1.write(data, length);//WriteUSART1(data, length); if (flush) - TXSerialFlush1(); + m_USART1.flush();//TXSerialFlush1(); #endif break; case 3U: #if defined(STM32F4_NUCLEO) && defined(STM32F4_NUCLEO_ARDUINO_HEADER) - WriteUSART1(data, length); + m_USART1.write(data, length); //WriteUSART1(data, length); if (flush) - TXSerialFlush1(); + m_USART1.flush(); #elif defined(DRCC_DVM) - WriteUSART2(data, length); + m_USART2.write(data, length);//WriteUSART2(data, length); if (flush) - TXSerialFlush2(); + m_USART2.flush();//TXSerialFlush2(); #else - WriteUART5(data, length); + m_UART5.write(data, length);//WriteUART5(data, length); if (flush) - TXSerialFlush5(); + m_UART5.flush();//TXSerialFlush5(); #endif break; default: @@ -984,3 +448,4 @@ void CSerialPort::writeInt(uint8_t n, const uint8_t* data, uint16_t length, bool } #endif + diff --git a/Tools/FMGenerateFilterCoefficients.py b/Tools/FMGenerateFilterCoefficients.py index 7d8a3db..4eb81d8 100644 --- a/Tools/FMGenerateFilterCoefficients.py +++ b/Tools/FMGenerateFilterCoefficients.py @@ -17,30 +17,30 @@ f2 = 2700 rp = 0.2 # scaling factor in bits, do not change ! -q = 0 +q = 15 # 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 +#os = signal.cheby1(4, rp, f2, '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) +sosrounded = np.round((sos) * scaling_factor) # print coefficients -for biquad in sos: +for biquad in sosrounded: for coeff in biquad: - #print(int(coeff),",",sep="",end="") - print((coeff),",",sep="",end="") + 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) +w,h = signal.freqz(b,a, worN=2048) 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.ylim(top=1,bottom=-30); pl.xlim(left=250, right=12000); pl.show() \ No newline at end of file diff --git a/YSFTX.cpp b/YSFTX.cpp index 090445a..cb6f8d4 100644 --- a/YSFTX.cpp +++ b/YSFTX.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2018 by Jonathan Naylor G4KLX + * Copyright (C) 2009-2018,2020 by Jonathan Naylor G4KLX * Copyright (C) 2017 by Andy Uribe CA6JAU * * This program is free software; you can redistribute it and/or modify @@ -73,7 +73,8 @@ void CYSFTX::process() m_poBuffer[m_poLen++] = YSF_START_SYNC; } else { for (uint8_t i = 0U; i < YSF_FRAME_LENGTH_BYTES; i++) { - uint8_t c = m_buffer.get(); + uint8_t c = 0U; + m_buffer.get(c); m_poBuffer[m_poLen++] = c; } } @@ -116,7 +117,7 @@ void CYSFTX::process() } } -uint8_t CYSFTX::writeData(const uint8_t* data, uint8_t length) +uint8_t CYSFTX::writeData(const uint8_t* data, uint16_t length) { if (length != (YSF_FRAME_LENGTH_BYTES + 1U)) return 4U; diff --git a/YSFTX.h b/YSFTX.h index 647767c..3a4fad5 100644 --- a/YSFTX.h +++ b/YSFTX.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2020 by Jonathan Naylor G4KLX * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,13 +21,13 @@ #include "Config.h" -#include "SerialRB.h" +#include "RingBuffer.h" class CYSFTX { public: CYSFTX(); - uint8_t writeData(const uint8_t* data, uint8_t length); + uint8_t writeData(const uint8_t* data, uint16_t length); void process(); @@ -38,7 +38,7 @@ public: void setParams(bool on, uint8_t txHang); private: - CSerialRB m_buffer; + CRingBuffer m_buffer; arm_fir_interpolate_instance_q15 m_modFilter; q15_t m_modState[16U]; // blockSize + phaseLength - 1, 4 + 9 - 1 plus some spare uint8_t m_poBuffer[1200U];