Sending parallax joystick input via bluetooth

Hi all,

I am truly struggling to grasp the basics of BT communication.

My setup goes like this... Arduino nano with parallax joystick and HC05 bt module (Master). This will be received by a second arduino nano with the slave HC06 module and two DC motors.

The Master and Slave modules have been paired and bound using these tutorials Connecting 2 Arduinos by Bluetooth using a HC-05 and a HC-06: Pair, Bind, and Link | Martyn Currey

To start with I'm trying to send simple letters "f" (forwards), "b" (backwards), 'l' (left), "r" (right) and "s" (stop) via HC05 bt module to the second arduino with a HC06 module.

I can write the code to drive the motors no problem but I don't know where to start with the bluetooth communication

Here is a basic sketch to print joy values to the serial monitor

int f;
int b;

int joyY = A0;
const int JoyDeadBand = 50;
const int JoyCenter = 507;

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

void loop(){
  int val = analogRead(joyY);
  
  if(val > JoyCenter + JoyDeadBand){
    Serial.println("f   ");
  }
  if(val < JoyCenter - JoyDeadBand){
    Serial.println("b   ");
  }
}

I was thinking I could incorporate that with the sketch from the tutorial.

#include <SoftwareSerial.h>
SoftwareSerial BTserial(2, 3); // RX | TX
// Connect the HC-05 TX to Arduino pin 2 RX. 
// Connect the HC-05 RX to Arduino pin 3 TX through a voltage divider.
char c = ' ';
 
void setup() 
{
    // start th serial communication with the host computer
    Serial.begin(9600);
    Serial.println("Arduino with HC-05 is ready");
 
    // start communication with the HC-05 using 38400
    BTserial.begin(9600);  
    Serial.println("BTserial started at 9600");
}
 
void loop()
{
 
  // Keep reading from HC-05 and send to Arduino Serial Monitor
    if (BTserial.available())
    {  
        c = BTserial.read();
        Serial.write(c);
    }
 
    // Keep reading from Arduino Serial Monitor and send to HC-05
    if (Serial.available())
    {
        c =  Serial.read();
 
        // mirror the commands back to the serial monitor
        // makes it easy to follow the commands
        Serial.write(c);   
        BTserial.write(c); 
    }
 
}

But I really don't know how??

There's lots of android to arduino sketches but nothing I can find that will send parallax joystick via bluetooth.
Does anyone know of a sketch I could use. It doesn't have to send letters, it could send the raw data from the joystick (0 to 1023). I just figured one letter was simple.

But I really don't know how??

You don't know how to do what?

    // Keep reading from Arduino Serial Monitor and send to HC-05
    if (Serial.available())

The comment is nonsense. The code does not keep doing anything. It does something no more than once. A while statement would keep doing something, if there was anything to do.

but nothing I can find that will send parallax joystick via bluetooth.

I fail to see how what data to send has ANY bearing on the problem of sending data. Sending the X value of a joystick is no different, from the point of view of shuffling bytes out, from sending "Hey, doofus".

If Serial.print() sends some data to the serial port that the Serial Monitor application is listening to, then it should be relatively obvious that BTSerial.print() would send data to the serial port that the bluetooth shield is listening to.

So, in the first sketch, you could replace Serial.print("f "); with BTSerial.print("f "); and send the data to the bluetooth shield instead.

Have a look at the 3rd example in Serial Input Basics and design your sending code to be compatible.

If you want to send 2 values the code might be like this

Serial.print('<');
Serial.print(valA);
Serial.print(',');
Serial.print(valB);
Serial.print('>');

If you are using SoftwareSerial you would obviously need to change that a little.

...R

Cool, thanks for your input. I now have the HC06 receiving the letters upon joystick inputs.

I'll have read of your tutorial Robin and see what I can come up with.

Here is my TX sketch.

#include <SoftwareSerial.h>
SoftwareSerial BTserial(2, 3); // RX | TX
// Connect the HC-05 TX to Arduino pin 2 RX. 
// Connect the HC-05 RX to Arduino pin 3 TX through a voltage divider.

int f; //forwards
int b; //backwards 
int l; //left
int r; //right

int joyY = A0;
int joyX = A1;
const int JoyDeadBand = 50;
const int JoyCenter = 507;

void setup(){
  Serial.begin(9600);
  BTserial.begin(9600); 
}

void loop(){
  int valY = analogRead(joyY);
  int valX = analogRead(joyX);
 
  if(valY > JoyCenter + JoyDeadBand){
    BTserial.println("f   ");
  }
  if(valY < JoyCenter - JoyDeadBand){
    BTserial.println("b   ");
  }
    if(valX > JoyCenter + JoyDeadBand){
    BTserial.println("r   ");
  }
  if(valX < JoyCenter - JoyDeadBand){
    BTserial.println("l   ");
  }
}
int f; //forwards
int b; //backwards
int l; //left
int r; //right

What are these variables for? They are never used.

One letter global variable names are a REALLY bad idea. Far too easy to reuse the same name later, and not understand why the global is not changing or is changing seemingly at random.

int joyY = A0;
int joyX = A1;

I really like to see Pin the name of variables that hold pin numbers, State in the name of variables that hold pin states, and Value in the names of variables that contain values read from analog pin.

To me, joyX looks like it should hold a value read from a pin, not the pin to read from. YMMV.

I've modified the Tx sketch to fit with your example #5 Robin and is working great. It's sending the raw data from the joystick and the HC06 arduino is receiving perfectly. I still don't fully understand some of what's happening in example #5 but I'm hoping once I can get a functional sketch (LEDs or motors working) on the Rx side it will all start to fall in place

Here is the HC05 sketch.

#include <SoftwareSerial.h>
SoftwareSerial BTserial(2, 3); // RX | TX
// Connect the HC-05 TX to Arduino pin 2 RX. 
// Connect the HC-05 RX to Arduino pin 3 TX through a voltage divider.

int joyYPin = A0;
int joyXPin = A1;
const int JoyDeadBand = 50;
const int JoyCenter = 507;

void setup(){
  
  BTserial.begin(9600);
  Serial.begin(9600);
  Serial.println("<Arduino is ready>"); 
}

void loop(){
  
    int valY = analogRead(joyYPin);
    int valX = analogRead(joyXPin);
  
  if(valY > JoyCenter + JoyDeadBand){
    BTserial.print("< Forward  ");
    BTserial.println(",");
    BTserial.print(valY);
    BTserial.println(">");
  }
  if(valY < JoyCenter - JoyDeadBand){
    BTserial.print("< Reverse  ");
    BTserial.println(",");
    BTserial.print(valY);
    BTserial.println(">");
  }
    if(valX > JoyCenter + JoyDeadBand){
    BTserial.print("< Right  ");
    BTserial.println(",");
    BTserial.print(valX);
    BTserial.println(">");
  }
  if(valX < JoyCenter - JoyDeadBand){
    BTserial.print("< Left  ");
    BTserial.println(",");
    BTserial.print(valX);
    BTserial.println(">");
  }
}

And here is the HC06 sketch I have so far.

#include <SoftwareSerial.h>
SoftwareSerial BTserial(2, 3); // RX | TX
// Connect the HC-05 TX to Arduino pin 2 RX. 
// Connect the HC-05 RX to Arduino pin 3 TX through a voltage divider.

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;

boolean newData = false;

const byte greenLED = 7;
const byte redLED = 6;

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

void setup() {
    BTserial.begin(9600);
    Serial.begin(9600);
    Serial.println("This demo expects 2 pieces of data - text and an integer ");
    Serial.println("Enter data in this style <HelloWorld, 12>  ");
    Serial.println();
    
    pinMode(greenLED, OUTPUT);
    pinMode(redLED, OUTPUT);
    digitalWrite(greenLED, LOW);
    digitalWrite(redLED, LOW);
}

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

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;
        LED();
    }
}

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

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

    while (BTserial.available() > 0 && newData == false) {
        rc = BTserial.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

}

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

void showParsedData() {
   
    Serial.print(messageFromPC);
    Serial.println(integerFromPC);
}

void LED(){
   
   if(messageFromPC == ("Forward")){
     digitalWrite(greenLED, HIGH);
     
     if(integerFromPC > 570){
       analogWrite(redLED, integerFromPC);
     }
   }
   else {
      digitalWrite(greenLED, LOW);
      digitalWrite(redLED, LOW); 
   } 
}

Can anyone explain why the LED's don't turn on. I don't understand why I can't take what the serial monitor is printing or what the HC06 is writing and use it to control something?

This does not work

if(messageFromPC == ("Forward")){

You need to use the strcmp() function

...R

Something like this??

if(strcmp(messageFromPC, "Forward")== 0){
     digitalWrite(greenLED, HIGH);}

I've managed get the redLED (PWM) working, albeit a bit erratic. But the greenLED is still not working?

The redLED if(statement) just needed some braces in the right place to work.

void LED(){
   
     if(strcmp(messageFromPC, "Forward")== 0){
       digitalWrite(greenLED, HIGH);
     }
     if(integerFromPC > 570){
       analogWrite(redLED, integerFromPC);
     }
   
     else  {
      digitalWrite(greenLED, LOW);
      digitalWrite(redLED, LOW); 
   }
}

Got the greenLED working. Yay... All it needed was a space after "Forward " to ==0.

Thank you very much for your help. Now I can get on to writing the rest of sketch. I'll post it here once it's finished. I may even have questions along the way.

If this was my project I would try very hard to reduce the commands to a single character for example 'F' rather than "Forward". Then there would be no need for strcmp()

...R

I reduced the commands to single letters but could get it to work without the stgcmp.

I've added a bit to run DC motors and it works ok'ish. It still has some faults. Here is the void Forward(), The other movement are similar.

void Forward(){
  
    if(strcmp(dir, "F")== 0){
       digitalWrite(MotorA1Pin, HIGH);
       digitalWrite(MotorA2Pin, LOW);
       digitalWrite(MotorB1Pin, HIGH);
       digitalWrite(MotorB2Pin, LOW);
       digitalWrite(StndbyPin, HIGH);
          
   int pwm = map(joyData, 550, 1023, 0, 255);
       analogWrite(MotorApwmPin, pwm);
       analogWrite(MotorBpwmPin, pwm);
     }
}

I might try to find a better differential steering code to work with because what I have for steering isn't that great.

When using single characters use single quotes.

Then you should be able to do something like if (receivedChar == 'F') {
or, if you are using an array, if (receivedChars[0] == 'F') {

...R

As promised I have finish a sketch that works. It's still a bit buggy but I'm not sure I can get it working any better.

I have set up the Master and Slave sketches to handle 2 bluetooth joysticks for future projects, however this Slave sketch only uses 1 joystick X and Y inputs. I didn't use the "char" section in the end but didn't know how to remove it without upsetting the rest. I kept getting 0000's for my integer readings when I deleted that section so I put it back in and everything was happy again.

Any suggestions, tips, comments to help improve on it are more that welcome.

Master:

#include <SoftwareSerial.h>
SoftwareSerial BTserial(2, 3); // RX | TX
// Connect the HC-05 TX to Arduino pin 2 RX. 
// Connect the HC-05 RX to Arduino pin 3 TX through a voltage divider.

int RightjoyYPin = A0;
int RightjoyXPin = A1;
int LeftjoyYPin = A2;
int LeftjoyXPin = A3;

void setup(){
  
  BTserial.begin(9600);
  Serial.begin(9600);
  Serial.println("<Arduino is ready>"); 
}

void loop(){
  
   int RightvalY = analogRead(RightjoyYPin);
   int RightvalX = analogRead(RightjoyXPin);
//   int LeftvalY = analogRead(LeftjoyYPin);
//   int LeftvalX = analogRead(LeftjoyXPin);
  
       
  BTserial.print("<");
     BTserial.print("G");
    BTserial.print(",");
       BTserial.print(RightvalY);
    BTserial.print(",");
       BTserial.print(RightvalX);
//    BTserial.print(",");
//       BTserial.print(LeftvalY);
//    BTserial.print(",");
//       BTserial.print(LeftvalX);
    BTserial.println(">");
    

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

/*  Serial.print("<");
       Serial.print(LeftvalY);
    Serial.print(",");
       Serial.print(LeftvalX);
    Serial.print(",");
       Serial.print(RightvalY);
    Serial.print(",");
       Serial.print(RightvalX);
    Serial.println(">");*/
    
    delay(100);

}

EDIT: I've updated these sketches (master and slave) to the latest bug free versions

The motor driver I'm using is this one https://nicegear.co.nz/robotics/pololu-tb6612fng-dual-motor-driver-carrier/
HC06 and HC05 bluetooth modules, and Arduino Nano. A quick video of it in action can be found here SandBeast - YouTube

Slave:

#include <SoftwareSerial.h>
SoftwareSerial BTserial(2, 3); // RX | TX
// Connect the HC-05 TX to Arduino pin 2 RX. 
// Connect the HC-05 RX to Arduino pin 3 TX through a voltage divider.

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

// variables to hold the parsed data
char Go[numChars] = {0};
int RightvalY = 0;
int RightvalX = 0;
int LeftvalY = 0;
int LeftvalX = 0;

boolean newData = false;

const byte MotorApwmPin = 5;
const byte MotorA1Pin = 6;
const byte MotorA2Pin = 7;
const byte MotorBpwmPin = 10;
const byte MotorB1Pin = 8;
const byte MotorB2Pin = 9;
const byte StndbyPin = 11;

const int DeadBand = 30;
unsigned int MidPoint = 508;
unsigned int Yspeed = 0;
unsigned int Xspeed = 0;

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

void setup() {
    BTserial.begin(9600);
    Serial.begin(9600);
    Serial.println("Arduino Ready ");
    Serial.println();
    
    pinMode(MotorA1Pin, OUTPUT);
    pinMode(MotorA2Pin, OUTPUT);
    pinMode(MotorB1Pin, OUTPUT);
    pinMode(MotorB2Pin, OUTPUT);
    
    pinMode(MotorApwmPin, OUTPUT);
    pinMode(MotorBpwmPin, OUTPUT);
    pinMode(StndbyPin, OUTPUT);
    
    digitalWrite(MotorA1Pin, LOW);
    digitalWrite(MotorA2Pin, LOW);
    digitalWrite(MotorB1Pin, LOW);
    digitalWrite(MotorB2Pin, LOW);
    
    digitalWrite(MotorApwmPin, LOW);
    digitalWrite(MotorBpwmPin, LOW);
    digitalWrite(StndbyPin, LOW);
}

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

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;
        Move();
        
    }
}

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

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

    while (BTserial.available() > 0 && newData == false) {
        rc = BTserial.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(Go, strtokIndx);       // copy it to messageFromPC
    
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    RightvalY = atoi(strtokIndx);    // convert this part to an integer
    
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    RightvalX = atoi(strtokIndx);    // convert this part to an integer
    
/*    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    LeftvalY = atoi(strtokIndx);     // convert this part to an integer
    
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    LeftvalX = atoi(strtokIndx);     // convert this part to an integer
*/
}

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

void showParsedData() {
  
//    Serial.print(Go);
//    Serial.print(LeftvalY);
//    Serial.print(LeftvalX);
//    Serial.print(RightvalY);
//    Serial.println(RightvalX);
}

void Move(){
  
//Reverse
  if(RightvalY > MidPoint + DeadBand){  
          Yspeed = (RightvalY - 514) /2;
      
    if(RightvalX > (MidPoint + DeadBand)){ //Adjusts Left Motor Speed for Turning
          Xspeed = (RightvalX - 514) /2;
          Xspeed = constrain(Xspeed, 0, Yspeed);
        analogWrite(MotorApwmPin, Yspeed);
        analogWrite(MotorBpwmPin, Yspeed - Xspeed);
        Reverse(); 

      }
    else if(RightvalX < (512 - DeadBand)){ //Adjusts Right Motor Speed for Turning
          Xspeed = (511 - RightvalX) / 2;
          Xspeed = constrain(Xspeed, 0, Yspeed);
        analogWrite(MotorApwmPin, Yspeed - Xspeed);
        analogWrite(MotorBpwmPin, Yspeed);
        Reverse();

    }
    else{
      
        analogWrite(MotorApwmPin, Yspeed);   //Straight Backwards
        analogWrite(MotorBpwmPin, Yspeed);
        Reverse();
        
    }
  }
//-----------------------------
//Forward
  else if(RightvalY <= (512 - DeadBand)){   
          Yspeed = (511 - RightvalY) / 2;
  
    if(RightvalX > (512 + DeadBand)){  //Adjusts Left Motor Speed for Turning
          Xspeed = (RightvalX - 514) / 2;
          Xspeed = constrain(Xspeed, 0, Yspeed);
        analogWrite(MotorApwmPin, Yspeed);
        analogWrite(MotorBpwmPin, Yspeed - Xspeed);
      Forward();

    }
    else if(RightvalX < (512 - DeadBand)){ //Adjusts Right Motor Speed for Turning
          Xspeed = (511 - RightvalX) / 2;
          Xspeed = constrain(Xspeed, 0, Yspeed);
        analogWrite(MotorApwmPin, Yspeed- Xspeed);
        analogWrite(MotorBpwmPin, Yspeed);
        Forward();
 
    }
    else{

        analogWrite(MotorApwmPin, Yspeed);  //Straight Forwards
        analogWrite(MotorBpwmPin, Yspeed);
        Forward();
    }
  }
//------------------------------
  else{ 
    
// Zero Point Turning  
    if(RightvalX > (512 + DeadBand)){ // zero point turn Left
         Xspeed = (RightvalX - 512) / 2;
      
        analogWrite(MotorApwmPin, Xspeed);
        analogWrite(MotorBpwmPin, Xspeed);
        ZeroTurnLeft();

    }
    else if(RightvalX < (512 - DeadBand)){// zero point turn Right
         Xspeed = (510 - RightvalX) / 2;
         
        analogWrite(MotorApwmPin, Xspeed);
        analogWrite(MotorBpwmPin, Xspeed);
        ZeroTurnRight();
    }
    else{
        FullStop();   
    } 
  }
 /*      Serial.print("Yspeed ");
         Serial.print(Yspeed);
         Serial.print("   Xspeed ");
         Serial.println(Xspeed);   */
}
void Forward(){
         digitalWrite(MotorA1Pin, HIGH);
         digitalWrite(MotorA2Pin, LOW);
         digitalWrite(MotorB1Pin, LOW);
         digitalWrite(MotorB2Pin, HIGH);
         digitalWrite(StndbyPin, HIGH);
  }
  
void Reverse(){
         digitalWrite(MotorA1Pin, LOW);
         digitalWrite(MotorA2Pin, HIGH);
         digitalWrite(MotorB1Pin, HIGH);
         digitalWrite(MotorB2Pin, LOW);
         digitalWrite(StndbyPin, HIGH);
  }
  
void ZeroTurnLeft(){
         digitalWrite(MotorA1Pin, HIGH);
         digitalWrite(MotorA2Pin, LOW);
         digitalWrite(MotorB1Pin, HIGH);
         digitalWrite(MotorB2Pin, LOW);
         digitalWrite(StndbyPin, HIGH);
  }
  
void ZeroTurnRight(){
         digitalWrite(MotorA1Pin, LOW);
         digitalWrite(MotorA2Pin, HIGH);
         digitalWrite(MotorB1Pin, LOW);
         digitalWrite(MotorB2Pin, HIGH);
         digitalWrite(StndbyPin, HIGH);
  }
  
void FullStop(){
         digitalWrite(MotorApwmPin,LOW);
         digitalWrite(MotorBpwmPin,LOW);
         digitalWrite(MotorA1Pin, LOW);
         digitalWrite(MotorA2Pin, LOW);
         digitalWrite(MotorB1Pin, LOW);
         digitalWrite(MotorB2Pin, LOW);
         digitalWrite(StndbyPin, LOW); 
}

You don't say what the problem is. "It's still a bit buggy " is the same as telling a doctor "I'm feeling poorly"

You seem to be sending data as fast as loop() can repeat so you are probably overloading both the sender and the receiver. I would only send data about 10 times per second. At 9600 baud each character requires about 1 millisecond. You are sending up to 22 characters.

...R

Sorry, I realise buggy is pretty non descriptive. It's kind of hard to explain. What you've mentioned about the loop() being the same speed might fix one anomaly. Would that be as simple as adding a delay(100); to the end of the master sketch?

The other issue is with the steering. If I move the joystick in a slow circle the robot becomes confused and out of sink with where the joystick is. Or when I'm driving full forward and go to do a slow turn (not zero point turn) if I roll the joystick to close to the X axis the motors start jittering and sometimes the robot will turn the opposite way. Same thing happens in reverse.
Does that make sense?

gyrojeremy:
Would that be as simple as adding a delay(100); to the end of the master sketch?

I presume you have tried that by now and know the answer.

The other issue is with the steering. If I move the joystick in a slow circle the robot becomes confused and out of sink with where the joystick is. Or when I'm driving full forward and go to do a slow turn (not zero point turn) if I roll the joystick to close to the X axis the motors start jittering and sometimes the robot will turn the opposite way. Same thing happens in reverse.
Does that make sense?

These sound like the sorts of problem you get when data is lost - probably because you are trying to send too much data.

Effective debugging requires problems to be sorted out one at a time. Get reliable communications working and maybe the other problems will go away.

...R

Yes the delay(100); fixed the communication lag when first starting the bot.

The steering issue appears to happen once the Xspeed reaches 255 or to be more correct when (Yspeed - Xspeed = 0). So when moving forward (or reverse) @ Yspeed and a gradual turn is applied (Xspeed 0 to 254) works great but once Xspeed hits 255 the Xspeed pwm goes on the fritz.

So I tried adjusting the Xspeed values, so that max would only be 250 but the same thing happens??

The zero point turns work fine.

Please post the latest version of your code.

...R