Self balancing bot using Arduino Nano
Self balancing bot using Arduino Nano
INTRODUCTION
A two wheeled bot is unstable when the bot is standing upright. But this bot can
be made stable by using proper control engineering. Since, it is an unstable system,
a PID controller-based approach is used for stabilization which can even handle small
disturbances. For this project Arduino Nano is used as a micro-controller. Components
used and their quantities are shown in Table 1.
Components Quantities
Ardunio Nano 1
MPU6050 1
BASIC PHYSICS
If the bot is left untouched, it falls on either side. But to prevent it from falling,
the bot must accelerate in the falling direction with the controlled acceleration
(computed by the controller). When the bot accelerates, the inertial force acting on the
CG are in the opposite of the falling direction and this inertia force prevents it from
falling. The full situation is depicted in the diagram below.
Figure 1: Basic Physics of Self Balancing Bot
CONTROLLER DESIGN
Design of the controller is based on the tilt angle. When the controller senses the tilt
angle deviates from equilibrium position (𝜃 = 900 ), then it commands the motor with
controlled speed in the direction of tilt (falling). Therefore, the input to controller is the
tilt angle and to design the controller, first it is required to compute the tilt angle of the
bot from its equilibrium position.
𝐶𝜓 𝐶𝜃 𝐶𝜓 𝑆𝜃 𝑆𝜙 − 𝑆𝜓 𝐶𝜙 𝐶𝜓 𝑆𝜃 𝐶𝜙 + 𝑆𝜓 𝑆𝜙
𝑹 = 𝑹𝒙 𝑹𝒚 𝑹𝒛 = [𝑆𝜓 𝐶𝜃 𝑆𝜓 𝑆𝜃 𝑆𝜙 + 𝐶𝜓 𝐶𝜙 𝑆𝜓 𝑆𝜃 𝐶𝜙 − 𝐶𝜓 𝑆𝜙 ]
−𝑆𝜃 𝐶𝜃 𝑆𝜙 𝐶𝜃 𝐶𝜙
0
𝒈 = {0}
𝑔
This force vector (𝒈), is transformed to local frame from earth fixed frame by
multiplying this vector to 𝑹𝑻 . Transformed force vector (𝒇) –
𝑎𝑥 −𝑆𝜃
𝒇 = {𝑎𝑦 } = 𝑹 𝒈 = {𝐶𝜃 𝑆𝜙 } 𝑔
𝑻
𝑎𝑧 𝐶𝜃 𝐶𝜙
Where, 𝐶() and 𝑆() are cos() and sin(), respectively and 𝑎𝑥 , 𝑎𝑦 and 𝑎𝑧 are the
acceleration values obtained from the sensor. Form the above equation the roll and
pitch angle can be computed as –
𝑎𝑦
tan 𝜙 =
𝑎𝑧
𝑎𝑥
tan 𝜃 = −
√𝑎𝑦2 + 𝑎𝑧2
Since, MPU6050 sensor also measures angular rotation rates (𝜔𝑥 , 𝜔𝑦 and 𝜔𝑧 ).
Roll, pitch and yaw angles can also be computed from angular rates by integration.
𝜃 = ∫ 𝜔𝑦 𝑑𝑡
𝜃𝑘 = 𝜃𝑘−1 + 𝜔𝑦 𝑘 Δ𝑡
Both the methods described above, have their own disadvantages. Angles
computed from accelerometer are very sensitive to vibration and noise whereas
angles obtained from gyro are drifting because during integration (summation), the
error also gets added (shown in figure below).
Angle
Time
Figure 2: Angle Vs Time from accelerometer and gyro
To minimize the disadvantages of the both the methods two different
approaches are used which are given as below –
1. Complementary Filter
2. Kalman Filter
𝐺𝑦𝑟𝑜
𝜙𝑘 = 𝑤1 𝜙𝑘 + 𝑤2 𝜙𝑘𝐴𝑐𝑐𝑒𝑙𝑒𝑟𝑜𝑚𝑒𝑡𝑒𝑟
Where, 𝑤1 + 𝑤2 = 1
One example is shown in figure below, in which 𝑤1 = 0.995 and 𝑤2 =
0.005.
Roll angle
𝑥̂ = 𝑤1 𝑥𝑚 + 𝑤2 𝑥𝑒
𝑤1 + 𝑤2 = 1
𝑥̂ = 𝑤1 𝑥𝑚 + (1 − 𝑤1 )𝑥𝑒
If the variance in measurement is ′𝑟′ and variance in estimation is ′𝑝′, then variance
in 𝑥̂ is given below –
𝑣𝑎𝑟(𝑥̂) = 𝑤12 𝑟 + (1 − 𝑤1 )2 𝑝
Kalman Filter is an optimum complementary filter which minimizes the variance
in the final estimation.
𝑑[𝑣𝑎𝑟(𝑥̂)]
= 2𝑤1 𝑟 − 2(1 − 𝑤1 )𝑝 = 0
𝑑𝑤1
𝑝
𝑤1 =
𝑝+𝑟
𝑑2 [𝑣𝑎𝑟(𝑥̂)]
=𝑟+𝑝 >0
𝑑𝑤12
𝑝
Therefore, for 𝑤1 = 𝑝+𝑟, 𝑣𝑎𝑟(𝑥̂) is minimum.
𝑉𝑎𝑟𝑖𝑎𝑛𝑐𝑒 𝑖𝑛 𝐸𝑠𝑡𝑖𝑚𝑎𝑡𝑖𝑜𝑛
Kalman Gain (𝑤1 ) =
𝑉𝑎𝑟𝑖𝑎𝑛𝑐𝑒 𝑖𝑛 𝐸𝑠𝑡𝑖𝑚𝑎𝑡𝑖𝑜𝑛+𝑉𝑎𝑟𝑖𝑎𝑛𝑐𝑒 𝑖𝑛 𝑀𝑒𝑎𝑠𝑢𝑟𝑒𝑚𝑒𝑛𝑡
𝑝
𝑥̂ = 𝑥𝑒 + (𝑥 − 𝑥𝑒 )
𝑝+𝑟 𝑚
Angle form MPU6050 is measured from accelerometer and gyro. Standard deviation
in angle from accelerometer is ′𝜎𝑚 ′ (which is ~30) and variance in angle estimated from
gyro is given by –
𝑝 = (Δ𝑡)2 𝜎𝜔2
𝑛
𝑎𝑥
𝜃𝑚 = − tan−1 ( )
√𝑎𝑦2 + 𝑎𝑧2
𝑝𝜃𝑛−1
𝜃̂𝑒𝑛 = 𝜃𝑒𝑛 + 𝑘𝜃𝑛 (𝜃𝑚
𝑛
− 𝜃𝑒𝑛 ) = 𝜃𝑒𝑛 + 𝑛−1 2
𝑛
(𝜃𝑚 − 𝜃𝑒𝑛 )
𝑝𝜃 + 𝜎𝑚
The block diagram of the controller and self-balancing bot are presented below –
Controller Plant
MPU 6050
Measured
tilt angle
(𝑒−𝑒𝑝 )
motorSpeed = 𝐾𝑝 𝑒 + 𝐾𝑖 (𝑒 + 𝑒𝑝 )Δ𝑡 + 𝐾𝑑
Δ𝑡
Where, 𝑒: present error
𝑒𝑝 : previous iteration error
float motorSpeed;
int ledPin = 13;
const int MPU = 0x68;
float AccX, AccY, AccZ;
float offsetAccX = 0; float offsetAccY = 0; float offsetAccZ = 0;
float Ts = 20.0;
float offestRollRate = 0.0;
float meaRoll;
float rollRate;
float roll;
float preError = 0;
float sumError = 0;
float error = 0;
float target = 0;
//1/4 step
int kp = 180;
int ki = 40;
int kd = 0.3 ;
void initializeSensor()
{
// Perform full reset as per MPU-6000/MPU-6050 Register Map and Descriptions, Section 4.28,
pages 40 to 41.
//Serial.print(F("Performing full reset of MPU-6050..."));
//Serial.println(F(" Done."));
// Disable SLEEP mode because the reset re-enables it. Section 3, PWR_MGMT_1 register, page 8.
//Serial.print(F("Disabling sleep mode of MPU-6050..."));
Wire.beginTransmission(MPU); // Device address.
Wire.write(0x6B); // PWR_MGMT_1 register.
Wire.write(0b00001000); // SLEEP = 0, TEMP_DIS = 1.
Wire.endTransmission();
//Serial.println(F(" Done."));
void gyro_signals(void){
Wire.beginTransmission(MPU);
//Getting Accelerations
Wire.beginTransmission(MPU);
Wire.write(0x3B);
Wire.endTransmission();
Wire.requestFrom(MPU, 6);
//Range of GyroRates
Wire.beginTransmission(MPU);
Wire.write(0x1B);
Wire.write(0x8);
Wire.endTransmission();
//Getting GyroRates
Wire.beginTransmission(MPU);
Wire.write(0x43);
Wire.endTransmission();
Wire.requestFrom(MPU, 6);
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
stepper1.setMaxSpeed(16000);
stepper2.setMaxSpeed(16000);
pinMode(ledPin, OUTPUT);
initializeSensor();
Wire.setClock(400000);
Wire.begin();
delay(250);
Wire.beginTransmission(MPU);
Wire.write(0x6B);
Wire.write(0x00);
Wire.endTransmission();
//Calibration of Gyro
Serial.println("\nCalibration is going on.....");
digitalWrite(ledPin, HIGH);
int N = 2000; float sumRoll = 0;
float sumX =0; float sumY = 0; float sumZ = 0;
for (int i = 1; i < N; i++){
gyro_signals();
sumRoll += rollRate;
sumX += AccX; sumY += AccY; sumZ += AccZ;
}
offestRollRate = sumRoll/N;
offsetAccX = sumX/N;
offsetAccY = sumY/N;
offsetAccZ = 1 - sumZ/N;
digitalWrite(ledPin, LOW);
roll = 0; meaRoll = 0;
Serial.println("Calibration is completed.");
}
void loop() {
static unsigned long timer = 0;
if ((millis() - timer) > Ts){
timer = millis();
gyro_signals();
error = target - roll;
motorSpeed = kp*error + kd*(error - preError)/(Ts/1000) + ki*(0.5*(error+preError) +
sumError)*(Ts/1000);
sumError += error;
preError = error;
if (abs(error) < 0.1 || abs(error) > 70){
motorSpeed = 0;}
/*Serial.print(error);
Serial.print("\t");
Serial.println(motorSpeed);*/
}
if (motorSpeed > 8000){
motorSpeed = 8000;
}
if (motorSpeed < -8000){
motorSpeed = -8000;
}
stepper1.setSpeed(-motorSpeed);
stepper2.setSpeed(-motorSpeed);
stepper1.runSpeed();
stepper2.runSpeed();
}