Sending GPS data over LoRa 433MHz

Hi,

So I'm building a weather balloon payload and am currently working on transmitting GPS and atmospheric data over LoRa to a ground station.

My setup is Sparkfun/Arduino Pro-micro 3.3v 8MHz, RFM9x LoRa 433MHz and a UBLOX M8N GPS.

The radio is transmitting fine to my receiver and sending a structure over no problem. The GPS on its own works great, but I'm having an issue adding it all together.

Take the code below;


#include <SPI.h>
#include <RH_RF95.h>
#include <NMEAGPS.h>

#define RFM95_CS 10
#define RFM95_RST 6
#define RFM95_INT 3
#define gpsPort Serial1
#define RF95_FREQ 433.0 

RH_RF95 rf95(RFM95_CS, RFM95_INT);  // Singleton instance of the radio driver

static NMEAGPS  gps; 

struct payload{
  uint16_t Secs_since_launch;
  float Lat;
  float Long;
  float GPS_Alt;
  float GPS_Speed;
  float VS;
  float Temp_in;
  float Temp_out;
  float V_batt;
};

payload test = {0, 53.42652, -2.24709, 9.99, 4.65, 5.5, -20.4, -56.34, 3.3};


void setup() 
{

  pinMode(RFM95_RST, OUTPUT);
  digitalWrite(RFM95_RST, HIGH);

  Serial.begin(9600);
  while (!Serial);
  Serial.println("Arduino LoRa TX Test!");

  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);

  while (!rf95.init()) {
    Serial.println("LoRa radio init failed");
    Serial.println(" ");
    while (1);
  }
  Serial.println("LoRa radio init OK!");

  if (!rf95.setFrequency(RF95_FREQ)) {
    Serial.println("setFrequency failed");
    while (1);
  }

  rf95.setTxPower(10, false); 
  rf95.setSpreadingFactor(12);
  rf95.setSignalBandwidth(62500);

  gpsPort.begin(38400);
  Serial.println("Setup Complete");
}


void loop()
{

  GPS();

  test.Secs_since_launch = millis() / 1000;
  rf95.send((uint8_t *)&test, sizeof(test));
  rf95.waitPacketSent();
  

       //while (gpsPort.available()) {.      //Testing code for GPS, works fine.  
       // char data = Serial1.read();
        //Serial.print(data); 
  }

}

void GPS(){

  
    while (gps.available(gpsPort)) {
    gps_fix fix = gps.read();
    if (fix.valid.location) {
    
    int32_t Lat_GPS_raw  = fix.latitudeL();
    int32_t Long_GPS_raw = fix.longitudeL();

    test.Lat = Lat_GPS_raw / 10000000.0;
    Serial.println(test.Lat, 5);
    test.Long = Long_GPS_raw / 10000000.0;
    test.GPS_Alt = fix.altitude(); //meters MSL
    float speed_mph = fix.speed_metersph();
    test.GPS_Speed = speed_mph / 3600.0;

      }
    }
    
}

I tested the LoRa code on its own, no issues. I tested the GPS code on its own, and it parses the data with no issues, but when I uncomment both the GPS code and LoRa code in the main loop, the LoRa is still sending data but it's not being updated with the GPS values nor is the Serial.print line in the GPS code printing.

I have a theory as to why this isn't working but my knowledge isn't good enough to work out how to fix it. I was struggling earlier today with just getting the GPS to run alone, I had a 'delay(500);' statement after 'GPS();' and that was enough to stop it working. So I think this is a similar issue something to do with the processor is running the delay() code while it should be doing things in the GPS.

I've tried a few things but nothing has worked, any ideas how do I best go about fixing it?

Many thanks.

  rf95.waitPacketSent();

is a delay, and can interfere with GPS data reception.

    Serial.println(test.Lat, 5);

So is printing at the glacially slow Baud rate of 9600. Use a modern rate of 115200 or even faster. However, there is no use for Serial.print() in a weather balloon.

Your loop structure should be rearranged to use millis() for timing when to send data packets, with no unnecessary delays or other actions.

