Added circular buffers for both tx and rx, use LoRa ISR for receve with optional possibility to switch it off

pull/15/head
sh123 2021-02-12 16:33:25 +02:00
rodzic c4addc78a0
commit 8636f1eb9d
7 zmienionych plików z 203 dodań i 82 usunięć

Wyświetl plik

@ -66,7 +66,7 @@ Install via libraries:
- Arduino ESP32 library: https://github.com/espressif/arduino-esp32
- LoRa arduino library: https://github.com/sandeepmistry/arduino-LoRa
- Arduino Timer library: https://github.com/contrem/arduino-timer
- cppQueue library: https://github.com/SMFSW/Queue
- CircularBuffer library: https://github.com/rlogiacco/CircularBuffer
# Software Setup
- **NB! select next partition scheme for ESP32 in Arduino IDE Tools menu:** "Minimal SPIFFS (1.9 MB APP with OTA/190 KB SPIFFS)"

Wyświetl plik

@ -34,6 +34,7 @@ void initializeConfig(LoraPrs::Config &cfg) {
cfg.LoraPinSs = CFG_LORA_PIN_SS;
cfg.LoraPinRst = CFG_LORA_PIN_RST;
cfg.LoraPinDio0 = CFG_LORA_PIN_DIO0;
cfg.LoraUseIsr = true;
// aprs configuration
cfg.AprsHost = "rotate.aprs2.net";

Wyświetl plik

@ -1,19 +1,22 @@
#include "kiss_processor.h"
namespace Kiss {
CircularBuffer<uint8_t, Processor::CfgSerialToRigQueueSize> Processor::serialToRigQueue_;
CircularBuffer<uint8_t, Processor::CfgRigToSerialQueueSize> Processor::rigToSerialQueue_;
CircularBuffer<uint8_t, Processor::CfgRigToSerialQueueSize> Processor::rigToSerialQueueIndex_;
Processor::Processor()
: state_(State::GetStart)
, txQueue_(new cppQueue(sizeof(unsigned char), CfgTxQueueSize))
{
}
void Processor::serialSend(Cmd cmd, const byte *b, int dataLength) {
void Processor::serialSend(Cmd cmd, const byte *packet, int packetLength) {
onSerialTx((byte)Marker::Fend);
onSerialTx((byte)cmd);
for (int i = 0; i < dataLength; i++) {
byte rxByte = b[i];
for (int i = 0; i < packetLength; i++) {
byte rxByte = packet[i];
if (rxByte == Marker::Fend) {
onSerialTx((byte)Marker::Fesc);
@ -26,33 +29,101 @@ void Processor::serialSend(Cmd cmd, const byte *b, int dataLength) {
else {
onSerialTx(rxByte);
}
yield();
}
onSerialTx((byte)Marker::Fend);
}
void Processor::serialProcessRx()
void ICACHE_RAM_ATTR Processor::serialQueueIsr(Cmd cmd, const byte *packet, int packetLength) {
if (!rigToSerialQueueIndex_.unshift(packetLength)) {
Serial.println("Rig to serial queue is full!");
return;
}
for (int i = 0; i < packetLength; i++) {
if (!rigToSerialQueue_.unshift(packet[i])) {
Serial.println("Rig to serial queue is full!");
return;
}
}
}
void Processor::rigQueue(Cmd cmd, const byte *packet, int packetLength) {
bool result = 1;
result &= serialToRigQueue_.unshift(Marker::Fend);
result &= serialToRigQueue_.unshift(cmd);
for (int i = 0; i < packetLength; i++) {
byte rxByte = packet[i];
if (rxByte == Marker::Fend) {
result &= serialToRigQueue_.unshift(Marker::Fesc);
result &= serialToRigQueue_.unshift(Marker::Tfend);
}
else if (rxByte == Marker::Fesc) {
result &= serialToRigQueue_.unshift(Marker::Fesc);
result &= serialToRigQueue_.unshift(Marker::Tfesc);
}
else {
result &= serialToRigQueue_.unshift(rxByte);
}
}
result &= serialToRigQueue_.unshift(Marker::Fend);
if (!result) {
Serial.println("Serial to rig queue overflow!");
}
}
bool Processor::processRigToSerial()
{
bool isProcessed = false;
if (rigToSerialQueueIndex_.isEmpty()) {
return isProcessed;
}
while (!rigToSerialQueueIndex_.isEmpty()) {
int rxPacketSize = rigToSerialQueueIndex_.pop();
byte buf[rxPacketSize];
for (int i = 0; i < rxPacketSize; i++) {
buf[i] = rigToSerialQueue_.pop();
}
serialSend(Cmd::Data, buf, rxPacketSize);
onRigPacket(&buf, rxPacketSize);
isProcessed = true;
yield();
}
return isProcessed;
}
bool Processor::processSerialToRig()
{
while (onSerialRxHasData() || !txQueue_->isEmpty()) {
bool isProcessed = false;
while (onSerialRxHasData() || !serialToRigQueue_.isEmpty()) {
byte rxByte;
if (onSerialRxHasData()) {
if (onSerialRxHasData()) {
if (onSerialRx(&rxByte)) {
if (!txQueue_->push((void *)&rxByte)) {
Serial.println("TX queue is full");
break;
}
if (!serialToRigQueue_.unshift(rxByte)) {
Serial.println("Serial to rig buffer is full!");
}
}
}
if (!txQueue_->isEmpty()) {
if (txQueue_->peek((void *)&rxByte)) {
if (receiveByte(rxByte)) {
txQueue_->drop();
}
if (!serialToRigQueue_.isEmpty()) {
rxByte = serialToRigQueue_.pop();
if (receiveByte(rxByte)) {
isProcessed = true;
} else {
serialToRigQueue_.push(rxByte);
}
}
yield();
}
return isProcessed;
}
bool Processor::processCommand(byte rxByte) {

Wyświetl plik

@ -2,9 +2,11 @@
#define KISS_PROCESSOR_H
#include <Arduino.h>
#include <cppQueue.h>
#include <memory>
#define CIRCULAR_BUFFER_INT_SAFE
#include <CircularBuffer.h>
namespace Kiss {
class Processor {
@ -41,19 +43,26 @@ protected:
Control
};
const int CfgTxQueueSize = 4096;
static const int CfgSerialToRigQueueSize = 4096;
static const int CfgRigToSerialQueueSize = 4096;
public:
Processor();
void serialSend(Cmd cmd, const byte *b, int dataLength);
void serialProcessRx();
void serialSend(Cmd cmd, const byte *packet, int packetLength);
static void ICACHE_RAM_ATTR serialQueueIsr(Cmd cmd, const byte *packet, int packetLength);
void rigQueue(Cmd cmd, const byte *packet, int packetLength);
bool processRigToSerial();
bool processSerialToRig();
protected:
virtual bool onRigTxBegin() = 0;
virtual void onRigTx(byte b) = 0;
virtual void onRigTxEnd() = 0;
virtual void onRigPacket(void *packet, int packetLength) = 0;
virtual void onSerialTx(byte b) = 0;
virtual bool onSerialRxHasData() = 0;
virtual bool onSerialRx(byte *b) = 0;
@ -69,8 +78,11 @@ private:
private:
State state_;
DataType dataType_;
std::shared_ptr<cppQueue> txQueue_;
std::vector<byte> cmdBuffer_;
static CircularBuffer<uint8_t, CfgSerialToRigQueueSize> serialToRigQueue_;
static CircularBuffer<uint8_t, CfgRigToSerialQueueSize> rigToSerialQueue_;
static CircularBuffer<uint8_t, CfgRigToSerialQueueSize> rigToSerialQueueIndex_;
};
} // Kiss

Wyświetl plik

@ -20,6 +20,7 @@ struct Config
byte LoraPinSs; // lora ss pin
byte LoraPinRst; // lora rst pin
byte LoraPinDio0; // lora dio0 pin
bool LoraUseIsr; // true to use interrupts
int AprsPort; // aprs server port, 14580
String AprsHost; // aprs server hostname, rotate.aprs2.net

Wyświetl plik

@ -20,18 +20,18 @@ void Service::setup(const Config &conf)
if (!ownCallsign_.IsValid()) {
Serial.println("Own callsign is not valid");
}
aprsLoginCommand_ = String("user ") + config_.AprsLogin + String(" pass ") +
config_.AprsPass + String(" vers ") + CfgLoraprsVersion;
if (config_.AprsFilter.length() > 0) {
aprsLoginCommand_ += String(" filter ") + config_.AprsFilter;
}
aprsLoginCommand_ += String("\n");
// peripherals
setupLora(config_.LoraFreq, config_.LoraBw, config_.LoraSf,
config_.LoraCodingRate, config_.LoraPower, config_.LoraSync, config_.LoraEnableCrc);
if (needsWifi()) {
setupWifi(config_.WifiSsid, config_.WifiKey);
}
@ -39,7 +39,7 @@ void Service::setup(const Config &conf)
if (needsBt() || config_.BtName.length() > 0) {
setupBt(config_.BtName);
}
if (needsAprsis() && config_.EnablePersistentAprsConnection) {
reconnectAprsis();
}
@ -125,8 +125,12 @@ void Service::setupLora(long loraFreq, long bw, int sf, int cr, int pwr, int syn
if (enableCrc) {
LoRa.enableCrc();
}
Serial.println("ok");
if (config_.LoraUseIsr) {
LoRa.onReceive(onLoraDataAvailableIsr);
LoRa.receive();
}
Serial.println("ok");
}
void Service::setupBt(const String &btName)
@ -152,11 +156,20 @@ void Service::loop()
}
// RX path, Rig -> Serial
if (int packetSize = LoRa.parsePacket()) {
onLoraDataAvailable(packetSize);
bool isRigToSerialProcessed = false;
if (config_.LoraUseIsr) {
isRigToSerialProcessed = processRigToSerial();
} else {
if (int packetSize = LoRa.parsePacket()) {
loraReceive(packetSize);
isRigToSerialProcessed = true;
}
}
// TX path, Serial -> Rig
else {
if (!isRigToSerialProcessed) {
long currentTime = millis();
if (currentTime > csmaSlotTimePrev_ + csmaSlotTime_ && random(0, 255) < csmaP_) {
if (aprsisConn_.available() > 0) {
@ -166,7 +179,9 @@ void Service::loop()
sendPeriodicBeacon();
}
else {
serialProcessRx();
if (processSerialToRig() && config_.LoraUseIsr) {
LoRa.receive();
}
}
csmaSlotTimePrev_ = currentTime;
}
@ -174,6 +189,18 @@ void Service::loop()
delay(CfgPollDelayMs);
}
ICACHE_RAM_ATTR void Service::onLoraDataAvailableIsr(int packetSize)
{
// TODO, move to separate ESP32 task
int rxBufIndex = 0;
byte rxBuf[packetSize];
for (int i = 0; i < packetSize; i++) {
rxBuf[rxBufIndex++] = LoRa.read();
}
serialQueueIsr(Cmd::Data, rxBuf, rxBufIndex);
}
void Service::sendPeriodicBeacon()
{
long currentMs = millis();
@ -212,7 +239,7 @@ void Service::sendToAprsis(const String &aprsMessage)
void Service::onAprsisDataAvailable()
{
String aprsisData;
while (aprsisConn_.available() > 0) {
char c = aprsisConn_.read();
if (c == '\r') continue;
@ -231,7 +258,7 @@ void Service::onAprsisDataAvailable()
sendAX25ToLora(payload);
}
else {
Serial.println("Unknown payload from APRSIS, ignoring...");
Serial.println("Unknown payload from APRSIS, ignoring");
}
}
}
@ -246,7 +273,7 @@ void Service::sendSignalReportEvent(int rssi, float snr)
serialSend(Cmd::SignalReport, (const byte *)&signalReport, sizeof(SignalReport));
}
bool Service::sendAX25ToLora(const AX25::Payload &payload)
bool Service::sendAX25ToLora(const AX25::Payload &payload)
{
byte buf[CfgMaxAX25PayloadSize];
int bytesWritten = payload.ToBinary(buf, sizeof(buf));
@ -254,63 +281,69 @@ bool Service::sendAX25ToLora(const AX25::Payload &payload)
Serial.println("Failed to serialize payload");
return false;
}
LoRa.beginPacket();
LoRa.write(buf, bytesWritten);
LoRa.endPacket();
rigQueue(Cmd::Data, buf, bytesWritten);
return true;
}
void Service::onLoraDataAvailable(int packetSize)
{
int rxBufIndex = 0;
byte rxBuf[packetSize];
while (LoRa.available()) {
byte rxByte = LoRa.read();
rxBuf[rxBufIndex++] = rxByte;
yield();
}
serialSend(Cmd::Data, rxBuf, rxBufIndex);
void Service::onRigPacket(void *packet, int packetLength)
{
long frequencyError = LoRa.packetFrequencyError();
if (config_.EnableAutoFreqCorrection && abs(frequencyError) > CfgFreqCorrMinHz) {
config_.LoraFreq -= frequencyError;
Serial.print("Correcting frequency: "); Serial.println(frequencyError);
LoRa.setFrequency(config_.LoraFreq);
if (config_.LoraUseIsr) {
LoRa.idle();
LoRa.receive();
}
}
if (config_.EnableKissExtensions) {
sendSignalReportEvent(LoRa.packetRssi(), LoRa.packetSnr());
}
if (!config_.IsClientMode) {
processIncomingRawPacketAsServer(rxBuf, rxBufIndex);
processIncomingRawPacketAsServer((const byte*)packet, packetLength);
}
}
void Service::loraReceive(int packetSize)
{
int rxBufIndex = 0;
byte rxBuf[packetSize];
while (LoRa.available()) {
rxBuf[rxBufIndex++] = LoRa.read();
}
serialSend(Cmd::Data, rxBuf, rxBufIndex);
onRigPacket(rxBuf, rxBufIndex);
}
void Service::processIncomingRawPacketAsServer(const byte *packet, int packetLength) {
float snr = LoRa.packetSnr();
int rssi = LoRa.packetRssi();
long frequencyError = LoRa.packetFrequencyError();
String signalReport = String(" ") +
String("rssi: ") +
String(snr < 0 ? rssi + snr : rssi) +
String("dBm, ") +
String("snr: ") +
String(snr) +
String("dB, ") +
String("err: ") +
String(frequencyError) +
String("Hz");
AX25::Payload payload(packet, packetLength);
if (payload.IsValid()) {
float snr = LoRa.packetSnr();
int rssi = LoRa.packetRssi();
long frequencyError = LoRa.packetFrequencyError();
String signalReport = String(" ") +
String("rssi: ") +
String(snr < 0 ? rssi + snr : rssi) +
String("dBm, ") +
String("snr: ") +
String(snr) +
String("dB, ") +
String("err: ") +
String(frequencyError) +
String("Hz");
String textPayload = payload.ToString(config_.EnableSignalReport ? signalReport : String());
Serial.println(textPayload);
if (config_.EnableRfToIs) {
sendToAprsis(textPayload);
Serial.println("Packet sent to APRS-IS");
@ -326,7 +359,7 @@ void Service::processIncomingRawPacketAsServer(const byte *packet, int packetLen
bool Service::onRigTxBegin()
{
delay(CfgPollDelayMs); // LoRa may drop packet if removed
delay(CfgPollDelayMs);
return (LoRa.beginPacket() == 1);
}

Wyświetl plik

@ -5,7 +5,6 @@
#include <SPI.h>
#include <LoRa.h>
#include <WiFi.h>
#include <cppQueue.h>
#include <endian.h>
#include "BluetoothSerial.h"
@ -30,8 +29,10 @@ private:
void reconnectWifi() const;
bool reconnectAprsis();
static ICACHE_RAM_ATTR void onLoraDataAvailableIsr(int packetSize);
void onLoraDataAvailable(int packetSize);
void loraReceive(int packetSize);
void onAprsisDataAvailable();
void sendSignalReportEvent(int rssi, float snr);
@ -51,7 +52,8 @@ protected:
virtual bool onRigTxBegin();
virtual void onRigTx(byte b);
virtual void onRigTxEnd();
virtual void onRigPacket(void *packet, int packetLength);
virtual void onSerialTx(byte b);
virtual bool onSerialRxHasData();
virtual bool onSerialRx(byte *b);
@ -79,12 +81,13 @@ private:
const String CfgLoraprsVersion = "LoRAPRS 0.1";
// processor config
const int CfgConnRetryMs = 500;
const int CfgPollDelayMs = 5;
const int CfgWiFiConnRetryMaxTimes = 10;
const int CfgMaxAX25PayloadSize = 512;
const int CfgFreqCorrMinHz = 150;
const int CfgMaxAprsInMessageSize = 255;
const int CfgConnRetryMs = 500; // connection retry delay, e.g. wifi
const int CfgPollDelayMs = 5; // main loop delay
const int CfgWiFiConnRetryMaxTimes = 10; // wifi number of connection retries
const int CfgMaxAX25PayloadSize = 512; // maximum ax25 payload size
const int CfgFreqCorrMinHz = 1000; // correct if deviation is larger than this number
// NB! small value causes frequent corrections, which locks LoRa ISR
const int CfgMaxAprsInMessageSize = 255; // maximum aprsis to rf message size
// csma parameters, overriden with KISS commands
const long CfgCsmaPersistence = 100; // 255 for real time, lower for higher traffic