serial motor direction problem

I have built a self balancing unicycle with a SyRen motor driver that receives packetized serial commands from an Arduino Uno. When the unicycle leans forward, the power is applied forward to balance. When it leans backwards, power is applied in reverse to balance it. It does work, but the problem is that, randomly(while running), it will become reversed (applying reverse torque when leaned forward, and forward torque when leaned backwards). Because the lean angle just increases when torque is applied the wrong way, it applies a great deal of power, and it goes nuts. It actually dealt quite a blow to my shin when it first happened. If I reset the Arduino it will return to normal. I am pretty new to programming, so it is most likely a code issue. The machine is complete, and this is the only thing that is keeping me from riding it, so any advice is greatly appreciated. Thanks.

Here is the part of my code that pertains to the packetized serial commands. I can't see anything wrong with this, but I'm a newbie, and this is where the problem most likely lies:

void loop()
{
  
  Int_Output = updatePid(BalancePoint, Pitch); //runs PID controller and returns output 
    
    SyRen_Output = abs(Int_Output); // number output will always be 0 to 127 (motor conrtoller only accepts positive integers in this range)
    if (angles[1]*2 > BalancePoint) {  //if pitch angle(x2 to make deadzone smaller) is positive (unicyle is leaned back), motor direction will be reverse
    Serial.write(128); //serial address of SyRen motor driver
  Serial.write((byte)0); //this determines direction of output
  Serial.write(SyRen_Output); //power output
  Serial.write((128+(byte)0+SyRen_Output) & 0b01111111);  //checksum to confirm correct commands (SyRen will not act on command packet if these values are different)
    }
    if (angles[1]*2 < BalancePoint) {     //same as other packet except direction is forward
     Serial.write(128);
  Serial.write(1);
  Serial.write(SyRen_Output);
  Serial.write((128+1+SyRen_Output) & 0b01111111);
    }

Here is the entire code is you want to see it:

//code for homemade self balancing unicycle:
//sparkfun 6dof IMU
//PID output control
//Dimension Engineering SyRen 25 motor driver
//Arduino IDE 1.0

#include <FreeSixIMU.h> //fusion filtering library

#include <FIMU_ADXL345.h> //accelerometer library

#include <FIMU_ITG3200.h> //gyro library

#include <Wire.h>

#define GUARD_GAIN 20.0

//timing
int STD_LOOP_TIME = 9;             
int lastLoopTime = STD_LOOP_TIME;
int lastLoopUsefulTime = STD_LOOP_TIME;
unsigned long loopStartTime = 0;


//for IMU:
float angles[3]; //yaw pitch roll (only pitch is necessary for one-axis balance in this application)
FreeSixIMU sixDOF = FreeSixIMU(); //set the FreeSixIMU object

//for PID controller: 

float K = 0.1;
int   Kp = 95;                      
int   Ki = 2;                   
int   Kd = 1;  
int last_error = 0;
int integrated_error = 0;
int pTerm = 0, iTerm = 0, dTerm = 0;
int error;
int BalancePoint = 0; //the angle at which the PID controller will attempt to balance the unicycle
int Pitch;

//for SyRen
int SyRen_Output; //final motor output value 
int Int_Output; //used to convert pid output to int


void setup()
{
  Serial.begin(9600); //set baud rate
  Wire.begin();
  
  delay(5);
  sixDOF.init(); //begin the IMU
  delay(5);
  
  
  
  
  Serial.write(170); //initializes SyRen controller and lets it determine baud rate
}

int updatePid(int BalancePoint, int Pitch)   {      //PID control: gets angles and calculates output from -127 to 127
  sixDOF.getEuler(angles);
  Pitch = angles[1] * 2;
  error = BalancePoint - Pitch; 
  pTerm = Kp * error;
  integrated_error += error;                                       
  iTerm = Ki * constrain(integrated_error, -GUARD_GAIN, GUARD_GAIN);
  dTerm = Kd * (error - last_error);                            
  last_error = error;
  return -constrain(K*(pTerm + iTerm + dTerm), -127, 127);
}

void loop()
{
  
  Int_Output = updatePid(BalancePoint, Pitch); //runs PID controller and returns output 
    
    SyRen_Output = abs(Int_Output); // number output will always be 0 to 127 (motor conrtoller only accepts positive integers in this range)
    if (angles[1]*2 > BalancePoint) {  //if pitch angle(x2 to make deadzone smaller) is positive (unicyle is leaned back), motor direction will be reverse
    Serial.write(128); //serial address of SyRen motor driver
  Serial.write((byte)0); //this determines direction of output
  Serial.write(SyRen_Output); //power output
  Serial.write((128+(byte)0+SyRen_Output) & 0b01111111);  //checksum to confirm correct commands (SyRen will not act on command packet if these values are different)
    }
    if (angles[1]*2 < BalancePoint) {     //same as other packet except direction is forward
     Serial.write(128);
  Serial.write(1);
  Serial.write(SyRen_Output);
  Serial.write((128+1+SyRen_Output) & 0b01111111);
    } 
    
  
    
    
  
 
  
  
  //Serial.print(SyRen_Output); //for debugging


//loop timing control
  lastLoopUsefulTime = millis()-loopStartTime;
  if(lastLoopUsefulTime<STD_LOOP_TIME)         delay(STD_LOOP_TIME-lastLoopUsefulTime);
  lastLoopTime = millis() - loopStartTime;
  loopStartTime = millis();
  

}

Many of the variables that participate in the calculations in updatePid() are declared as integer. I wonder if that's leading to integer arithmetic artifacts that you aren't anticipating, like overflow and underflow (particularly with integrated_error).

Does it work better if you make the whole calculation chain floating point by declaring the intermediate variables as float instead of int?

-br

Edit: the judicious use of Serial.print() to log values may also help if you can manage that in your prototype.

Its not a bad idea to explicitly limit the integral term to fixed bounds - otherwise it increases both the
risk of sign-reversal due to overflow and the recovery time from sharp transients.

EDIT: The problem still persists! It isn't as bad though now that all my variables are float. Now, if I return the unicycle to balanced position, it will resume normal operation until the problem happens again. The frequency of the issue seems to be the same though. I really have no idea what to do, does anyone have any other suggestions?

billroy:
Many of the variables that participate in the calculations in updatePid() are declared as integer. I wonder if that's leading to integer arithmetic artifacts that you aren't anticipating, like overflow and underflow (particularly with integrated_error).

Does it work better if you make the whole calculation chain floating point by declaring the intermediate variables as float instead of int?

-br

Edit: the judicious use of Serial.print() to log values may also help if you can manage that in your prototype.

I changed all of my PID variables to float, the unicycle seems to be working fine, but only time will tell if the problem is gone. I wasn't even aware that such problems could occur with integer. Thank you.

MarkT:
Its not a bad idea to explicitly limit the integral term to fixed bounds - otherwise it increases both the
risk of sign-reversal due to overflow and the recovery time from sharp transients.

The integral term is already limited to 20, but I didn't know that could be an issue, I copied the PID from a self balancing robot code by kas from page 3 of this thread: Arduino Forum and I wondered why he limited integral, I left it because I was unsure, but now I know why. Thank you.

iTerm = Ki * constrain(integrated_error, -GUARD_GAIN, GUARD_GAIN);

you can see here how iTerm is constrained by GUARD_GAIN which I set to 20 just like the code it was copied from

It turned out that my problem had to do with the function that calls the angles from the IMU library. I replaced "getEulerAngles" with "getYawPitchRoll". When the IMU detected that it was spun around (yaw), the library is programmed to invert the pitch angle when using getEulerAngles. I don't know why, but it is an intention part of the library. The yaw drift in the IMU (due to lack of magnetometer) caused it to invert the pitch angle at a random time. Using getYawPitchRoll eliminates the inversion of pitch.

I know this is getting a little older now, but wanted to say thanks for posting code and providing a resolution.
I will be taking on one of these soon. Do you ever ride it?

tgfeminella:
It turned out that my problem had to do with the function that calls the angles from the IMU library. I replaced "getEulerAngles" with "getYawPitchRoll". When the IMU detected that it was spun around (yaw), the library is programmed to invert the pitch angle when using getEulerAngles. I don't know why, but it is an intention part of the library. The yaw drift in the IMU (due to lack of magnetometer) caused it to invert the pitch angle at a random time. Using getYawPitchRoll eliminates the inversion of pitch.

Hi tgferminella,

I'm newbie, and keen to experiment in building my own self balancing unicycle after trying the Solowheel but find it too expensive to own one.
Can I sincerely ask if you could advice and share with me your experiences, the code you have, the wiring schematics, and parts list to start with?

From what I gather to build it, these are the parts.

  1. Arduino Board.
  2. IMU.
  3. Motor controller
  4. Arduino shield or relay board
  5. Battery
  6. Wheel frame
  7. PID controller
  8. Battery
  9. Voltage regulators, 9-12v to Arduino board and IMU
  10. Single electric bicycle motor 16" wheel, and a self made airless tire (putting big O-rings into tire)
  11. Bunch of colored wires, connectors, etc.
  12. C++ programming intelligence, Arduino Sketch know how ...
    Lastly, Patience, Patience, Patience, ... foremost guts to try it ...

Thanks and regards.