mirror of https://github.com/g4klx/MMDVM.git
Add AX.25 Slot Time and P-Persist processing for simplex work.
This commit is contained in:
parent
2c6e2c2a11
commit
29c8cf9414
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
76
AX25RX.cpp
76
AX25RX.cpp
|
@ -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
|
||||||
|
}
|
||||||
|
|
15
AX25RX.h
15
AX25RX.h
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue