Glitches in HC12 communication

The Project: I have an HC12 transmit device running on a Nano. Attached is a joystick. The HC12 is at default settings of 9600 baud, chan 1, and FU3. The receiver is attached to a ProMicro controlling 2 gear motors. Power is 7.4 LiIon battery for the motors, 5 volts for the HC12. Most of the program was captured from How To Mechatronics and I built up from that.
The Operation: Moving the joystick left or right turns the bot left or right. Pushing up on the stick makes it go forward and pulling down makes it go in reverse. When (spring-loaded) stick is at center the bot should stop.
The Issues: I have two. 1)When I push the stick UP to go forward, it does this but it latches. When stick returns to center, the motors continue to more forward. This does NOT happen with any other movement, eg going in reverse works as expected. 2).With everything sitting still at idle, with values of X and Y streaming and printing on the robot (receiver) monitor, the static value of the Y axis suddenly goes to -1 (or -4, see program). When this happens, the motors start by themselves. Here is a screen print:
ScreenShot_20230712134802
Here is the sketch of the receiver:Use code tags to format code for the forum


</*/*  Name: RemoteControlBotV1.1   was previously named DejanMouseFEV24.ino
    By:            Bob Found, Indian Harbour, NS Canada
    Start Date:    28Apr2023
    Last Rev:      12Jul2023
    MCU:           Arduino ProMicro 3.3 volt 8 Mhz
    Hardware:      2 motors, HC12 module, battery, MCU, motor driver L9110 module
    Description:   convert microsoft mouse into a a mouse robot by stuffing all that hardware 
    into a gutted mouse shell.  Use PWM and remote control joystick to operate.
    Vers History: V1.0   Start using Dejan Nedelkovski code
    1.5  Using L9110 motor driver without enables as Dejan had, modified defines and variables
    2.0  Changed processor from Nano to ProMini, 3.3 V 8Mhz 328 processor, 17500 battery
    2.1  Got all logic to driver module and motors consistent with code
    2.2  Put a deadband around joystick center.  Motors now stop when stick is idle
    2.3  Removed deadband and just made values start from center to deadband, to simplify program
    2.4  Removed software serial and changed MCU to Pro Micro instead of ProMini.  Using Serial1
    which does not impact USB serial to monitor.
    Using two gear motors instead of high speed toy car motors.  
    1.0  Changed name
    1.1  Changes to make motors stop when stick at center

    Arduino Robot Car Wireless Control using the HC-12 long range wireless module
    by Dejan Nedelkovski, www.HowToMechatronics.com
 
*/
#define LeftMotor 5    // pin 5 Left Motor   A1A  //recommended is 1A=speed (PWM)
#define LeftDir 3      // pin 3              A1B  // 1B=direction
#define RightMotor 9   // pin 9 Right motor  B1A  //recommended is 1A=speed (PWM)
#define RightDir 6     // pin 6              B1B  // 1B=direction
#define DIR_DELAY 250  // brief delay for abrupt motor changes
int xAxis, yAxis;
int x = 0;
int y = 0;
int LeftSpeed = 0;
int RightSpeed = 0;
int minSpeed = 100;             //min PWM value that moves motors
int maxSpeed = 255;            //max PWM value  max is 255
int deadband = 20;             //deadband for around center stick
int XCenter = 512;             //these are idle values for Nunchuk stick  516   512 for Joy on Nano
int YCenter = 496;             // 528 for Nunchuk  496 for joy on Nano
int Xlo = XCenter - deadband;  //20 is deadband, amount off center
int Xhi = XCenter + deadband;
int Ylo = YCenter - deadband;  //i.e. 528-30=498
int Yhi = YCenter + deadband;
const int LED = 10;                //LED for front of Microsoft mouse
int ledState = LOW;                // initial state
unsigned long previousMillis = 0;  // store last time LED was updated
const long interval = 0;           // blink rate in milliseconds
//************************** Setup ********************************************
void setup() {
  pinMode(LeftMotor, OUTPUT);
  pinMode(LeftDir, OUTPUT);
  pinMode(RightMotor, OUTPUT);
  pinMode(RightDir, OUTPUT);
  pinMode(LED, OUTPUT);  //blink while idle with power on
  Serial1.begin(9600);
  Serial.begin(9600);
  // Serial.println("     This is RemoteControlBotV1.1 on Pro Micro");  //program name, tests print to monitor ability
  // delay(1000);
}
//*************************** Loop ********************************************
void loop() {
  // Read the incoming data from the HC12
  while (Serial1.available() == 0) {}  //waits here forever until there is data in the buffer
  delay(100);                          //need this
  x = Serial1.read();                  //gets here if there is data waiting   
  y = Serial1.read();
  delay(10);
  // Convert the 0 - maxSpeed range  back to 0 - 1023
  xAxis = x * 4;
  yAxis = y * 4;
  Serial.print("xAxis= ");
  Serial.print(xAxis);
  Serial.print("\t  yAxis= ");
  Serial.println(yAxis);  //will only print if the HC12 comm is working
  // LED will NOT blink if no HC12 data, blinks if data coming in, and prints statement above
   //if (yAxis ==-4); {yAxis=YCenter;}
  // Y-axis for Forward and Reverse
  //****************Reverse******************  Stick down=1023
  if (yAxis > Yhi) {
    // Set Motor A backward
    digitalWrite(LeftMotor, HIGH);
    digitalWrite(LeftDir, LOW);
    // Set Motor B backward
    digitalWrite(RightMotor, HIGH);
    digitalWrite(RightDir, LOW);
    // Convert the declining Y-axis readings for going backward
    LeftSpeed = map(yAxis, Yhi, 1023, 0, maxSpeed);  //stick is 548 to 1023 going backward
    RightSpeed = map(yAxis, Yhi, 1023, 0, maxSpeed);

  }
  //******************Forward*********************  Stick up=0  LATCHES
  else if (yAxis < Ylo) {
    // Wait();
    //delay( DIR_DELAY );
    // Set Motor A forward
    digitalWrite(LeftMotor, HIGH);
    digitalWrite(LeftDir, HIGH);
    // Set Motor B forward
    digitalWrite(RightMotor, HIGH);
    digitalWrite(RightDir, HIGH);

    // Convert the increasing Y-axis readings for going forward
    LeftSpeed = map(yAxis, 0, Ylo, 0, maxSpeed);  //stick is Ylo to zero going FORWARD
    RightSpeed = map(yAxis, 0, Ylo, 0, maxSpeed);
    //if (yAxis ==-4); {yAxis=YCenter;}//prevents bad data (-4) from starting motor
    //if joystick stays in middle the motors should not turn
  } else {
    LeftSpeed = 0;
    RightSpeed = 0;
  }
    
   // if (yAxis ==-4); {yAxis=YCenter;}//prevents bad data (-4) from starting motor
      //if (yAxis<548 && yAxis >508 &&  xAxis<536 && xAxis >196)  {LeftSpeed=0; RightSpeed=0;}

  // X-axis used for left and right control
  //*******************turn LEFT*******************  X left=1023
  // Wait();
  // delay( DIR_DELAY );
  if (xAxis > Xhi) {
    // Convert the increasing X-axis readings from XCenter to 1023 into increasing 0 to maxSpeed value
    int xMapped = map(xAxis, Xhi, 1023, 0, maxSpeed);  //Stick goes from 536 to 1023 when moving LEFT
    // Move to left - decrease left motor speed, increase right motor speed
    LeftSpeed = LeftSpeed - xMapped;
    RightSpeed = RightSpeed + xMapped;

    // Confine the range from 0 to maxSpeed
    if (LeftSpeed < 0) { LeftSpeed = 0; }
    if (RightSpeed > maxSpeed) {
      RightSpeed = maxSpeed;
    }

    else {
      LeftSpeed = 0;
      RightSpeed = 0;
    }
  }
  //*******************turn Right******************** x right=0
  // Wait();
  //  delay( DIR_DELAY );
  if (xAxis < Xlo) {
    // Convert the decreasing X-axis readings from XCenter to 0 into 0 to maxSpeed value
    int xMapped = map(xAxis, Xlo, 0, 0, maxSpeed);  //Stick goes from 196 to 0 when moving RIGHT
    // Move right - decrease right motor speed, increase left motor speed
    LeftSpeed = LeftSpeed + xMapped;
    RightSpeed = RightSpeed - xMapped;

    // Confine the range from 0 to maxSpeed
    if (LeftSpeed > maxSpeed) { LeftSpeed = maxSpeed; }
    if (RightSpeed < 0) { RightSpeed = 0; }
  }
  // Prevent buzzing at low speeds
  if (LeftSpeed < minSpeed) { LeftSpeed = 0; }
  if (RightSpeed < minSpeed) { RightSpeed = 0; }

  analogWrite(LeftMotor, LeftSpeed);    // Send PWM signal to motor A
  analogWrite(RightMotor, RightSpeed);  // Send PWM signal to motor B

  //****************************************************
  /* unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {//note where loop bracket is at very end.
    previousMillis = currentMillis;
   // if the LED is off turn it on and vice-versa:
  if (ledState == LOW) {ledState = HIGH;} 
  else {ledState = LOW;}
  digitalWrite(LED, ledState);
  }//end millis
  */
  //******************************************************
}  //end loop
void Wait() {
  digitalWrite(LeftDir, LOW);
  digitalWrite(LeftMotor, LOW);
  digitalWrite(RightDir, LOW);
  digitalWrite(RightMotor, LOW);
}>

**What I've Tried:** I tried changing timing around the reading of the HC12 received bytes.  I've added delays (10-20 mS) around the commands.  I tried using a conditional "if Y==-1, make Y=YCenter" or similar. I've tried to see if the "miss" was from the transmitter or receiver and it seems somewhere in between, ie the transmission itself.  I put a capacitor across HC12 power to try to keep voltage steady when transmitting.  Matters not if power is coming from battery or if it's coming from USB for the receiver.   The transmitter is powered by USB but I have option for 12 volts to the Nano.  The two devices are 12 inches apart.   (Maybe too close?).  I have an LCD readout on the Tx that is steady when the joystick is not moved.   I've changed out HC12, in fact changed the entire transmitter- either transmitter results in -1 appearing in Receiver, so it's unlikely the source of the problem.  HOWEVER, if I leave the transmitter powered off...the bot motors do not start up.   This makes sense since the line while (Serial1.available() == 0) {} prevents any code from running until there is something in the receive buffer.

**What to do Next:**  Well I don't know and that's why I'm here.  Should I play with baud rate (up or down)?  Or change channel to another, thinking there is interference with other devices on this frequency?  Note that the motors don't run if my transmitter is off so there is no interference from something else.


`

I am on my phone so it is difficult to analyse your code fully. One suggestion that i would make is to send the data in a comma delimited packet. Then, on the receive side, only one read and parse the data from the packet. That prevents the receiver from gettin "out of sync" with the transmitter. See Robin2's serial input basics tutorial.

Right from the start, this is just asking for problems. Radio is inherently unreliable, and so is wired serial, although much less so.

What happens if the receiver gets out of sync with the transmitter? You have no "start of message" or any error checking in the serial data transmission protocol.

Have a look at the Serial Input Basics tutorial on this forum.

This may not be the only problem in your code.

Hello queenidog

To get shure check your communication protocol without the radio transceivers.

Here is example code using the methods from Robin2's excellent tutorial. It uses start and end markers to sync the transmitter and receiver. The receiver strips off the start and end markers, parses the data and places the joystick data into numeric variables for further processing (see example 5).
It uses only one write to send a packet containing the joystick information and one read to receive.
Tested with real hardware.

Sender:

// send joystick data using HC12 transievers.
// by groundFungus aka c.goulding

#include <SoftwareSerial.h>

const byte rxPin = 5;
const byte txPin = 6;
const byte setPin = 7;
const byte xAxisPin = A0;
const byte yAxisPin = A1;

SoftwareSerial HC12(rxPin, txPin);

unsigned long sendInterval = 200;

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

   pinMode(setPin, OUTPUT);
   digitalWrite(setPin, HIGH);
   HC12.println("SENDER ready ********");
   Serial.println("SENDER ready");
}

void loop()
{
   static unsigned long sendTimer = 0;
   if (millis() - sendTimer >= sendInterval)
   {
      sendTimer = millis();
      int xAxisValue = analogRead(xAxisPin);
      int yAxisValue = analogRead(yAxisPin);
      char packet[15];
      snprintf(packet, 14, "<%d,%d>", xAxisValue, yAxisValue);
      Serial.print("Sending >>>> ");
      Serial.println(packet);
      HC12.println(packet);
   }
}

Receiver:

// recieve joystick data using HC12 transievers.
// by groundFungus aka c.goulding

#include <SoftwareSerial.h>

const byte rxPin = 5;
const byte txPin = 6;
const byte setPin = 7;

SoftwareSerial HC12(rxPin, txPin);

const byte numChars = 16;
char receivedChars[numChars];
char tempChars[numChars];
bool newData = false;

int xAxisValue = 0;
int yAxisValue = 0;

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

   pinMode(setPin, OUTPUT);
   digitalWrite(setPin, HIGH);
   HC12.println("RECIEVER ready ********");
   Serial.println("RECIEVER ready");
}

void loop()
{
   recvWithStartEndMarkers();
   //showNewData(); // incoming raw data
   if (newData)
   {
      strcpy(tempChars, receivedChars);
      parseData(); // parse and print
   }
}

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 showNewData()
{
   if (newData == true)
   {
      Serial.print("This just in ... ");
      Serial.println(receivedChars);
      //newData = false;
   }
}

void parseData()
{
   char *strings[2]; // an array of pointers to the pieces of the above array after strtok()
   char *ptr = NULL; byte index = 0;
   ptr = strtok(receivedChars, ",");  // delimiters, comma
   while (ptr != NULL)
   {
      strings[index] = ptr;
      index++;
      ptr = strtok(NULL, ",");
   }

   /*
      // print all the parts
      Serial.println("The Pieces separated by strtok()");
      for (int n = 0; n < index; n++)
      {
      Serial.print("piece ");
      Serial.print(n);
      Serial.print(" = ");
      Serial.println(strings[n]);
      }
   */

   // convert string data to numbers
   xAxisValue = atoi(strings[0]);
   yAxisValue = atoi(strings[1]);
   // print data
   Serial.print("xAxisValue = ");
   Serial.print(xAxisValue);
   Serial.print("   yAxisValue = ");
   Serial.print(yAxisValue);

   Serial.println(); // blank line
   newData = false;
}

Thank you Mr. Fungus. I had read Robins tutorial a few times (before anyone here suggested it), looking at the best way to do what I want to do. In the past I used NRF24L01 and 233Mhz radio and HC-12, all sending information from 3 sensors to a desktop computer. It was just an exercise to see how they would work. I had to use the parse command, with * separators. There’s a lot of code here…will try and understand it and strip out what I don’t need, and see what goes. I only need two bytes to be transmitted and received.

I read an article about the NIH (National Institute of Health) using HC-12 in a farming app with multiple nodes, so there is good reason to use these devices. Not sure how they got reliable transmission (over 800 feet).

GF, I used your code and have communication between two devices with HC12 on them. For the most part it works BUT...like the simpler version I was using, there is a random character thrown in. Refer to photo, note the "<" in front of a packet. This will probably start my motors as the "-1" did. What do you think? While waiting for a reply, I'll try and load the receive part into the robot device and see if it does get affected.
ScreenShot_20230713150428

That is not random, it's your start or end marker.

The '<' is a start marker that was not removed properly. I have never seen that before. The '<' should not make it passed the itoa() function that converts the string to a number since '<' is not numeric. What is printed by the parseData() function after conversion?

There it is for piece 0 at time xxx.147. This happens with same frequency as the -1 I was getting before.

Oh...I am using Robin's transmit code where he uses the hex equivalent of < and >. I copied your code verbatim but Robin's I picked and chose.
image

Okay, I think I found the problem. I went to the Tx sketch (Robins, from his tutorial). I was printing the axes values to LCD. Since I rem'ed those out, there has not been one anomaly. I have Serial O/P from Tx and Rx side-by-side, syncronized so I can see if I'm sending anything funny, after receiving something. But, it's been running for 5 minutes now with no problems.

I spoke too soon. Now it's back. I changed the send interval in TX from 200 to 100 and then 200 to 300. In both cases the < symbol was displayed on the receiver. Digging in...

RX on left, TX on right, roughly synchronized to show no anomalies in sending but TWO in Receiving, at xxx.376 and xxx.755. I might have had scrolling off when I thought everything was working.

and this is the output when I have an anomaly. I'm not moving the joystick and the sender values do not change for either axis.
image

and when I print the "pieces"
image

So when the errant < appears, it makes the XAxisValue=0. This will make the motors turn

I was wrong when I thought that the atoi() would ignore non numbers, it seems.

I needed to read the f** manual.

If the first sequence of non-whitespace characters in str is not a valid integral number, or if no such sequence exists because either str is empty or it contains only whitespace characters, no conversion is performed and zero is returned.

I have no idea how that is sneaking through.

Yes, I knew it was. By random I meant “random appearing” because it was not periodic.

OR….before I send the value to the motor controller, I check for valid input?

Would it be possible then to maybe generate a CRC, or identify a non-valid integer in the stream? A filter of sorts? How about reading the value, sending it back to verify, then resend if okay. Will cut throughput drastically, but maybe I can compensate another way like increase the baud rate.

Something like:

 // convert string data to numbers
   int oldxAxisValue = xAxisValue;
   char c = strings[0][0];  // first character of x data string
   if (isDigit(c) != 0)  // is it numeric?
   {
      xAxisValue = atoi(strings[0]); // yes
   }
   else
   {
      xAxisValue = oldxAxisValue; //no
   }

Check if the first character of the x axis data string is a numeric character and use the last value of x data if it is not numeric. If the occasional reception of '<' is the only problem that is a simple solution. If you want more robust error checking you can certainly do that. There are probably libraries to help with CRC.

Thanks for this. I will try it. Meanwhile I found a library called Serial Transfer by PowerBroker. It has these benefits:

This library:

  • can be downloaded via the Arduino IDE's Libraries Manager (search "SerialTransfer.h")
  • works with "software-serial", "software-I2C", and "software-SPI" libraries
  • is non blocking
  • uses packet delimiters
  • uses consistent overhead byte stuffing
  • uses CRC-8 (Polynomial 0x9B with lookup table)
  • allows the use of dynamically sized packets (packets can have payload lengths anywhere from 1 to 254 bytes)
  • supports user-specified callback functions
  • can transfer bytes, ints, floats, structs, even large files like JPEGs and CSVs!!

There is little information on how to implement it so I’m still studying it. Have you used?