kopia lustrzana https://github.com/sh123/esp32_loraprs
Added circular buffers for both tx and rx, use LoRa ISR for receve with optional possibility to switch it off
rodzic
c4addc78a0
commit
8636f1eb9d
|
@ -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)"
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue