[SOLVED] SoftwareSerial Data Structure send and receive

Dear Arduino gurus,

Half a newbie here.
I beg your assistance with sending and receiving data structures via SoftwareSerial

I’m trying to communicate between 2 arduinos (nano) via a HC-12 module

I have tried numerous ways (which my pea brain understands) and it kind of works but not very reliable.

For simplicity I have reduced the code to just the essential stuff.

“Master” part

#include <SoftwareSerial.h>
#define  Rx_PIN  7
#define  Tx_PIN 8
SoftwareSerial mySerial(Rx_PIN, Tx_PIN);//rx tx of HC-12 module

struct MasterStructure { // values from buttons on master
  int txbutton1;
  int txbutton2;
  int txbutton3;
  int txbutton4;
  int txbutton5;
};
MasterStructure MasterData;

struct SlaveStructure { // values from buttons on slave
  int Rxbutton1;
  int Rxbutton2;
  int Rxbutton3;
  int Rxbutton4;
  int Rxbutton5;
};
SlaveStructure SlaveData;


void setup() {
  Serial.begin(9600);
  mySerial.begin(57600);
}

void loop() {
  
// display SLAVE data in serial monitor
  Serial.print("Rxbutton1 ");
  Serial.print(SlaveData.Rxbutton1);
  Serial.print(" Rxbutton2 ");
  Serial.print(SlaveData.Rxbutton2);
  Serial.print(" Rxbutton3 ");
  Serial.print(SlaveData.Rxbutton3);
  Serial.print(" Rxbutton4 ");
  Serial.print(SlaveData.Rxbutton4);
  Serial.print(" Rxbutton5 ");
  Serial.println(SlaveData.Rxbutton5);

//*HERE's the big question: HOW to send and receive via HC-12 (or any wireless device?) ******

}

“Slave” part

#include <SoftwareSerial.h>
#define  Rx_PIN  7
#define  Tx_PIN 8
SoftwareSerial mySerial(Rx_PIN, Tx_PIN);//rx tx of HC-12 module

struct MasterStructure { // values from buttons on master
  int txbutton1;
  int txbutton2;
  int txbutton3;
  int txbutton4;
  int txbutton5;
};
MasterStructure MasterData;

struct SlaveStructure { // values from buttons on slave
  int Rxbutton1;
  int Rxbutton2;
  int Rxbutton3;
  int Rxbutton4;
  int Rxbutton5;
};
SlaveStructure SlaveData;


void setup() {
  Serial.begin(9600);
  mySerial.begin(57600);
}

void loop() {

// display MASTER data in serial monitor
  Serial.print("txbutton1 ");
  Serial.print(MasterData.txbutton1);
  Serial.print(" txbutton2 ");
  Serial.print(MasterData.txbutton2);
  Serial.print(" txbutton3 ");
  Serial.print(MasterData.txbutton3);
  Serial.print(" txbutton4 ");
  Serial.print(MasterData.txbutton4);
  Serial.print(" txbutton5 ");
  Serial.println(MasterData.txbutton5);

//**HERE's the big question: HOW to send and receive via HC-12 (or any wireless device?) *****
}

And this is where I need your help please. HOW do I send and receive (does not have to be simultaneously) a data structure via softwareSerial?

Any help or pointers will be highly appreciated.

Well, first, a structure is a logical artifact to tell the compiler how you wish to treat the fields as a single whole. The actual location is not assigned until you first put something into the fields. Which brings up another question. How big a value are you intending to place in the 16 bit integers you name as "buttons". If they are only going to be 0 or 1, why not use byte?

Paul

Thank you Paul for replying.

The values (for now) will be from 0-1023 (the values are derived in a separate loop; the current sketch is just for demo and understanding purpose)

Once I get a good understanding of how to send and receive a structure, I might look into sending other types (like long / char / bool etc).

use SoftwareSerial only at 9600 baud

Juraj: use SoftwareSerial only at 9600 baud

Hi Juraj,

Many thanks for your advice. I will keep it in mind once I have the basic understanding of sending and receiving a Structure.

Is there any significant value is sending the struct as raw binary data rather than sending its values with a series of Serial.print() statements. Sending data with print statements is much easier to debug and I would only send binary data if it was essential for performance reasons.

If you really do need to send the raw binary data then you could send the struct with Serial.write(). But receiving it is a bit more complex - how would the receiving Arduino know when the data starts and ends. OK, it may know to expect N bytes - but it has to know where to start counting from.

Adding start- and end-markers to binary data is possible but is more complex if it is possible for the marker values to be included as legitimate data.

...R Serial Input Basics

Hi Robin2,

There is no specific need to sending and receiving a struct. It just looks cleaner to me :) (and performance is a bonus ey?).

Serial.write() should be "mySerial.write"? (I'm using softwareSerial - (I declared it as "mySerial")

on the receiving end..... yes.... a lot more complex! I'll give your excellent article (as per your link) another go. I have read it probably a zillion times but I just cannot get my pea brain around it. I just have this coding mental block the last few weeks :o

Perhaps the next "in-depth" read with a Beer will return that "AHA" moment!

Many Thanks for your help.

OK, I think caught the devil by the tail :)

First of all many thanks to Juraj. Bringing down the baud rate has helped tremendously! Then a shout out to Robin2. Re-reading your article has solved a lot of questions. (plenty beers to you)

The first stumbling block seems to be the speed at which the HC-12 sends data. I tried a couple of different delay(); times and the results vary but one-way communication (sending from Master to Slave) works pretty reliable with delay(50);.

Where the s**t hits the fan, however, is when I try sending data from the Slave to the Master! It somehow messes up the communication. But I shall not give up!

Chilli_Paste:
on the receiving end… yes… a lot more complex!

The code in the earlier Posts in this Python - Arduino demo sends binary data- with start and end-markers.

Normally I refer readers to the simpler example starting at Reply #4

It just looks cleaner to me

My primary requirement is that code works properly and is easy to maintain :slight_smile:

…R

Hi Robin2,

Yes, I did read the python demo.
But the first thing here in Africa is to stay well clear of anything called “Python” as those snakes are really nasty :slight_smile:
The other thing is that I have no clue whatsoever what that “snake” language means!

If I may, could you perhaps have a look at what I have so far. (unashamedly copied from your demo & modified slightly)

Here is the code so far (one-way communication works excellent & reliable)

Master:

#include <SoftwareSerial.h>
#define  Rx_PIN  7   // Rx pin of HC-12
#define  Tx_PIN 8    // Tx pin of HC-12
SoftwareSerial mySerial(Rx_PIN, Tx_PIN);//rx tx

int int1 = 99; // just a number for now
float float1 = 12.34; // just a number for now

void setup() {
  //Serial.begin(9600);  // disabled for now as nothing is there to print - YET!
  mySerial.begin(9600);
}

void loop() {
  delay(50); // could possibly reduce it in this sketch, but in the final code there are plenty of other loops and things that the nano has to do so might even increase it to delay(100) or delay(1)?
  mySerial.print('<'); //  note the "single" quotes - would a " (double quote) make a difference? - gotta test this and research ASCII stuff
  mySerial.print("MasterData");
  mySerial.print(","); //  note the "double" quotes - as per comment above
  mySerial.print(int1); // mySerial.write does not work - gotta investigate why
  mySerial.print(",");
  mySerial.print(float1);
  mySerial.print('>');
} // end of loop

Slave:

#include <SoftwareSerial.h>
#define  Rx_PIN  7   // Rx pin of HC-12
#define  Tx_PIN 8    // Tx pin of HC-12
SoftwareSerial mySerial(Rx_PIN, Tx_PIN);//rx tx

// Example 5 - Receive with start- and end-markers combined with parsing

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

// variables to hold the parsed data
char messageFromPC[numChars] = {0};
int integerFromPC = 0;
float floatFromPC = 0.0;
boolean newData = false;
//============
void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
}
//============
void loop() {
  recvWithStartEndMarkers();
  if (newData == true) {
    strcpy(tempChars, receivedChars);
    // this temporary copy is necessary to protect the original data
    //   because strtok() used in parseData() replaces the commas with \0
    parseData();
    showParsedData();
    newData = false;
  }
} // end of loop
//============
void recvWithStartEndMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;
  while (mySerial.available() > 0 && newData == false) {
    rc = mySerial.read();
    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      }
      else {
        receivedChars[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }
    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}
//============
void parseData() {      // split the data into its parts
  char * strtokIndx; // this is used by strtok() as an index
  strtokIndx = strtok(tempChars, ",");     // get the first part - the string
  strcpy(messageFromPC, strtokIndx); // copy it to messageFromPC
  strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
  integerFromPC = atoi(strtokIndx);     // convert this part to an integer
  strtokIndx = strtok(NULL, ",");
  floatFromPC = atof(strtokIndx);     // convert this part to a float
}
//============
void showParsedData() {
  Serial.print("Message ");
  Serial.print(messageFromPC);
  Serial.print(" Integer ");
  Serial.print(integerFromPC);
  Serial.print(" Float ");
  Serial.println(floatFromPC);
}

Now, how to tell the “Slave” to stop listening and the “Master” to stop sending and reverse the whole process? (Keeping in mind that delays (timing) seems to be a critical component while transmitting via HC-12 between two Arduinos)

Again, Many Thanks for your patience and help!

P.S. There’s actually a third Arduino in the mix but those 2 are hardwired via I2C and there I have no problem as I use the fantastic “I2C_Anything.h” library by (I think) Nick Gammon.
Wouldn’t it be AWESOME to have a similar library for softwareSerial? (I’m just to plain stupid currently to write such a library). :o Let this Post Script however not distract you from the current question :slight_smile:

Chilli_Paste: Now, how to tell the "Slave" to stop listening and the "Master" to stop sending and reverse the whole process? (Keeping in mind that delays (timing) seems to be a critical component while transmitting via HC-12 between two Arduinos)

If you want to be able to send data from the slave to the master then you need a recvWithStartEndMarkers() function in the master. Indeed, the master code should probably have most (or all) of what is now in the slave. And both of them need a function to send a message.

The way the code in recvWithStartEndMarkers() is written it is effectively listening all the time.

After that, the issue is to design the logic of the system so that things happen in the order you want and at the time that is required.

But DO NOT use delay() to manage timing as it blocks other things from happening. Have a look at how millis() is used to manage timing without blocking in Several things at a time. And have a look at Using millis() for timing. A beginners guide if you need more explanation.

...R

I used the delay() to simply kickstart the sketch and to see where it would take me. Having tested over and over again, I realised that there should be a delay of some sort to get the data across. And delay() was just the laziest way out ey? (LOL - I'm just trying to find an excuse for my laziness / stupidity)

I'm pretty well versed with the millis() delay strategy and will implement it duly! Many thanks for that tip.

I've kind of replicated the code on both Arduinos but then there is no communication whatsoever. Seems they miss each others start bit?

I will play with the following in the meantime:

1) Implement millis() delay/timing strategy 2) Try and play with mySerial.available (i.e. !available vs. available)

//side Note:

The project started out with 2 nRF24L01 units and using the RF24.h library was a breeze to use with bi-directional comms. But then I blew one of them with a short circuit and had no spares (getting parts here in Africa/Namibia takes on average 12 weeks - if ever it arrives). The only thing I had was a pair of HC-12 tranceivers.

Why can't it be done so easily with SoftwareSerial?

This is the code code used for the nRF24L01:

// sending
  radio.stopListening();
  if (!radio.write( &masterData, sizeof(masterData) )) {
    digitalWrite(13, HIGH);
  }
  else {
    digitalWrite(13, LOW);
  }
//****************************
//receiving
  radio.startListening();
  if ( radio.available()) {
    while (!radio.available());
    radio.read(&slaveData, sizeof(slaveData));
  }

Can the RF24.h library possibly be adapted to work with SoftwareSerial? That would be awesome!!

quote:
I’m pretty well versed with the millis() delay strategy and will implement it duly! Many thanks for that tip.
unquote.

Don’t even “think” delay. Think “when do I want to do something” or “when do I want to stop doing something”.

Paul

I have not used the HC12 modules. Their interface is very much simpler than the nRF24 so I don't think there is any value in trying to adapt nRF24 code.

Bidirectional communication should be straightforward - just like sending between two serial ports. I suspect something like this should work (pseudo code)

void loop()
   recvWithStartEndMarker()
   if (newData == true) {
    // use the data
    newData = false; // prepare for next message
   }
   if (there is data to send) {
     // send the data
   }
}

Have you seen this tutorial - first thing when I searched with DuckDuckGo with "hc12 transceiver"

...R

Thanks Paul. Yes. You're absolutely right! @Robin2, Thank you. Nearly full circle :) This is what I started out with a few days ago and then I got stuck at sending a data structure bidirectional.

With all your help and pointers over the last few post, I'll try and marry the code. I'll report back soon.

Many thanks for sharing your knowledge. Much appreciated.

As mentioned above… “I’ll try and marry the code”… (and I code like a caveman) :slight_smile:

It works! It works! It works!

Although the original question/idea/wish has not been achieved (sending & receiving a Structure), the current setup works reliable for Sending & Receiving multiple values bidirectionally.

The very last thing I would have thought of was additional power to the HC12 transmitter module!!! Having it run only on USB power was not enough and once I supplied it with its own power source, the serial monitor came to life!

Herewith the two working test codes for anyone who has a similar problem/setup:

Master:

/*
 * Many Thanks go to the following people who helped me out tremendously in getting this to work:
 * Robin2 / Paul_KD7HB / Juraj
 */

#include <SoftwareSerial.h>
#define  Rx_PIN  7   // Rx pin of HC-12
#define  Tx_PIN 8    // Tx pin of HC-12
SoftwareSerial mySerial(Rx_PIN, Tx_PIN);//rx tx

#define Gate A3 // Please ignore. This is only to activate ext power to transmitter

//***************************************
const byte numCharsSlave = 32;
char receivedSlaveChars[numCharsSlave];
char tempSlaveChars[numCharsSlave];        // temporary array for use when parsing

// variables to hold the parsed data
char messageFromSlave[numCharsSlave] = {0};
int integer1Slave = 0;
float float1Slave = 0.0;
int integer2Slave;
boolean newData = false; // invert this on Slave arduino

static boolean recvInProgress = false;
static byte ndx = 0;
char startMarker = '<';
char endMarker = '>';
char rc;
//***************************************
unsigned long currentMillis1 = 0;
unsigned long previousMillis1 = 0;
long interval1 = 150;           // 150 milliseconds

int integer1Master = 99; // just a number for now
float float1Master = 12.34; // just a number for now
int integer2Master = 77;
//***************************************
//============
void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  pinMode(Gate, OUTPUT);// Please ignore. This is only to activate ext power to transmitter
  digitalWrite(Gate, LOW);// Please ignore. This is only to activate ext power to transmitter
}
//============
void loop() {
  recvWithStartEndMarkers();
  if (newData == true) {
    strcpy(tempSlaveChars, receivedSlaveChars);// this temporary copy is necessary to protect the original data because strtok() used in parseData() replaces the commas with \0
    parseData();
    showParsedData();
    newData = false;
  }

  currentMillis1 = millis();
  if (currentMillis1 - previousMillis1 > interval1) {
    previousMillis1 = currentMillis1;
    sendDataToSlave ();
  }
} // end of loop
//============
void recvWithStartEndMarkers() {
  while (mySerial.available() > 0 && newData == false) {
    rc = mySerial.read();
    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedSlaveChars[ndx] = rc;
        ndx++;
        if (ndx >= numCharsSlave) {
          ndx = numCharsSlave - 1;
        }
      }
      else {
        receivedSlaveChars[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }
    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}
//============
void parseData() {      // split the data into its parts
  char * strtokIndx; // this is used by strtok() as an index
  strtokIndx = strtok(tempSlaveChars, ",");     // get the first part - the string
  strcpy(messageFromSlave, strtokIndx); // copy it to messageFromSlave
  strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
  integer1Slave = atoi(strtokIndx);     // convert this part to an integer
  strtokIndx = strtok(NULL, ",");
  float1Slave = atof(strtokIndx);     // convert this part to a float
  strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
  integer2Slave = atoi(strtokIndx);     // convert this part to an integer
}
//============
void showParsedData() {
  Serial.print("messageFromSlave ");
  Serial.print(messageFromSlave);
  Serial.print(" integer1Slave ");
  Serial.print(integer1Slave);
  Serial.print(" float1Slave ");
  Serial.print(float1Slave);
  Serial.print(" integer2Slave ");
  Serial.println(integer2Slave);
}
//============
void sendDataToSlave () {
  mySerial.print('<');
  mySerial.print("Master text");
  mySerial.print(",");
  mySerial.print(integer1Master);
  mySerial.print(",");
  mySerial.print(float1Master);
  mySerial.print(",");
  mySerial.print(integer2Master);
  mySerial.print('>');
  //}
}

Slave:

#include <SoftwareSerial.h>
#define  Rx_PIN  7   // Rx pin of HC-12
#define  Tx_PIN 8    // Tx pin of HC-12
SoftwareSerial mySerial(Rx_PIN, Tx_PIN);//rx tx

#define killpin 9 // Please ignore. This is only to activate ext power to transmitter

//***************************************
const byte numCharsMaster = 32;
char receivedMasterChars[numCharsMaster];
char tempMasterChars[numCharsMaster];        // temporary array for use when parsing

// variables to hold the parsed data
char messageFromMaster[numCharsMaster] = {0};
int integer1Master = 0;
float float1Master = 0.0;
int integer2Master;
boolean newData = true; // invert this on Master arduino

static boolean recvInProgress = false;
static byte ndx = 0;
char startMarker = '<';
char endMarker = '>';
char rc;
//***************************************
unsigned long currentMillis1 = 0;
unsigned long previousMillis1 = 0;
long interval1 = 150;           // 150 milliseconds

int integer1Slave = 1023; // just a number for now
float float1Slave = 23.45; // just a number for now
int integer2Slave = 55; // just a number for now
//***************************************
//============
void setup() {
  Serial.begin(9600);
  mySerial.begin(9600);
  pinMode(killpin, OUTPUT);// Please ignore. This is only to activate ext power to transmitter
  digitalWrite(killpin, HIGH);// Please ignore. This is only to activate ext power to transmitter
}
//============
void loop() {
  recvWithStartEndMarkers();
  if (newData == true) {
    strcpy(tempMasterChars, receivedMasterChars);// this temporary copy is necessary to protect the original data because strtok() used in parseData() replaces the commas with \0
    parseData();
    showParsedData();
    newData = false;
  }

  currentMillis1 = millis();
  if (currentMillis1 - previousMillis1 > interval1) {
    previousMillis1 = currentMillis1;
    sendDataToMaster ();
  }
} // end of loop
//============
void recvWithStartEndMarkers() {
  while (mySerial.available() > 0 && newData == false) {
    rc = mySerial.read();
    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedMasterChars[ndx] = rc;
        ndx++;
        if (ndx >= numCharsMaster) {
          ndx = numCharsMaster - 1;
        }
      }
      else {
        receivedMasterChars[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }
    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}
//============
void parseData() {      // split the data into its parts
  char * strtokIndx; // this is used by strtok() as an index
  strtokIndx = strtok(tempMasterChars, ",");     // get the first part - the string
  strcpy(messageFromMaster, strtokIndx); // copy it to messageFromMaster
  strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
  integer1Master = atoi(strtokIndx);     // convert this part to an integer
  strtokIndx = strtok(NULL, ",");
  float1Master = atof(strtokIndx);     // convert this part to a float
  strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
  integer2Master = atoi(strtokIndx);     // convert this part to an integer
}
//============
void showParsedData() {
  Serial.print("messageFromMaster ");
  Serial.print(messageFromMaster);
  Serial.print(" Integer ");
  Serial.print(integer1Master);
  Serial.print(" Float ");
  Serial.print(float1Master);
  Serial.print(" integer2Master ");
  Serial.println(integer2Master);
}
//============
void sendDataToMaster () {
  mySerial.print('<');
  mySerial.print("Slave text");
  mySerial.print(",");
  mySerial.print(integer1Slave);
  mySerial.print(",");
  mySerial.print(float1Slave);
  mySerial.print(",");
  mySerial.print(integer2Slave);
  mySerial.print('>');
}

Thank you all again for your patience and insight. I truly appreciate it.