Self-balancing and Line-following robot
> #PID #ROS2 #Gazebo
> In this blog, we will first learn about PID. Then using PID, we will implement self-balancing and line-following algorithms on a two wheeled robot. Finally, we will combine the two algorithms to make our bot balance itself and follow a line.
Authors : Aryaman Shardul and Marck Koothoor
error
. Actual reading is compared with the set_point and multiplied by proportional gain (Kp). P_term = Kp*error
I_term = Ki*∫ e(t) dt ; where Ki = Integral gain,∫ e(t) dt = Cumulative error
D_term = Kd*(de/dt) ,where Kd = Derivative gain,de/dt = Change in error w.r.t time
P_term + I_term + D_term
Following are some of the doubts asked by :
P-D-I) Firstly set the Kp term (Kd = Ki = 0), such that the performance is close to our desired state,
then set the Kd term (Ki = 0), noticing that perfomance is improved and now set the Ki term and
also tweak the limits for cumulative area. After ample of time, if this doesn't seem working for you ,
then for (P-I-D)
This can occur if the mass of the bot is comparatively higher than it's wheels ,
so try reducing the mass.
If you notice P-I-D working fine for a span , do not change the values for Kp,Kd,Ki, I repeat !
Spike can possibly occur due to the increasing cumulative area, so tweak the limits.
Note : If working with ROS , you can use plotjuggler to tweak values seeing the graph.
Replied to the comment:
PaIne Daxton : I tried this solution, but it didn't work.
Truely possible , PID works different in different situation.
Here are two more reasons and solutions for spike:
- Derivative kick : Any change in set_point causes an instantaneous change in error.
The derivative of this change is infinity (in practice, since dt isn’t 0 it just winds up being a really
big number).This number gets fed into the pid equation, which results in an undesirable spike in the output.
Solution for this is that we take the rate of change in inputs directly instead of taking it for the error
(where, error = set_point - input;)
error_rate = (input - last_input); and not error_rate = (error - last_error);
- Integral kick : When tuning, there might be a drastic change in I_term,
this is because suddenly the cumulative area gets halved (Because all of a sudden,
you multiply this new Ki times the entire error sum that you have accumulated).
Recommended solution for this is to multiply Ki initially itself instead at last.
I_term += (ki * error);
Just change the set_point = 0; to a positive or negative value as per the performance.
Import required libraries and declare the variables.
Creating a class for publisher-subscriber node. Subcribe to IMU sensor-plugin /demo/imu
and publish velocity to differential drive plugin /cmd/vel
Define a function to convert pitch readings from radians to degrees.
Define the balacned state of the bot.
pitch = msg->orientation.y; // Assigning value of orientation.y taken from imu
error = Convert(pitch); // Converting into degrees
// Calculations for Pitch error, pitch error difference, previous pitch error, pitch rate, pitch area
pitch_error = 5*(error-pitch_desired);
pitch_rate = pitch_error - prev_pitch_error;
pitch_area += pitch_error ;
// Resetting pitch_area zero everytime it crosses maximum value
if(fabs(pitch_area) > 5.0/1.4)
{
pitch_area = 0.0;
}
// Calculating PID terms
P_term = Kppitch_error;
D_term = Kdpitch_rate;
I_term = pitch_area*Ki;
correction_speed = P_term + D_term + I_term;
prev_pitch_error = pitch_error;
}
// Callback function for publisher
void timer_callback()
{
auto message = geometry_msgs::msg::Twist();
message.linear.x = correction_speed;
RCLCPP_INFO(this->get_logger(), "Publishing: '%lf'", message.linear.x); publisher_->publish(message);
}
if( r > 125.00)
{
if(flag == 1)
{
index = i+1; // Assigning position of first white pixel to variable index
flag = 0;
}
count ++; // Counting total number of white pixels
}
}
if(count == 10) // If we have all white pixels in range of camera
{
if(index <= 12) // Checking position of first white pixel with respect to ideal position of first white pixel
{
error = 16 - index - 4; // Calculating error by checking for deviation between position of current centre white pixel and ideal centre white pixel if deviation is on the left side
direction = 1;
}
else
{
error = index + 4 - 17; // Calculating error by checking for deviation between position of current centre white pixel and ideal centre white pixel if deviation is on the right side
direction = 2;
}
}
else if(count >= 5 && count < 10) // If we don't have all white pixels in range of camera
{
if(index < 12)
{
error = 12 - index; // Calculating error by checking for deviation between position of current first white pixel and ideal first white pixel if deviation is on the left side
direction = 1;
}
else if(index > 21)
{
error = index - 21; // Calculating error by checking for deviation between position of current first white pixel and ideal first white pixel if deviation is on the right side
direction = 2;
}
}
Now we calculate the PID terms.
error_area += error;
error_diff = error - prev_error;
prev_error = error;
correction = kp*error + kd*error_diff + ki*error_area;
ang_speed = correction;
In the callback function of the publisher, we decide the direction in which the bot has to turn depending upon the error calculated above and accordingly provide the angular velocity in the appropriate direction.
if(direction == 1)
{
auto message = geometry_msgs::msg::Twist();
message.linear.x = 0.2;
message.angular.z = -ang_speed ;
publisher_->publish(message);
}
else if(direction == 2)
{
auto message = geometry_msgs::msg::Twist();
message.linear.x = 0.2;
message.angular.z = ang_speed ;
publisher_->publish(message);
}
// If bot is not balanced, provide only linear velocity for balancing else provide both linear and angular velocity
{
if (balanced_state == false)
{ auto message = geometry_msgs::msg::Twist();
message.linear.x = correction;
message.angular.z = 0.0;
RCLCPP_INFO(this->get_logger(), "Publishing: '%lf'", message.linear.x);
publisher_->publish(message);
}
else
{
if (direction == 1)
{
auto message = geometry_msgs::msg::Twist();
message.linear.x = speed;
message.angular.z = -ang_speed; publisher_->publish(message);
RCLCPP_INFO(this->get_logger(), "Publishing ang: '%lf;", message.angular.z);
}
else if (direction == -1)
{
auto message = geometry_msgs::msg::Twist();
message.linear.x = speed;
message.angular.z = ang_speed;
publisher_->publish(message);
RCLCPP_INFO(this->get_logger(), "Publishing ang: '%lf;", message.angular.z);
}
}
}
The following video is a demonstration of all the three algorithms implemented on our two wheeled bot.
PID controller is a widely used concept in various projects and real life applications. In this blog, with the help of PID ,we implemented three algorithms on a two wheeled self-balancing + line-following robot. We hope you all enjoyed reading this blog and hope that this blog encourages you all to implement them yourselves. You can have a look at our project's github repository.