Data assignment in Data Structure causes loss of data

In my program I read wind speed and direction. I display the data on an LCD and I assign the data values to a new variable in a data structure which is then transmitted to a second Arduino Mega using a nRF24 radio link. On the LCD the wind speed and direction varies between the actual value and zero.

At the point in the code where the wind is read in from a software serial input the data is always correct but when it gets back to the point on the next iteration the data value has become zero before it is updated again. Here is that part of the code and sample data:

void Get_WIMWV()
   {
    const int print_MWV = 0;
    j_MAX = 5;  // number of Words in NEMA Sentence
    Parse_Wind ();
    //Serial.print("Checksum Status "); Serial.println(checksum_status);
    if(checksum_status){  // if checksum is true process data 
    for(int j = 0; j<j_MAX; j++) // Since 
     {
      data_MWV[j] = data_IN[j];  //see void Parse Sentence
     } 
   //    *************  PROCESS DATA  **********************  
 
     Serial.print("wind Dir before update "); Serial.println(Wind_Dir,0); 
    string1 = data_MWV[1];
    NEMA_TO_FLOAT(1); // this takes the char data that looks like the wind bearing and converts it to a floating point value
    Wind_Dir = float3;  
     Serial.print("wind Dir after update in WIMWV "); Serial.println(Wind_Dir,0);   
    //ETdata.SD_course = course;
         
        // data_MWV[2], R or T Relative or True
        
        // data_MWV[3], Wind Speed
    string1 = data_MWV[3];
    NEMA_TO_FLOAT(1); // this takes the char data that looks like the wind speed and converts it to a floating point value
    Wind_Speed = float3;
       
        // data_MWV[4], Wind_Speed Units K/M/N N = knots
       
      if(print_MWV) {PRINT_MWV();}      
    } // end if checksum_status true     
    //PRINT_MWV();        
   }  //end of void WIMWV() case

Sample output:

wind Dir before update 0
wind Dir after update in WIMWV 119
wind Dir before update 0
wind Dir after update in WIMWV 122
wind Dir before update 0
wind Dir after update in WIMWV 129

I have fixed the problem by making a temporary data assignment but I do not understand why it fixes the problem.

I have looked for obvious causes like data being assigned a new value somewhere else in the code or code that is too big and causing overflow.

The code uses a data structure so that data can be transmitted and received between nRF24 radios and they will have the same structure so the data can be read directly. Here is the data structure which is 32 bytes. This structure is defined before the void setup().

  struct RF_DATA
     {
      // total data per packet is 32 byte. int = 2, float = 4, + char text

      char RFD_text[8];
      int16_t RFD_int1;
      int16_t RFD_int2;
      int16_t RFD_int3;
      int16_t RFD_int4;
      int8_t  RFD_int5;
      int16_t RFD_int6;
      int16_t RFD_int7;
      int16_t RFD_int8;
      int8_t  RFD_int9;
      int16_t RFD_int10; 
      int16_t RFD_int11;
      int16_t RFD_int12;
      int16_t RFD_int13;

     }; // end RF_DATA1 structure    
    RF_DATA RFdata; //NAME THE DATA STRUCTURE

The Wind_Speed and Wind_Dir variables are type float. In the subroutine they are converted to int type and the assignments are made to the RFdata in the send data subroutine and here is that code.

/*  RADIO RF24 */
// based on formatted data sketch
/* 
Sending formatted data packet with nRF24L01. 
Maximum size of data struct is 32 bytes.
contributted by iforce2d

The wire numbers listed are using a ribbon wire and a two row ribbon wire connector
1 - GND, wire 2
2 - VCC 3.3V !!! NOT 5V, wire 1
3 - CE to Arduino pin 9, wire 4
4 - CSN to Arduino pin 10, wire 3 
5 - SCK to Arduino pin 13 for Uno, 52 on Mega, wire 6
6 - MOSI to Arduino pin 11 for Uno, 51  on Mega, wire 5
7 - MISO to Arduino pin 12 for Uno,  50 on Mega, wire 8
8 - UNUSED, wire 7
*/

void sendData1() 
{
 Active_waypoint.toCharArray(RFdata.RFD_text,8);
 RFdata.RFD_int1 = int(heading);
 RFdata.RFD_int2 = int(heading_to_steer);
 RFdata.RFD_int3 = int(course);
 RFdata.RFD_int4 = int(course_to_steer);
 RFdata.RFD_int5 = byte(Steering_Mode);
 RFdata.RFD_int6 = int(bearingrate);   
 RFdata.RFD_int7 = int(Waypoint_Bearing_From[WPT_index]); //BOD
 RFdata.RFD_int8 = int(Bearing_to_destination_by_LatLon);
 RFdata.RFD_int9 = byte(MSG);
 RFdata.RFD_int10 = bnoCAL_status;
 Serial.print("Wind A "); Serial.println(Wind_Dir);
 RFdata.RFD_int11 = int(Wind_Dir);
 Serial.print("Wind B "); Serial.println(Wind_Dir);
 RFdata.RFD_int12 = int(Wind_Speed);
 RFdata.RFD_int13 = int(XTE);

radio.stopListening(); // stop listening so we can send data 
radio.powerUp();
delay(3);
radio.write(&RFdata, sizeof(RF_DATA));  
radio.startListening(); // resume listening
}

Here is some typical output:

Wind A 124.50
Wind B 124.50
Wind A 124.50
Wind B 124.50
Wind A 0.00
Wind B 0.00
Wind A 137.00
Wind B 137.00
Wind A 137.00
Wind B 137.00
Wind A 137.00
Wind B 137.00
Wind A 0.00
Wind B 0.00
Wind A 110.50
Wind B 110.50
Wind A 110.50
Wind B 110.50
Wind A 110.50
Wind B 110.50
Wind A 0.00
Wind B 0.00
Wind A 123.50
Wind B 123.50
Wind A 123.50
Wind B 123.50
Wind A 123.50
Wind B 123.50
Wind A 137.50
Wind B 137.50
Wind A 137.50
Wind B 137.50
Wind A 0.00
Wind B 0.00
Wind A 0.00
Wind B 0.00
Wind B 0.00
Wind A 0.00
Wind B 0.00
Wind A 117.50
Wind B 117.50
Wind A 0.00

Now if I make the type int() conversions to temporary variables separately from the assignment to RFdata the code works correctly. Here is the revised code and output:

 /*  RADIO RF24 */
 // based on formatted data sketch
 /* 
Sending formatted data packet with nRF24L01. 
Maximum size of data struct is 32 bytes.
 contributted by iforce2d

The wire numbers listed are using a ribbon wire and a two row ribbon wire connector
1 - GND, wire 2
2 - VCC 3.3V !!! NOT 5V, wire 1
3 - CE to Arduino pin 9, wire 4
4 - CSN to Arduino pin 10, wire 3 
5 - SCK to Arduino pin 13 for Uno, 52 on Mega, wire 6
6 - MOSI to Arduino pin 11 for Uno, 51  on Mega, wire 5
7 - MISO to Arduino pin 12 for Uno,  50 on Mega, wire 8
8 - UNUSED, wire 7
*/

void sendData1() 
{
 int tmp1 = int(Wind_Dir); 
 int tmp2 = int(Wind_Speed);
 Active_waypoint.toCharArray(RFdata.RFD_text,8);
 RFdata.RFD_int1 = int(heading);
 RFdata.RFD_int2 = int(heading_to_steer);
 RFdata.RFD_int3 = int(course);
 RFdata.RFD_int4 = int(course_to_steer);
 RFdata.RFD_int5 = byte(Steering_Mode);
 RFdata.RFD_int6 = int(bearingrate);   
 RFdata.RFD_int7 = int(Waypoint_Bearing_From[WPT_index]); //BOD
 RFdata.RFD_int8 = int(Bearing_to_destination_by_LatLon);
 RFdata.RFD_int9 = byte(MSG);
 RFdata.RFD_int10 = bnoCAL_status;
 Serial.print("Wind A "); Serial.println(Wind_Dir);
 RFdata.RFD_int11 = tmp1; //int(Wind_Dir);
 Serial.print("Wind B "); Serial.println(Wind_Dir);
 RFdata.RFD_int12 = tmp2; //int(Wind_Speed);
 RFdata.RFD_int13 = int(XTE);
 radio.stopListening(); // stop listening so we can send data 
 radio.powerUp();
 delay(3);
 radio.write(&RFdata, sizeof(RF_DATA));  
 radio.startListening(); // resume listening
}

Typical output no longer has any zero values, here is typical output.

