LoRa corrupt data if not using floats

Hi, I'm building a high altitude balloon payload using 433Mhz RFM9X LoRa module running Radiohead library. My latest idea was to reduce the size of the structure I'm using to send the data, so take floats and use int16_t instead, saving 2 bytes of data per field. I ran into some strange problems where half the structure sends fine, but the second half is corrupted. The numbers are all over the place, stable, but very wrong. After debugging I changed all the fields back to float and it worked perfectly.

What is it about having integers in the structure that is doesn't like? Thanks!

Receiver code

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

#define RFM95_CS 53
#define RFM95_RST 2
#define RFM95_INT 3
#define RF95_FREQ 433.050

RH_RF95 rf95(RFM95_CS, RFM95_INT);


struct payload{
  uint32_t Secs_since_launch;
  int32_t Lat;
  int32_t Long;
  float GPS_Alt;
  float GPS_Speed;
  float VS;   
  float Temp_out;
  float V_batt;      
  float Max_Alt;     
  uint8_t SF;         
};
payload Data;


void setup() {
    Serial.begin(115200);
  delay(500);

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

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

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

  if (!rf95.setFrequency(RF95_FREQ)) {
    while (1);
  }

  rf95.setTxPower(14, false);
  rf95.setSpreadingFactor(7);
  rf95.setCodingRate4(5);
  rf95.setSignalBandwidth(62500);

  Serial.println("Setup Finished");
}

void loop() {

  if (rf95.available())
  {
    uint8_t len = 251; 
    if (rf95.recv((uint8_t*)&Data, &len))
    {
      //Print data
    }
  }
 
} 

Transmitter code

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

#define RFM95_CS 5
#define RFM95_RST 6
#define RFM95_INT 7 // Must be hardware interrupt
#define gpsPort Serial1
#define RF95_FREQ 433.050    
#define Serial SerialUSB // Use this line for the MKR Zero!!

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

static NMEAGPS  gps; 

struct payload{
  uint32_t Secs_since_launch;
  int32_t Lat;
  int32_t Long;
  uint16_t GPS_Alt;
  uint16_t GPS_Speed;
  int16_t VS;   
  float Temp_out;
  uint16_t V_batt;      
  uint16_t Max_Alt;     
  uint8_t SF;         
};


payload data = {0, 0, 0, 0, 0, 0, 0, 0, 0, 7};
unsigned long previousMillis = 0;
float last_Alt; 
int Receive_count = 0; 

uint32_t timer, looptime = 4000;

void setup() 
{

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

  Serial.begin(115200);
  //while (!Serial);
  delay(500);

  Serial.println(F("SerialUSB started"));
  
  digitalWrite(RFM95_RST, LOW); // manual reset
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);

  GPS_setup();

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

  if (!rf95.setFrequency(RF95_FREQ)) {
    while (1);
  }

  rf95.setTxPower(10, false);  //set transmitter powers from 5 to 23 dBm:
  rf95.setSpreadingFactor(data.SF);
  rf95.setSignalBandwidth(62500); //Don't go lower than this without temp compensated Xtal
  rf95.setCodingRate4(5);
  Serial.println(F("LoRa power, freq, BW & SF set"));

  //sensors.begin(); //Initialise temp sensors

  /*if (!sd.begin(chipSelect, SPI_FULL_SPEED)){
    SD_present = false;
  }
  else{
    SD_present = true;
    SD_header();
  }*/

  //Buzzer to signal setup complete.
  Serial.println("Setup Complete");
}


void loop()
{

  GPS();

  if ((millis() - previousMillis) >= looptime){ 
    previousMillis += looptime;


    //Read Battery voltage
    analogReadResolution(8);
    int sensorValue = analogRead(A1);
    data.V_batt = sensorValue * 2 * (3.3 / 2.56);
    float V_batt_temp = data.V_batt / 100.0;
    
    
    //Read Temp data
    float Temp_in_temp  = 23.5;
    float Temp_out_temp = -65.6;
    data.Temp_out = Temp_out_temp;
    
    
    //Calculate VS
    float dT = looptime / 1000;
    float VS10 = (data.GPS_Alt - last_Alt)*10 / dT;   
    data.VS = VS10;
    float VS_temp = data.VS / 10.0;
    last_Alt = data.GPS_Alt;


    //Send data packet over LoRa
    data.Secs_since_launch = millis() / 1000ul;
    rf95.send((uint8_t *)&data, sizeof(data));
    rf95.waitPacketSent();

  }
} //end of main loop

In a nutshell, This doesn't work:

