the mountain of changes needed to kinda make tx work compiles.

This commit is contained in:
geeksville 2020-04-30 09:44:16 -07:00
parent 074ac33b8a
commit fce31560c6
9 changed files with 465 additions and 49 deletions

View File

@ -47,6 +47,9 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At
## Items to be 'feature complete'
- figure out what the correct current limit should be for the sx1262, currently we just use the default 100
- use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits.
- put sx1262 in sleepmode when processor gets shutdown (or rebooted), ideally even for critical faults (to keep power draw low). repurpose deepsleep state for this.
- good power management tips: https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/optimizing-power-on-nrf52-designs
- call PMU set_ADC_CONV(0) during sleep, to stop reading PMU adcs and decrease current draw
- do final power measurements

View File

@ -7,11 +7,7 @@
#ifdef RF95_IRQ_GPIO
/// A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need
#define MAX_RHPACKETLEN 251
static uint8_t radiobuf[MAX_RHPACKETLEN];
CustomRF95::CustomRF95() : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO), txQueue(MAX_TX_QUEUE) {}
CustomRF95::CustomRF95() : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO) {}
bool CustomRF95::canSleep()
{
@ -52,13 +48,11 @@ ErrorCode CustomRF95::send(MeshPacket *p)
// We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one).
// To do otherwise would be doubly bad because not only would we drop the packet that was on the way in,
// we almost certainly guarantee no one outside will like the packet we are sending.
if (_mode == RHModeIdle || (_mode == RHModeRx && !isReceiving())) {
if (_mode == RHModeIdle || !isReceiving()) {
// if the radio is idle, we can send right away
DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id,
txGood(), rxGood(), rxBad());
waitPacketSent(); // Make sure we dont interrupt an outgoing message
if (!waitCAD())
return false; // Check channel activity
@ -159,29 +153,20 @@ void CustomRF95::handleIdleISR()
/// This routine might be called either from user space or ISR
void CustomRF95::startSend(MeshPacket *txp)
{
assert(!sendingPacket);
// DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
assert(txp->has_payload);
lastTxStart = millis();
size_t numbytes = pb_encode_to_bytes(radiobuf, sizeof(radiobuf), SubPacket_fields, &txp->payload);
sendingPacket = txp;
size_t numbytes = beginSending(txp);
setHeaderTo(txp->to);
setHeaderId(txp->id);
// if the sender nodenum is zero, that means uninitialized
assert(txp->from);
setHeaderFrom(txp->from); // We must do this before each send, because we might have just changed our nodenum
assert(numbytes <= 251); // Make sure we don't overflow the tiny max packet size
// uint32_t start = millis(); // FIXME, store this in the class
int res = RH_RF95::send(radiobuf, numbytes);
// This legacy implementation doesn't use our inserted packet header
int res = RH_RF95::send(radiobuf + sizeof(PacketHeader), numbytes - sizeof(PacketHeader));
assert(res);
}

View File

@ -13,10 +13,6 @@ class CustomRF95 : public RH_RF95, public RadioInterface
{
friend class MeshRadio; // for debugging we let that class touch pool
PointerQueue<MeshPacket> txQueue;
uint32_t lastTxStart = 0L;
public:
/** pool is the pool we will alloc our rx packets from
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool

View File

@ -6,7 +6,10 @@
#include <pb_decode.h>
#include <pb_encode.h>
RadioInterface::RadioInterface() {}
RadioInterface::RadioInterface() : txQueue(MAX_TX_QUEUE)
{
assert(sizeof(PacketHeader) == 4); // make sure the compiler did what we expected
}
ErrorCode SimRadio::send(MeshPacket *p)
{
@ -21,3 +24,32 @@ void RadioInterface::deliverToReceiver(MeshPacket *p)
assert(rxDest->enqueue(p, 0)); // NOWAIT - fixme, if queue is full, delete older messages
}
/***
* given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of payload bytes to send
*/
size_t RadioInterface::beginSending(MeshPacket *p)
{
assert(!sendingPacket);
// DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
assert(p->has_payload);
lastTxStart = millis();
PacketHeader *h = (PacketHeader *)radiobuf;
h->from = p->from;
h->to = p->to;
h->flags = 0;
h->id = p->id;
// if the sender nodenum is zero, that means uninitialized
assert(h->from);
size_t numbytes = pb_encode_to_bytes(radiobuf + sizeof(PacketHeader), sizeof(radiobuf), SubPacket_fields, &p->payload) + sizeof(PacketHeader);
assert(numbytes <= MAX_RHPACKETLEN);
sendingPacket = p;
return numbytes;
}

View File

@ -8,6 +8,18 @@
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
#define MAX_RHPACKETLEN 256
/**
* This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility
* wtih the old radiohead implementation.
*/
typedef struct {
uint8_t to, from, id, flags;
} PacketHeader;
/**
* Basic operations all radio chipsets must implement.
*
@ -20,6 +32,13 @@ class RadioInterface
protected:
MeshPacket *sendingPacket = NULL; // The packet we are currently sending
PointerQueue<MeshPacket> txQueue;
uint32_t lastTxStart = 0L;
/**
* A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need
* */
uint8_t radiobuf[MAX_RHPACKETLEN];
/**
* Enqueue a received packet for the registered receiver
@ -82,6 +101,14 @@ class RadioInterface
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool reconfigure() = 0;
protected:
/***
* given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of bytes to send (including the PacketHeader & payload).
*
* Used as the first step of
*/
size_t beginSending(MeshPacket *p);
};
class SimRadio : public RadioInterface

View File

@ -8,8 +8,26 @@ RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq
SPIClass &spi, PhysicalLayer *_iface)
: module(cs, irq, rst, busy, spi, spiSettings), iface(*_iface)
{
assert(!instance); // We assume only one for now
instance = this;
}
void INTERRUPT_ATTR RadioLibInterface::isrRxLevel0()
{
instance->pending = ISR_RX;
instance->disableInterrupt();
}
void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0()
{
instance->pending = ISR_TX;
instance->disableInterrupt();
}
/** Our ISR code currently needs this to find our active instance
*/
RadioLibInterface *RadioLibInterface::instance;
/**
* Convert our modemConfig enum into wf, sf, etc...
*/
@ -41,9 +59,144 @@ void RadioLibInterface::applyModemConfig()
}
}
/// Send a packet (possibly by enquing in a private fifo). This routine will
/// later free() the packet to pool. This routine is not allowed to stall because it is called from
/// bluetooth comms code. If the txmit queue is empty it might return an error
ErrorCode RadioLibInterface::send(MeshPacket *p)
{
return ERR_NONE;
// We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one).
// To do otherwise would be doubly bad because not only would we drop the packet that was on the way in,
// we almost certainly guarantee no one outside will like the packet we are sending.
if (canSendImmediately()) {
// if the radio is idle, we can send right away
DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, -1,
-1, -1);
startSend(p);
return ERRNO_OK;
} else {
DEBUG_MSG("enqueuing packet for send from=0x%x, to=0x%x\n", p->from, p->to);
ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN;
if (res != ERRNO_OK) // we weren't able to queue it, so we must drop it to prevent leaks
packetPool.release(p);
return res;
}
}
void RadioLibInterface::loop()
{
PendingISR wasPending = pending; // atomic read
if (wasPending) {
pending = ISR_NONE; // If the flag was set, it is _guaranteed_ the ISR won't be running, because it masked itself
if (wasPending == ISR_TX)
handleTransmitInterrupt();
else if (wasPending == ISR_RX)
handleReceiveInterrupt();
else
assert(0);
// First send any outgoing packets we have ready
MeshPacket *txp = txQueue.dequeuePtr(0);
if (txp)
startSend(txp);
else {
// Nothing to send, let's switch back to receive mode
// FIXME - RH_RF95::setModeRx();
}
}
}
void RadioLibInterface::handleTransmitInterrupt()
{
assert(sendingPacket); // Were we sending?
// FIXME - check result code from ISR
// We are done sending that packet, release it
packetPool.release(sendingPacket);
sendingPacket = NULL;
// DEBUG_MSG("Done with send\n");
}
void RadioLibInterface::handleReceiveInterrupt()
{
// FIXME
}
#if 0
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as
// necessary
void CustomRF95::handleInterrupt()
{
RH_RF95::handleInterrupt();
enableInterrupt(); // Let ISR run again
if (_mode == RHModeIdle) // We are now done sending or receiving
{
// If we just finished receiving a packet, forward it into a queue
if (_rxBufValid) {
// We received a packet
// Skip the 4 headers that are at the beginning of the rxBuf
size_t payloadLen = _bufLen - RH_RF95_HEADER_LEN;
uint8_t *payload = _buf + RH_RF95_HEADER_LEN;
// FIXME - throws exception if called in ISR context: frequencyError() - probably the floating point math
int32_t freqerr = -1, snr = lastSNR();
// DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n",
// srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr);
MeshPacket *mp = packetPool.allocZeroed();
SubPacket *p = &mp->payload;
mp->from = _rxHeaderFrom;
mp->to = _rxHeaderTo;
mp->id = _rxHeaderId;
//_rxHeaderId = _buf[2];
//_rxHeaderFlags = _buf[3];
// If we already have an entry in the DB for this nodenum, goahead and hide the snr/freqerr info there.
// Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will
// already have a record we can hide this debugging info in.
NodeInfo *info = nodeDB.getNode(mp->from);
if (info) {
info->snr = snr;
info->frequency_error = freqerr;
}
if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) {
packetPool.release(mp);
} else {
// parsing was successful, queue for our recipient
mp->has_payload = true;
deliverToReceiver(mp);
}
clearRxBuf(); // This message accepted and cleared
}
handleIdleISR();
}
}
#endif
/** start an immediate transmit */
void RadioLibInterface::startSend(MeshPacket *txp)
{
size_t numbytes = beginSending(txp);
int res = iface.startTransmit(radiobuf, numbytes);
assert(res);
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
enableInterrupt(isrTxLevel0);
}
/**
@ -53,39 +206,178 @@ ErrorCode RadioLibInterface::send(MeshPacket *p)
// include the library
// save transmission state between loops
int transmissionState = ERR_NONE;
void loop() {
Serial.print(F("[SX1262] Transmitting packet ... "));
void setup() {
Serial.begin(9600);
// initialize SX1262 with default settings
Serial.print(F("[SX1262] Initializing ... "));
// carrier frequency: 434.0 MHz
// bandwidth: 125.0 kHz
// spreading factor: 9
// coding rate: 7
// sync word: 0x12 (private network)
// output power: 14 dBm
// current limit: 60 mA
// preamble length: 8 symbols
// TCXO voltage: 1.6 V (set to 0 to not use TCXO)
// regulator: DC-DC (set to true to use LDO)
// CRC: enabled
int state = lora.begin();
if (state == ERR_NONE) {
Serial.println(F("success!"));
} else {
Serial.print(F("failed, code "));
Serial.println(state);
while (true);
}
// set the function that will be called
// when packet transmission is finished
lora.setDio1Action(setFlag);
// start transmitting the first packet
Serial.print(F("[SX1262] Sending first packet ... "));
// you can transmit C-string or Arduino string up to
// 256 characters long
// NOTE: transmit() is a blocking method!
// See example SX126x_Transmit_Interrupt for details
// on non-blocking transmission method.
int state = lora.transmit("Hello World!");
transmissionState = lora.startTransmit("Hello World!");
// you can also transmit byte array up to 256 bytes long
byte byteArr[] = {0x01, 0x23, 0x45, 0x56, 0x78, 0xAB, 0xCD, 0xEF};
int state = lora.transmit(byteArr, 8);
byte byteArr[] = {0x01, 0x23, 0x45, 0x67,
0x89, 0xAB, 0xCD, 0xEF};
state = lora.startTransmit(byteArr, 8);
}
// flag to indicate that a packet was sent
volatile bool transmittedFlag = false;
// disable interrupt when it's not needed
volatile bool enableInterrupt = true;
// this function is called when a complete packet
// is transmitted by the module
// IMPORTANT: this function MUST be 'void' type
// and MUST NOT have any arguments!
void setFlag(void)
{
// check if the interrupt is enabled
if (!enableInterrupt) {
return;
}
// we sent a packet, set the flag
transmittedFlag = true;
}
void loop()
{
// check if the previous transmission finished
if (transmittedFlag) {
// disable the interrupt service routine while
// processing the data
enableInterrupt = false;
// reset flag
transmittedFlag = false;
if (transmissionState == ERR_NONE) {
// packet was successfully sent
Serial.println(F("transmission finished!"));
// NOTE: when using interrupt-driven transmit method,
// it is not possible to automatically measure
// transmission data rate using getDataRate()
} else {
Serial.print(F("failed, code "));
Serial.println(transmissionState);
}
// wait a second before transmitting again
delay(1000);
// send another one
Serial.print(F("[SX1262] Sending another packet ... "));
// you can transmit C-string or Arduino string up to
// 256 characters long
transmissionState = lora.startTransmit("Hello World!");
// you can also transmit byte array up to 256 bytes long
byte byteArr[] = {0x01, 0x23, 0x45, 0x67,
0x89, 0xAB, 0xCD, 0xEF};
int state = lora.startTransmit(byteArr, 8);
// we're ready to send more packets,
// enable interrupt service routine
enableInterrupt = true;
}
}
// this function is called when a complete packet
// is received by the module
// IMPORTANT: this function MUST be 'void' type
// and MUST NOT have any arguments!
void setFlag(void)
{
// check if the interrupt is enabled
if (!enableInterrupt) {
return;
}
// we got a packet, set the flag
receivedFlag = true;
}
void loop()
{
// check if the flag is set
if (receivedFlag) {
// disable the interrupt service routine while
// processing the data
enableInterrupt = false;
// reset flag
receivedFlag = false;
// you can read received data as an Arduino String
String str;
int state = lora.readData(str);
// you can also read received data as byte array
byte byteArr[8];
int state = lora.readData(byteArr, 8);
if (state == ERR_NONE) {
// the packet was successfully transmitted
Serial.println(F("success!"));
// packet was successfully received
Serial.println(F("[SX1262] Received packet!"));
// print measured data rate
Serial.print(F("[SX1262] Datarate:\t"));
Serial.print(lora.getDataRate());
Serial.println(F(" bps"));
// print data of the packet
Serial.print(F("[SX1262] Data:\t\t"));
Serial.println(str);
} else if (state == ERR_PACKET_TOO_LONG) {
// the supplied packet was longer than 256 bytes
Serial.println(F("too long!"));
// print RSSI (Received Signal Strength Indicator)
Serial.print(F("[SX1262] RSSI:\t\t"));
Serial.print(lora.getRSSI());
Serial.println(F(" dBm"));
} else if (state == ERR_TX_TIMEOUT) {
// timeout occured while transmitting packet
Serial.println(F("timeout!"));
// print SNR (Signal-to-Noise Ratio)
Serial.print(F("[SX1262] SNR:\t\t"));
Serial.print(lora.getSNR());
Serial.println(F(" dB"));
} else if (state == ERR_CRC_MISMATCH) {
// packet was received, but is malformed
Serial.println(F("CRC error!"));
} else {
// some other error occurred
@ -93,7 +385,12 @@ if (state == ERR_NONE) {
Serial.println(state);
}
// wait for a second before transmitting again
delay(1000);
// put module back to listen mode
lora.startReceive();
// we're ready to receive more packets,
// enable interrupt service routine
enableInterrupt = true;
}
*/
}
*/

