Guidance on structures (structs)

i have some code for basically an NRF24 to rs485 translator to allow wireless access to the network for various devices.

my question is for a small section of the code regarding structs. the code is very rough but here it is >

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include "SerialTransfer.h"

RF24 radio(10, 2);   // nRF24L01 (CE, CSN) on raptor protosystem using NANO CE0 is pin 10, CSN attacheed to pin2 for NRF24-PRI
const byte address[6] = "00001";

#define RS485inout  9 // RS485 Transmit or Receive status | on raptor protosystem using NANO no direct connection can be made to RE-DE TIE so jumper must be place to gpio9
#define RS485Transmit  HIGH
#define RS485Receive  LOW

#define NetCommandRX 0
#define NetCommandTX 1

#define THIS_NODE_ID 0x1A
#define CENTRAL_PROC_ID 0x01

unsigned long lastReceiveTime = 0;
unsigned long currentTime = 0;

byte TXRXMode = 1;                //rs485 Listen/transmit mode


// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte command;
  byte remID;
  byte j1PotX;
  byte j1PotY;
  byte j1Button;
  byte j2PotX;
  byte j2PotY;
  byte j2Button;
  byte Sel;
  byte Start;
  byte X;
  byte Circle;
  byte Square;
  byte Triangle;
  byte L1;
  byte L2;
  byte R1;
  byte R2;
  byte U;
  byte D;
  byte L;
  byte R;

};

struct RS485Data_Package {
  byte NetCommand;         //00 Recieve (NetworkControl),  01 Transmit (NetworkControl)
  byte remID;             //Sender ID# (See List of Available IDs)
  byte destID;      //Intended Reciever (i.e ThisNode)
  byte ControlCommand;    //What to Do with this information
  byte Byte0;     //
  byte Byte1;     //
  byte Byte2;     //
  byte Byte3;     //
  byte Byte4;     //
  byte Byte5;     //
  byte Byte6;     //
  byte Byte7;     //
  byte Byte8;     //
  byte Byte9;     //
  byte Byte10;      //
  byte Byte11;      //
  byte Byte12;      //
  byte Byte13;      //
  byte Byte14;      //
  byte Byte15;      //
  byte Byte16;      //
  byte Byte17;      //
  byte Byte18;      //
  byte Byte19;      //
  byte Byte20;      //
  byte Byte21;      //
  byte Byte22;      //
  byte Byte23;      //
  byte Byte24;      //
  byte Termination;   // End of Transmission Marker
};

Data_Package data; //Create a variable with the above structure
RS485Data_Package DatStruct;
RS485Data_Package IncommingStruct;
SerialTransfer myTransfer;


void setup() {

  Serial.begin(115200);
  myTransfer.begin(Serial);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_LOW);
  radio.startListening(); //  Set the module as receiver
  resetData();
}
void loop() {
  // Check whether there is data to be received
  if (radio.available()) {
    radio.read(&data, sizeof(Data_Package)); // Read the whole data and store it into the 'data' structure
    lastReceiveTime = millis(); // At this moment we have received the data
    RS485Data_Package DatStruct = {NetCommandRX,
                                   CENTRAL_PROC_ID,
                                   THIS_NODE_ID,
                                   0,
                                   data.command,
                                   data.remID,
                                   data.j1PotX,
                                   data.j1PotY,
                                   data.j1Button,
                                   data.j2PotX,
                                   data.j2PotY,
                                   data.j2Button,
                                   data.Sel,
                                   data.Start,
                                   data.X,
                                   data.Circle,
                                   data.Square,
                                   data.Triangle,
                                   data.L1,
                                   data.L2,
                                   data.R1,
                                   data.R2,
                                   data.U,
                                   data.D,
                                   data.L,
                                   data.R,
                                   0,
                                   0,
                                   0xFF
                                  };
    if (TXRXMode != 2) {
      if (TXRXMode != 1) { // Recieve all Packets
        if (myTransfer.available())
        {
          digitalWrite(RS485inout, RS485Receive);
          myTransfer.rxObj(IncommingStruct);
          delay(1);
          ParseDatStruct();
        }
      } else {                        // Send Packet
        delay(5);
        digitalWrite(RS485inout, RS485Transmit);
        myTransfer.sendDatum(DatStruct);
        delay(5);
        //TXRXMode = 0;
        digitalWrite(RS485inout, RS485Receive);
      }
    }

  }
  // Check whether we keep receving data, or we have a connection between the two modules
  currentTime = millis();
  if ( currentTime - lastReceiveTime > 1000 ) { // If current time is more then 1 second since we have recived the last data, that means we have lost connection
    resetData(); // If connection is lost, reset the data. It prevents unwanted behavior, for example if a drone has a throttle up and we lose connection, it can keep flying unless we reset the values
  }
  // Print the data in the Serial Monitor
  //Serial.print("j1PotX: ");
  //Serial.print(data.j1PotX);
  //Serial.print("; j1PotY: ");
  //Serial.print(data.j1PotY);
  //Serial.print("; button1: ");
  //Serial.print(data.X);
  //Serial.print("; j2PotX: ");
  //Serial.println(data.j2PotX);

}

void resetData() {
  // Reset the values when there is no radio connection - Set initial default values
  data.command = 0;
  data.remID = 0;
  data.j1PotX = 0;
  data.j1PotY = 0;
  data.j1Button = 0;
  data.j2PotX = 0;
  data.j2PotY = 0;
  data.j2Button = 0;
  data.Sel = 0;
  data.Start = 0;
  data.X = 0;
  data.Circle = 0;
  data.Square = 0;
  data.Triangle = 0;
  data.L1 = 0;
  data.L2 = 0;
  data.R1 = 0;
  data.R2 = 0;
  data.U = 0;
  data.D = 0;
  data.L = 0;
  data.R = 0;
}

