HC-12 wwireless communications between 2 Arduinos: data structuring question

2 HC-12 modules each connected to an Arduino communicate succesfully sending a piece of text from the serial interface from the one to the serial output/interface of the other and vice versa. Code below. Source: https://www.allaboutcircuits.com/projects/understanding-and-implementing-the-hc-12-wireless-transceiver-module/

Now I am looking to send data collected from three (or more) sensors on the one Arduino (A) and send these values once a minute (or once an hour, whatever) to the other Arduino (B).

These values will be integers (2 bytes).

B has to store these values to do lots of calculations, so I want to put them in a database with FIFO setup, and with maximum 10 values to be stored. So, ten minutes (or ten hours depending on the TX side setup).

EDIT: correction, not ten minutes, or ten hours, but one minute (or hour)

What would be the best concept to send these values every time: as a one piece of data in some sort of string, or as individual integers with a precursor marker (ie a byte or single letter to identify the sensor from which the value comes from? Or another way of transmitting these three values every minute (or hour)?

//https://www.allaboutcircuits.com/projects/understanding-and-implementing-the-hc-12-wireless-transceiver-module/

/*  HC12 Send/Receive Example Program 1
    By Mark J. Hughes 
    for AllAboutCircuits.com
    
    Connect HC12 "RXD" pin to Arduino Digital Pin 4
    Connect HC12 "TXD" pin to Arduino Digital Pin 5
    Connect HC12 "Set" pin to Arduino Digital Pin 6
   
    Do not power over USB.  Per datasheet, 
    power HC12 with a supply of at least 100 mA with 
    a 22 uF - 1000 uF reservoir capacitor.
    Upload code to two Arduinos connected to two computers.
    
    Transceivers must be at least several meters apart to work.
   
 */

#include <SoftwareSerial.h>

const byte HC12RxdPin = 4;                  // Recieve Pin on HC12
const byte HC12TxdPin = 5;                  // Transmit Pin on HC12

SoftwareSerial HC12(HC12TxdPin,HC12RxdPin); // Create Software Serial Port

void setup() {
  Serial.begin(9600);                       // Open serial port to computer
  HC12.begin(9600);                         // Open serial port to HC12
}

void loop() {

  if(HC12.available()){                     // If Arduino's HC12 rx buffer has data
    Serial.write(HC12.read());              // Send the data to the computer
    }
  if(Serial.available()){                   // If Arduino's computer rx buffer has data
    HC12.write(Serial.read());              // Send that data to serial
  }

  
}

Have a look at the 3rd example and the parse example in Serial Input Basics

...R

Robin2:
Have a look at the 3rd example and the parse example in Serial Input Basics

...R

Fantastic!!!

What a post, I am amazed, thank you for the link.

May I ask you why you use

if (ndx >= numChars) {
                    ndx = numChars - 1;
                }

? This keeps the index to its stated max value and will it not overwrite the characters coming in at the end of the array?

Yes. Its just a mechanism to "lose" any extra characters if too many are sent.

Unlike some interpreted languages C/C++ does not check or out-of-bounds writing to arrays.

...R

Robin2:
Yes. Its just a mechanism to "lose" any extra characters if too many are sent.

Unlike some interpreted languages C/C++ does not check or out-of-bounds writing to arrays.

...R

Thanks!

And 40k+ posts, and still find the time to answer this little post (which helped me like nothing before)!

And one post with 35k+ reads!! I stand in awe.

Robin2 major work helped show the way for me on my question in this thread.

However, if all that is needed are the sending of one character and one byte (representing a value from a sensor), there is the choice of
A. Using a string as Robin2 does
B. Using 2 separate transmissions, first the character and then the byte, immediately after the character.

The purpose is to send a value once a minute (or once an hour) from 1 transmitter, called "A", have it received by Arduino Receiver (called "C"), and also have, once a minute (or once an hour, but the same as "A") a value sent by transmitter "B".
Each transmitted value has to be preceeded by the character "A" or "B" depending on the transmitter, so that at the receiver end the value is stored to the correct variable for either "A" or "B".

If a string is still preferred, should it then probably be something like:

HC12.write(<A, sensorValueA>); //send value from sensorA from transmitter A ?

..and should it be parsed as shown in example 5 of Robin2's famous thread Serial Input Basics - updated - Introductory Tutorials - Arduino Forum ?

EDIT: grammar

brice3010:
If a string is still preferred, should it then probably be something like:

HC12.write(<A, sensorValueA>); //send value from sensorA from transmitter A ?

..and should it be parsed as shown in example 5 of Robin2's famous thread

That is how I would do it. If you send the identifier and the value as two separate messages it is possible that only one part would be received and then everything would be confused.

...R

Robin2:
That is how I would do it. If you send the identifier and the value as two separate messages it is possible that only one part would be received and then everything would be confused.

...R

Ok, thank you.

In your program examples you do not use a String object constructor such as String readBuffer = "this is a string";

Question 1: Is there a reason for that?

I am trying to figure out how to introduce your examples to a setup where it is a transmitting HC12 -not the serial monitor- (at the sensor side) that sends, per message, in a string, a sensor identifier ("A" or "B") and the sensor integer value.

Using String constructor objects (and with HC12 as serial interface instead of the serial output window) in an example found at Arduino and HC-12 Long Range Wireless Communication Module - How To Mechatronics the following code (if no mistakes?) could be used.

Question 2: How would you evaluate this way of working?

Question 3: Or should I rather stick with the strict use of strings and their parsing as shown in your example #5?

Question 4: And if so, how should I send the string over an HC12 rather than typing as shown in your example?
Would this be correct: HC12.write(<A, sensorValueA>); //send value from sensorA from transmitter A ?

String object constructor code:

  1. send an integer value from sensor A from the transmitting HC12 (situated at the sensor):
HC12.write(A, sensorValueA);
  1. read incoming data and store in String object (from the receiving HC12, at the main controller):
#include <SoftwareSerial.h>
SoftwareSerial HC(10, 12);
byte incomingByte;
String readBuffer = "";
void setup() {
Serial.Begin(9600);
HC12.begin(9600);}
void loop() {
while (HC12.available()) {  //store incoming data on receiving HC12
incomingByte = HC12.read();
readBuffer += char(incomingByte);}
  1. check whether the String just received starts with a specific character, for example "A":
void checkStartCharacter () {
if (readBuffer.startsWith("A")) {


//..now retrieve the remainder from the string, ie a integer value..
}

Q1, Q2.
It is not a good idea to use the String (capital S) class on an Arduino as it can cause memory corruption in the small memory on an Arduino. Just use cstrings - char arrays terminated with 0.

Q3,
Yes

Q4 Would this be correct: HC12.write(<A, sensorValueA>);
No.

Do it like this

HC12.print("<A,");
HC12.print(sensorValueA);
HC12.print('>');

QX.
Look again at the parse example in my tutorial.

...R

Robin2:
Q1, Q2.
It is not a good idea to use the String (capital S) class on an Arduino as it can cause memory corruption in the small memory on an Arduino. Just use cstrings - char arrays terminated with 0.

Q3,
Yes

Q4 Would this be correct: HC12.write(<A, sensorValueA>);
No.

Do it like this

HC12.print("<A,");

HC12.print(sensorValueA);
HC12.print('>');




QX.
Look again at the parse example in my tutorial.

...R

Fantastic!! Thank you for your answer, and I will be doing the necessary study of example 5.
Grts,
Erik

Robin2:
Do it like this

HC12.print("<A,");

HC12.print(sensorValueA);
HC12.print('>');




QX.
Look again at the parse example in my tutorial.

...R

..is it HC12.print('>');

..or is it HC12.print(">");

?

EDIT: (added) And what is the difference between HC12.write() and HC12.print()?

EDIT2: found it:
Single quotes or double quotes?
Strings are always defined inside double quotes ("Abc") and characters are always defined inside single quotes('A'). from https://www.arduino.cc/en/Reference/String

EDIT: (added) And what is the difference between HC12.write() and HC12.print()?

The same as between Serial.print() and Serial.write(). The print() functions convert the value to a string. The write() functions do NO conversion.

PaulS:
The same as between Serial.print() and Serial.write(). The print() functions convert the value to a string. The write() functions do NO conversion.

Thank you Paul, and meanwhile I had found this thread about this subject: serial.print or serial.write? - Syntax & Programs - Arduino Forum

So, if I understand correctly, everytime I want the HC12 to send a string, such as would be the case with Robin2's examples, I must use HC12.print.

And when retrieving the string information, I must use the same procedure as Robin2 uses in example 5 topic 396450 discussed here above?
I start to get it :wink:

brice3010:
So, if I understand correctly, everytime I want the HC12 to send a string, such as would be the case with Robin2's examples, I must use HC12.print.

And when retrieving the string information, I must use the same procedure as Robin2 uses in example 5 topic 396450 discussed here above?
I start to get it :wink:

Yes.

The single quotes are used in C/C++ to denote a single character such as 'H' and double quotes to denote a string of several characters such as "Hello".

...R

This code below I have converted from example 5 from the Robin2 post topic 396450.

Changes made:

  1. instead of using the serial window I use HC12 and an analog input from pin 5 to communicate (set at 9600baud)
  2. instead of using a string with text, an integer and a float, I use one character and an integer (actually a byte, between 0 and 255)
  3. the HC12 is switched on as soon as a change to the sensor value at pin 5 is detected.

This setup worked well before, ie when simply sending the integer as an integer (HC12.write(val5);).

Now with the string I cannot get it to work for the time being.

Question: can someone please have a look at my code and give a hint where it is wrong when compared to example #5 of Robin2?

Code for the transmitter (where the sensor is being read at pin 5):

// http://forum.arduino.cc/index.php?topic=499210.msg3407044#msg3407044
// http://forum.arduino.cc/index.php?topic=396450.0

// HC12 Communication between Arduinos

/*
  This program serves to send one integer (or byte) only from two different HC12
  transmitters at one sensor each (A and B)to one HC12 receiver at the central
  controller Arduino. The receiving HC12 has to know from which transmitter A or B
  the value comes.
  The data are sent with start- and endmarkers

  // TRANSMITTER PART
*/

#include <SoftwareSerial.h>
const byte HC12RxdPin = 4;                  // Recieve Pin on HC12
const byte HC12TxdPin = 5;                  // Transmit Pin on HC12
SoftwareSerial HC12(HC12TxdPin, HC12RxdPin); // Create Software Serial Port
int analogValue5, val5;
int previousAnalogValue5;
int HC12power = 8;
const long interval = 1000; // display time 1 in milliseconds
unsigned long previousMillis = 0; // timer

void setup()
{
  Serial.begin(9600);     // Open serial port to computer
  HC12.begin(9600);       // Open serial port to HC12

  pinMode(HC12power, OUTPUT);
}

void loop()
{
  unsigned long currentMillis = millis();

  // read analog pin 5
  analogValue5 = analogRead(5);
  // remap values from the analogValue5 variable to 0 / 255
  val5 = map(analogValue5, 0, 1023, 0, 255);
  // detect if there is a change in value, if so: switch on HC12
  if (analogValue5 > (previousAnalogValue5 + 20) || analogValue5 < (previousAnalogValue5 - 20)) {
    previousAnalogValue5 = analogValue5;
    digitalWrite(HC12power, HIGH);
    delay(5);
    //HC12.write(val5);
    HC12.print("<A,");
    HC12.print(val5);
    HC12.print('>');
    delay(100);
  }
  else {
    //when no more change in value for analogValue5, then start timer before switching of HC12
    if ((currentMillis - previousMillis) >= interval) {
      previousMillis = currentMillis;
      digitalWrite(HC12power, LOW);
    }
  }
}

Code for the receiver, where the parsing takes place and where the received value from pin 5 (transmitter side) is written to PWM pin 11 (when character 'A' precedes the sent message):

// http://forum.arduino.cc/index.php?topic=499210.msg3407044#msg3407044
// http://forum.arduino.cc/index.php?topic=396450.0

// HC12 Communication between Arduinos

/*
  This program serves to send one integer (or byte) only from two different HC12
  transmitters at one sensor each (A and B)to one HC12 receiver at the central
  controller Arduino. The receiving HC12 has to know from which transmitter A or B
  the value comes.
  The data are sent with start- and endmarkers

  // RECEIVER PART
*/

#include <SoftwareSerial.h>
const byte HC12RxdPin = 4;                  // Recieve Pin on HC12
const byte HC12TxdPin = 5;                  // Transmit Pin on HC12
SoftwareSerial HC12(HC12TxdPin, HC12RxdPin); // Create Software Serial Port
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);
  HC12.begin(9600);       // Open serial port to HC12

  // declare pin 11 & 12 as output
  pinMode (11, OUTPUT);
  pinMode (12, OUTPUT);

  delay(1000);
}

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();
    useParsedData();
    newData = false;
  }
}

//============

void recvWithStartEndMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;

  while (HC12.available() > 0 && newData == false) {
    rc = HC12.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 useParsedData() {
  if (messageFromPC == 'A') analogWrite (11, integerFromPC);
  if (messageFromPC == 'B') analogWrite (12, integerFromPC);
}
  if (messageFromPC == 'A') analogWrite (11, integerFromPC);

What does it mean, to you, to compare an array with the character 'A'? What you are ACTUALLY comparing is the address of the first element in the array (NOT the contents of that element) to the letter 'A'.

   if(messageFromPC[0] == 'A')
      analogWrite(11, integerFromPC);

PaulS:

  if (messageFromPC == 'A') analogWrite (11, integerFromPC);

What does it mean, to you, to compare an array with the character 'A'? What you are ACTUALLY comparing is the address of the first element in the array (NOT the contents of that element) to the letter 'A'.

   if(messageFromPC[0] == 'A')

analogWrite(11, integerFromPC);

"headbangwall" thank you Paul, really stupid of me: I should have known messageFromPC is an array.

But.. is index 0 not equal to the startmarker < ?

But.. is index 0 not equal to the startmarker < ?

Look at the code for recvWithStartEndMarkers(). The start and end markers are NOT stored in the array.

PaulS:
Look at the code for recvWithStartEndMarkers(). The start and end markers are NOT stored in the array.

I did look at it but obviously not well enough, and again I should have known because right after your post I corrected it and it works.

Thank you!!

In Robin2's example he parses a string with text first, then an integer, then a float. I worked this out with Robin2's help to work on a HC12 communications link between 2 Arduinos (first 2 code sections below).

Then I tried with first an integer and then text. Somehow I can't get it right (last two code sections below).

This code here shows Robin's example for sending, then parsing, text and then an integer with HC12 (working):

HC12.print("<B,");
HC12.print(val5);
HC12.print('>');
  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
  integer1FromPC = atoi(strtokIndx);     // convert this part to an integer
void useParsedData() {
  if (messageFromPC[0] == 'A') analogWrite (11, integer1FromPC);
  if (messageFromPC[0] == 'B') analogWrite (12, integer1FromPC);
}

Here below my conversion to: first an integer, then text (not working); first the sending part, then the parsing:
EDIT: removed line 4 and included with line 3 below

 HC12.print('<');
 HC12.print(val5);
 HC12.print(",B>");
  char * strtokIndx; // this is used by strtok() as an index
  strtokIndx = strtok(tempChars, ",");     // get the first part - the integer
  integer1FromPC = atoi(strtokIndx);     // convert this part to an integer

  strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
  strcpy(messageFromPC, strtokIndx); // copy it to messageFromPC

void useParsedData() {
  if (messageFromPC[0] == 'A') analogWrite (11, integer1FromPC);
  if (messageFromPC[0] == 'B') analogWrite (12, integer1FromPC);
}

Question 1: what is wrong in my code for first integer, then text?

Question 2: I cannot find n explanation for the first line in the parsing code: char * strtokIndx; what is the purpose of char and of *?