autotune.cpp
Go to the documentation of this file.
00001 /***************************************************************************/ 
00036 // Use the Ziegler Nichols Method to calculate reasonable PID gains.
00037 // The ZN Method is based on setting Ki & Kd to zero, then cranking up Kp until
00038 // oscillations are observed.
00039 // This node varies Kp through a range until oscillations are just barely
00040 // observed,
00041 // then calculates the other parameters automatically.
00042 // See https://en.wikipedia.org/wiki/PID_controller
00043 
00044 #include <math.h>
00045 #include <ros/ros.h>
00046 #include <std_msgs/Float64.h>
00047 #include <string>
00048 
00049 // Use dynamic_reconfigure to adjust Kp, Ki, and Kd
00050 #include <dynamic_reconfigure/Config.h>
00051 #include <dynamic_reconfigure/DoubleParameter.h>
00052 #include <dynamic_reconfigure/Reconfigure.h>
00053 
00054 void setKiKdToZero();
00055 void setKp(double Kp);
00056 void setpointCallback(const std_msgs::Float64& setpoint_msg);
00057 void stateCallback(const std_msgs::Float64& state_msg);
00058 void setFinalParams();
00059 
00060 namespace autotune
00061 {
00062 double Ku = 0.;
00063 double Tu = 0.;
00064 double setpoint = 0.;
00065 double state = 0.;
00066 std::string ns = "/left_wheel_pid/";
00067 int oscillation_count = 0;
00068 int num_loops = 100;  // Will look for oscillations for num_loops*loopRate
00069 int initial_error = 0;
00070 double Kp_ZN = 0.;
00071 double Ki_ZN = 0.;
00072 double Kd_ZN = 0.;
00073 bool found_Ku = false;
00074 std::vector<double> oscillation_times(3);  // Used to calculate Tu, the oscillation period
00075 }
00076 
00077 int main(int argc, char** argv)
00078 {
00079   ros::init(argc, argv, "autotune_node");
00080   ros::NodeHandle autotune_node;
00081   ros::start();
00082   ros::Subscriber setpoint_sub = autotune_node.subscribe("/setpoint", 1, setpointCallback);
00083   ros::Subscriber state_sub = autotune_node.subscribe("/state", 1, stateCallback);
00084   ros::Rate loopRate(50);
00085 
00086   // Set Ki and Kd to zero for the ZN method with dynamic_reconfigure
00087   setKiKdToZero();
00088 
00089   // Define how rapidly the value of Kp is varied, and the max/min values to try
00090   double Kp_max = 10.;
00091   double Kp_min = 0.5;
00092   double Kp_step = 1.0;
00093 
00094   for (double Kp = Kp_min; Kp <= Kp_max; Kp += Kp_step)
00095   {
00097     // Get the error sign.
00099     // Need to wait for new setpoint/state msgs
00100     ros::topic::waitForMessage<std_msgs::Float64>("setpoint");
00101     ros::topic::waitForMessage<std_msgs::Float64>("state");
00102 
00103     // Try a new Kp.
00104     setKp(Kp);
00105     ROS_INFO_STREAM("Trying Kp = " << Kp);  // Blank line on terminal
00106     autotune::oscillation_count = 0;        // Reset to look for oscillations again
00107 
00108     for (int i = 0; i < autotune::num_loops; i++)  // Collect data for loopRate*num_loops seconds
00109     {
00110       ros::spinOnce();
00111       loopRate.sleep();
00112       if (i == 0)  // Get the sign of the initial error
00113       {
00114         autotune::initial_error = (autotune::setpoint - autotune::state);
00115       }
00116 
00117       // Did the system oscillate about the setpoint? If so, Kp~Ku.
00118       // Oscillation => the sign of the error changes
00119       // The first oscillation is caused by the change in setpoint. Ignore it.
00120       // Look for 2 oscillations.
00121       // Get a fresh state message
00122       ros::topic::waitForMessage<std_msgs::Float64>("state");
00123       double new_error = (autotune::setpoint - autotune::state);  // Sign of the error
00124       // ROS_INFO_STREAM("New error: "<< new_error);
00125       if (std::signbit(autotune::initial_error) != std::signbit(new_error))
00126       {
00127         autotune::oscillation_times.at(autotune::oscillation_count) =
00128             loopRate.expectedCycleTime().toSec() * i;  // Record the time to calculate a period, Tu
00129         autotune::oscillation_count++;
00130         ROS_INFO_STREAM("Oscillation occurred. Oscillation count:  " << autotune::oscillation_count);
00131         autotune::initial_error = new_error;  // Reset to look for another oscillation
00132 
00133         // If the system is definitely oscillating about the setpoint
00134         if (autotune::oscillation_count > 2)
00135         {
00136           // Now calculate the period of oscillation (Tu)
00137           autotune::Tu = autotune::oscillation_times.at(2) - autotune::oscillation_times.at(0);
00138           ROS_INFO_STREAM("Tu (oscillation period): " << autotune::Tu);
00139           // ROS_INFO_STREAM( "2*sampling period: " <<
00140           // 2.*loopRate.expectedCycleTime().toSec() );
00141 
00142           // We're looking for more than just the briefest dip across the
00143           // setpoint and back.
00144           // Want to see significant oscillation
00145           if (autotune::Tu > 3. * loopRate.expectedCycleTime().toSec())
00146           {
00147             autotune::Ku = Kp;
00148 
00149             // Now calculate the other parameters with ZN method
00150             autotune::Kp_ZN = 0.6 * autotune::Ku;
00151             autotune::Ki_ZN = 1.2 * autotune::Ku / autotune::Tu;
00152             autotune::Kd_ZN = 3. * autotune::Ku * autotune::Tu / 40.;
00153 
00154             autotune::found_Ku = true;
00155             goto DONE;
00156           }
00157           else
00158             break;  // Try the next Kp
00159         }
00160       }
00161     }
00162   }
00163 DONE:
00164 
00165   if (autotune::found_Ku == true)
00166   {
00167     setFinalParams();
00168   }
00169   else
00170     ROS_INFO_STREAM("Did not see any oscillations for this range of Kp. Adjust "
00171                     "Kp_max and Kp_min to broaden the search.");
00172 
00173   ros::shutdown();
00174   return 0;
00175 }
00176 
00177 // Set Ki and Kd to zero with dynamic_reconfigure
00178 void setKiKdToZero()
00179 {
00180   dynamic_reconfigure::ReconfigureRequest srv_req;
00181   dynamic_reconfigure::ReconfigureResponse srv_resp;
00182   dynamic_reconfigure::DoubleParameter double_param;
00183   dynamic_reconfigure::Config config;
00184   double_param.name = "Ki";
00185   double_param.value = 0.0;
00186   config.doubles.push_back(double_param);
00187   double_param.name = "Kd";
00188   double_param.value = 0.0;
00189   config.doubles.push_back(double_param);
00190   srv_req.config = config;
00191   ros::service::call(autotune::ns + "set_parameters", srv_req, srv_resp);
00192 }
00193 
00194 // Set Kp with dynamic_reconfigure
00195 void setKp(double Kp)
00196 {
00197   dynamic_reconfigure::ReconfigureRequest srv_req;
00198   dynamic_reconfigure::ReconfigureResponse srv_resp;
00199   dynamic_reconfigure::DoubleParameter double_param;
00200   dynamic_reconfigure::Config config;
00201 
00202   // A blank service call to get the current parameters into srv_resp
00203   ros::service::call(autotune::ns + "set_parameters", srv_req, srv_resp);
00204 
00205   double_param.name = "Kp";
00206   double_param.value = Kp / srv_resp.config.doubles.at(0).value;  // Adjust for the scale slider on the GUI
00207   config.doubles.push_back(double_param);
00208   srv_req.config = config;
00209   ros::service::call(autotune::ns + "set_parameters", srv_req, srv_resp);
00210 }
00211 
00212 void setpointCallback(const std_msgs::Float64& setpoint_msg)
00213 {
00214   autotune::setpoint = setpoint_msg.data;
00215 }
00216 
00217 void stateCallback(const std_msgs::Float64& state_msg)
00218 {
00219   autotune::state = state_msg.data;
00220 }
00221 
00222 // Print out and set the final parameters as calculated by the autotuner
00223 void setFinalParams()
00224 {
00225   ROS_INFO_STREAM(" ");
00226   ROS_INFO_STREAM("The suggested parameters are: ");
00227   ROS_INFO_STREAM("Kp  " << autotune::Kp_ZN);
00228   ROS_INFO_STREAM("Ki  " << autotune::Ki_ZN);
00229   ROS_INFO_STREAM("Kd  " << autotune::Kd_ZN);
00230 
00231   // Set the ZN parameters with dynamic_reconfigure
00232   dynamic_reconfigure::ReconfigureRequest srv_req;
00233   dynamic_reconfigure::ReconfigureResponse srv_resp;
00234   dynamic_reconfigure::DoubleParameter double_param;
00235   dynamic_reconfigure::Config config;
00236 
00237   // A blank service call to get the current parameters into srv_resp
00238   ros::service::call(autotune::ns + "set_parameters", srv_req, srv_resp);
00239 
00240   double_param.name = "Kp";
00241   double_param.value = autotune::Kp_ZN / srv_resp.config.doubles.at(0).value;  // Adjust for the scale slider on the GUI
00242   config.doubles.push_back(double_param);
00243 
00244   double_param.name = "Ki";
00245   double_param.value = autotune::Ki_ZN / srv_resp.config.doubles.at(0).value;  // Adjust for the scale slider on the GUI
00246   config.doubles.push_back(double_param);
00247 
00248   double_param.name = "Kd";
00249   double_param.value = autotune::Kd_ZN / srv_resp.config.doubles.at(0).value;  // Adjust for the scale slider on the GUI
00250   config.doubles.push_back(double_param);
00251 
00252   srv_req.config = config;
00253   ros::service::call(autotune::ns + "set_parameters", srv_req, srv_resp);
00254 }


pid
Author(s): Andy Zelenak , Paul Bouchier
autogenerated on Mon Apr 15 2019 02:40:47