void ParseDatStruct() {
  if (!IncommingStruct.destID == THIS_NODE_ID) {
    return;
  } else {
    switch (IncommingStruct.ControlCommand) {
      case 0x00:                                     //Command 0 = Copy Data
        //CopyStructData();
        break;
      case 0x01:                                     //Command 1 = Change Serial Mode
        ChangeSerialMode();
        break;
      case 0x13:                                    //for this to work the command sent needs to be NetCommand = 1,CENTRAL_PROC_ID = 0, THIS_NODE_ID = radio translator id ,ControlCommand = 0x13                         
        delay(5);                                      
        digitalWrite(RS485inout, RS485Transmit);
        myTransfer.sendDatum(DatStruct);
        delay(5);
        digitalWrite(RS485inout, RS485Receive);
        break;
      default:
        return;
        break;
    }
  }

}

void ChangeSerialMode() {
  TXRXMode =  IncommingStruct.NetCommand;
}

my question pertains to the section of code in the main loop :

 RS485Data_Package DatStruct = {NetCommandRX,
                                   CENTRAL_PROC_ID,
                                   THIS_NODE_ID,
                                   0,
                                   data.command,
                                   data.remID,
                                   data.j1PotX,
                                   data.j1PotY,
                                   data.j1Button,
                                   data.j2PotX,
                                   data.j2PotY,
                                   data.j2Button,
                                   data.Sel,
                                   data.Start,
                                   data.X,
                                   data.Circle,
                                   data.Square,
                                   data.Triangle,
                                   data.L1,
                                   data.L2,
                                   data.R1,
                                   data.R2,
                                   data.U,
                                   data.D,
                                   data.L,
                                   data.R,
                                   0,
                                   0,
                                   0xFF
                                  };

will this set all the variables in the named struct or does this redeclare the entire struct as a new locally named struct? I am new to the handling of structs in arduino so some guidance would be helpful .

if i have to set all the variables within the struct using the DatStruct.Netcommand = 0x00 and set each variable in the struct then i do not gain much aside from a nifty organization of the data.

to clarify my thoughts i suppose what i am asking is how do you set all the variables in a struct at the same time. Have i done this correctly, if not can you please advise how becasue i have googled it to death and i keep coming across answers that don't indicate how to do this .

thanks in advance.

Yes. That creates and fills in the packet that is then sent, using this line:
myTransfer.sendDatum(DatStruct);

Your code can be improved by using a byte array instead of Byte0..Byte24

struct RS485Data_Package {
  byte NetCommand;         //00 Recieve (NetworkControl),  01 Transmit (NetworkControl)
  byte remID;             //Sender ID# (See List of Available IDs)
  byte destID;      //Intended Reciever (i.e ThisNode)
  byte ControlCommand;    //What to Do with this information
  byte dta[25];
  byte Termination;   // End of Transmission Marker
};

Another improvement is to copy the data struct in one go using memcpy instead of copying the individual bytes; below based on the above (although it could have been done with your Byte0 as well). This assumes that Byte0..Byte24 in your original code match the format of Data_Package which seems to be the case.

  memcpy(&DatStruct.dta, &data, sizeof(data));

Full code to demonstrate

// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte command;
  byte remID;
  byte j1PotX;
  byte j1PotY;
  byte j1Button;
  byte j2PotX;
  byte j2PotY;
  byte j2Button;
  byte Sel;
  byte Start;
  byte X;
  byte Circle;
  byte Square;
  byte Triangle;
  byte L1;
  byte L2;
  byte R1;
  byte R2;
  byte U;
  byte D;
  byte L;
  byte R;
};


// demo received data
Data_Package data =
{
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
};

struct RS485Data_Package {
  byte NetCommand;         //00 Recieve (NetworkControl),  01 Transmit (NetworkControl)
  byte remID;             //Sender ID# (See List of Available IDs)
  byte destID;      //Intended Reciever (i.e ThisNode)
  byte ControlCommand;    //What to Do with this information
  byte dta[25];
  byte Termination;   // End of Transmission Marker
};


void setup()
{
  RS485Data_Package DatStruct;
  
  // fill header
  DatStruct.NetCommand = 0x20;
  DatStruct.remID = 0x21;
  DatStruct.destID = 0x22;
  DatStruct.ControlCommand = 0x23;
  DatStruct.Termination = 0xFF;

  Serial.begin(115200);
  Serial.println(F("\r\nData"));
  printFormattedData(data);
  Serial.println(F("\r\nRS485Data"));
  printFormattedRS485Data(DatStruct);
  // copy data into DatStruct
  memcpy(&DatStruct.dta, &data, sizeof(data));
  Serial.println(F("\r\nRS485Data after copy"));
  printFormattedRS485Data(DatStruct);
}

void loop()
{
  // put your main code here, to run repeatedly:

}

void printHex(byte b)
{
  Serial.print("0x");
  if (b < 0x10)
  {
    Serial.print("0");
  }
  Serial.print(b, HEX);
}


void printFormattedData(Data_Package &pkg)
{
  Serial.print(F("command:  ")); printHex(pkg.command); Serial.println();
  Serial.print(F("remID:    ")); printHex(pkg.remID); Serial.println();
  Serial.print(F("j1PotX:   ")); printHex(pkg.j1PotX); Serial.println();
  Serial.print(F("j1PotY:   ")); printHex(pkg.j1PotY); Serial.println();
  Serial.print(F("j1Button: ")); printHex(pkg.j1Button); Serial.println();
  Serial.print(F("j2PotX:   ")); printHex(pkg.j2PotX); Serial.println();
  Serial.print(F("j2PotY:   ")); printHex(pkg.j2PotY); Serial.println();
  Serial.print(F("j2Button: ")); printHex(pkg.j2Button); Serial.println();
  Serial.print(F("Sel:      ")); printHex(pkg.Sel); Serial.println();
  Serial.print(F("Start:    ")); printHex(pkg.Start); Serial.println();
  Serial.print(F("X:        ")); printHex(pkg.X); Serial.println();
  Serial.print(F("Circle:   ")); printHex(pkg.Circle); Serial.println();
  Serial.print(F("Square:   ")); printHex(pkg.Square); Serial.println();
  Serial.print(F("Triangle: ")); printHex(pkg.Triangle); Serial.println();
  Serial.print(F("L1:       ")); printHex(pkg.L1); Serial.println();
  Serial.print(F("L2:       ")); printHex(pkg.L2); Serial.println();
  Serial.print(F("R1:       ")); printHex(pkg.R1); Serial.println();
  Serial.print(F("R2:       ")); printHex(pkg.R2); Serial.println();
  Serial.print(F("U:        ")); printHex(pkg.U); Serial.println();
  Serial.print(F("D:        ")); printHex(pkg.D); Serial.println();
  Serial.print(F("L:        ")); printHex(pkg.L); Serial.println();
  Serial.print(F("R:        ")); printHex(pkg.R); Serial.println();
}

void printFormattedRS485Data(RS485Data_Package &rs485Pkg)
{
  Serial.print(F("NetCmd:   ")); printHex(rs485Pkg.NetCommand); Serial.println();
  Serial.print(F("remID:    ")); printHex(rs485Pkg.remID); Serial.println();
  Serial.print(F("destID:   ")); printHex(rs485Pkg.destID); Serial.println();
  Serial.print(F("CtrlCmd:  ")); printHex(rs485Pkg.ControlCommand); Serial.println();
  printFormattedData((Data_Package&)rs485Pkg.dta);
  Serial.print(F("Term:     ")); printHex(rs485Pkg.Termination); Serial.println();

}

why not use directly the type that was declared for the payload ?

struct RS485Data_Package {
  byte NetCommand;         //00 Recieve (NetworkControl),  01 Transmit (NetworkControl)
  byte remID;             //Sender ID# (See List of Available IDs)
  byte destID;      //Intended Reciever (i.e ThisNode)
  byte ControlCommand;    //What to Do with this information
  Data_Package payload;  // <=== the payload 
  byte Termination;   // End of Transmission Marker
};

then you can assign it with just an =

DatStruct. payload = data;

The motivation was that OP might want to transfer different data; a byte array is more universal.

Does that actually work?

fair indeed. (you would then need a field to know what was actually sent in the payload, but might be part of the payload itself)

sure

try this:

struct newType {
  int x;
  int y;
};

newType s1 = {10, 20};
newType s2 = {30, 40};

void setup() {
  Serial.begin(115200);
  Serial.println("before assignment");
  Serial.print("s2.x = "); Serial.println(s2.x);
  Serial.print("s2.y = "); Serial.println(s2.y);
  s2 = s1;
  Serial.println("after assignment");
  Serial.print("s2.x = "); Serial.println(s2.x);
  Serial.print("s2.y = "); Serial.println(s2.y);
}

void loop() {}

C++ provides an Implicitly-declared copy constructor and it's a Trivial copy constructor here

1 Like

I was planning to

Thanks. I guess I'm still too much of a self-taught C programmer; I also battle with these ideas in C# :frowning:

even in C, for simple flat structures you can either use memcpy like you do, or just assign from one to the other. The compiler will create code to copy the structure for you but it's a shallow copy kinda the same behaviour you would get with memcpy().

that's why you'll see people sometimes put an array in a struct, just to make it easy to assign an array into another array

First and foremost, thank you for being so helpful. Hard to find this excellent level of assistance on the internet in most places these days.

i have modified the code that i started with and will test it over the next few days.

follow up questions that i have :

is the strict difference between

Data_Package payload;  // <=== the payload 

and this

byte dta[25];

that the byte array is a numbered system and the payload would be basically a nested named list? Does one carry any advantage over the other ?

Both of the complete examples went a long way to helping me understand a small amount more on how to assign to these structures. I suppose when you are in object oriented mode you forget to step back and pair the code down to test the functions that you are unsure of. Thank you for taking the time out of your life to do that!

below is what I have revised the code to;

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include "SerialTransfer.h"

RF24 radio(10, 2);   // nRF24L01 (CE, CSN) on raptor protosystem using NANO CE0 is pin 10, CSN attacheed to pin2 for NRF24-PRI
const byte address[6] = "00001";

#define RS485inout  9 // RS485 Transmit or Receive status | on raptor protosystem using NANO no direct connection can be made to RE-DE TIE so jumper must be place to gpio9
#define RS485Transmit  HIGH
#define RS485Receive  LOW

#define NetCommandRX 0
#define NetCommandTX 1

#define THIS_NODE_ID 0x1A
#define CENTRAL_PROC_ID 0x01

unsigned long lastReceiveTime = 0;
unsigned long currentTime = 0;

byte TXRXMode = 1;                //rs485 Listen/transmit mode


// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte command;
  byte remID;
  byte j1PotX;
  byte j1PotY;
  byte j1Button;
  byte j2PotX;
  byte j2PotY;
  byte j2Button;
  byte Sel;
  byte Start;
  byte X;
  byte Circle;
  byte Square;
  byte Triangle;
  byte L1;
  byte L2;
  byte R1;
  byte R2;
  byte U;
  byte D;
  byte L;
  byte R;
};

struct RS485Data_Package {
  byte NetCommand;                                //00 Recieve (NetworkControl),  01 Transmit (NetworkControl)
  byte remID;                                     //Sender ID# (See List of Available IDs)
  byte destID;                                    //Intended Reciever (i.e ThisNode)
  byte ControlCommand;                            //What to Do with this information
  Data_Package payload;                           // <=== the payload
  byte Termination;                               // End of Transmission Marker
};


Data_Package NRF24data;                           //Create a Struct to hold incomming NRF Wireless information
RS485Data_Package RS485OutData;                   //Create a Struct to hold outgoing RS485 information
RS485Data_Package RS485InData;                    //Create a Struct to hold Incomming RS485 Command information
SerialTransfer myTransfer;


void setup() {

  Serial.begin(115200);
  myTransfer.begin(Serial);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_LOW);
  radio.startListening(); //  Set the module as receiver
  resetData();
}
void loop() {
  // Check whether there is data to be received
  if (radio.available()) {
    radio.read(&NRF24data, sizeof(Data_Package)); // Read the whole data and store it into the 'data' structure
    lastReceiveTime = millis(); // At this moment we have received the data

    currentTime = millis();
    if ( currentTime - lastReceiveTime > 1000 ) { // If current time is more then 1 second since we have recived the last data, that means we have lost connection
      resetData(); // If connection is lost, reset the data. It prevents unwanted behavior, for example if a drone has a throttle up and we lose connection, it can keep flying unless we reset the values
    }
  }
  RS485OutData. payload = NRF24data;

  if (TXRXMode != 2) {
    if (TXRXMode != 1) { // Recieve all Packets
      if (myTransfer.available())
      {
        digitalWrite(RS485inout, RS485Receive);
        myTransfer.rxObj(RS485InData);
        delay(1);
        ParseDatStruct();
      }
    } else {                        // Send Packet
      delay(5);
      digitalWrite(RS485inout, RS485Transmit);
      myTransfer.sendDatum(RS485OutData);
      delay(5);
      //TXRXMode = 0;
      digitalWrite(RS485inout, RS485Receive);
    }
  }
}

void resetData() {
  // Reset the values when there is no radio connection - Set initial default values
  NRF24data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

}

void ParseDatStruct() {
  if (!RS485InData.destID == THIS_NODE_ID) {
    return;
  } else {
    switch (RS485InData.ControlCommand) {
      case 0x00:                                     //Command 0 = Copy Data
        //memcpy(&RS485OutData, &RS485InData, sizeof(RS485InData));
        break;
      case 0x01:                                     //Command 1 = Change Serial Mode
        ChangeSerialMode();
        break;
      case 0x13:                                    //for this to work the command sent needs to be NetCommand = 1,CENTRAL_PROC_ID = 0, THIS_NODE_ID = radio translator id ,ControlCommand = 0x13
        delay(5);
        digitalWrite(RS485inout, RS485Transmit);
        myTransfer.sendDatum(RS485InData);
        delay(5);
        digitalWrite(RS485inout, RS485Receive);
        break;
      default:
        return;
        break;
    }
  }

}

void ChangeSerialMode() {
  TXRXMode =  RS485InData.NetCommand;
}

still very coarse but I feel it is coming together now instead of the stall I was in for the past few weeks .

My long-term goal is to turn this into a communication protocol that can be turned into a library and facilitate communications between all of my future projects.

it's not really a list as you could imagine it with key/values. in both cases you just have the same bunch of bytes in memory that the compiler knows about. the difference is indeed in how you access the bytes. If you have a byte array you can access any byte just through an index, which makes the payload kind of content agnostic (cf @sterretje's point)— you have 25 bytes to play with — whereas if you use the struct, you have sructured data that you access through their field names

What I seem to be missing in your code is the initialisation of the header in RS485OutData. And below can be a lot less typing if you use memset(3) - Linux manual page

void resetData() {
  // Reset the values when there is no radio connection - Set initial default values
  NRF24data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
}

vs