struct payload{
  uint32_t Secs_since_launch;
  int32_t Lat;
  int32_t Long;
  uint16_t GPS_Alt;
  uint16_t GPS_Speed;
  int16_t VS;   
  float Temp_out;
  uint16_t V_batt;      
  uint16_t Max_Alt;     
  uint8_t SF;         
};

But this does:

struct payload{
  uint32_t Secs_since_launch;
  int32_t Lat;
  int32_t Long;
  float GPS_Alt;
  float GPS_Speed;
  float VS;   
  float Temp_out;
  float V_batt;      
  float Max_Alt;     
  uint8_t SF;         
};

When using the first struct all values after Temp_out are garbage. Really drawing a blank with this one.

So that would be V_batt, Max_Alt, SF.
Show us the code that assigns data to those fields for the version you say is wrong.

SF is initialised at startup.

V_batt is in the main loop:

//Read Battery voltage
    analogReadResolution(8);
    int sensorValue = analogRead(A1);
    data.V_batt = sensorValue * 2 * (3.3 / 2.56);
    float V_batt_temp = data.V_batt / 100.0;

And Max_Alt:

data.GPS_Alt = fix.altitude(); //meters MSL
    if(data.GPS_Alt > data.Max_Alt){
      data.Max_Alt = data.GPS_Alt;
    }

This last one is from the GPS code which is very simple and I know the problem isn't there.

Um, if the data is messed up, the code that messes it up is what you have to look at. Even without seeing the GPS module I can see you have a misunderstanding. You do nothing in the assignment for Max_Alt, it is wrong for either float or int without a cast, probably the same for the GPS code.

On the transmitter code I omitted the GPS tab but here it is for completeness.

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();

    data.Lat = Lat_GPS_raw;
    data.Long = Long_GPS_raw;
    data.GPS_Alt = fix.altitude(); //meters MSL
    if(data.GPS_Alt > data.Max_Alt){
      data.Max_Alt = data.GPS_Alt;
    }
    float speed_mph = fix.speed_metersph(); //meters per hour
    data.GPS_Speed = speed_mph / 3600.0;

      }
    }
    
}

Max_Alt is assigned within the structure, is that what you mean?

Which Arduinos are you using for the transmitter and receiver ?

What is the datatype, when we say ALL the code we mean ALL tabs, ino files, cppo files, .h files. Doesn't matter now, it's obvious what is wrong.

MKR Zero for Tx and Mega Pro for Rx.

In general, one shouldn't assume that a struct on one type of processor occupies exactly the same bytes on a different type of processor. This could be due to sizes of types and padding. I'm not saying it's causing a problem here, just something to be aware of.

Yep care needed when sending a LoRa packet thats a structure from one Arduino type to another.

This is an example that I have used to work around the different structure padding issues;

struct controllerStructure
{
  uint16_t destinationNode;
  uint8_t outputNumber;
  uint8_t onoroff;
} __attribute__((packed, aligned(1)));          //remove structure padding so there is compatibility between 8bit and 32bit Arduinos

WOW, now you introduced possible architecture differences as well as data conversion errors mixing float and int. Do you have any education/training in basic data representations, compatible data types? Writing the code is the easy part, keeping track of all the conversions going on is a real challenge.
In this case, trying to save a couple bytes for whatever reason is waaaaaay more trouble than it's worth. I almost forgot, there is also endianness(sp).

This is a good example of why we create a single copy of a structure definition so it doesn't get copied in error.
Either put all the 4 byte 32 bit vars first, the single 8 bit last or the floats first then the integers then the char.
I 'think' that is natural alignment, but adding packed and aligned(4) may help.

WOW, now you introduced possible architecture differences as well as data conversion errors mixing float and int. Do you have any education/training in basic data representations, compatible data types?

Doesn't matter now, it's obvious what is wrong.

No I don't have any formal training or education in computing, software or coding. I do it because it's fun and I'm shooing in the dark most times but manage to muddle my way through it. Thank you for your really constructive replies and helpful comments. Always appreciated.

Thanks! I'll look into this and have a play.

You are very welcome, it's always fun to help a noob in the first hour of his coding life.
Get it working without trying to save those few bytes, then see how it can be optimized.

Yes, that too. I know it's inefficient, but sending messages in JSON might be a way forward. OTOH it introduces another learning curve to work out how to use the JSON library. The old school way, writing a spec of byte contents and offsets, then forcing each end of the link to conform to that with explicit code for packing / unpacking the message is another option. Or just rely on luck and try sending / receiving a struct :slight_smile:

I only started coding 2 weeks ago so it’s been a steep learning curve. Luckily there are humble people like you help us noobs out :slight_smile:

I'm not aware of any Big Endian boards / processors in the Arduino ecosystem. They're all Little Endian.