mirror of https://github.com/g4klx/MMDVM.git
First version of the AX25 TX functionality.
This commit is contained in:
parent
7ad70d04a4
commit
3de10fe98b
|
@ -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 = 294U; // Callsign (7) + Callsign (7) + 3 Digipeaters (21) +
|
||||||
|
// Control (1) + Data (256) + Checksum (2)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "AX25Demodulator.h"
|
#include "AX25Demodulator.h"
|
||||||
|
#include "AX25Defines.h"
|
||||||
|
|
||||||
const float32_t SAMPLE_RATE = 24000.0F;
|
const float32_t SAMPLE_RATE = 24000.0F;
|
||||||
const float32_t SYMBOL_RATE = 1200.0F;
|
const float32_t SYMBOL_RATE = 1200.0F;
|
||||||
|
@ -178,7 +179,7 @@ bool CAX25Demodulator::PLL(bool input)
|
||||||
|
|
||||||
bool CAX25Demodulator::HDLC(bool b)
|
bool CAX25Demodulator::HDLC(bool b)
|
||||||
{
|
{
|
||||||
if (m_hdlcOnes == 5U) {
|
if (m_hdlcOnes == AX25_MAX_ONES) {
|
||||||
if (b) {
|
if (b) {
|
||||||
// flag byte
|
// flag byte
|
||||||
m_hdlcFlag = true;
|
m_hdlcFlag = true;
|
||||||
|
@ -203,8 +204,8 @@ bool CAX25Demodulator::HDLC(bool b)
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|
||||||
switch (m_hdlcBuffer) {
|
switch (m_hdlcBuffer) {
|
||||||
case 0x7E:
|
case AX25_FRAME_END:
|
||||||
if (m_frame.m_length >= 17U) {
|
if (m_frame.m_length >= AX25_MIN_FRAME_LENGTH) {
|
||||||
result = m_frame.checkCRC();
|
result = m_frame.checkCRC();
|
||||||
if (!result)
|
if (!result)
|
||||||
m_frame.m_length = 0U;
|
m_frame.m_length = 0U;
|
||||||
|
@ -216,7 +217,7 @@ bool CAX25Demodulator::HDLC(bool b)
|
||||||
m_hdlcBits = 0U;
|
m_hdlcBits = 0U;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xFE:
|
case AX25_FRAME_ABORT:
|
||||||
// Frame aborted
|
// Frame aborted
|
||||||
m_frame.m_length = 0U;
|
m_frame.m_length = 0U;
|
||||||
m_hdlcState = AX25_IDLE;
|
m_hdlcState = AX25_IDLE;
|
||||||
|
|
|
@ -54,6 +54,15 @@ const uint16_t CCITT_TABLE[] = {
|
||||||
0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,
|
0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330,
|
||||||
0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78 };
|
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() :
|
CAX25Frame::CAX25Frame() :
|
||||||
m_data(),
|
m_data(),
|
||||||
m_length(0U),
|
m_length(0U),
|
||||||
|
@ -92,3 +101,22 @@ bool CAX25Frame::checkCRC()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,15 @@ const uint16_t AX25_MAX_PACKET_LEN = 300U;
|
||||||
|
|
||||||
class CAX25Frame {
|
class CAX25Frame {
|
||||||
public:
|
public:
|
||||||
|
CAX25Frame(const uint8_t* data, uint16_t length);
|
||||||
CAX25Frame();
|
CAX25Frame();
|
||||||
|
|
||||||
bool append(uint16_t c);
|
bool append(uint16_t c);
|
||||||
|
|
||||||
bool checkCRC();
|
bool checkCRC();
|
||||||
|
|
||||||
|
void addCRC();
|
||||||
|
|
||||||
uint8_t m_data[AX25_MAX_PACKET_LEN];
|
uint8_t m_data[AX25_MAX_PACKET_LEN];
|
||||||
uint16_t m_length;
|
uint16_t m_length;
|
||||||
uint16_t m_fcs;
|
uint16_t m_fcs;
|
||||||
|
|
134
AX25TX.cpp
134
AX25TX.cpp
|
@ -20,30 +20,160 @@
|
||||||
#include "Globals.h"
|
#include "Globals.h"
|
||||||
#include "AX25TX.h"
|
#include "AX25TX.h"
|
||||||
|
|
||||||
|
#include "AX25Defines.h"
|
||||||
|
#include "AX25Frame.h"
|
||||||
|
|
||||||
CAX25TX::CAX25TX()
|
|
||||||
|
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_twist(-6),
|
||||||
|
m_poBuffer(),
|
||||||
|
m_poLen(0U),
|
||||||
|
m_poPtr(0U),
|
||||||
|
m_txDelay(120U),
|
||||||
|
m_tablePtr(0U),
|
||||||
|
m_nrzi(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAX25TX::process()
|
void CAX25TX::process()
|
||||||
{
|
{
|
||||||
|
if (m_poLen == 0U)
|
||||||
|
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)
|
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 (already NRZI)
|
||||||
|
for (uint16_t i = 0U; i < m_txDelay; i++, m_poLen++)
|
||||||
|
WRITE_BIT1(m_poBuffer, m_poLen, false);
|
||||||
|
|
||||||
|
// 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(START_FLAG, i) != 0U;
|
||||||
|
bool b2 = NRZI(b1);
|
||||||
|
WRITE_BIT1(m_poBuffer, m_poLen, b2);
|
||||||
|
m_poLen++;
|
||||||
|
|
||||||
|
if (b1) {
|
||||||
|
ones++;
|
||||||
|
if (ones == AX25_MAX_ONES) {
|
||||||
|
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;
|
return 0U;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CAX25TX::writeBit(bool b)
|
||||||
|
{
|
||||||
|
q15_t in[AX25_RADIO_SYMBOL_LENGTH];
|
||||||
|
for (uint8_t i = 0U; i < AX25_RADIO_SYMBOL_LENGTH; i++) {
|
||||||
|
in[i] = AUDIO_TABLE_DATA[m_tablePtr];
|
||||||
|
if (b)
|
||||||
|
m_tablePtr += 6U;
|
||||||
|
else
|
||||||
|
m_tablePtr += 11U;
|
||||||
|
|
||||||
|
if (m_tablePtr >= AUDIO_TABLE_LEN)
|
||||||
|
m_tablePtr -= AUDIO_TABLE_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
q15_t out[AX25_RADIO_SYMBOL_LENGTH];
|
||||||
|
m_twist.process(in, out, AX25_RADIO_SYMBOL_LENGTH);
|
||||||
|
|
||||||
|
io.write(STATE_AX25, out, AX25_RADIO_SYMBOL_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
void CAX25TX::setParams(int8_t twist)
|
void CAX25TX::setParams(int8_t twist)
|
||||||
{
|
{
|
||||||
|
m_twist.setTwist(twist);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAX25TX::setTXDelay(uint8_t delay)
|
void CAX25TX::setTXDelay(uint8_t delay)
|
||||||
{
|
{
|
||||||
|
m_txDelay = delay * 12U;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t CAX25TX::getSpace() const
|
uint8_t CAX25TX::getSpace() const
|
||||||
{
|
{
|
||||||
return 0U;
|
return m_poLen == 0U ? 255U : 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAX25TX::NRZI(bool b)
|
||||||
|
{
|
||||||
|
bool result = (b == m_nrzi);
|
||||||
|
|
||||||
|
m_nrzi = b;
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
AX25TX.h
12
AX25TX.h
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
|
||||||
#include "SerialRB.h"
|
#include "AX25Twist.h"
|
||||||
|
|
||||||
class CAX25TX {
|
class CAX25TX {
|
||||||
public:
|
public:
|
||||||
|
@ -38,6 +38,16 @@ public:
|
||||||
uint8_t getSpace() const;
|
uint8_t getSpace() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
CAX25Twist m_twist;
|
||||||
|
uint8_t m_poBuffer[560U];
|
||||||
|
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
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue