[AX25] Added AX.25 support
This commit is contained in:
parent
4fa214a0fd
commit
ddb478afff
8 changed files with 966 additions and 0 deletions
|
@ -29,6 +29,7 @@ RadioLib was originally created as a driver for [__RadioShield__](https://github
|
|||
* __HTTP__ for modules: ESP8266
|
||||
* __RTTY__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101 and nRF24L01
|
||||
* __Morse Code__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101 and nRF24L01
|
||||
* __AX.25__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231 and CC1101
|
||||
|
||||
### Supported platforms:
|
||||
* __AVR__ - tested with hardware on Uno, Mega and Leonardo
|
||||
|
|
167
examples/AX25/AX25_Frames/AX25_Frames.ino
Normal file
167
examples/AX25/AX25_Frames/AX25_Frames.ino
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
RadioLib AX.25 Frame Example
|
||||
|
||||
This example shows how to send various
|
||||
AX.25 frames using SX1278's FSK modem.
|
||||
|
||||
Other modules that can be used for AX.25:
|
||||
- SX127x/RFM9x
|
||||
- RF69
|
||||
- SX1231
|
||||
- CC1101
|
||||
- SX126x
|
||||
- nRF24
|
||||
|
||||
Using raw AX.25 frames requires some
|
||||
knowledge of the protocol, refer to
|
||||
AX25_Transmit for basic operation.
|
||||
Frames shown in this example are not
|
||||
exhaustive; all possible AX.25 frames
|
||||
should be supported.
|
||||
*/
|
||||
|
||||
// include the library
|
||||
#include <RadioLib.h>
|
||||
|
||||
// SX1278 has the following connections:
|
||||
// NSS pin: 10
|
||||
// DIO0 pin: 2
|
||||
// RESET pin: 9
|
||||
// DIO1 pin: 3
|
||||
SX1278 fsk = new Module(10, 2, 9, 3);
|
||||
|
||||
// or using RadioShield
|
||||
// https://github.com/jgromes/RadioShield
|
||||
//SX1278 fsk = RadioShield.ModuleA;
|
||||
|
||||
// create AX.25 client instance using the FSK module
|
||||
AX25Client ax25(&fsk);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
|
||||
// initialize SX1278
|
||||
Serial.print(F("[SX1278] Initializing ... "));
|
||||
// carrier frequency: 434.0 MHz
|
||||
// bit rate: 1.2 kbps (1200 baud AFSK AX.25)
|
||||
// frequency deviation: 0.5 kHz (1200 baud AFSK AX.25)
|
||||
int state = fsk.beginFSK(434.0, 1.2, 0.5);
|
||||
|
||||
// when using one of the non-LoRa modules for AX.25
|
||||
// (RF69, CC1101, etc.), use the basic begin() method
|
||||
// int state = fsk.begin();
|
||||
|
||||
if(state == ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
while(true);
|
||||
}
|
||||
|
||||
// initialize AX.25 client
|
||||
Serial.print(F("[AX.25] Initializing ... "));
|
||||
// source station callsign: "N7LEM"
|
||||
// source station SSID: 0
|
||||
// preamble length: 8 bytes
|
||||
state = ax25.begin("N7LEM");
|
||||
if(state == ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
while(true);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// create AX.25 Unnumbered Information frame
|
||||
// destination station callsign: "NJ7P"
|
||||
// destination station SSID: 0
|
||||
// source station callsign: "N7LEM"
|
||||
// source station SSID: 0
|
||||
// control field: UI, P/F not used, unnumbered frame
|
||||
// protocol identifier: no layer 3 protocol implemented
|
||||
// information field: "Hello World!"
|
||||
AX25Frame frameUI("NJ7P", 0, "N7LEM", 0, AX25_CONTROL_U_UNNUMBERED_INFORMATION |
|
||||
AX25_CONTROL_POLL_FINAL_DISABLED | AX25_CONTROL_UNNUMBERED_FRAME,
|
||||
AX25_PID_NO_LAYER_3, "Hello World (unnumbered)!");
|
||||
|
||||
// send the frame
|
||||
Serial.print(F("[AX.25] Sending UI frame ... "));
|
||||
int state = ax25.sendFrame(&frameUI);
|
||||
if (state == ERR_NONE) {
|
||||
// the packet was successfully transmitted
|
||||
Serial.println(F("success!"));
|
||||
|
||||
} else {
|
||||
// some error occurred
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
|
||||
// create AX.25 Receive Ready frame
|
||||
// destination station callsign: "NJ7P"
|
||||
// destination station SSID: 0
|
||||
// source station callsign: "N7LEM"
|
||||
// source station SSID: 0
|
||||
// control field: RR, P/F not used, supervisory frame
|
||||
AX25Frame frameRR("NJ7P", 0, "N7LEM", 0, AX25_CONTROL_S_RECEIVE_READY |
|
||||
AX25_CONTROL_POLL_FINAL_DISABLED | AX25_CONTROL_SUPERVISORY_FRAME);
|
||||
|
||||
// set receive sequence number (0 - 7)
|
||||
frameRR.setRecvSequence(0);
|
||||
|
||||
// send the frame
|
||||
Serial.print(F("[AX.25] Sending RR frame ... "));
|
||||
state = ax25.sendFrame(&frameRR);
|
||||
if (state == ERR_NONE) {
|
||||
// the packet was successfully transmitted
|
||||
Serial.println(F("success!"));
|
||||
|
||||
} else {
|
||||
// some error occurred
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
|
||||
// create AX.25 Information frame
|
||||
// destination station callsign: "NJ7P"
|
||||
// destination station SSID: 0
|
||||
// source station callsign: "N7LEM"
|
||||
// source station SSID: 0
|
||||
// control field: P/F not used, information frame
|
||||
// protocol identifier: no layer 3 protocol implemented
|
||||
// information field: "Hello World (numbered)!"
|
||||
AX25Frame frameI("NJ7P", 0, "N7LEM", 0, AX25_CONTROL_POLL_FINAL_DISABLED |
|
||||
AX25_CONTROL_INFORMATION_FRAME, AX25_PID_NO_LAYER_3,
|
||||
"Hello World (numbered)!");
|
||||
|
||||
// set receive sequence number (0 - 7)
|
||||
frameI.setRecvSequence(0);
|
||||
|
||||
// set send sequence number (0 - 7)
|
||||
frameI.setSendSequence(0);
|
||||
|
||||
// send the frame
|
||||
Serial.print(F("[AX.25] Sending I frame ... "));
|
||||
state = ax25.sendFrame(&frameI);
|
||||
if (state == ERR_NONE) {
|
||||
// the packet was successfully transmitted
|
||||
Serial.println(F("success!"));
|
||||
|
||||
} else {
|
||||
// some error occurred
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
}
|
88
examples/AX25/AX25_Transmit/AX25_Transmit.ino
Normal file
88
examples/AX25/AX25_Transmit/AX25_Transmit.ino
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
RadioLib AX.25 Transmit Example
|
||||
|
||||
This example sends AX.25 messages using
|
||||
SX1278's FSK modem.
|
||||
|
||||
Other modules that can be used for AX.25:
|
||||
- SX127x/RFM9x
|
||||
- RF69
|
||||
- SX1231
|
||||
- CC1101
|
||||
- SX126x
|
||||
- nRF24
|
||||
*/
|
||||
|
||||
// include the library
|
||||
#include <RadioLib.h>
|
||||
|
||||
// SX1278 has the following connections:
|
||||
// NSS pin: 10
|
||||
// DIO0 pin: 2
|
||||
// RESET pin: 9
|
||||
// DIO1 pin: 3
|
||||
SX1278 fsk = new Module(10, 2, 9, 3);
|
||||
|
||||
// or using RadioShield
|
||||
// https://github.com/jgromes/RadioShield
|
||||
//SX1278 fsk = RadioShield.ModuleA;
|
||||
|
||||
// create AX.25 client instance using the FSK module
|
||||
AX25Client ax25(&fsk);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
|
||||
// initialize SX1278
|
||||
Serial.print(F("[SX1278] Initializing ... "));
|
||||
// carrier frequency: 434.0 MHz
|
||||
// bit rate: 1.2 kbps (1200 baud AFSK AX.25)
|
||||
// frequency deviation: 0.5 kHz (1200 baud AFSK AX.25)
|
||||
int state = fsk.beginFSK(434.0, 1.2, 0.5);
|
||||
|
||||
// when using one of the non-LoRa modules for AX.25
|
||||
// (RF69, CC1101, etc.), use the basic begin() method
|
||||
// int state = fsk.begin();
|
||||
|
||||
if(state == ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
while(true);
|
||||
}
|
||||
|
||||
// initialize AX.25 client
|
||||
Serial.print(F("[AX.25] Initializing ... "));
|
||||
// source station callsign: "N7LEM"
|
||||
// source station SSID: 0
|
||||
// preamble length: 8 bytes
|
||||
state = ax25.begin("N7LEM");
|
||||
if(state == ERR_NONE) {
|
||||
Serial.println(F("success!"));
|
||||
} else {
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
while(true);
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// send AX.25 unnumbered infomration frame
|
||||
Serial.print(F("[AX.25] Sending UI frame ... "));
|
||||
// destination station callsign: "NJ7P"
|
||||
// destination station SSID: 0
|
||||
int state = ax25.transmit("Hello World!", "NJ7P");
|
||||
if (state == ERR_NONE) {
|
||||
// the packet was successfully transmitted
|
||||
Serial.println(F("success!"));
|
||||
|
||||
} else {
|
||||
// some error occurred
|
||||
Serial.print(F("failed, code "));
|
||||
Serial.println(state);
|
||||
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
}
|
12
keywords.txt
12
keywords.txt
|
@ -39,6 +39,8 @@ HTTPClient KEYWORD1
|
|||
RTTYClient KEYWORD1
|
||||
MorseClient KEYWORD1
|
||||
PagerClient KEYWORD1
|
||||
AX25Client KEYWORD1
|
||||
AX25Frame KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
|
@ -176,6 +178,12 @@ getNumBytes KEYWORD2
|
|||
sendSMS KEYWORD2
|
||||
shutdown KEYWORD2
|
||||
|
||||
# AX.25
|
||||
setRepeaters KEYWORD2
|
||||
setRecvSequence KEYWORD2
|
||||
setSendSequence KEYWORD2
|
||||
sendFrame KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
|
@ -262,3 +270,7 @@ ERR_SPI_CMD_INVALID LITERAL1
|
|||
ERR_SPI_CMD_FAILED LITERAL1
|
||||
ERR_INVALID_SLEEP_PERIOD LITERAL1
|
||||
ERR_INVALID_RX_PERIOD LITERAL1
|
||||
|
||||
ERR_INVALID_CALLSIGN LITERAL1
|
||||
ERR_INVALID_NUM_REPEATERS LITERAL1
|
||||
ERR_INVALID_REPEATER_CALLSIGN LITERAL1
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
|
||||
// physical layer protocols
|
||||
#include "protocols/PhysicalLayer/PhysicalLayer.h"
|
||||
#include "protocols/AX25/AX25.h"
|
||||
#include "protocols/Morse/Morse.h"
|
||||
#include "protocols/RTTY/RTTY.h"
|
||||
|
||||
|
|
|
@ -540,6 +540,29 @@
|
|||
*/
|
||||
#define ERR_INVALID_RX_PERIOD -709
|
||||
|
||||
// AX.25-specific status codes
|
||||
|
||||
/*!
|
||||
\brief The provided callsign is invalid.
|
||||
|
||||
The specified callsign is longer than 6 ASCII characters.
|
||||
*/
|
||||
#define ERR_INVALID_CALLSIGN -801
|
||||
|
||||
/*!
|
||||
\brief The provided repeater configuration is invalid.
|
||||
|
||||
The specified number of repeaters does not match number of repeater IDs or their callsigns.
|
||||
*/
|
||||
#define ERR_INVALID_NUM_REPEATERS -802
|
||||
|
||||
/*!
|
||||
\brief One of the provided repeater callsigns is invalid.
|
||||
|
||||
The specified callsign is longer than 6 ASCII characters.
|
||||
*/
|
||||
#define ERR_INVALID_REPEATER_CALLSIGN -803
|
||||
|
||||
/*!
|
||||
\}
|
||||
*/
|
||||
|
|
382
src/protocols/AX25/AX25.cpp
Normal file
382
src/protocols/AX25/AX25.cpp
Normal file
|
@ -0,0 +1,382 @@
|
|||
#include "AX25.h"
|
||||
|
||||
AX25Frame::AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control)
|
||||
: AX25Frame(destCallsign, destSSID, srcCallsign, srcSSID, control, 0, NULL, 0) {
|
||||
|
||||
}
|
||||
|
||||
AX25Frame::AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, const char* info)
|
||||
: AX25Frame(destCallsign, destSSID, srcCallsign, srcSSID, control, protocolID, (uint8_t*)info, strlen(info)) {
|
||||
|
||||
}
|
||||
|
||||
AX25Frame::AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, uint8_t* info, uint16_t infoLen) {
|
||||
// destination callsign/SSID
|
||||
memcpy(this->destCallsign, destCallsign, strlen(destCallsign));
|
||||
this->destCallsign[strlen(destCallsign)] = '\0';
|
||||
this->destSSID = destSSID;
|
||||
|
||||
// source callsign/SSID
|
||||
memcpy(this->srcCallsign, srcCallsign, strlen(srcCallsign));
|
||||
this->srcCallsign[strlen(srcCallsign)] = '\0';
|
||||
this->srcSSID = srcSSID;
|
||||
|
||||
// set repeaters
|
||||
this->numRepeaters = 0;
|
||||
#ifndef RADIOLIB_STATIC_ONLY
|
||||
this->repeaterCallsigns = NULL;
|
||||
this->repeaterSSIDs = NULL;
|
||||
#endif
|
||||
|
||||
// control field
|
||||
this->control = control;
|
||||
|
||||
// sequence numbers
|
||||
this->rcvSeqNumber = 0;
|
||||
this->sendSeqNumber = 0;
|
||||
|
||||
// PID field
|
||||
this->protocolID = protocolID;
|
||||
|
||||
// info field
|
||||
this->infoLen = infoLen;
|
||||
if(infoLen > 0) {
|
||||
#ifndef RADIOLIB_STATIC_ONLY
|
||||
this->info = new uint8_t[infoLen];
|
||||
#endif
|
||||
memcpy(this->info, info, infoLen);
|
||||
}
|
||||
}
|
||||
|
||||
AX25Frame::~AX25Frame() {
|
||||
#ifndef RADIOLIB_STATIC_ONLY
|
||||
// deallocate info field
|
||||
if(infoLen > 0) {
|
||||
delete[] this->info;
|
||||
}
|
||||
|
||||
// deallocate repeaters
|
||||
if(this->numRepeaters > 0) {
|
||||
for(uint8_t i = 0; i < this->numRepeaters; i++) {
|
||||
delete[] this->repeaterCallsigns[i];
|
||||
}
|
||||
delete[] this->repeaterCallsigns;
|
||||
delete[] this->repeaterSSIDs;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int16_t AX25Frame::setRepeaters(char** repeaterCallsigns, uint8_t* repeaterSSIDs, uint8_t numRepeaters) {
|
||||
// check number of repeaters
|
||||
if((numRepeaters < 1) || (numRepeaters > 8)) {
|
||||
return(ERR_INVALID_NUM_REPEATERS);
|
||||
}
|
||||
|
||||
// check repeater configuration
|
||||
if(!(((repeaterCallsigns == NULL) && (repeaterSSIDs == NULL) && (numRepeaters == 0)) ||
|
||||
((repeaterCallsigns != NULL) && (repeaterSSIDs != NULL) && (numRepeaters != 0)))) {
|
||||
return(ERR_INVALID_NUM_REPEATERS);
|
||||
}
|
||||
for(uint16_t i = 0; i < numRepeaters; i++) {
|
||||
if(strlen(repeaterCallsigns[i]) > AX25_MAX_CALLSIGN_LEN) {
|
||||
return(ERR_INVALID_REPEATER_CALLSIGN);
|
||||
}
|
||||
}
|
||||
|
||||
// create buffers
|
||||
#ifndef RADIOLIB_STATIC_ONLY
|
||||
this->repeaterCallsigns = new char*[numRepeaters];
|
||||
for(uint8_t i = 0; i < numRepeaters; i++) {
|
||||
this->repeaterCallsigns[i] = new char[strlen(repeaterCallsigns[i])];
|
||||
}
|
||||
this->repeaterSSIDs = new uint8_t[numRepeaters];
|
||||
#endif
|
||||
|
||||
// copy data
|
||||
this->numRepeaters = numRepeaters;
|
||||
for(uint8_t i = 0; i < numRepeaters; i++) {
|
||||
memcpy(this->repeaterCallsigns[i], repeaterCallsigns[i], strlen(repeaterCallsigns[i]));
|
||||
}
|
||||
memcpy(this->repeaterSSIDs, repeaterSSIDs, numRepeaters);
|
||||
|
||||
return(ERR_NONE);
|
||||
}
|
||||
|
||||
void AX25Frame::setRecvSequence(uint8_t seqNumber) {
|
||||
this->rcvSeqNumber = seqNumber;
|
||||
}
|
||||
|
||||
void AX25Frame::setSendSequence(uint8_t seqNumber) {
|
||||
this->sendSeqNumber = seqNumber;
|
||||
}
|
||||
|
||||
AX25Client::AX25Client(PhysicalLayer* phy) {
|
||||
_phy = phy;
|
||||
}
|
||||
|
||||
int16_t AX25Client::begin(const char* srcCallsign, uint8_t srcSSID, uint8_t preambleLen) {
|
||||
// set source SSID
|
||||
_srcSSID = srcSSID;
|
||||
|
||||
// check source callsign length (6 characters max)
|
||||
if(strlen(srcCallsign) > AX25_MAX_CALLSIGN_LEN) {
|
||||
return(ERR_INVALID_CALLSIGN);
|
||||
}
|
||||
|
||||
// copy callsign
|
||||
memcpy(_srcCallsign, srcCallsign, strlen(srcCallsign));
|
||||
_srcCallsign[strlen(srcCallsign)] = '\0';
|
||||
|
||||
// save preamble length
|
||||
_preambleLen = preambleLen;
|
||||
|
||||
// disable physical layer data shaping and set encoding to NRZ
|
||||
int16_t state = _phy->setDataShaping(0.0);
|
||||
RADIOLIB_ASSERT(state);
|
||||
|
||||
state = _phy->setEncoding(0);
|
||||
return(state);
|
||||
}
|
||||
|
||||
int16_t AX25Client::transmit(const char* str, const char* destCallsign, uint8_t destSSID) {
|
||||
// create control field
|
||||
uint8_t controlField = AX25_CONTROL_U_UNNUMBERED_INFORMATION | AX25_CONTROL_POLL_FINAL_DISABLED | AX25_CONTROL_UNNUMBERED_FRAME;
|
||||
|
||||
// build the frame
|
||||
AX25Frame frame(destCallsign, destSSID, _srcCallsign, _srcSSID, controlField, AX25_PID_NO_LAYER_3, (uint8_t*)str, strlen(str));
|
||||
|
||||
// send Unnumbered Information frame
|
||||
return(sendFrame(&frame));
|
||||
}
|
||||
|
||||
int16_t AX25Client::sendFrame(AX25Frame* frame) {
|
||||
// check destination callsign length (6 characters max)
|
||||
if(strlen(frame->destCallsign) > AX25_MAX_CALLSIGN_LEN) {
|
||||
return(ERR_INVALID_CALLSIGN);
|
||||
}
|
||||
|
||||
// check repeater configuration
|
||||
#ifndef RADIOLIB_STATIC_ONLY
|
||||
if(!(((frame->repeaterCallsigns == NULL) && (frame->repeaterSSIDs == NULL) && (frame->numRepeaters == 0)) ||
|
||||
((frame->repeaterCallsigns != NULL) && (frame->repeaterSSIDs != NULL) && (frame->numRepeaters != 0)))) {
|
||||
return(ERR_INVALID_NUM_REPEATERS);
|
||||
}
|
||||
for(uint16_t i = 0; i < frame->numRepeaters; i++) {
|
||||
if(strlen(frame->repeaterCallsigns[i]) > AX25_MAX_CALLSIGN_LEN) {
|
||||
return(ERR_INVALID_REPEATER_CALLSIGN);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// calculate frame length without FCS (destination address, source address, repeater addresses, control, PID, info)
|
||||
size_t frameBuffLen = ((2 + frame->numRepeaters)*(AX25_MAX_CALLSIGN_LEN + 1)) + 1 + 1 + frame->infoLen;
|
||||
// create frame buffer without preamble, start or stop flags
|
||||
#ifndef RADIOLIB_STATIC_ONLY
|
||||
uint8_t* frameBuff = new uint8_t[frameBuffLen + 2];
|
||||
#else
|
||||
uint8_t frameBuff[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||
#endif
|
||||
uint8_t* frameBuffPtr = frameBuff;
|
||||
|
||||
// set destination callsign - all address field bytes are shifted by one bit to make room for HDLC address extension bit
|
||||
memset(frameBuffPtr, ' ' << 1, AX25_MAX_CALLSIGN_LEN);
|
||||
for(uint8_t i = 0; i < strlen(frame->destCallsign); i++) {
|
||||
*(frameBuffPtr + i) = frame->destCallsign[i] << 1;
|
||||
}
|
||||
frameBuffPtr += AX25_MAX_CALLSIGN_LEN;
|
||||
|
||||
// set destination SSID
|
||||
*(frameBuffPtr++) = AX25_SSID_COMMAND_DEST | AX25_SSID_RESERVED_BITS | (frame->destSSID & 0x0F) << 1 | AX25_SSID_HDLC_EXTENSION_CONTINUE;
|
||||
|
||||
// set source callsign - all address field bytes are shifted by one bit to make room for HDLC address extension bit
|
||||
memset(frameBuffPtr, ' ' << 1, AX25_MAX_CALLSIGN_LEN);
|
||||
for(uint8_t i = 0; i < strlen(frame->srcCallsign); i++) {
|
||||
*(frameBuffPtr + i) = frame->srcCallsign[i] << 1;
|
||||
}
|
||||
frameBuffPtr += AX25_MAX_CALLSIGN_LEN;
|
||||
|
||||
// set source SSID
|
||||
*(frameBuffPtr++) = AX25_SSID_COMMAND_SOURCE | AX25_SSID_RESERVED_BITS | (frame->srcSSID & 0x0F) << 1 | AX25_SSID_HDLC_EXTENSION_END;
|
||||
|
||||
// set repeater callsigns
|
||||
for(uint16_t i = 0; i < frame->numRepeaters; i++) {
|
||||
memset(frameBuffPtr, ' ' << 1, AX25_MAX_CALLSIGN_LEN);
|
||||
for(uint8_t j = 0; j < strlen(frame->repeaterCallsigns[i]); j++) {
|
||||
*(frameBuffPtr + j) = frame->repeaterCallsigns[i][j] << 1;
|
||||
}
|
||||
frameBuffPtr += AX25_MAX_CALLSIGN_LEN;
|
||||
*(frameBuffPtr++) = AX25_SSID_HAS_NOT_BEEN_REPEATED | AX25_SSID_RESERVED_BITS | (frame->repeaterSSIDs[i] & 0x0F) << 1 | AX25_SSID_HDLC_EXTENSION_CONTINUE;
|
||||
}
|
||||
|
||||
// set HDLC extension end bit
|
||||
*frameBuffPtr |= AX25_SSID_HDLC_EXTENSION_END;
|
||||
|
||||
// set sequence numbers of the frames that have it
|
||||
uint8_t controlField = frame->control;
|
||||
if((frame->control & 0x01) == 0) {
|
||||
// information frame, set both sequence numbers
|
||||
controlField |= frame->rcvSeqNumber << 5;
|
||||
controlField |= frame->sendSeqNumber << 1;
|
||||
} else if((frame->control & 0x02) == 0) {
|
||||
// supervisory frame, set only receive sequence number
|
||||
controlField |= frame->rcvSeqNumber << 5;
|
||||
}
|
||||
|
||||
// set control field
|
||||
*(frameBuffPtr++) = controlField;
|
||||
|
||||
// set PID field of the frames that have it
|
||||
if(frame->protocolID != 0x00) {
|
||||
*(frameBuffPtr++) = frame->protocolID;
|
||||
}
|
||||
|
||||
// set info field of the frames that have it
|
||||
if(frame->infoLen > 0) {
|
||||
memcpy(frameBuffPtr, frame->info, frame->infoLen);
|
||||
frameBuffPtr += frame->infoLen;
|
||||
}
|
||||
|
||||
// flip bit order
|
||||
for(size_t i = 0; i < frameBuffLen; i++) {
|
||||
frameBuff[i] = flipBits(frameBuff[i]);
|
||||
}
|
||||
|
||||
// calculate FCS
|
||||
uint16_t fcs = getFrameCheckSequence(frameBuff, frameBuffLen);
|
||||
*(frameBuffPtr++) = (uint8_t)((fcs >> 8) & 0xFF);
|
||||
*(frameBuffPtr++) = (uint8_t)(fcs & 0xFF);
|
||||
|
||||
// prepare buffer for the final frame (stuffed, with added preamble + flags and NRZI-encoded)
|
||||
#ifndef RADIOLIB_STATIC_ONLY
|
||||
// worst-case scenario: sequence of 1s, will have 120% of the original length, stuffed frame also includes both flags
|
||||
uint8_t* stuffedFrameBuff = new uint8_t[_preambleLen + 1 + (6*frameBuffLen)/5 + 2];
|
||||
#else
|
||||
uint8_t stuffedFrameBuff[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||
#endif
|
||||
|
||||
// stuff bits (skip preamble and both flags)
|
||||
uint16_t stuffedFrameBuffLenBits = 8*(_preambleLen + 1);
|
||||
uint8_t count = 0;
|
||||
for(uint16_t i = 0; i < frameBuffLen + 2; i++) {
|
||||
for(int8_t shift = 7; shift >= 0; shift--) {
|
||||
uint16_t stuffedFrameBuffPos = stuffedFrameBuffLenBits + 7 - 2*(stuffedFrameBuffLenBits%8);
|
||||
if((frameBuff[i] >> shift) & 0x01) {
|
||||
// copy 1 and increment counter
|
||||
SET_BIT_IN_ARRAY(stuffedFrameBuff, stuffedFrameBuffPos);
|
||||
stuffedFrameBuffLenBits++;
|
||||
count++;
|
||||
|
||||
// check 5 consecutive 1s
|
||||
if(count == 5) {
|
||||
// get the new position in stuffed frame
|
||||
stuffedFrameBuffPos = stuffedFrameBuffLenBits + 7 - 2*(stuffedFrameBuffLenBits%8);
|
||||
|
||||
// insert 0 and reset counter
|
||||
CLEAR_BIT_IN_ARRAY(stuffedFrameBuff, stuffedFrameBuffPos);
|
||||
stuffedFrameBuffLenBits++;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
// copy 0 and reset counter
|
||||
CLEAR_BIT_IN_ARRAY(stuffedFrameBuff, stuffedFrameBuffPos);
|
||||
stuffedFrameBuffLenBits++;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// deallocate memory
|
||||
#ifndef RADIOLIB_STATIC_ONLY
|
||||
delete[] frameBuff;
|
||||
#endif
|
||||
|
||||
// set preamble bytes and start flag field
|
||||
for(uint16_t i = 0; i < _preambleLen + 1; i++) {
|
||||
stuffedFrameBuff[i] = AX25_FLAG;
|
||||
}
|
||||
|
||||
// get stuffed frame length in bytes
|
||||
size_t stuffedFrameBuffLen = stuffedFrameBuffLenBits/8 + 1;
|
||||
uint8_t trailingLen = stuffedFrameBuffLenBits % 8;
|
||||
|
||||
// set end flag field (may be split into two bytes due to misalignment caused by extra stuffing bits)
|
||||
if(trailingLen != 0) {
|
||||
stuffedFrameBuffLen++;
|
||||
stuffedFrameBuff[stuffedFrameBuffLen - 2] = AX25_FLAG >> trailingLen;
|
||||
stuffedFrameBuff[stuffedFrameBuffLen - 1] = AX25_FLAG << (8 - trailingLen);
|
||||
} else {
|
||||
stuffedFrameBuff[stuffedFrameBuffLen - 1] = AX25_FLAG;
|
||||
}
|
||||
|
||||
// convert to NRZI
|
||||
for(size_t i = _preambleLen + 1; i < stuffedFrameBuffLen*8; i++) {
|
||||
size_t currBitPos = i + 7 - 2*(i%8);
|
||||
size_t prevBitPos = (i - 1) + 7 - 2*((i - 1)%8);
|
||||
if(TEST_BIT_IN_ARRAY(stuffedFrameBuff, currBitPos)) {
|
||||
// bit is 1, no change, copy previous bit
|
||||
if(TEST_BIT_IN_ARRAY(stuffedFrameBuff, prevBitPos)) {
|
||||
SET_BIT_IN_ARRAY(stuffedFrameBuff, currBitPos);
|
||||
} else {
|
||||
CLEAR_BIT_IN_ARRAY(stuffedFrameBuff, currBitPos);
|
||||
}
|
||||
|
||||
} else {
|
||||
// bit is 0, transition, copy inversion of the previous bit
|
||||
if(TEST_BIT_IN_ARRAY(stuffedFrameBuff, prevBitPos)) {
|
||||
CLEAR_BIT_IN_ARRAY(stuffedFrameBuff, currBitPos);
|
||||
} else {
|
||||
SET_BIT_IN_ARRAY(stuffedFrameBuff, currBitPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// transmit
|
||||
int16_t state = _phy->transmit(stuffedFrameBuff, stuffedFrameBuffLen);
|
||||
|
||||
// deallocate memory
|
||||
#ifndef RADIOLIB_STATIC_ONLY
|
||||
delete[] stuffedFrameBuff;
|
||||
#endif
|
||||
|
||||
return(state);
|
||||
}
|
||||
|
||||
/*
|
||||
CCITT CRC implementation based on https://github.com/kicksat/ax25
|
||||
|
||||
Licensed under Creative Commons Attribution-ShareAlike 4.0 International
|
||||
https://creativecommons.org/licenses/by-sa/4.0/
|
||||
*/
|
||||
uint16_t AX25Client::getFrameCheckSequence(uint8_t* buff, size_t len) {
|
||||
uint8_t outBit = 0;
|
||||
uint16_t mask = 0x0000;
|
||||
uint16_t shiftReg = CRC_CCITT_INIT;
|
||||
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
for(uint8_t b = 0x80; b > 0x00; b /= 2) {
|
||||
outBit = (shiftReg & 0x01) ? 0x01 : 0x00;
|
||||
shiftReg >>= 1;
|
||||
mask = XOR((buff[i] & b), outBit) ? CRC_CCITT_POLY_REVERSED : 0x0000;
|
||||
shiftReg ^= mask;
|
||||
}
|
||||
}
|
||||
|
||||
return(flipBits16(~shiftReg));
|
||||
}
|
||||
|
||||
uint8_t AX25Client::flipBits(uint8_t b) {
|
||||
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
|
||||
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
|
||||
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
|
||||
return b;
|
||||
}
|
||||
|
||||
uint16_t AX25Client::flipBits16(uint16_t i) {
|
||||
i = (i & 0xFF00) >> 8 | (i & 0x00FF) << 8;
|
||||
i = (i & 0xF0F0) >> 4 | (i & 0x0F0F) << 4;
|
||||
i = (i & 0xCCCC) >> 2 | (i & 0x3333) << 2;
|
||||
i = (i & 0xAAAA) >> 1 | (i & 0x5555) << 1;
|
||||
return i;
|
||||
}
|
292
src/protocols/AX25/AX25.h
Normal file
292
src/protocols/AX25/AX25.h
Normal file
|
@ -0,0 +1,292 @@
|
|||
#ifndef _RADIOLIB_AX25_H
|
||||
#define _RADIOLIB_AX25_H
|
||||
|
||||
#include "../../TypeDef.h"
|
||||
#include "../PhysicalLayer/PhysicalLayer.h"
|
||||
|
||||
// macros to access bits in byte array, from http://www.mathcs.emory.edu/~cheung/Courses/255/Syllabus/1-C-intro/bit-array.html
|
||||
#define SET_BIT_IN_ARRAY(A, k) ( A[(k/8)] |= (1 << (k%8)) )
|
||||
#define CLEAR_BIT_IN_ARRAY(A, k) ( A[(k/8)] &= ~(1 << (k%8)) )
|
||||
#define TEST_BIT_IN_ARRAY(A, k) ( A[(k/8)] & (1 << (k%8)) )
|
||||
#define GET_BIT_IN_ARRAY(A, k) ( (A[(k/8)] & (1 << (k%8))) ? 1 : 0 )
|
||||
|
||||
// CRC-CCITT calculation macros
|
||||
#define XOR(A, B) ( ((A) || (B)) && !((A) && (B)) )
|
||||
#define CRC_CCITT_POLY 0x1021 // generator polynomial
|
||||
#define CRC_CCITT_POLY_REVERSED 0x8408 // CRC_CCITT_POLY in reversed bit order
|
||||
#define CRC_CCITT_INIT 0xFFFF // initial value
|
||||
|
||||
// maximum callsign length in bytes
|
||||
#define AX25_MAX_CALLSIGN_LEN 6
|
||||
|
||||
// flag field MSB LSB DESCRIPTION
|
||||
#define AX25_FLAG 0b01111110 // 7 0 AX.25 frame start/end flag
|
||||
|
||||
// address field
|
||||
#define AX25_SSID_COMMAND_DEST 0b10000000 // 7 7 frame type: command (set in destination SSID)
|
||||
#define AX25_SSID_COMMAND_SOURCE 0b00000000 // 7 7 command (set in source SSID)
|
||||
#define AX25_SSID_RESPONSE_DEST 0b00000000 // 7 7 response (set in destination SSID)
|
||||
#define AX25_SSID_RESPONSE_SOURCE 0b10000000 // 7 7 response (set in source SSID)
|
||||
#define AX25_SSID_HAS_NOT_BEEN_REPEATED 0b00000000 // 7 7 not repeated yet (set in repeater SSID)
|
||||
#define AX25_SSID_HAS_BEEN_REPEATED 0b10000000 // 7 7 repeated (set in repeater SSID)
|
||||
#define AX25_SSID_RESERVED_BITS 0b01100000 // 6 5 reserved bits in SSID
|
||||
#define AX25_SSID_HDLC_EXTENSION_CONTINUE 0b00000000 // 0 0 HDLC extension bit: next octet contains more address information
|
||||
#define AX25_SSID_HDLC_EXTENSION_END 0b00000001 // 0 0 address field end
|
||||
|
||||
// control field
|
||||
#define AX25_CONTROL_U_SET_ASYNC_BAL_MODE 0b01101100 // 7 2 U frame type: set asynchronous balanced mode (connect request)
|
||||
#define AX25_CONTROL_U_SET_ASYNC_BAL_MODE_EXT 0b00101100 // 7 2 set asynchronous balanced mode extended (connect request with module 128)
|
||||
#define AX25_CONTROL_U_DISCONNECT 0b01000000 // 7 2 disconnect request
|
||||
#define AX25_CONTROL_U_DISCONNECT_MODE 0b00001100 // 7 2 disconnect mode (system busy or disconnected)
|
||||
#define AX25_CONTROL_U_UNNUMBERED_ACK 0b01100000 // 7 2 unnumbered acknowledge
|
||||
#define AX25_CONTROL_U_FRAME_REJECT 0b10000100 // 7 2 frame reject
|
||||
#define AX25_CONTROL_U_UNNUMBERED_INFORMATION 0b00000000 // 7 2 unnumbered information
|
||||
#define AX25_CONTROL_U_EXHANGE_IDENTIFICATION 0b10101100 // 7 2 exchange ID
|
||||
#define AX25_CONTROL_U_TEST 0b11100000 // 7 2 test
|
||||
#define AX25_CONTROL_POLL_FINAL_ENABLED 0b00010000 // 4 4 control field poll/final bit: enabled
|
||||
#define AX25_CONTROL_POLL_FINAL_DISABLED 0b00000000 // 4 4 disabled
|
||||
#define AX25_CONTROL_S_RECEIVE_READY 0b00000000 // 3 2 S frame type: receive ready (system ready to receive)
|
||||
#define AX25_CONTROL_S_RECEIVE_NOT_READY 0b00000100 // 3 2 receive not ready (TNC buffer full)
|
||||
#define AX25_CONTROL_S_REJECT 0b00001000 // 3 2 reject (out of sequence or duplicate)
|
||||
#define AX25_CONTROL_S_SELECTIVE_REJECT 0b00001100 // 3 2 selective reject (single frame repeat request)
|
||||
#define AX25_CONTROL_INFORMATION_FRAME 0b00000000 // 0 0 frame type: information (I frame)
|
||||
#define AX25_CONTROL_SUPERVISORY_FRAME 0b00000001 // 1 0 supervisory (S frame)
|
||||
#define AX25_CONTROL_UNNUMBERED_FRAME 0b00000011 // 1 0 unnumbered (U frame)
|
||||
|
||||
// protocol identifier field
|
||||
#define AX25_PID_ISO_8208 0x01
|
||||
#define AX25_PID_TCP_IP_COMPRESSED 0x06
|
||||
#define AX25_PID_TCP_IP_UNCOMPRESSED 0x07
|
||||
#define AX25_PID_SEGMENTATION_FRAGMENT 0x08
|
||||
#define AX25_PID_TEXNET_DATAGRAM_PROTOCOL 0xC3
|
||||
#define AX25_PID_LINK_QUALITY_PROTOCOL 0xC4
|
||||
#define AX25_PID_APPLETALK 0xCA
|
||||
#define AX25_PID_APPLETALK_ARP 0xCB
|
||||
#define AX25_PID_ARPA_INTERNET_PROTOCOL 0xCC
|
||||
#define AX25_PID_ARPA_ADDRESS_RESOLUTION 0xCD
|
||||
#define AX25_PID_FLEXNET 0xCE
|
||||
#define AX25_PID_NET_ROM 0xCF
|
||||
#define AX25_PID_NO_LAYER_3 0xF0
|
||||
#define AX25_PID_ESCAPE_CHARACTER 0xFF
|
||||
|
||||
/*!
|
||||
\class AX25Frame
|
||||
|
||||
\brief Abstraction of AX.25 frame format.
|
||||
*/
|
||||
class AX25Frame {
|
||||
public:
|
||||
/*!
|
||||
\brief Callsign of the destination station.
|
||||
*/
|
||||
char destCallsign[AX25_MAX_CALLSIGN_LEN + 1];
|
||||
|
||||
/*!
|
||||
\brief SSID of the destination station.
|
||||
*/
|
||||
uint8_t destSSID;
|
||||
|
||||
/*!
|
||||
\brief Callsign of the source station.
|
||||
*/
|
||||
char srcCallsign[AX25_MAX_CALLSIGN_LEN + 1];
|
||||
|
||||
/*!
|
||||
\brief SSID of the source station.
|
||||
*/
|
||||
uint8_t srcSSID;
|
||||
|
||||
/*!
|
||||
\brief Number of repeaters to be used.
|
||||
*/
|
||||
uint8_t numRepeaters;
|
||||
|
||||
/*!
|
||||
\brief The control field.
|
||||
*/
|
||||
uint8_t control;
|
||||
|
||||
/*!
|
||||
\brief The protocol identifier (PID) field.
|
||||
*/
|
||||
uint8_t protocolID;
|
||||
|
||||
/*!
|
||||
\brief Number of bytes in the information field.
|
||||
*/
|
||||
uint16_t infoLen;
|
||||
|
||||
/*!
|
||||
\brief Receive sequence number.
|
||||
*/
|
||||
uint8_t rcvSeqNumber;
|
||||
|
||||
/*!
|
||||
\brief Send sequence number.
|
||||
*/
|
||||
uint16_t sendSeqNumber;
|
||||
|
||||
#ifndef RADIOLIB_STATIC_ONLY
|
||||
uint8_t* info;
|
||||
char** repeaterCallsigns;
|
||||
uint8_t* repeaterSSIDs;
|
||||
#else
|
||||
uint8_t info[RADIOLIB_STATIC_ARRAY_SIZE];
|
||||
char repeaterCallsigns[8][AX25_MAX_CALLSIGN_LEN + 1];
|
||||
uint8_t repeaterSSIDs[8];
|
||||
#endif
|
||||
|
||||
/*!
|
||||
\brief Overloaded constructor, for frames without info field.
|
||||
|
||||
\param destCallsign Callsign of the destination station.
|
||||
|
||||
\param destSSID SSID of the destination station.
|
||||
|
||||
\param srcCallsign Callsign of the source station.
|
||||
|
||||
\param srcSSID SSID of the source station.
|
||||
|
||||
\param control The control field.
|
||||
*/
|
||||
AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control);
|
||||
|
||||
/*!
|
||||
\brief Overloaded constructor, for frames with C-string info field.
|
||||
|
||||
\param destCallsign Callsign of the destination station.
|
||||
|
||||
\param destSSID SSID of the destination station.
|
||||
|
||||
\param srcCallsign Callsign of the source station.
|
||||
|
||||
\param srcSSID SSID of the source station.
|
||||
|
||||
\param control The control field.
|
||||
|
||||
\param protocolID The protocol identifier (PID) field. Set to zero if the frame doesn't have this field.
|
||||
|
||||
\param info Information field, in the form of null-terminated C-string.
|
||||
*/
|
||||
AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, const char* info);
|
||||
|
||||
/*!
|
||||
\brief Default constructor.
|
||||
|
||||
\param destCallsign Callsign of the destination station.
|
||||
|
||||
\param destSSID SSID of the destination station.
|
||||
|
||||
\param srcCallsign Callsign of the source station.
|
||||
|
||||
\param srcSSID SSID of the source station.
|
||||
|
||||
\param control The control field.
|
||||
|
||||
\param protocolID The protocol identifier (PID) field. Set to zero if the frame doesn't have this field.
|
||||
|
||||
\param info Information field, in the form of arbitrary binary buffer.
|
||||
|
||||
\param infoLen Number of bytes in the information field.
|
||||
*/
|
||||
AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, uint8_t* info, uint16_t infoLen);
|
||||
|
||||
/*!
|
||||
\brief Default destructor.
|
||||
*/
|
||||
~AX25Frame();
|
||||
|
||||
/*!
|
||||
\brief Method to set the repeater callsigns and SSIDs.
|
||||
|
||||
\param repeaterCallsigns Array of repeater callsigns in the form of null-terminated C-strings.
|
||||
|
||||
\param repeaterSSIDs Array of repeater SSIDs.
|
||||
|
||||
\param numRepeaters Number of repeaters, maximum is 8.
|
||||
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t setRepeaters(char** repeaterCallsigns, uint8_t* repeaterSSIDs, uint8_t numRepeaters);
|
||||
|
||||
/*!
|
||||
\brief Method to set receive sequence number.
|
||||
|
||||
\param seqNumber Sequence number to set, 0 to 7.
|
||||
*/
|
||||
void setRecvSequence(uint8_t seqNumber);
|
||||
|
||||
/*!
|
||||
\brief Method to set send sequence number.
|
||||
|
||||
\param seqNumber Sequence number to set, 0 to 7.
|
||||
*/
|
||||
void setSendSequence(uint8_t seqNumber);
|
||||
};
|
||||
|
||||
/*!
|
||||
\class AX25Client
|
||||
|
||||
\brief Client for AX25 communication.
|
||||
*/
|
||||
class AX25Client {
|
||||
public:
|
||||
/*!
|
||||
\brief Default constructor.
|
||||
|
||||
\param phy Pointer to the wireless module providing PhysicalLayer communication.
|
||||
*/
|
||||
AX25Client(PhysicalLayer* phy);
|
||||
|
||||
// basic methods
|
||||
|
||||
/*!
|
||||
\brief Initialization method.
|
||||
|
||||
\param srcCallsign Callsign of the source station.
|
||||
|
||||
\param srcSSID 4-bit SSID of the source station (in case there are more stations with the same callsign). Defaults to 0.
|
||||
|
||||
\param preambleLen Number of "preamble" bytes (AX25_FLAG) sent ahead of the actual AX.25 frame. Does not include the first AX25_FLAG byte, which is considered part of the frame. Defaults to 8.
|
||||
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t begin(const char* srcCallsign, uint8_t srcSSID = 0x00, uint8_t preambleLen = 8);
|
||||
|
||||
/*!
|
||||
\brief Transmit unnumbered information (UI) frame.
|
||||
|
||||
\param str Data to be sent.
|
||||
|
||||
\param destCallsign Callsign of the destination station.
|
||||
|
||||
\param destSSID 4-bit SSID of the destination station (in case there are more stations with the same callsign). Defaults to 0.
|
||||
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t transmit(const char* str, const char* destCallsign, uint8_t destSSID = 0x00);
|
||||
|
||||
/*!
|
||||
\brief Transmit arbitrary AX.25 frame.
|
||||
|
||||
\param frame Frame to be sent.
|
||||
|
||||
\returns \ref status_codes
|
||||
*/
|
||||
int16_t sendFrame(AX25Frame* frame);
|
||||
|
||||
#ifndef RADIOLIB_GODMODE
|
||||
private:
|
||||
#endif
|
||||
PhysicalLayer* _phy;
|
||||
|
||||
char _srcCallsign[AX25_MAX_CALLSIGN_LEN + 1];
|
||||
uint8_t _srcSSID;
|
||||
uint16_t _preambleLen;
|
||||
|
||||
uint16_t getFrameCheckSequence(uint8_t* buff, size_t len);
|
||||
uint8_t flipBits(uint8_t b);
|
||||
uint16_t flipBits16(uint16_t i);
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue