StuartsProjects / SX12XX-LoRa

Library for SX12XX LoRa devices
315 stars 68 forks source link

Non blocking ping pong style example #61

Closed orhanyor closed 1 year ago

orhanyor commented 1 year ago

Hi, Thank you for the awesome library. This is not an issue I wanted ask a question about some functionality regarding the library.

I'm looking to create a non blocking ping pong style transmitter receiver setup. I checked out some of the github issues and examples but I still have things to figure out.

The way I want to setup the whole thing is, transmitter is going to send a packet in 150ms intervals no matter what.

And if there's a received package as a response it will process it. Much like ACK with DATA example. Receiver is mostly going to stay in receive status and if it receives anything it will quickly reply and go back into receive mode.

I'm a newbie so I might have gotten some things awfully wrong along the lines. For non blocking code I get that I have to use NO_WAIT functions and check DIO1(sx1280) status for activity.

The way I thought I might do it:

if ( millis() - lastTransmit >= 150)       // 150ms interval for transmit
  {
    lastTransmit  = millis();
    LT.transmitSXBuffer(0, len, 0, TXpower, NO_WAIT);
    LT.receiveSXBuffer(0, 0, NO_WAIT);    // not sure if i can do that?
  }

Here right after transmit can I immediately call LT.receiveSXBuffer(0, 0, NO_WAIT); to put the module into receiving mode until the 150ms passes? or I have to wait till transmit is done and then put it into receiving mode? If the latter is true I was thinking to extend the code like this:

// add this to setup() function
{
LT.setDioIrqParams(IRQ_RADIO_ALL, (IRQ_TX_DONE + IRQ_RX_DONE ), 0, 0);  // set DIO1 for RX and TX done
}
// loop() function

if ( millis() - lastTransmit >= 150)                  // 150ms interval for transmit
  {
    lastTransmit  = millis();

    LT.transmitSXBuffer(0, len, 0, TXpower, NO_WAIT);    // transmit or receive resets DIO1 as far as I understood

    transmitDone = false;            // since we setup 2 triggers for DIO1 we can use a flag to indicate which is which 
 }

if(digitalRead(DIO1))
{
  if (!transmitDone )         // DIO1 raised for completed transmit
  {
  LT.receiveSXBuffer(0, 0, NO_WAIT);  // transmit complete, switch to receiving mode and DIO1 resets here

  transmitDone = true;    // Next time DIO1 is raised it will be for receiving

  } else {                        // DIO1 raised for received package

.... code here to read SX buffer

}

Please let me know if anything is wrong with the general idea or if there's a better way to do it?

Actually the 215_Reliable_Transmitter_ACK_withData.ino example is pretty much what I'm looking to achieve but I guess these 2 lines work in a blocking manner

TXPacketL = LT.transmitReliable(buff, sizeof(buff), NetworkID, TXtimeout, TXpower, WAIT_TX);

RXPacketL = LT.waitReliableACK(RXBUFFER, sizeof(RXBUFFER), NetworkID, PayloadCRC, ACKtimeout);

May be I can use transmitSXReliable with NO_WAIT but im not sure how to deal with waitReliableACK or how to get around making it non blocking.

StuartsProjects commented 1 year ago

Whilst the LoRa device is still transmitting you cannot set it up for receive. And starting a transmit cancels the receive.

orhanyor commented 1 year ago

Since transmit and receive should happen in the 150ms interval and receive can only happen once after a transmit, I think its better to try to read the sxbuffer right before transmit happens. And in that case I think I don't need to check IRQ_RX_DONE, instead checking for IRQ_TX_DONE should be enough so I can put the module into receiving mode.

For now DIO1 is checked in the loop but may be if i attach it to an interrupt would be better. Anyhow the new loop should be something like this,


// loop() function

if ( millis() - lastTransmit >= 150)                  // 150ms interval for transmit
  {
     if (RXPacketL != 0)
     {
    .... code here to read SX buffer
     }

    LT.transmitSXBuffer(0, len, 0, TXpower, NO_WAIT);    // transmit or receive resets DIO1 as far as I understood

    lastTransmit  = millis();

 }

if(digitalRead(DIO1))
{
  uint16_t IRQStatus;

  IRQStatus = LT.readIrqStatus();

     if (IRQStatus & IRQ_TX_DONE)
    {
      LT.receiveSXBuffer(0, 0, NO_WAIT);  // according to IRQ register TX is done so we can put the module into receiving mode
    }

}
orhanyor commented 1 year ago

Thank you for the response, Yes that made sense, after I posted my initial post but just to be sure i wanted to ask :) I think DIO1 integration is a solution to my approach. Im just not sure when It goes low, or should I manually put any code to force it low after i take actions in the code like receive or transmit or reading buffer etc. I saw you mentioned it resets after receive or transmit function, which I believe it goes low?

StuartsProjects commented 1 year ago

"Im just not sure when It goes low, or should I manually put any code to force it low after i take actions in the code like receive or transmit or reading buffer etc."

The DIOs stay high until they are set low by the library code. Follow the library code for the function you are using and it should be clear when the DIOs are setup, the process is also described in the SX128X datasheet.

orhanyor commented 1 year ago

"Im just not sure when It goes low, or should I manually put any code to force it low after i take actions in the code like receive or transmit or reading buffer etc."

The DIOs stay high until they are set low by the library code. Follow the library code for the function you are using and it should be clear when the DIOs are setup, the process is also described in the SX128X datasheet.

I ll check them but in the meantime i found one of your replies in another issue

You appear to be trying to use LT.receive(); in a way not as intended.

Check the library code; line 1803