void resetData() {
  // Reset the values when there is no radio connection - Set initial default values
  memset(&NRF24data, 0, sizeof(NRF24data);
}

The byte array is like your Byte0..Byte24 that you can access with dta[0]..dta[24] if needed. The payload is simply your struct where you can access the fields by name.

To convert the byte array to a struct so you can use the field names, you can use a cast. This was demonstrated in my code with printFormattedData((Data_Package&)rs485Pkg.dta) where (Data_Package&) is the cast.

The Data_Package payload approach has the advantage that you can directly access the fields by name. The byte array approach has (in my view) the advantage that you can send different types of payloads regardless of the format (as long as they don't exceed the size of the byte array).

You need to make sure of course that the structure had no padding and fields were in the right order. using __attribute__((packed)) for the structure declaration helps (although here with just bytes you are probably safe)

(and probably a reinterpret_cast would be better than a C like cast in C++)

Yes, I'm just trying to figure out the best way to go about that.

I have a knack for programming something today and then coming back to it when it has worked for years and trying to figure out, what was what several laptops and hard drives later.

so I have conceived a file that basically hard maps network objects grouped by their respective types. from here I can either use the group like a subnet and use byte zero to address the device or just use the group address to access information. Half duplex makes it pretty straight forward currently i conceive of it working something like this

check for network quiet
if 2ms with no traffic hijack network
send communication packet to required node
required node changes from listen to send at the same time as sender goes back into listen mode
node receives and acts on information
repeat.

given that the rs485 hardware I have doesn't include a RTS line I have no real flow control, so collisions will probably happen a lot. don't really know if I can mitigate that. I have tried writing this code a few dozen times over the years on different platforms and I never have gotten it completed or right I just end up overwhelmed. Hopefully this time is different .

Blockquote
The Data_Package payload approach has the advantage that you can directly access the fields by name. The byte array approach has (in my view) the advantage that you can send different types of payloads regardless of the format (as long as they don't exceed the size of the byte array).

so then if I wanted to think in a larger scope and send a float I would just use a byte array and then send the float and it would occupy 2 bytes of the byte array then i would use some magic on the other end to reassemble the float and perform functions on it?

Blockquote
The byte array is like your Byte0..Byte24 that you can access with dta[0]..dta[24] if needed. The payload is simply your struct where you can access the fields by name.

To convert the byte array to a struct so you can use the field names, you can use a cast. This was demonstrated in my code with printFormattedData((Data_Package&)rs485Pkg.dta) where (Data_Package&) is the cast.

so then to come out with the best solution i should send a byte array and receive it as a struct so that it can be named and used easily?

how does it work internally for example if it was to have 24 bytes cast into 12 doubles would it just pick the first two and put data together to make a double?

below for clarification is the currently under development list of objects that will be on this network. I think that if i can get this list ironed out to every possible combination of hardware that i will be using it will be simple to come up with a commands list and that will allow me to iron out the types of data i will be transmitting therefor proving or debunking if i will ever transmit a variable larger than a byte.

DEC	HEX	Control Command
0	0	Highest Level Processor
1	1	Main Hardware Processor
2	2	Storage Processor
3	3	Sub Processor 1
4	4	Sub Processor 2
5	5	Sub Processor 3
6	6	Sub Processor 4
7	7	Sub Processor 5
8	8	General Purpose Digital I/O 1
9	9	General Purpose Digital I/O 2
10	A	General Purpose Digital I/O 3
11	B	General Purpose Digital I/O 4
12	C	General Purpose Digital I/O 5
13	D	General Purpose Digital I/O 6
14	E	General Purpose Digital I/O 7
15	F	General Purpose Digital I/O 8
16	10	General Purpose Digital I/O 9
17	11	General Purpose Analog I/O 1
18	12	General Purpose Analog I/O 2
19	13	General Purpose Analog I/O 3
20	14	General Purpose Analog I/O 4
21	15	General Purpose Analog I/O 5
22	16	General Purpose Analog I/O 6
23	17	General Purpose Analog I/O 7
24	18	General Purpose Analog I/O 8
25	19	General Purpose Analog I/O 9
26	1A	General Purpose Servo 1
27	1B	General Purpose Servo 2
28	1C	General Purpose Servo 3
29	1D	General Purpose Servo 4
30	1E	General Purpose I2C Translator 1
31	1F	General Purpose I2C Translator 2
32	20	General Purpose I2C Translator 3
33	21	General Purpose I2C Translator 4
34	22	General Purpose Relay Interface 1
35	23	General Purpose Relay Interface 2
36	24	General Purpose Relay Interface 3
37	25	General Purpose Relay Interface 4
38	26	General Purpose Mosfet Interface 1
39	27	General Purpose Mosfet Interface 2
40	28	General Purpose Mosfet Interface 3
41	29	General Purpose Mosfet Interface 4
42	2A	General Purpose Mosfet Interface 5
43	2B	General Purpose Mosfet Interface 6
44	2C	General Purpose Mosfet Interface 7
45	2D	General Purpose Mosfet Interface 8
46	2E	HX711 interface 1
47	2F	HX711 interface 2
48	30	HX711 interface 3
49	31	HX711 interface 4
50	32	ThermoCouple Interface 1
51	33	ThermoCouple Interface 2
52	34	ThermoCouple Interface 3
53	35	ThermoCouple Interface 4
54	36	Gas Sensor Interface 1
55	37	Gas Sensor Interface 2
56	38	Gas Sensor Interface 3
57	39	Gas Sensor Interface 4
58	3A	Motion Controller Interface 1
59	3B	Motion Controller Interface 2
60	3C	Motion Controller Interface 3
61	3D	Motion Controller Interface 4
62	3E	Power Monitor Subsystem 1
63	3F	Power Monitor Subsystem 2
64	40	Power Monitor Subsystem 3
65	41	Power Monitor Subsystem 4
66	42	Power Monitor Subsystem 5
67	43	Power Monitor Subsystem 6
68	44	Power Monitor Subsystem 7
69	45	Power Monitor Subsystem 8
70	46	Solar Charge Control Subsystem 1
71	47	Solar Charge Control Subsystem 2
72	48	Solar Charge Control Subsystem 3
73	49	Solar Charge Control Subsystem 4
74	4A	Secondary Charge Subsystem 1
75	4B	Secondary Charge Subsystem 2
76	4C	Secondary Charge Subsystem 3
77	4D	Secondary Charge Subsystem 4
78	4E	Auxillary Charge Subsystem 1
79	4F	Auxillary Charge Subsystem 2
80	50	GPS 1
81	51	GPS 2
82	52	IMU 1
83	53	IMU 2
84	54	Lidar 1
85	55	Lidar 2
86	56	Sonar Subsystem 1
87	57	Sonar Subsystem 2
88	58	Sonar Subsystem 3
89	59	Sonar Subsystem 4 
90	5A	Infrared Object Collision Subsystem 1
91	5B	Infrared Object Collision Subsystem 2
92	5C	Infrared Object Collision Subsystem 3
93	5D	Infrared Object Collision Subsystem 4
94	5E	Robotic Arm Subsystem 1
95	5F	Robotic Arm Subsystem 2
96	60	Robotic Arm Subsystem 3
97	61	Robotic Arm Subsystem 4
98	62	Robotic Arm Subsystem 5
99	63	Robotic Arm Subsystem 6
100	64	Robotic Arm Subsystem 7
101	65	Robotic Arm Subsystem 8
102	66	Primary Weapon Subsystem 
103	67	Primary Weapon Subsystem 
104	68	Primary Weapon Subsystem 
105	69	Primary Weapon Subsystem 
106	6A	Defensive Weapon Subsystem 
107	6B	Defensive Weapon Subsystem 
108	6C	Defensive Weapon Subsystem 
109	6D	Defensive Weapon Subsystem 
110	6E	Fire Works Subsystem 
111	6F	Fire Works Subsystem 
112	70	Fire Works Subsystem 
113	71	Fire Works Subsystem 
114	72	Ancillary Weapon Subsystem
115	73	Ancillary Weapon Subsystem
116	74	Ancillary Weapon Subsystem
117	75	Ancillary Weapon Subsystem
118	76	Auxillary Wapon Subsystem 
119	77	Auxillary Wapon Subsystem 
120	78	Auxillary Wapon Subsystem 
121	79	Auxillary Wapon Subsystem 
122	7A	AI Camera Interface 
123	7B	AI Camera Interface 
124	7C	Optical Input system 
125	7D	Optical Input system 
126	7E	Optical Input system 
127	7F	Optical Input system 
128	80	Weather telemetry Subsystem
129	81	Time Control Subsystem
130	82	Communication (BLE) 
131	83	Communication (BLE) 
132	84	Communication (BLE) 
133	85	Communication (BLE) 
134	86	Communication(NRF24) 
135	87	Communication(NRF24) 
136	88	Communication(NRF24) 
137	89	Communication(NRF24) 
138	8A	Communication(LAN) 
139	8B	Communication(LAN) 
140	8C	Communication(LAN) 
141	8D	Communication(LAN) 
142	8E	Communication(WiFi)
143	8F	Communication(WiFi)
144	90	Communication(WiFi)
145	91	Communication(WiFi)
146	92	Communication(Other) 
147	93	Communication(Other) 
148	94	Communication(Other) 
149	95	Communication(Other) 
150	96	Kinematics Processor 
151	97	Kinematics Processor 
152	98	Kinematics Processor 
153	99	Kinematics Processor 
154	9A	Spatial Positioning Telematics
155	9B	Spatial Positioning Telematics
156	9C	Spatial Positioning Telematics
157	9D	Spatial Positioning Telematics
158	9E	Speech Synthesis
159	9F	Speech Recognition
160	A0	Illumination Control
161	A1	Illumination Control
162	A2	Graphics Controller (LCD)
163	A3	Graphics Controller (LCD)
164	A4	Graphics Controller (LCD)
165	A5	Graphics Controller (LCD)
166	A6	Graphics Controller(Other/Simple)
167	A7	Graphics Controller(Other/Simple)
168	A8	Graphics Controller(Other/Simple)
169	A9	Graphics Controller(Other/Simple)
170	AA	Graphics Controller(Other/Simple)
171	AB	Graphics Controller(Other/Simple)
172	AC	Graphics Controller(Other/Simple)
173	AD	Graphics Controller(Other/Simple)
174	AE	AHRS Processor 
175	AF	AHRS Processor 
176	B0	Tilt Indication Subsystem

A float is 4 bytes, a double 4 or 8 depending on the arduino.

You would indeed copy the bytes into the payload with some information describing what’s in this payload, so one or a few more bytes are needed so that on the receiving end you know what just came in.

To insert the bytes into to payload and extract the information from the byte stream on the other side and get the relevant data you should use memcpy()

i got a little more ironed out today here is where i am:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include "SerialTransfer.h"


//----------------------------------------------------------------------------------------------------------------------User Config 
#define RS485inout  9 // RS485 Transmit or Receive status | on raptor protosystem using NANO no direct connection can be made to RE-DE TIE so jumper must be place to gpio9
#define RS485Transmit  HIGH
#define RS485Receive  LOW

#define NetCommandRX 0
#define NetCommandTX 1

#define THIS_NODE_SUBNET    0xB1
#define THIS_NODE_ID        0x00
#define CENTRAL_PROC_SUBNET 0x00
#define CENTRAL_PROC_ID     0x00
//----------------------------------------------------------------------------------------------------------------------DO NOT EDIT BELOW THIS LINE

#define GLOBAL_CARRIER_MESSAGE 0x87

RF24 radio(10, 2);   // nRF24L01 (CE, CSN) on raptor protosystem using NANO CE0 is pin 10, CSN attacheed to pin2 for NRF24-PRI
const byte address[6] = "00001";

unsigned long lastReceiveTime = 0;
unsigned long currentTime = 0;

byte TXRXMode = 1;                //rs485 Listen/transmit mode


// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte command;
  byte remID;
  byte j1PotX;
  byte j1PotY;
  byte j1Button;
  byte j2PotX;
  byte j2PotY;
  byte j2Button;
  byte Sel;
  byte Start;
  byte X;
  byte Circle;
  byte Square;
  byte Triangle;
  byte L1;
  byte L2;
  byte R1;
  byte R2;
  byte U;
  byte D;
  byte L;
  byte R;
};

struct RS485Data_Package {
  byte NetCommand;                                //00 Recieve (NetworkControl),  01 Transmit (NetworkControl)
  byte remSubNet;                                 //Sender Subnet
  byte remID;                                     //Sender ID# (See List of Available IDs)
  byte destSubNet;                                //Intended Reciever subnet
  byte destID;                                    //Intended Reciever (i.e ThisNode)
  byte ControlCommand;                            //What to Do with this information
  Data_Package payload;                           // <=== the payload
  byte Termination;                               // End of Transmission Marker
};


Data_Package NRF24data;                           //Create a Struct to hold incomming NRF Wireless information
RS485Data_Package RS485OutData;                   //Create a Struct to hold outgoing RS485 information
RS485Data_Package RS485InData;                    //Create a Struct to hold Incomming RS485 Command information
SerialTransfer myTransfer;


void setup() {

  Serial.begin(115200);
  myTransfer.begin(Serial);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_LOW);
  radio.startListening(); //  Set the module as receiver
  resetData();
}
void loop() {
  // Check whether there is data to be received
  if (radio.available()) {
    radio.read(&NRF24data, sizeof(Data_Package)); // Read the whole data and store it into the 'data' structure
    lastReceiveTime = millis(); // At this moment we have received the data

    currentTime = millis();
    if ( currentTime - lastReceiveTime > 1000 ) { // If current time is more then 1 second since we have recived the last data, that means we have lost connection
      resetData(); // If connection is lost, reset the data. It prevents unwanted behavior, for example if a drone has a throttle up and we lose connection, it can keep flying unless we reset the values
    }
  }
  RS485OutData. payload = NRF24data;

  if (TXRXMode != 2) {
    if (TXRXMode != 1) { // Recieve all Packets
      RcvRS485();
    }
  }
}

void resetData() {
  memset(&NRF24data, 0x87, sizeof(NRF24data));    //Reset the values when there is no radio connection - Set initial default values
}
/*
Dec Hex Net Command
0   0   Receive All
1   1   Send All 
2   2   Go Offline Forever
3   3   Send to (byte zero is to address)
4   4   Reply to sender
5   5   go offline for delay
6   6   
7   7   
8   8   
9   9   
10  A   

 */
void ParseNetworkHeader() {
  if (!RS485InData.destSubNet == THIS_NODE_SUBNET && !RS485InData.destID == THIS_NODE_ID || !RS485InData.destSubNet == GLOBAL_CARRIER_MESSAGE ) {            
    return;                                                                                       //if the recieved data was not intended for this node or Global Command not issued then go back to main loop 
  } else {                                                                                        //if the recieved data was intended for this node then begin by parsing Network command
    switch (RS485InData.NetCommand) {
      case 0x00:                                                                                  //Command 0 = Recieve: recieve information from MASTER processor ::MOSI::
        //memcpy(&RS485OutData, &RS485InData, sizeof(RS485InData));
        break;
      case 0x01:                                                                                  //Command 1 = Send: clear to send information to the Central MASTER processor ::MISO::
        RS485OutData.remSubNet = THIS_NODE_SUBNET;
        RS485OutData.remID = THIS_NODE_ID;
        RS485OutData.destSubNet = CENTRAL_PROC_SUBNET;
        RS485OutData.destID = CENTRAL_PROC_ID;
        RS485OutData.ControlCommand = 0x00;
        RS485OutData.Termination = 0x87;
        SndRS485();
        break;
      case 0x02:                                                                                  //Command 2 = Go Offline Forever: this allows a defective module to not interfere in communications
        ChangeSerialOfflineForever();
        break;
      case 0x03:                                                                                  //Command 3 = Send to: B0 of RS485InData.payload is to address where information needs to be sent. used to facilitate communication between two SLAVES. MASTER sends a message to initialize communication, SLAVE replies to address given by MASTER Second SLAVE recieves Information from first SLAVE
        RS485OutData.remSubNet = THIS_NODE_SUBNET;
        RS485OutData.remID = THIS_NODE_ID;
        RS485OutData.destSubNet = RS485InData.payload.command;
        RS485OutData.destID = RS485InData.payload.remID;
        RS485OutData.ControlCommand = 0x00;
        RS485OutData.Termination = 0x87;
        SndRS485();
        break;
      case 0x04:                                                                                  //Command 4 =  Reply to sender: used for inter slave communications incase send to instruction requires specific reply from 2nd SLAVE
        RS485OutData.remSubNet = THIS_NODE_SUBNET;
        RS485OutData.remID = THIS_NODE_ID;
        RS485OutData.destSubNet = RS485InData.remSubNet;
        RS485OutData.destID = RS485InData.remID;
        RS485OutData.ControlCommand = 0x00;
        RS485OutData.Termination = 0x87;
        SndRS485();
        break;
      case 0x05:                                                                                  //Command 5 = go offline for delay:
        ChangeSerialOfflineDelay(RS485InData.payload.command, RS485InData.payload.remID);
        break;
      default:
        return;
        break;
    }
  }

}
void ParseControlCommand() {                                                                      //Section for detrmining what is done with recieved data. This is specific to the module to code is placed on. since this code is intended for a network translator it is constantly doing the work that is intended to be placed here.
    switch (RS485InData.NetCommand) {                                                             //As where i envision that certain other modules will use this section to collect information and wrap that information in payloads that will be sent over the network. 
      case 0x00:                                                                                  //Command 0 = Recieve: recieve information from MASTER processor ::MOSI::

        break;
      case 0x01:                                                                                  //Command 1 = Send: clear to send information to the Central MASTER processor ::MISO::

        break;
      case 0x02:                                                                                  //Command 2 = Go Offline Forever: this allows a defective module to not interfere in communications

        break;
      case 0x03:                                                                                  //Command 3 = Send to: B0 of RS485InData.payload is to address where information needs to be sent. used to facilitate communication between two SLAVES. MASTER sends a message to initialize communication, SLAVE replies to address given by MASTER Second SLAVE recieves Information from first SLAVE

        break;
      case 0x04:                                                                                  //Command 4 =  Reply to sender: used for inter slave communications incase send to instruction requires specific reply from 2nd SLAVE

        break;
      case 0x05:                                                                                  //Command 5 = go offline for delay:

        break;
      default:
        return;
        break;
    }
  
}
void ChangeSerialMode() {
  TXRXMode =  RS485InData.NetCommand;
}
void ChangeSerialOfflineDelay(int Duration, int Modifier) {
  TXRXMode =  RS485InData.NetCommand;
}
void ChangeSerialOfflineForever() {
  TXRXMode = 2;
}
void RcvRS485() {
  if (myTransfer.available())
  {
    digitalWrite(RS485inout, RS485Receive);
    myTransfer.rxObj(RS485InData);
    delay(1);
    ParseNetworkHeader();
  }
}
void SndRS485() {
  delay(5);
  digitalWrite(RS485inout, RS485Transmit);
  myTransfer.sendDatum(RS485OutData);
  delay(5);
  //TXRXMode = 0;
  digitalWrite(RS485inout, RS485Receive);
}

currentTime - lastReceiveTime will be 0 most of the time, may be 1 or 2 in t some rare case

There is zero chance you’ll ever call reset as you do this within the available() if block.

There are a few articles on the web about collision detection (not avoidance): arduino rs485 collision detection - Google Search

You receive it as a byte array and cast it to a struct. If you transfer different types of data packets to the same node, you will need to know what type of struct so you can cast correctly and you need an additional identifier in e.g. the header.

When using anything but byte fields, you must be aware of Endianness - Wikipedia. Your master might be using little endian and some of your slaves might be using big endian. Below code demonstrates for a AVR based Arduino

void setup()
{
  Serial.begin(115200);
  while (!Serial);

  int x = 0x1234;
  byte dta[2];
  memcpy(dta, &x, sizeof(x));

  Serial.print("x = ");
  Serial.println(x, HEX);
  Serial.print("dta = ");
  for(uint8_t cnt=0;cnt<sizeof(int);cnt++)
  {
    if (dta[cnt] < 0x10)
      Serial.print("0");
    Serial.print(dta[cnt], HEX);
  }
  Serial.println();
}

void loop()
{
}

The output will show you that 0x34 is located in dta[0] and 0x12 is stored in dta[1]. If you send this and your receiver expects it the other way around, you will see the numeric value 0x3412.

Another problem will be that on some architectures an int is 16 bits and on other architectures it is 32 bit. This can be solved by consistently using int16_t (or int32_t) instead of int at both sides.

As already mentioned by @J-M-L, it will be 3 doubles; in the AVR world, there is no difference between a float and a double so it will actually be 6 floats in 24 bytes. The compiler will generate the code that will correctly pick the 4 bytes of the float and convert it.

that is on the nr24lf01 radio side of things , its just to keep the remote device from continuing its last received function. like if you were telling the drone to advance upward then you lose radio link to the remote device and you stopped telling it to advance upward then it will set all positions to zero. its a rather dangerous safeguard. with the nr24lf01 radios they do drop the link quit often when the second object is in motion.

i had looked at this the most commonly recommended solutions are to use a checksum and a delay

Jacob.Schultz

09-12-2019, 08:09 PM

One way is to make a short random delay before a master begins the transmission so they don't start at the same time. The normal procedure is to make a simple protocol with a checksum so you can detect errors. You can find many open source examples on Google if you need it.

the serial transfer library that handles the transmission of the structs was well written and includes a low level crc function to check if the packets were messed up in transmission, i haven't looked deeply in it to see if it automatically requests the re-transmission of corrupt packages.

and before i send the packets i already have a line settle delay.

i have come across this before but it requires more time to investigate and more pins

A simpler scheme is to be watching the bus before you send. If there has been no sign of any traffic for more than a certain threshold time then you can assume that the bus is currently quiet. You can then try and send your data. You don't know if it got through OK though, since something else could have decided that the bus was quiet long enough for it to send too.

i cant wrap my head around this one because of the transceiver. i don't have a clear idea on how i would listen for the buss to be quiet and then time it out after a certain period of time. since i am specifically using the hardware serial uart into the rs485 receiver is there a method built into the default serial that would make it time out ? i suppose i should give a schematic for clarification.

i am on the fence about this idea because it would mean sending an additional packet to confirm i received a valid packet in the first place

So you would have to have some kind of ACK signal coming back from the other end that says the packet got received OK. Keep retrying until you get that ACK back. Of course, the ACK could be colliding as well, so it may have been received, but you just haven't been told. So your remote device would have to be able to gracefully handle receiving the same packet multiple times and discarding (yet still ACKing) the repeated packets.

What I am saying is that your code does not work. The if condition will never be true