HC05 Bluetooth Car

I've made a bluetooth car that is programmed with an Arduino Nano, and a Bluetooth Joystick that is programmed with an Arduino Uno. Both of the devices have an HC05 Bluetooth module. The car works perfectly, but every time the hc05 on both sides loose connection, the car will start moving by its own to the left repeatedly until connection is back. I don't want that to happen; I want the car to instantly stop the moment it loses connection.

Joystick code:

const int xPin = A1;
const int yPin = A0;

int xDir, yDir;
void setup () {
  Serial.begin(38400);
}

void loop () {
  xDir = analogRead(yPin);
  yDir = analogRead(xPin);
  Serial.write(xDir / 4); // converts data from 10-bit to 8-bit
  Serial.write(yDir / 4);
  delay(20);
}

Car code:

const int en1 = 3; // L293N pins 
const int mc1_l = 4;
const int mc2_l = 5;
const int en2 = 6;
const int mc1_r = 7;
const int mc2_r = 8;

int xData;
int yData;

void setup() {
  Serial.begin(38400);
  for (int i = 3; i <= 8; i++) {
    pinMode(i, OUTPUT);
  }
  brake();
  delay(2000);
  brake();
}
void loop() {
  while (Serial.available() >= 2) {
    xData = Serial.read();
    delay(10);
    yData = Serial.read();
  }
  
  xData *= 4; // gets data back to 10-bit
  yData *= 4;
  
  delay(20);

  if (xData > 600) {
    turnRight();
  } else if (xData < 400) {
    turnLeft();
  } else if (yData > 600) {
    forward();
  } else if (yData < 400) {
    backward();
  } else {
    brake();
  }
}

void forward() {
  digitalWrite(en1, LOW);
  digitalWrite(mc1_r, HIGH);
  digitalWrite(mc2_r, LOW);
  digitalWrite(en1, HIGH);
  digitalWrite(en2, LOW);
  digitalWrite(mc1_l, HIGH);
  digitalWrite(mc2_l, LOW);
  digitalWrite(en2, HIGH);
}

void backward() {
  digitalWrite(en1, LOW);
  digitalWrite(mc1_r, LOW);
  digitalWrite(mc2_r, HIGH);
  digitalWrite(en1, HIGH);
  digitalWrite(en2, LOW);
  digitalWrite(mc1_l, LOW);
  digitalWrite(mc2_l, HIGH);
  digitalWrite(en2, HIGH);
}

void turnLeft() {
  digitalWrite(en1, LOW);
  digitalWrite(mc1_r, HIGH);
  digitalWrite(mc2_r, LOW);
  digitalWrite(en1, HIGH);
  digitalWrite(en2, LOW);
  digitalWrite(mc1_l, LOW);
  digitalWrite(mc2_l, LOW);
  digitalWrite(en2, HIGH);
}

void turnRight() {
  digitalWrite(en1, LOW);
  digitalWrite(mc1_r, LOW);
  digitalWrite(mc2_r, LOW);
  digitalWrite(en1, HIGH);
  digitalWrite(en2, LOW);
  digitalWrite(mc1_l, HIGH);
  digitalWrite(mc2_l, LOW);
  digitalWrite(en2, HIGH);
}

void brake() {
  digitalWrite(en1, LOW);
  digitalWrite(mc1_r, LOW);
  digitalWrite(mc2_r, LOW);
  digitalWrite(en1, HIGH);
  digitalWrite(en2, LOW);
  digitalWrite(mc1_l, LOW);
  digitalWrite(mc2_l, LOW);
  digitalWrite(en2, HIGH);
}

Send one fixed value byte before the 2 joystick data bytes. Then at the receiver, if the fixed byte is not read right, stop the car. The fixed value byte could be a start marker. If that byte is not received the subsequent data is invalid . See the serial input basics tutorial. It will help you to send, receive and parse multiple bytes and shows the use of start and end markers.

Or you can send data at a regular interval, say every 1/2 second. If data is not received in the 1/2 second window, stop the car.

Use millis() for non-blocking timing.
Blink without delay().
Beginner's guide to millis().
Several things at a time.

I tried something similar before but that just stopped the entire car from moving (even when connected), I may not be doing it right. But what I basically did is that I wrote a code on the car where it sends a character 's', and once the joystick microcontroller receives this 's', I want it to write the data to the car and in addition write an 'a' char, and once the car microcontroller received 'a', I want it to start the wheels, or else brake(). It sounds complicated I know, but I thought this would work. Unfortunately, it did not.

My problem really is the coding logic behind this, I'm not really sure how to do this.

Here is example code using the methods from the serial input basics tutorial to send, receive and parse the data and using millis() timing to sense data drop outs.

sender (master):

#include <SoftwareSerial.h>

const byte xPin = A0;
const byte yPin = A1;

SoftwareSerial ss(4, 7);

void setup()
{
   Serial.begin(115200);
   ss.begin(9600);
}

void loop()
{
   static unsigned long timer = 0;
   unsigned long interval = 1000;
   if (millis() - timer >= interval)
   {
      timer = millis();
      int xValue = analogRead(xPin);
      int yValue = analogRead(yPin);
      Serial.print("x value = ");
      Serial.print(xValue);
      Serial.print("   y value = ");
      Serial.println(yValue);
      char buffer[15];
      snprintf(buffer, 14, "%d,%d", xValue, yValue);
      Serial.println(buffer);
      ss.println(buffer);
   }
}

receiver (slave):
EDIT I made a change to the sender code to reset the state of the runFlag if reception is restored.

#include <SoftwareSerial.h>

int xValue = 0;
int yValue = 0;

const byte numChars = 12;
char receivedChars[numChars];   // an array to store the received data

boolean newData = false;

SoftwareSerial ss(4, 7);

static unsigned long timer = 0;
unsigned long interval = 1500;

bool runFlag = true;  // true if there is valid serial data

void setup()
{
   Serial.begin(115200);
   ss.begin(9600);
}

void loop()
{
   recvWithEndMarker();
   //showNewData();
   if (newData)
   {
      parseData();
      displayData();
      newData = false;
   }
   else if (millis() - timer >= interval)
   {

      // timer so the serial port doesn't get flooded
      static unsigned long printTimer = 0;
      unsigned long printInterval = 500;
      if (millis() - printTimer >= printInterval)
      {
         printTimer = millis();
         Serial.print("data not received");
         // make running the motors conditional on the state of this flag
         runFlag = false; // serial data not valid
         Serial.print("    >>> run flag = ");
         Serial.print(runFlag);
      }
   }
}

void recvWithEndMarker()
{
   static byte ndx = 0;
   char endMarker = '\n';
   char rc;

   while (ss.available() > 0 && newData == false)
   {
      rc = ss.read();

      if (rc != endMarker)
      {
         receivedChars[ndx] = rc;
         ndx++;
         if (ndx >= numChars)
         {
            ndx = numChars - 1;
         }
      }
      else
      {
         receivedChars[ndx] = '\0'; // terminate the string
         ndx = 0;
         newData = true;
         runFlag = true;  // reste run flag
         timer = millis();
      }
   }
}

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, ",");  // delimiter comma
   while (ptr != NULL)
   {
      strings[index] = ptr;
      index++;
      ptr = strtok(NULL, ",");
   }
   //Serial.println(index);
   /*
      // 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
   xValue = atoi(strings[0]);
   yValue = atoi(strings[1]);
}

void displayData()
{
   Serial.print("x value = ");
   Serial.print(xValue);
   Serial.print("   y value = ");
   Serial.print(yValue);
   Serial.print("   run flag = ");
   Serial.print(runFlag);
   Serial.println(); // blank line
}

Output with sender disabled and re-enabled:

x value = 497 y value = 486 run flag = 1
x value = 518 y value = 508 run flag = 1
x value = 506 y value = 485 run flag = 1
data not received
run flag = 0data not received
run flag = 0data not received
run flag = 0data not received
run flag = 0data not received
run flag = 0data not received
run flag = 0x value = 512 y value = 501 run flag = 1
x value = 517 y value = 499 run flag = 1
x value = 499 y value = 489 run flag = 1
x value = 503 y value = 495 run flag = 1

You might be able to use the State pin on the HC05 module to determine connected status. Be aware, not all modules have working State pins.

There is an extensive discussion here https://forum.arduino.cc/index.php?topic=597070.0

NOTE: I made a change to the sender code to reset the state of the runFlag if reception is restored.

Thank you, I really appreciate your help. But since I'm still a beginner, your code killed my brain. I have a very hard time understanding your code :sweat_smile: And I'm still not familiar with this SoftwareSerial library.

I found a way, I tried changing the whole code. I used character instead, where if if the receiver received, for instance, the character 'F', the car moves forward; if it receives 'R', the car will move to the right, etc..

Joystick microcontroller code:

const int xPin = A1;
const int yPin = A0;

int xDir, yDir;
void setup () {
  Serial.begin(38400);
}

void loop () {
  xDir = analogRead(yPin);
  yDir = analogRead(xPin);
  if (xDir > 600) {
    Serial.write('R');
  } else if (xDir < 400) {
    Serial.write('L');
  } else if (yDir > 600) {
    Serial.write('F');
  } else if (yDir < 400) {
    Serial.write('B');
  } else {
    Serial.write('X');
  }
  delay(10);
}

Car code:

const int en1 = 3;
const int mc1_l = 4;
const int mc2_l = 5;
const int en2 = 6;
const int mc1_r = 7;
const int mc2_r = 8;

char directionData; 

void setup() {
  Serial.begin(38400);
  for (int i = 3; i <= 8; i++) {
    pinMode(i, OUTPUT);
  }
  brake();
  delay(2000);
  brake();
}
void loop() {
  while (Serial.available() > 0) {
    directionData = Serial.read();
  }
  delay(10);

  if (directionData == 'R') {
    turnRight();
  } else if (directionData == 'L') {
    turnLeft();
  } else if (directionData == 'F') {
    forward();
  } else if (directionData == 'B') {
    backward();
  } else {
    brake();
  }


}

void forward() {
  digitalWrite(en1, LOW);
  digitalWrite(mc1_r, HIGH);
  digitalWrite(mc2_r, LOW);
  digitalWrite(en1, HIGH);
  digitalWrite(en2, LOW);
  digitalWrite(mc1_l, HIGH);
  digitalWrite(mc2_l, LOW);
  digitalWrite(en2, HIGH);
}

void backward() {
  digitalWrite(en1, LOW);
  digitalWrite(mc1_r, LOW);
  digitalWrite(mc2_r, HIGH);
  digitalWrite(en1, HIGH);
  digitalWrite(en2, LOW);
  digitalWrite(mc1_l, LOW);
  digitalWrite(mc2_l, HIGH);
  digitalWrite(en2, HIGH);
}

void turnLeft() {
  digitalWrite(en1, LOW);
  digitalWrite(mc1_r, HIGH);
  digitalWrite(mc2_r, LOW);
  digitalWrite(en1, HIGH);
  digitalWrite(en2, LOW);
  digitalWrite(mc1_l, LOW);
  digitalWrite(mc2_l, LOW);
  digitalWrite(en2, HIGH);
}

void turnRight() {
  digitalWrite(en1, LOW);
  digitalWrite(mc1_r, LOW);
  digitalWrite(mc2_r, LOW);
  digitalWrite(en1, HIGH);
  digitalWrite(en2, LOW);
  digitalWrite(mc1_l, HIGH);
  digitalWrite(mc2_l, LOW);
  digitalWrite(en2, HIGH);
}

void brake() {
  digitalWrite(en1, LOW);
  digitalWrite(mc1_r, LOW);
  digitalWrite(mc2_r, LOW);
  digitalWrite(en1, HIGH);
  digitalWrite(en2, LOW);
  digitalWrite(mc1_l, LOW);
  digitalWrite(mc2_l, LOW);
  digitalWrite(en2, HIGH);
}

but every time the hc05 on both sides loose connection, the car will start moving by its own to the left repeatedly until connection is back. I don't want that to happen; I want the car to instantly stop the moment it loses connection.

How does you code deal with the case where the transmission stops? The receiver will continue to execute based on the last letter received.

Ignore my post #10. This was not the solution, if I was moving forward and suddenly the connection dropped, the car will keep moving according the the last letter received.

Yes, read my post #12. Do you have any solution to suggest?

You have already been given two possible solution

In Post #6

using millis() timing to sense data drop outs.

In Post #7

use the State pin on the HC05 module to determine connected status.

If you are waiting for an exact solution custom tailored to your car and for free you may be in for a long wait.

If you attempt to incorporate one of the solutions presented and have trouble and then we can help you to make it work.