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


pid
Author(s): Andy Zelenak , Paul Bouchier
autogenerated on Tue May 2 2017 02:49:51