setDioIrqParams(IRQ_RADIO_ALL, (IRQ_RX_DONE + IRQ_RX_TX_TIMEOUT + IRQ_HEADER_ERROR), 0, 0); //set for IRQ on RX done or timeout

So LT.receive(); clears the DIO1 IRQ, which makes DIO1 go low and then LT.receive(); waits for DIO1 to go high indicating a packet received or a timeout.

In this manner DIO1 should work in the code above or I can try to use LT.clearIrqStatus(IRQ_RADIO_ALL); after some of the events and see how it behaves. I will report back when I try it, thank you.

orhanyor commented 1 year ago

Got the modules, tested the code and everything works perfectly. One change I made was instead of polling for DIO1, I attached it to an interrupt. Everything is blazing fast and responsive with NO_WAIT. Also another thing, on the receiver side I made the mistake of calling LT.receiveSXBuffer(0, 0, NO_WAIT); in the loop which should be avoided with a simple flag. Closing the issue and thank you for the help.

simon88 commented 1 year ago

Hi @orhanyor, Maybe it's too late, but I'm looking for an example like you have done no wait ping pong, is it possible to have your example code ? Do you have a repositorie with it ? Tanks a lot

orhanyor commented 1 year ago

Hi @orhanyor, Maybe it's too late, but I'm looking for an example like you have done no wait ping pong, is it possible to have your example code ? Do you have a repositorie with it ? Tanks a lot

Mine is heavily modified so I dont have a simple code to show you. But it is fairly straight forward, I can give you instructions. for both receiver and transmitter you need to attach DIO1 pin (for sx1280) to interrupt capable pin of your MCU. Arduinos have limited number of interrupt pins so be careful here, 32bit mcu's have no problem with it you can choose any pin. and then in the setup function add this line after the Setuplora function.

LT.setupLoRa(Frequency, Offset, SpreadingFactor, Bandwidth, CodeRate);  //configure frequency and LoRa settings
attachInterrupt(digitalPinToInterrupt(DIO1), ISR1, RISING);

after creating this interrupt ISR1 you need to actually create the function so the mcu knows what to do when pin is pulled high.

void ISR1() {
  DEBUG_PRINTLN("DIO1 is HIGH!!");
  uint16_t IRQStatus;

  IRQStatus = LT.readIrqStatus();

  if (IRQStatus & IRQ_TX_DONE) {

    LT.receiveSXBuffer(0, 0, NO_WAIT);  // according to IRQ register TX is done so we can put the module into receiving mode
    DEBUG_PRINTLN("Module in receiving mode");
  }
  if (IRQStatus & IRQ_RX_DONE) {

    uint16_t Packetcode;

    LT.startReadSXBuffer(0);
    Packetcode = LT.readUint16();
    LT.endReadSXBuffer();
  }
}

Actual sending operation is done in the loop and you can attach it to a button press to some event or a time interval

this is how you send it

uint8_t len;

  LT.startWriteSXBuffer(0);
  LT.writeUint16(data);   //data to send
  len = LT.endWriteSXBuffer();  //close buffer write

  LT.transmitSXBuffer(0, len, 0, TXpower, NO_WAIT);

Basically you want to send your data in the loop and after TX is done interrupt will trigger and the interrupt function will put the sender into a receiving mode to listen if theres anything from the receiver.

For the receiver code its very similar you just need to be careful to not call LT.receiveSXBuffer(0, 0, NO_WAIT); in the loop in a repeated manner otherwise you wont be able to receive anything. I used a simple flag to check the status so thats how i avoided repeated LT.receiveSXBuffer(0, 0, NO_WAIT); calls.

And of course you can make it a lot more complicated its all up to you.

simon88 commented 11 months ago

Hi @orhanyor first of all thanks for your answer I'll try to create basic ping pong example without blocking mode. So for example if I want to send data every 1s I souhld do something like this



void setup(){
   LT.setupLoRa(Frequency, Offset, SpreadingFactor, Bandwidth, CodeRate);  //configure frequency and LoRa settings
   attachInterrupt(digitalPinToInterrupt(DIO1), ISR1, RISING);
}

void ISR1() {
  DEBUG_PRINTLN("DIO1 is HIGH!!");
  uint16_t IRQStatus;

  IRQStatus = LT.readIrqStatus();

  if (IRQStatus & IRQ_TX_DONE) {

    LT.receiveSXBuffer(0, 0, NO_WAIT);  // according to IRQ register TX is done so we can put the module into receiving mode
    DEBUG_PRINTLN("Module in receiving mode");
  }
  if (IRQStatus & IRQ_RX_DONE) {

    uint16_t Packetcode;

    LT.startReadSXBuffer(0);
    Packetcode = LT.readUint16();  //here I have receive data in Packetcode how to print data ??
    LT.endReadSXBuffer();
  }
}

void loop(){

  if(1s ellapsed){
     uint8_t len;
     uint8_t data[16] = "PING";  // I'm not sure about this line
     LT.startWriteSXBuffer(0);
     LT.writeUint16(data);   //data to send
     len = LT.endWriteSXBuffer();  //close buffer write

     LT.transmitSXBuffer(0, len, 0, TXpower, NO_WAIT);
  }
}
orhanyor commented 11 months ago

Yes, as a general TX code this should work. Receiver code is a bit different. You want to initiate your receiver loop code with ' LT.receiveSXBuffer(0, 0, NO_WAIT);' then you will do the replying part(sending) right after unpacking the received message either in the ISR or in the loop with a variable passed from the ISR. one critical mistake is to spam LT.receiveSXBuffer(0, 0, NO_WAIT); in the loop, so make sure you are not spamming either by checking a variable or leave it to ISR function. So you will go, receive -- unpack -- send -- send done -- put back into receive