Using the Serial Monitor to update PID values via bluetooth HC-06

Dear Arduino's

I am currently building a 2 wheeled balancing robot as part of my thesis. I have everything working, all I am wanting to do is update my PID values via bluetooth. I am able to read data from my robot fine via bluetooth, but it seems I do not know how to write to my program to edit the 3 PID values. The program works great with a USB cable, but now I want to tune the robot via bluetooth.

The code below is used for "online" tuning. I have gotten the idea from Kas. Basically, if I type in "p+", Kp value changes by +1, etc.

At present, when I type "p+" it just returns the Kp value I had programmed initially when uploading the code. It is not updating the code as there is no change in robot behavior even though it is not showing the adjustment cause I thought maybe it was changing the values, I was just not sending them back to the serial monitor correctly.

What should happen is as follows. The code is uploaded with pre-assigned values to Kp = 100, Ki = 0, Kd =0, K = 1; The program runs, I type in "p+", and the output is Kp = 100, Ki = 0, Kd =0, K = 1. not Kp = 101, Ki = 0, Kd =0, K = 1
.

int getTuning() 
{
  if(!mySerial.available())      return 0;
  delay(10);                  
  char param = mySerial.read();                            // get parameter byte
  if(!mySerial.available())    return 0;
  char cmd = mySerial.read();                              // get command byte
  mySerial.flush();
  switch (param) 
  {
    case 'p':
      if(cmd=='+')  Kp+=1;
      break;
      if(cmd=='-')    Kp-=1;
      break;
    case 'i':
      if(cmd=='+')    Ki+=0.5;
      if(cmd=='-')    Ki-=0.5;
      break;
    case 'd':
      if(cmd=='+')    Kd+=2;
      if(cmd=='-')    Kd-=1;
      break;
    case 'k':
      if(cmd=='+')     K +=1;
      if(cmd=='-')    K -=0.5;
      break;
    default: 
      mySerial.print("?");          mySerial.print(param);
      mySerial.print("?");          mySerial.println(cmd);
    }
    
  mySerial.println();
  mySerial.print("K:");           mySerial.print(K);
  mySerial.print("   Kp:");       mySerial.print(Kp);
  mySerial.print("   Ki:");      mySerial.print(Ki);
  mySerial.print("   Kd:");       mySerial.println(Kd);
  
  }

This is why I think I am missing a line of code to update my parameters, or to write from the serial monitor to my code.

Using an Arduino Uno and Arduino HC-06 bluetooth module.

Thank you for your time, and thanks in advance for any assistance.

Nick from Cape Town :slight_smile:

Full code needed to find out whats going on with your program.

  if(!mySerial.available())      return 0;

The available() method does not return true or false. Don't treat it like it does.

  delay(10);                  
  char param = mySerial.read();                            // get parameter byte

Once you know that there is something in the buffer to read, you look like an idiot standing around with your thumb up your butt. Get rid of the stupid delay().

  mySerial.flush();

Why? There is no reason to use this function in either of its forms. Blocking until all pending output has been sent is useless. Throwing away random amounts of unread data is equally useless. The code only does one of these. But which one? And why?

Plus what omersiar said.

Thanks for getting back to me. I was under the impression that maybe it was a code line that I am not aware of. Here is my code, it is very extensive as this is my first time using an arduino so it is not optimised at all. The robot works well, I am just not able to update my PID values via the serial monitor. I have included the main, the PID file and the Tuning file as I cant upload it all due to character restriction.

NL SHEAD Balancing Robot Main file

#include <SoftwareSerial.h>             // Serial to bluetooth
#include <math.h>          
#include <TinkerKit.h>

TKGyro gyro(I2, I3, TK_X4);             // creating the object 'gyro' that belongs to the 'gyro' class 
                                        // and giving the values to the desired input pins
TKAccelerometer accelerometer(I4, I5);  // creating the object 'accelerometer' that belongs to the 'TKAccelerometer' class 
                                        // and giving the values to the desired input pins
SoftwareSerial mySerial(4, 7);          // RX, TX                                        

/********  Motor Variables  *********/

#define PWMA 3                                        
#define PWMB 11
#define DIR_A    12
#define DIR_B    13


#define   GUARD_GAIN   20.0
unsigned long lastMilli = 0;                    // loop timing 
unsigned long lastMilliPrint = 0;               // loop timing
long count = 0;                                 // rotation counter
long countInit;
long tickNumber = 0;
boolean run = false;                                     // motor moves