Wind B 106.00
Wind A 106.00
Wind B 106.00
Wind A 106.00
Wind B 106.00
Wind A 106.00
Wind B 106.00
Wind A 105.50
Wind B 105.50
Wind A 105.50
Wind B 105.50
Wind A 105.50
Wind B 105.50
Wind A 105.50
Wind B 105.50
Wind A 105.50

It is interesting to note that the int() type conversion and assignments for the first ten variables do not exhibit this error. This part works fine.

 Active_waypoint.toCharArray(RFdata.RFD_text,8);
 RFdata.RFD_int1 = int(heading);
 RFdata.RFD_int2 = int(heading_to_steer);
// if(Steering_Mode == 4) RFdata.RFD_int2 = int(wind_to_steer);
 RFdata.RFD_int3 = int(course);
 RFdata.RFD_int4 = int(course_to_steer);
 RFdata.RFD_int5 = byte(Steering_Mode);
 RFdata.RFD_int6 = int(bearingrate);   
 RFdata.RFD_int7 = int(Waypoint_Bearing_From[WPT_index]); //BOD
 RFdata.RFD_int8 = int(Bearing_to_destination_by_LatLon);
 RFdata.RFD_int9 = byte(MSG);
 RFdata.RFD_int10 = bnoCAL_status;

I am hoping someone can help me understand why the original code didn't work and why it works when the int() type conversion is done separately.

The behaviour where adding variables solves the problem might indicate memory issues. Seeing signs of the use of String (capital S) in your code might explain that.

Please post your full code; if too big, attach the ino file to a post.

Thank you for your reply.

The code is named Autopilot_JNE_15.0D it is in 31 files/tabs, the main tab is attached. The full code is in a DropBox folder and here is a link.

Autopilot_JNE_15.0D.ino (34.4 KB)

Please do others a favour and zip the complete sketch directory and attach that (or put it on dropbox). Downloading 31 files is no fun; I know as I went through that exercise :wink:

I strongly suspect the heavy use of the String class (capital S) to be the culprit. The String class is extremely convenient but it can leave holes in your memory where you can hide an elephant and as a result you have memory issues.

It will be a lot of work and possibly thinking, but use of nul-terminated character arrays (so called c-strings) is preferred.

To ease the memory usage a bit, move all your fixed strings to PROGMEM using the F macro. E.g.

void PRINT_MWV()
{  
    // Serial.println();
    Serial.println(F("---------------"));   

    Serial.print(F("Header: "));  
    Serial.println(data_MWV[0]);

    Serial.print(F("Wind Bearing: "));    
    Serial.println(Wind_Dir);

    ...
    ...
}

You can do this for the LCD as well.

This will not solve your problem, only postpone when it will show its ugly head.

Thank you again for the help. Attached is a zip file of the code. It can also be downloaded from the dropbox as a single folder instead of 31 separate files.

I did not understand the difference between the flash memory and the SRAM memory. Following your advice I looked at PROGMEM and the Arduino description of the memory types. When I compile the compiler message is:

Sketch uses 44534 bytes (17%) of program storage space. Maximum is 253952 bytes.
Global variables use 4009 bytes (48%) of dynamic memory, leaving 4183 bytes for local variables. Maximum is 8192 bytes.

I am not sure how to interpret the use of local variables but I will explore using PROGMEM where I can. I will also explore using char[] arrays instead of Strings.

So I'll see what I can do with those ideas.

Autopilot_JNE_15.0D.zip (75.7 KB)

WileCoyote:
Sketch uses 44534 bytes (17%) of program storage space. Maximum is 253952 bytes.
Global variables use 4009 bytes (48%) of dynamic memory, leaving 4183 bytes for local variables. Maximum is 8192 bytes.

I am not sure how to interpret the use of local variables but I will explore using PROGMEM where I can.

You have a number of variables declared in the beginning of Autopilot_JNE_15.0D; those are outside any functions and are called global variables.

If you declare a variable inside a function, it's local to that function; those are the local variables. The IDE does not count them when reporting the memory usage. So if you declare an int x in loop(), your reported global variables will stay at 4009 but when the code runs, every time when the loop() function is entered the code will actually use 4011 (2 bytes extra for an int).

The IDE also has no idea how big your Strings will grow, so it also does not report on the actual size of them.

If you want to know more about global and local, read up on c++ scope