Add AX.25 Slot Time and P-Persist processing for simplex work.

This commit is contained in:
Jonathan Naylor 2020-07-01 12:33:08 +01:00
parent 2c6e2c2a11
commit 29c8cf9414
6 changed files with 163 additions and 6 deletions

View File

@ -41,6 +41,18 @@ q15_t LPF_FILTER_COEFFS[] = {
-34, -47, -47, -40, -28, -17, -8, -2 -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. // 64 Hz loop filter.
// scipy.signal: // scipy.signal:
// loop_coeffs = firwin(9, [64.0/(1200/2)], width = None, // loop_coeffs = firwin(9, [64.0/(1200/2)], width = None,
@ -63,6 +75,9 @@ m_pllState(),
m_pllLast(false), m_pllLast(false),
m_pllBits(1U), m_pllBits(1U),
m_pllCount(0.0F), m_pllCount(0.0F),
m_pllJitter(0.0F),
m_pllDCD(false),
m_iirHistory(),
m_hdlcOnes(0U), m_hdlcOnes(0U),
m_hdlcFlag(false), m_hdlcFlag(false),
m_hdlcBuffer(0U), m_hdlcBuffer(0U),
@ -78,6 +93,9 @@ m_hdlcState(AX25_IDLE)
m_pllFilter.numTaps = PLL_FILTER_LEN; m_pllFilter.numTaps = PLL_FILTER_LEN;
m_pllFilter.pState = m_pllState; m_pllFilter.pState = m_pllState;
m_pllFilter.pCoeffs = PLL_FILTER_COEFFS; 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 CAX25Demodulator::process(q15_t* samples, uint8_t length, CAX25Frame& frame)
@ -154,10 +172,20 @@ bool CAX25Demodulator::PLL(bool input)
if (m_pllCount > PLL_LIMIT) if (m_pllCount > PLL_LIMIT)
m_pllCount -= SAMPLES_PER_SYMBOL; 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 offset = m_pllCount / float32_t(m_pllBits);
float32_t jitter; float32_t jitter;
::arm_fir_f32(&m_pllFilter, &offset, &jitter, 1U); ::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_pllCount -= jitter / 2.0F;
m_pllBits = 1U; m_pllBits = 1U;
} else { } else {
@ -260,3 +288,29 @@ void CAX25Demodulator::setTwist(int8_t n)
m_twist.setTwist(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;
}

View File

@ -38,6 +38,8 @@ public:
void setTwist(int8_t n); void setTwist(int8_t n);
bool isDCD();
private: private:
CAX25Frame m_frame; CAX25Frame m_frame;
CAX25Twist m_twist; CAX25Twist m_twist;
@ -51,6 +53,9 @@ private:
bool m_pllLast; bool m_pllLast;
uint8_t m_pllBits; uint8_t m_pllBits;
float32_t m_pllCount; float32_t m_pllCount;
float32_t m_pllJitter;
bool m_pllDCD;
float32_t m_iirHistory[5U];
uint16_t m_hdlcOnes; uint16_t m_hdlcOnes;
bool m_hdlcFlag; bool m_hdlcFlag;
uint16_t m_hdlcBuffer; uint16_t m_hdlcBuffer;
@ -61,6 +66,7 @@ private:
bool NRZI(bool b); bool NRZI(bool b);
bool PLL(bool b); bool PLL(bool b);
bool HDLC(bool b); bool HDLC(bool b);
float32_t iir(float32_t input);
}; };
#endif #endif

View File

@ -65,11 +65,21 @@ m_demod1(3),
m_demod2(6), m_demod2(6),
m_demod3(9), m_demod3(9),
m_lastFCS(0U), 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.numTaps = FILTER_LEN;
m_filter.pState = m_state; m_filter.pState = m_state;
m_filter.pCoeffs = FILTER_COEFFS; m_filter.pCoeffs = FILTER_COEFFS;
initRand();
} }
void CAX25RX::samples(q15_t* samples, uint8_t length) 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"); 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_demod1.setTwist(twist - 3);
m_demod2.setTwist(twist); m_demod2.setTwist(twist);
m_demod3.setTwist(twist + 3); 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
}

View File

@ -29,7 +29,9 @@ public:
void samples(q15_t* samples, uint8_t length); 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: private:
arm_fir_instance_q15 m_filter; arm_fir_instance_q15 m_filter;
@ -39,6 +41,17 @@ private:
CAX25Demodulator m_demod3; CAX25Demodulator m_demod3;
uint16_t m_lastFCS; uint16_t m_lastFCS;
uint32_t m_count; 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 #endif

View File

@ -63,6 +63,14 @@ void CAX25TX::process()
if (m_poLen == 0U) if (m_poLen == 0U)
return; return;
if (!m_duplex) {
if (m_poPtr == 0U) {
bool tx = ax25RX.canTX();
if (!tx)
return;
}
}
uint16_t space = io.getSpace(); uint16_t space = io.getSpace();
while (space > AX25_RADIO_SYMBOL_LENGTH) { while (space > AX25_RADIO_SYMBOL_LENGTH) {

View File

@ -109,7 +109,7 @@ const uint8_t MMDVM_DEBUG5 = 0xF5U;
#define HW_TYPE "MMDVM" #define HW_TYPE "MMDVM"
#endif #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) #if defined(GITVERSION)
#define concat(h, a, b, c) h " " a " " b " GitID #" c "" #define concat(h, a, b, c) h " " a " " b " GitID #" c ""
@ -282,7 +282,7 @@ void CSerialPort::getVersion()
uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length) uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length)
{ {
if (length < 24U) if (length < 26U)
return 4U; return 4U;
bool rxInvert = (data[0U] & 0x01U) == 0x01U; 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 ax25TXDelay = data[23U];
uint8_t ax25SlotTime = data[24U];
uint8_t ax25PPersist = data[25U];
setMode(modemState); setMode(modemState);
m_dstarEnable = dstarEnable; m_dstarEnable = dstarEnable;
@ -391,7 +395,7 @@ uint8_t CSerialPort::setConfig(const uint8_t* data, uint16_t length)
ysfTX.setParams(ysfLoDev, ysfTXHang); ysfTX.setParams(ysfLoDev, ysfTXHang);
p25TX.setParams(p25TXHang); p25TX.setParams(p25TXHang);
nxdnTX.setParams(nxdnTXHang); 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); io.setParameters(rxInvert, txInvert, pttInvert, rxLevel, cwIdTXLevel, dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, pocsagTXLevel, fmTXLevel, ax25TXLevel, txDCOffset, rxDCOffset);