/********  Timing Variables  *********/
unsigned long time;
int sampleTime = 10;
int   STD_LOOP_TIME  =          9;             
int lastLoopTime = STD_LOOP_TIME;
int lastLoopUsefulTime = STD_LOOP_TIME;
unsigned long loopStartTime = 0;

/********  Accelerometer Variables  *********/
int xAxisValue = 0;           // a variable to store theaccelerometer's x value
int yAxisValue = 0;           // a variable to store theaccelerometer's y value
float Vref = 3.33;                //Reference VOltage
float V_x;
float V_y;
float V_xzero = 0.99;
float V_yzero = 1.65;
float deltaVolts_x;
float deltaVolts_y;
float sensitivity_ACC = 0.3;
float X_accel;
float Y_accel;
float R;
float X_accel_angle;
float Y_accel_angle;
float X_accel_degrees;
float Y_accel_degrees;
float Theta_accelerometerX;
float Theta_accelerometerY;

float AccAngle;

/********  Gyroscope Variables  *********/
float rollADC, pitchADC;
float rollZeroADC = 0, pitchZeroADC = 0;
float rollRate, pitchRate;
float prev_rollRate = 0, prev_pitchRate = 0;
float rollAngle = 0, pitchAngle = 0;

float GyroPitchRate;

/********  Accuracy Variables  *********/
float correctionX = 0, correctionY = 0;
float Threshold = 5;
float cal = 100;
float sensitivity = 0.2077;

/********  Filter Variables  *********/
float Theta = 0;
int error = 0;
/********  PID and Drive Variables  *********/
float setPoint = 0;
int drive = 0;
//int iTerm;

float Theta_gyro;
void setup() 
{
  Serial.begin(115200);      //Initialise serial bps
  pinMode(DIR_A, OUTPUT);    // Direction pin on channel A
  pinMode(DIR_B, OUTPUT);    // Direction pin on channel B
  delay(100);
  GyroCalibrate(); 
  
  mySerial.begin(115200);    //initialise bluetooth
  mySerial.println("Online");  //show bluetooth online
}

void loop()
{
 
  
/*****************    SENSORS    ***********************************/

GyroPitchRate = GetGyroRate();
AccAngle =  GetAccAngle();   
 
/*****************    Filter    ***********************************/

 Theta =  CompFilter(AccAngle,GyroPitchRate,lastLoopTime);  

/**********     Controller and Drive    ***************************/
 
 drive = updatePid(setPoint, Theta);      // PID algorithm
  
  if(Theta>(setPoint-30) && Theta<(setPoint+30))  
     Drive_Motor(drive); 
  else                   
     Drive_Motor(0);                      // stop motors if situation is not recoverable

/*****************    PID Tuning    ***********************************/

  getTuning();
  
/********************** LOOP TIMING CONTROL **************************/

  lastLoopUsefulTime = millis()-loopStartTime;
  if(lastLoopUsefulTime<STD_LOOP_TIME)        
  delay(STD_LOOP_TIME-lastLoopUsefulTime);
  lastLoopTime = millis() - loopStartTime;
  loopStartTime = millis();

/*****************    Serial Output    ***********************************/ 
// serialOut_timing();
//Serial.print(time);
//Serial.print(",");
//Serial.print(Theta);
//Serial.print(",");
//Serial.print(Y_accel_angle);
//Serial.print(",");
//Serial.print(Theta_gyro);
//Serial.print(",");
//Serial.print(Theta_comp);
//Serial.print("\n");

  
}

NL SHEAD PID CONTROLLER file

#define   GUARD_GAIN   20.0      

float K = 1;                              //Gain factor
float Kp = 190;                             //Proportional Gain 
float Ki = 0;                             //Integral Term
float Kd = 6;                             //Derivative Gain 
int last_error = 0;                        
int integrated_error = 0;
int pTerm = 0, iTerm = 0, dTerm = 0;

int ierror=0;

int updatePid(int SetPoint, int Theta)   
{
  error = SetPoint - Theta; 
  pTerm = Kp * error;
  integrated_error += error;                                       
  iTerm = Ki * constrain(integrated_error, -GUARD_GAIN, GUARD_GAIN);
  dTerm = Kd * (error - last_error);                            
  last_error = error;
  ierror = constrain(integrated_error, -GUARD_GAIN, GUARD_GAIN);
//  Serial.print(ierror);
//  Serial.print("\n");
  return -constrain(K*(pTerm + iTerm + dTerm), -255, 255);
  
 }

