sandeepmistry / arduino-LoRa

An Arduino library for sending and receiving data using LoRa radios.
MIT License
1.62k stars 621 forks source link

Delay in loop() stops receive #176

Closed GIPdA closed 3 years ago

GIPdA commented 5 years ago

Hi,

I experience a strange issue with the library: if I put a delay in the loop(), the LoRa module fails to receive almost all packets. I use the delay to simulate some long blocking operations. I send 30 bytes packets every 2 seconds.

From the example LoRaReceiver, just added the delay and I get maybe 1 packet out of 10. Without the delay, I receive every packet. Why is that?

void loop() {
  delay(100);
  // try to parse packet
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    // received a packet
    Serial.print("Received packet '");

    // read packet
    while (LoRa.available()) {
      Serial.print((char)LoRa.read());
    }

    // print RSSI of packet
    Serial.print("' with RSSI ");
    Serial.println(LoRa.packetRssi());
  }
}

Hardware: LoRa32u4 II board (https://bsfrance.fr/lora-long-range/1345-LoRa32u4-II-Lora-LiPo-Atmega32u4-SX1276-HPD13-868MHZ-EU-Antenna.html)

Thanks, Best regards, Benjamin

morganrallen commented 5 years ago

I'm unable to replicate this as you've described. I modified the Sender example to send ~30 bytes ever 2 seconds and added a 100ms delay to the Receiver example and I still receive every packet at around -110 RSSI. What RSSI levels are you seeing on the packets you do receive?

PS You should use triple ticks (```) for code blocks instead of a single

GIPdA commented 5 years ago

By adding LoRa.receive() after LoRa.parsePacket() (with the delay as above), I almost get every packet, but not reliably and the values are often wrong.

I have a RSSI around -30 with the sender 50cm away, and around -60 when 10m away (without added delay, just the Receive example). I use a small 868MHz antenna connected to the UFL connector on the board.

GIPdA commented 5 years ago

I just tried with some other hardware for a receiver: STM32 with a SX1276 board. Same problem. The emitter didn't change.

What hardware are you using? And how do you get 110 RSSI? I thought the values could only be negative...

morganrallen commented 5 years ago

I'm using the TTGO ESP32 board. Sorry, I meant -110

GIPdA commented 5 years ago

Ok, thanks. I still have the issue, I worked around it for now but I'm still looking for a solution. I reduced the blocking time to a minimum, but I'm still loosing some packets, after a while it stops receiving, and it may takes some time to start receiving (30s to 1 or 2min). Not very reliable.

The long blocking operation I have is driving an e-paper screen with the GxEPD lib. I thought the issue may be coming from a SPI conflict of some sort, but I couldn't find anything. And the simple serial feed loop for TinyGPS++ caused problems too. I'm out of ideas for now...

I will not be able to test anything for the next two weeks but I hope that a solution will arise in the meantime :P

sandeepmistry commented 5 years ago

If LoRa.parsePacket() or LoRa.receive() is not called periodically, the Semtec will exit receive mode from my understanding.

I would suggest you use the callback approach if you don't want to miss any packets.

GIPdA commented 5 years ago

Thanks for your answer. That seems to be the case indeed, but sounds strange that it works like that. Any idea how to change that?

The callback method works on interrupts, but what happens if I am in the middle of an other SPI transmission... there is no easy way to prevent concurrent access AFAIK.

sandeepmistry commented 5 years ago

Any idea how to change that?

You can change some of the code to enter continuous receive mode instead of single RX mode.

The callback method works on interrupts, but what happens if I am in the middle of an other SPI transmission... there is no easy way to prevent concurrent access AFAIK.

Good question, you'd have to do some testing to find the behaviour. There's some changes that could be made to wrap more operations in SPI transactions which block interrupts.

Another option would be to use noInterrupts() and interrupts() to disable them, not ideal but an option...

GIPdA commented 5 years ago

I'm back! :p Took some time, but I found a suitable solution.

In "packet" mode, the module is configured in RX Single: it waits for one packet or a timeout, then goes back to Standby. This timeout is what's important. On the Lora modules sx127x, the register is SymbTimeout and its value can range from 4 to 1023 symbols. The lib doesn't currently set this register.

The parsePacket() method checks if the module is in RX mode or not and put it back if not. If you can't call this method fast enough, you have "dead times" because the default receive window is only 100ms (see below). By increasing the timeout, the receive window is increased (and power consumption too) and the dead times reduced.

I tested it and it solves the original issue. What do you think about adding a method to set this timeout? I can add a pull request if you want.

Another, less ideal solution is to use the RX Continuous mode: enable it before a long blocking operation, and call idle() before parsePacket() to stop receiving (or you could receive a packet in the middle of a read and get garbage). Can be useful for very long blocking operations lasting longer than the max timeout.


The duration of a symbol is 2^SF / BW (SF: Spreading Factor, BW: Bandwidth), so with default values = 2^7 / 125kHz = 1.024ms Default RX Timeout value is 100 (0x64) symbols -> 100ms Max timeout is juste over 1sec (1023*1.024ms).

Some code I added to the lib (header & begin):

#define REG_SYMB_TIMEOUT_LSB     0x1f

  // set rx timeout
  uint16_t const rxTimeoutSymb = 550;
  writeRegister(REG_MODEM_CONFIG_2, (readRegister(REG_MODEM_CONFIG_2)&(~0x03)) | ((rxTimeoutSymb>>8)&0x03));
  writeRegister(REG_SYMB_TIMEOUT_LSB, rxTimeoutSymb);
morgan-wild commented 3 years ago

I'm very interested by this post. I noticed the same issue and I still looking for a good solution.

I already tried interrupts but I faced this issue #379 For now I'm using a xTask running fast to pool LoRa.parsePacket(). Not ideal but it is the better thing I have for the moment.

I will try your tip about the timeout register.

morgan-wild commented 3 years ago

I noticed a small amelioration with your tip in loop, and a more significant with my xTask.

The timeoutSymb was fixed to 255 (the second parameter of writeRegister is an uint8, not 16 so I can't use more).

GIPdA commented 3 years ago

The timeout value is split over 2 registers, 2 more bits are in REG_MODEM_CONFIG_2 (so a total of 10 bits).

Are you missing packets by just polling parsePacket in your task?

morgan-wild commented 3 years ago

Are you missing packets by just polling parsePacket in your task?

Hi! With the pooling approach it is the case, some packet are missing if there is something working hard in loop(). Your example gives to me this warning: large integer implicitly truncated to unsigned type [-Woverflow]

Nevertheless I'm trying it :)

macedolfm commented 3 years ago

I am using LoRa.h recommended for my Heltec V2 board, I noticed that everytime we call parsePacket() and there is a CRC error the function resets de RX mode to MODE_RX_SINGLE. As I wrote my code expecting the receiver to stay in MODE_RX_CONTINUOUS after I called receive() I was not dealing with this unexpected reset. Just replaced the SINGLE with the CONTINUOUS flag and all the problems solved, errors came down from 1% to 0.1%:

    if((irqFlags & IRQ_RX_DONE_MASK) && (irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0)
    {
        // received a packet
        _packetIndex = 0;
        // read packet length
        if(_implicitHeaderMode)
        {
            packetLength = readRegister(REG_PAYLOAD_LENGTH);
        } else
        {
            packetLength = readRegister(REG_RX_NB_BYTES);
        }
        // set FIFO address to current RX address
        writeRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR));
        // put in standby mode
        idle();
    }
    else if(readRegister(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS)) // was MODE_RX_SINGLE
    {
        // not currently in RX mode
        // reset FIFO address
        writeRegister(REG_FIFO_ADDR_PTR, 0);
        // put in single RX mode
        writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_CONTINUOUS); // was MODE_RX_SINGLE
    }
IoTThinks commented 3 years ago

I am using LoRa.h recommended for my Heltec V2 board, I noticed that everytime we call parsePacket() and there is a CRC error the function resets de RX mode to MODE_RX_SINGLE. As I wrote my code expecting the receiver to stay in MODE_RX_CONTINUOUS after I called receive() I was not dealing with this unexpected reset. Just replaced the SINGLE with the CONTINUOUS flag and all the problems solved, errors came down from 1% to 0.1%:

AFAIK, parsePacket will set the mode to RX_SINGLE. It is correct. If you want to use RX_CONTINUOUS RX, you can use receice() and receive callback.

macedolfm commented 3 years ago

Well, I am doing a time critical multi stepper control and I cannot be interrupted unless I say so, then I expected de LoRa receiver to behave like a UART, receives the data silently and waits until I can read it...callback are not an option

IoTThinks commented 3 years ago

@macedolfm The callback can just set a flag to indicate a packet is received. You can process the packet any time later based on the flag.

If you insist to use parsePacket, it is always RX_SINGLE.

macedolfm commented 3 years ago

@IoTThinks Thanks !!! Will go with ISR & flag....

macedolfm commented 3 years ago

Just an update, the ISR routine flag messes up with the OLED.display() from SSD1306Wire.h as it is really picky and dos not like to be interrupted or it messes up with the display data, Being so I falled back into a mixed approach of using parsePacket() and when it results in greater than ZERO I read the buffer data and put it back on MODE_RX_CONTINUOUS calling receive();

uint16_t loraRead(uint8_t* data, uint16_t max_len)
{
    if(LoRa.parsePacket() == 0 || max_len == 0)
    {
        return 0;
    }

    LoRa.idle();

    int16_t rssi = LoRa.packetRssi();
    int16_t snr = LoRa.packetSnr();

    // CLEAR BUFFER
    memset(data, 0, max_len);

    // LORA RX
    uint16_t count = 0; 
    while(LoRa.available())
    {
        if(count < max_len)
        {
            data[count++] = LoRa.read();
        }
        else
        {
            LoRa.read();
        }
    }   
    LoRa.receive();
    return count;
}