View File

@ -4,8 +4,30 @@
#include <RadioLib.h>
// ESP32 has special rules about ISR code
#ifdef ARDUINO_ARCH_ESP32
#define INTERRUPT_ATTR IRAM_ATTR
#else
#define INTERRUPT_ATTR
#endif
class RadioLibInterface : public RadioInterface
{
enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX };
/**
* What sort of interrupt do we expect our helper thread to now handle */
volatile PendingISR pending;
/** Our ISR code currently needs this to find our active instance
*/
static RadioLibInterface *instance;
/**
* Raw ISR handler that just calls our polymorphic method
*/
static void isrRxLevel0(), isrTxLevel0();
protected:
float bw = 125;
uint8_t sf = 9;
@ -27,6 +49,16 @@ class RadioLibInterface : public RadioInterface
*/
PhysicalLayer &iface;
/**
* Glue functions called from ISR land
*/
virtual void disableInterrupt() = 0;
/**
* Enable a particular ISR callback glue function
*/
virtual void enableInterrupt(void (*)()) = 0;
public:
RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi,
PhysicalLayer *iface);
@ -51,9 +83,20 @@ class RadioLibInterface : public RadioInterface
/// \return true if initialisation succeeded.
virtual bool init() { return true; }
virtual void loop(); // Idle processing
protected:
/**
* Convert our modemConfig enum into wf, sf, etc...
*/
void applyModemConfig();
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
virtual bool canSendImmediately() = 0;
/** start an immediate transmit */
void startSend(MeshPacket *txp);
void handleTransmitInterrupt();
void handleReceiveInterrupt();
};