NL SHEAD TUNING file

int getTuning() 
{
  if(!mySerial.available())      return 0;
  delay(10);                  
  char param = mySerial.read();                            // get parameter byte
  if(!mySerial.available())    return 0;
  char cmd = mySerial.read();                              // get command byte
  mySerial.flush();
  switch (param) 
  {
    case 'p':
      if(cmd=='+')  Kp+=1;
      break;
      if(cmd=='-')    Kp-=1;
      break;
    case 'i':
      if(cmd=='+')    Ki+=0.5;
      if(cmd=='-')    Ki-=0.5;
      break;
    case 'd':
      if(cmd=='+')    Kd+=2;
      if(cmd=='-')    Kd-=1;
      break;
    case 'k':
      if(cmd=='+')     K +=1;
      if(cmd=='-')    K -=0.5;
      break;
    default: 
      mySerial.print("?");          mySerial.print(param);
      mySerial.print("?");          mySerial.println(cmd);
    }
    


  mySerial.println();
  mySerial.print("K:");           mySerial.print(K);
  mySerial.print("   Kp:");       mySerial.print(Kp);
  mySerial.print("   Ki:");      mySerial.print(Ki);
  mySerial.print("   Kd:");       mySerial.println(Kd);
  
  
}

Apologies again for so much code, but maybe it will help solve this issue.

Thanks,
Nick

int getTuning()

Not every path returns a value. Fix that, and the other stuff that's been pointed out, and add print() statements to show what the function actually receives, and then we'll talk. If needed.

PaulS, sorry, it is probably a ridiculous question, but do you mind elaborating about "not every path returns a value?" And how do I go about fixing it?

I have removed the mySerial.flush(), but not sure about removing the if(!mySerial.available()) return 0; as if I do remove it, the output is still unchanged, only it is constantly displaying the output without waiting for my serial input.

do you mind elaborating about "not every path returns a value?" And how do I go about fixing it?

This is your function, with some code snipped and replaced with comments:

int getTuning() 
{
  if(!mySerial.available())      return 0;
  delay(10);                  
  char param = mySerial.read();                            // get parameter byte
  if(!mySerial.available())    return 0;
  char cmd = mySerial.read();                              // get command byte
  mySerial.flush();

 // Deal with param and cmd
 
  mySerial.println();
  mySerial.print("K:");           mySerial.print(K);
  mySerial.print("   Kp:");       mySerial.print(Kp);
  mySerial.print("   Ki:");      mySerial.print(Ki);
  mySerial.print("   Kd:");       mySerial.println(Kd);
}

If there is no serial data to read, you quit, and return 0.
If there is, you wait 10 milliseconds (uselessly), then read it. Then, if there is no more serial data, you quit and return 0.
(Think about what will happen next time, when the cmd value is the first thing in the buffer.)
If there is something to read, though, you read it, diddle with, and print, some values, and then quit, without returning anything.

That is a really bad idea.

You need to rewrite this function. It does not need to return a value, so it's return type should be void. You should NOT call this function unless there are 2 bytes to read, so this function should NOT check that there is data available. It should simply read two bytes, and deal with them.

It should NOT flush anything, and it should NOT delay().

Thanks for your help. I have managed to get it to work. In main, just before i getTuning, I wait for data to be available. then run void getTuning(). i then just assigned a character to each action I want and it now works as i want. I will maybe try to get it to convert the received chars into a string, but for now its working fine.

Thanks again PaulS

if(mySerial.available() > 0) 
  getTuning();
void getTuning() 
{

   char param = mySerial.read();                            // get parameter byte
   switch (param) 
 {
   case 'p':
    Kp+=1;
      break;
      
    case 'i':
      Ki+=0.5;
      break;
      
    case 'd':
    Kd+=2;
      break;
      
    case 'k':
      K +=1;
      break;
      
     case 'o':
    Kp-=1;
      break;
      
    case 'u':
      Ki-=0.5;
      break;
      
    case 's':
    Kd-=2;
      break;
      
    case 'j':
      K -=1;  
      break;
      
    default: 
      mySerial.print("?");          mySerial.print(param);
   }

  mySerial.println();
  mySerial.print("K:");           mySerial.print(K);
  mySerial.print("   Kp:");       mySerial.print(Kp);
  mySerial.print("   Ki:");      mySerial.print(Ki);
  mySerial.print("   Kd:");       mySerial.println(Kd);
  
  
}

You could test for available() to return a value greater than or equal to 2, and then read p+, p-, k+, k-, etc. like you were trying to.

Just one quick question Paul, what is happening now is that if I run the code via bluetooth with the usb cable still plugged into the uno, it works fine. If I remove the USB cable it still works, but it restarts the program when I move the robot.. Any ideas?

updated main

/****************************************************************
/*    
/*                NL SHEAD Balancing Robot Main file
/*    
/****************************************************************/    


void setup() 
{
  Serial.begin(115200);      //Initialise serial bps
  pinMode(DIR_A, OUTPUT);    // Direction pin on channel A
  pinMode(DIR_B, OUTPUT);    // Direction pin on channel B
  delay(100);
  GyroCalibrate(); 
  
  mySerial.begin(115200);
  mySerial.println("Hello, world?");
}

void loop()
{
 
  
/*****************    SENSORS    ***********************************/

GyroPitchRate = GetGyroRate();
AccAngle =  GetAccAngle();   
 
/*****************    Filter    ***********************************/

 Theta =  CompFilter(AccAngle,GyroPitchRate,lastLoopTime);  

/**********     Controller and Drive    ***************************/
 
 drive = updatePid(setPoint, Theta);      // PID algorithm
  
  if(Theta>(setPoint-45) && Theta<(setPoint+45))  
     Drive_Motor(drive); 
  else                   
     Drive_Motor(0);                      // stop motors if situation is not recoverable

/*****************    PID Tuning    ***********************************/
if(mySerial.available())
  getTuning();
  
/********************** LOOP TIMING CONTROL **************************/

  lastLoopUsefulTime = millis()-loopStartTime;
  if(lastLoopUsefulTime<STD_LOOP_TIME)        
  delay(STD_LOOP_TIME-lastLoopUsefulTime);
  lastLoopTime = millis() - loopStartTime;
  loopStartTime = millis();

 
}

getTuning

void getTuning() 
{

   char param = mySerial.read();                            // get parameter byte
   switch (param) 
 {
   case 'p':
    Kp+=1;
      break;
      
    case 'i':
      Ki+=0.5;
      break;
      
    case 'd':
    Kd+=2;
      break;
      
    case 'k':
      K +=1;
      break;
      
     case 'o':
    Kp-=1;
      break;
      
    case 'u':
      Ki-=0.5;
      break;
      
    case 's':
    Kd-=2;
      break;
      
    case 'j':
      K -=1;  
      break;
      
    default: 
      mySerial.print("?");          mySerial.print(param);
   }
//    
//
//
  mySerial.println();
  mySerial.print("K:");           mySerial.print(K);
  mySerial.print("   Kp:");       mySerial.print(Kp);
  mySerial.print("   Ki:");      mySerial.print(Ki);
  mySerial.print("   Kd:");       mySerial.println(Kd);
}

Its almost like I havent told it somewhere to carry on running. Just before it executes getTuning, should the test for serial data not occur right in the beginning of the main loop?

Thanks

The first question that I have is why do you use the values of Kp, Ki, and Kd, and then get new values?

The logical thing to do is to (possibly) get new values, and then use the values. Move the if(mySerial.available() > 0) statement and the subsequent call to getTuning() to the start of loop().

The second question is why you think you need loop() to always take exactly as long to execute? Using millis() and delay() in the same code is almost always a mistake.

If you want to wait a while (delay()), it's because you know it isn't time to do whatever needs doing. Turn that around, and do the thing that needs doing only when it is time to do it. No need for any delay()s that way.

It seems the resetting is due to a power issue. If I separate the power from the motors and uno, no resetting.. It probably was using power from the USB and external supply with the USB plugged in, hence no reset.. but as I removed the USB it begins to reset. Gosh, always something.

Wrt the k values, I initially found ballpark figures for these parameters, but wanted to fine tune them with no USB cable which tends to add an extra disturbance force.

Thanks for the advice regarding delays.. I will definitely optimise my code at a later stage.

Do you think the Uno resets due to the extra current absorption from the HC-06 bluetooth module?