From 29c8cf941451b34add208e1261f3c0c1ce0c5e8a Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Wed, 1 Jul 2020 12:33:08 +0100 Subject: [PATCH] Add AX.25 Slot Time and P-Persist processing for simplex work. --- AX25Demodulator.cpp | 54 ++++++++++++++++++++++++++++++++ AX25Demodulator.h | 6 ++++ AX25RX.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++-- AX25RX.h | 15 ++++++++- AX25TX.cpp | 8 +++++ SerialPort.cpp | 10 ++++-- 6 files changed, 163 insertions(+), 6 deletions(-) diff --git a/AX25Demodulator.cpp b/AX25Demodulator.cpp index 98f235c..df3e13c 100644 --- a/AX25Demodulator.cpp +++ b/AX25Demodulator.cpp @@ -41,6 +41,18 @@ q15_t LPF_FILTER_COEFFS[] = { -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, @@ -63,6 +75,9 @@ 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), @@ -78,6 +93,9 @@ m_hdlcState(AX25_IDLE) 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) @@ -154,10 +172,20 @@ bool CAX25Demodulator::PLL(bool 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); + if (!m_duplex) { + 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 { @@ -260,3 +288,29 @@ 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 index b6b87a4..d1baff8 100644 --- a/AX25Demodulator.h +++ b/AX25Demodulator.h @@ -38,6 +38,8 @@ public: void setTwist(int8_t n); + bool isDCD(); + private: CAX25Frame m_frame; CAX25Twist m_twist; @@ -51,6 +53,9 @@ private: 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; @@ -61,6 +66,7 @@ private: bool NRZI(bool b); bool PLL(bool b); bool HDLC(bool b); + float32_t iir(float32_t input); }; #endif diff --git a/AX25RX.cpp b/AX25RX.cpp index 2e2592b..4836701 100644 --- a/AX25RX.cpp +++ b/AX25RX.cpp @@ -65,11 +65,21 @@ m_demod1(3), m_demod2(6), m_demod3(9), m_lastFCS(0U), -m_count(0U) +m_count(0U), +m_slotTime(30U), +m_slotCount(0U), +m_pPersist(128U), +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) @@ -110,12 +120,74 @@ void CAX25RX::samples(q15_t* samples, uint8_t length) } DEBUG1("Decoder 3 reported"); } + + if (!m_duplex) { + 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) + m_canTX = false; + else + m_canTX = m_pPersist >= rand(); + } + } } -void CAX25RX::setParams(int8_t twist) +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 index d6e55c3..49186d2 100644 --- a/AX25RX.h +++ b/AX25RX.h @@ -29,7 +29,9 @@ public: void samples(q15_t* samples, uint8_t length); - void setParams(int8_t twist); + void setParams(int8_t twist, uint8_t slotTime, uint8_t pPersist); + + bool canTX() const; private: arm_fir_instance_q15 m_filter; @@ -39,6 +41,17 @@ private: 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_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 index fb3bd26..a5b6c95 100644 --- a/AX25TX.cpp +++ b/AX25TX.cpp @@ -63,6 +63,14 @@ 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) { diff --git a/SerialPort.cpp b/SerialPort.cpp index 2a3d209..e8d7812 100644 --- a/SerialPort.cpp +++ b/SerialPort.cpp @@ -109,7 +109,7 @@ const uint8_t MMDVM_DEBUG5 = 0xF5U; #define HW_TYPE "MMDVM" #endif -#define DESCRIPTION "20200630 (D-Star/DMR/System Fusion/P25/NXDN/POCSAG/FM/AX.25)" +#define DESCRIPTION "20200701 (D-Star/DMR/System Fusion/P25/NXDN/POCSAG/FM/AX.25)" #if defined(GITVERSION) #define concat(h, a, b, c) h " " a " " b " GitID #" c "" @@ -282,7 +282,7 @@ void CSerialPort::getVersion() uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) { - if (length < 24U) + if (length < 26U) return 4U; bool rxInvert = (data[0U] & 0x01U) == 0x01U; @@ -362,6 +362,10 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) uint8_t ax25TXDelay = data[23U]; + uint8_t ax25SlotTime = data[24U]; + + uint8_t ax25PPersist = data[25U]; + setMode(modemState); m_dstarEnable = dstarEnable; @@ -391,7 +395,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) ysfTX.setParams(ysfLoDev, ysfTXHang); p25TX.setParams(p25TXHang); nxdnTX.setParams(nxdnTXHang); - ax25RX.setParams(ax25RXTwist); + ax25RX.setParams(ax25RXTwist, ax25SlotTime, ax25PPersist); io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, pocsagTXLevel, fmTXLevel, ax25TXLevel, txDCOffset, rxDCOffset);