View File

@ -27,6 +27,9 @@ bool SX1262Interface::init()
int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO);
DEBUG_MSG("LORA init result %d\n", res);
if (res != ERR_NONE)
res = lora.setCRC(SX126X_LORA_CRC_ON);
return res == ERR_NONE;
}
@ -69,3 +72,19 @@ bool SX1262Interface::reconfigure()
return true;
}
/** Could we send right now (i.e. either not actively receving or transmitting)? */
bool SX1262Interface::canSendImmediately()
{
return true; // FIXME
#if 0
// We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one).
// To do otherwise would be doubly bad because not only would we drop the packet that was on the way in,
// we almost certainly guarantee no one outside will like the packet we are sending.
if (_mode == RHModeIdle || isReceiving()) {
// if the radio is idle, we can send right away
DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id,
txGood(), rxGood(), rxBad());
}
#endif
}

View File

@ -18,4 +18,18 @@ class SX1262Interface : public RadioLibInterface
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool reconfigure();
protected:
/**
* Glue functions called from ISR land
*/
virtual void INTERRUPT_ATTR disableInterrupt() { lora.clearDio1Action(); }
/**
* Enable a particular ISR callback glue function
*/
virtual void enableInterrupt(void (*callback)()) { lora.setDio1Action(callback); }
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
virtual bool canSendImmediately();
};