self-balancing unicycle problem

Edit: problem solved! I’ve begun riding the unicycle although its very difficult with the cheap child’s bicycle seat I’m using. The PID controller also needs some more tuning. To make it all worse, I have no unicycle experience.

I’ve built a self-balancing unicycle using a sparkfun 6dof IMU,a 250w motor,a SyRen 25 motor controller from Dimension Engineering, with an Arduino Uno. For balance control, I have a PID control system. My problem is that after a little while the directions of the output reverses for no apparent reason. I’m new to Arduino and coding so I can’t figure out what is causing the problem.

Here is my code:

//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 <PID_v1.h> //PID control library

#include <Wire.h>

//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: 

double BalancePoint; //angle that unicycle attempts to balance at
double PID_Output;
double Pitch;
PID myPID(&Pitch, &PID_Output, &BalancePoint,1.5,0.5,0.2, DIRECT); // numbers are (respectively): Kp,Ki,Kd

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

void setup()
{
  Serial.begin(2400); //the SyRen is completely non-responsive if a higher baudrate is chosen
  Wire.begin();
  
  delay(5);
  sixDOF.init(); //begin the IMU
  delay(5);
  
  //initialize PID variables
  Pitch = angles[1];
  BalancePoint = 2; //this should be set to what pitch angle reads when unicyle is balanced
  myPID.SetMode(AUTOMATIC);
  myPID.SetOutputLimits(-127,127);
  Serial.write(170);
}

void loop()
{
  
  //reads angles from IMU then plugs pitch into the PID controller
  sixDOF.getEuler(angles);
  Pitch = (double) angles[1];
  myPID.Compute();
  Int_Output = (int) PID_Output; //converts to int for SyRen controller
  
  
  //when output is negative, this code reverses the direction of the motor
  if (Int_Output < 0) {
    SyRen_Output = abs(Int_Output);
    Direction = 0;
  }
  else
  {
    SyRen_Output = Int_Output;
    Direction = 1;
  }
  //packetized serial control:
  Serial.write(128);
  Serial.write(Direction);
  Serial.write(SyRen_Output);
  Serial.write((128+Direction+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();
}

The serial mode used by the motor driver needs the address(128-135, direction(0 or 1), and a speed (0-127) to work. The PID is configured to give an output of -127 to 127. When it drops below zero, I set it to invert so that it runs 0-127and I set direction to reverse. I am guessing that this is the part where my problem lies, am I right? what can I do to fix this?

Thanks in advance.

How are you stopping the unicycle falling over sideways ?

Try building a device with two wheels, first.

I should have specified that this a is a ride on machine that relies on the user for left/right balance

I managed to fix the reversing issue by changing this changing this section of the code from this:

if (Int_Output < 0) {
    SyRen_Output = abs(Int_Output);
    Direction = 0;
  }
  else
  {
    SyRen_Output = Int_Output;
    Direction = 1;

to this:

SyRen_Output = abs(Int_Output);
    if (angles[1] > BalancePoint && Direction == 1) {
      Direction = 0;
    }
    if (angles[1] < BalancePoint && Direction == 0) {
     Direction = 1;
    }

I have no idea why that worked, but for now, that seems to have done the trick.

scratch that, the motor reversing problem is back, and it seems to be at least partially linked to enabling my 9v external power supply as opposed to the USB power from the computer

I have confirmed that my Arduino is acting up because of power issues. If I use a 9v battery, it is most profound, but will only occur on USB power if the connection is disturbed. I just need a more powerful external supply. My unicycle is now balancing great! With a little more tuning, and some finishing touches, I should be ready to take a spin!

You can power an actual ride-on device with a 9V battery ? How do you accomplish that ?

michinyon:
You can power an actual ride-on device with a 9V battery ? How do you accomplish that ?

The motor driver is powered by two 10amp hour 12v scooter batteries in series, I just used the 9v to power the Arduino. I switched to a 7.2v 1200mah battery though. I have tuned the unicycle somewhat and it will now balance and accelerate with me riding it. I believe I just solved my reversing issue: For packetized serial commands, you must transmit a checksum after each packet to confirm that the data is correct. To serial write 0, you must specify that it is a byte. In the packet, I did remember to do this, but I forgot in the checksum. The code would work, but any momentary power interruptions (not perceivable) would somehow cause the numbers to switch. Before if you wiggled the USB cable or unplugged it for a moment, the command would reverse and the unicycle would fall and quickly go to max power (injuring me twice in the process). I added that checksum and so far momentarily unplugging the power will no longer upset the machine. Its too soon to say for sure, but I believe my problem might be over.

I’m so angry! The problem has returned with a vengeance! I am completely out of ideas. My unicycle is done now and I could be riding if it weren’t for this! I feel that this is most likely a code issue. If anyone can help me, please! I am desperate, I have tried everything I can think of. It could be a simple issue that an adept coder could easily spot, I have no clue. Here is the latest form of my code:

//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;
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);
}

int updatePid(int BalancePoint, int Pitch)   {
  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);
  
    SyRen_Output = abs(Int_Output);
    if (angles[1]*2 > BalancePoint) {
    Serial.write(128);
  Serial.write((byte)0);
  Serial.write(SyRen_Output);
  Serial.write((128+(byte)0+SyRen_Output) & 0b01111111);  
    }
    if (angles[1]*2 < BalancePoint) {
     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();
  

}

If anyone cares, I finally discovered the real problem: The function getEulerAngles in the IMU library for my digital IMU would invert the pitch measurement if the IMU had spun around. I don't know why, but that was an intentional part of the library. Because this IMU lacks a magnetometer, it cannot accurately measure yaw, only change in yaw. As the yaw drifted around, it would finally pass 90 degrees, which told the library to invert the pitch. I discovered that using getYawPitchRoll instead will eliminate the pitch inversion which completely solved my problem. In an earlier attempt to fix it, I completely revamped my PID controller which turned out to be worth it anyway as it greatly improved balancing performance. I will have a bit more tuning to do, and my seat mount suffered damage in an accident due to the reversing problem, but I will soon be rolling one-wheeled!

It would be really interesting to see a picture on your unicycle :)

LarryXz: It would be really interesting to see a picture on your unicycle :)

Here it is:

I have just started riding it, and it is very difficult. The cheap child's bicycle seat is far too narrow which makes left and right balancing very hard. I should have bought a bigger one, but I didn't feel like paying $20 as opposed to $3. Also, my PID controller stills needs some tuning. To make all of this much worse, I have never ridden a Unicycle before.

Here is a YouTube clip of some of my first attempts:

http://www.youtube.com/watch?v=mEee1iI6emE

so did you end up fixing the problem completely?

I am really amazed that you built your own self balancing unicycle from scratch. I hope everything ended up perfectly for you. I was planning to something like you but at this point my knowledge is the biggest problem. Anyway I’m inspired by your work and a little bit more motivated. So hopefully I’m gonna move forward very soon.

Edit: I’ve made some arrangements and I’m finally starting this January ;).

It's a good thing that you were able to figure out your problem with the self-balancing electric unicycle. Congrats! I'm also having a problem because I couldn't make it work. I'm trying to make it look like this one here but I always mess up with the codes! I don't have unicycle experience as well so it's kinda hard to do this stuff. Anyway, I'm hoping for the best and hopefully, I'll be able to make this work.

tgfeminella: If anyone cares, I finally discovered the real problem: The function getEulerAngles in the IMU library for my digital IMU would invert the pitch measurement if the IMU had spun around.

Nice work. This certainly highlights the benefits of testing the IMU with your hand, and finding out whether it behaves properly using extensive manual testing - before the IMU is even used for anything, like a bike etc. That is..... moving the IMU around, and observe the measured data in the serial monitor.....to ensure the readings are always behaving.

Pretty amazing how you got a plane parked inside your garage as well.

http://www.youtube.com/watch?v=mEee1iI6emE

Also..... when it comes to the control system --- try to minimise all sources of delay in the code. Eg.... if using serial.begin .... turn it off during real-performance testing..... because sending serial data can slow down the control system.

Also...... remove serial writes for your 'real performance' testing.

It is possible that your 2400 bit per second serial rate is letting you down. If you are using that rate during actual testing (ie... you riding on the unicycle)....then see what happens if you turn off serial communications. You might be pleasantly surprised to find that things start behaving much better.

micros() could also provide improvement ..... instead of millis().