nor is the Serial.print line in the GPS code printing.

That suggests no satellite fix. If you are indoors, move outside to where the setup has a clear view of the sky.

:so should I just exclude:


rf95.waitPacketSent();

?

Eventually I’ll run it in a 5 second loop so it doesn’t transmit any more than that, I just haven’t done it at the moment as with the slow data rates it’s taking about 3 seconds to send each packet.

I just have the serial print for debugging, it won’t make the Final Cut! :slight_smile:

waitPacketSent does nothing except delay while the transmitter is active.

Did you test the setup outdoors?

You can give waitPacketSent() an argument for a timeout period, it will return true if the packet has been sent, false if it times out.

  while (!rf95.waitPacketSent(5)) //wait a maximum of 5mS
  {
    GPS(); //feed the GPS while the packet is being send
  }

Good that you are using a Pro Micro, that avoids the problems with using SoftwareSerial.

The way you have loop() written in the code, its unlikely you will ever get valid GPS data. The majority of the time is spent in waitPacketSent(), where the GPS receive buffer is overflowing, corrupting the data.

Ok great thanks for that.

So if I had another few quick functions to run, such as; get barometric pressure, measure battery voltage and measure the temperature sensors, would they be best placed outside the while loop?

Something like this:

void loop()
{
  GPS();
  if ((millis() - previousMillis) >= 5000ul)
  {
    previousMillis += 5000ul;
    //read barometric pressure
    //measure battery voltage
    //read temperature sensors
    //send data via rf95
  }
}

You might want to check that the previous packet has been send immediately before sending the new data, but if the transmit time exceeds the 5 second interval you are going to have problems anyway.

What type of temperature sensors are you using? The DS18B20 can have a significant time delay if you do not read it properly.

Thanks I’ll give that a whirl. The send time is currently around 3 seconds but I can just adjust the loop to keep it well clear of that. Even sending data every 10 isn’t an issue on a flight that takes 3hrs potentially.

Yes I’ve got some DS18B20 sensors but haven’t had a chance to play with them yet. Is it worth just using the library or is there a better approach?

The library (DallasTemperature) for the DS18B20 should work ok, you just need to be careful how you read the sensor. There is a considerable delay between when you request a temperature reading and when the sensor is ready to send it (anywhere from 93.5mS to 750mS depending on the number of bits of resolution). By default the library uses a blocking method, where you call the function that reads the temperature, and it only returns when the data has been received from the sensor. The alternative method allows you to request the sensor to read the temperature, but relies on your code to wait the required time before actually reading the data (see the WaitForConversion and WaitForConversion2 example sketches).

1 Like

Also, don't use the ...ByIndex() version of the functions to fetch the temperature after conversion. That causes a scan the OneWire address space in order to determine the address of the indexed sensor. Instead gather the sensor address(es) once in setup() and save them. Then, when processing in loop(), use the functions such as getTempC() and getTempF() which take a OneWire address as their parameter.

1 Like
  GPS();
  if ((millis() - previousMillis) >= 6000ul){
    previousMillis += 6000ul;
    test.Secs_since_launch = millis() / 1000;
    rf95.send((uint8_t *)&test, sizeof(test));
    //rf95.waitPacketSent();
  }

So I've commented out the waitPacket sent and I'm getting a steady stream of GPS data through and also sending packets every 6 seconds, great.

But are there any potential pitfalls with not having the waitpacketsent statement?

The hazard is that you will try to send a packet before the previous packet has finished transmitting. I would call waitPacketSent() before sending the new data to avoid this. At this point waitPacketSent() should return immediately and not cause any delay, since there should have been plenty of time for the previous data to finish sending.

  GPS();
  if ((millis() - previousMillis) >= 6000ul) {
    previousMillis += 6000ul;
    while (!rf95.waitPacketSent(5)) {
      GPS();
    }
    test.Secs_since_launch = millis() / 1000;
    rf95.send((uint8_t *)&test, sizeof(test));
  }
1 Like