pid.cpp
Go to the documentation of this file.
1 
2 #include <pid/pid.h>
3 
4 using namespace pid_ns;
5 
6 PidObject::PidObject() : error_(3, 0), filtered_error_(3, 0), error_deriv_(3, 0), filtered_error_deriv_(3, 0)
7 {
8  ros::NodeHandle node;
9  ros::NodeHandle node_priv("~");
10 
11  while (ros::ok() && ros::Time(0) == ros::Time::now())
12  {
13  ROS_INFO("controller spinning, waiting for time to become non-zero");
14  sleep(1);
15  }
16 
17  // Get params if specified in launch file or as params on command-line, set
18  // defaults
19  node_priv.param<double>("Kp", Kp_, 1.0);
20  node_priv.param<double>("Ki", Ki_, 0.0);
21  node_priv.param<double>("Kd", Kd_, 0.0);
22  node_priv.param<double>("upper_limit", upper_limit_, 1000.0);
23  node_priv.param<double>("lower_limit", lower_limit_, -1000.0);
24  node_priv.param<double>("windup_limit", windup_limit_, 1000.0);
25  node_priv.param<double>("cutoff_frequency", cutoff_frequency_, -1.0);
26  node_priv.param<std::string>("topic_from_controller", topic_from_controller_, "control_effort");
27  node_priv.param<std::string>("topic_from_plant", topic_from_plant_, "state");
28  node_priv.param<std::string>("setpoint_topic", setpoint_topic_, "setpoint");
29  node_priv.param<std::string>("pid_enable_topic", pid_enable_topic_, "pid_enable");
30  node_priv.param<double>("max_loop_frequency", max_loop_frequency_, 1.0);
31  node_priv.param<double>("min_loop_frequency", min_loop_frequency_, 1000.0);
32  node_priv.param<std::string>("pid_debug_topic", pid_debug_pub_name_, "pid_debug");
33  node_priv.param<double>("setpoint_timeout", setpoint_timeout_, -1.0);
34  ROS_ASSERT_MSG(setpoint_timeout_ ==-1 || setpoint_timeout_ > 0,
35  "setpoint_timeout set to %.2f but needs to -1 or >0", setpoint_timeout_);
36 
37  // Two parameters to allow for error calculation with discontinous value
38  node_priv.param<bool>("angle_error", angle_error_, false);
39  node_priv.param<double>("angle_wrap", angle_wrap_, 2.0 * 3.14159);
40 
41  // Update params if specified as command-line options, & print settings
43  if (not validateParameters())
44  std::cout << "Error: invalid parameter\n";
45 
46  // instantiate publishers & subscribers
47  control_effort_pub_ = node.advertise<std_msgs::Float64>(topic_from_controller_, 1);
48  pid_debug_pub_ = node.advertise<std_msgs::Float64MultiArray>(pid_debug_pub_name_, 1);
49 
50  ros::Subscriber plant_sub_ = node.subscribe(topic_from_plant_, 1, &PidObject::plantStateCallback, this);
51  ros::Subscriber setpoint_sub_ = node.subscribe(setpoint_topic_, 1, &PidObject::setpointCallback, this);
52  ros::Subscriber pid_enabled_sub_ = node.subscribe(pid_enable_topic_, 1, &PidObject::pidEnableCallback, this);
53 
54  if (!plant_sub_ || !setpoint_sub_ || !pid_enabled_sub_)
55  {
56  ROS_ERROR_STREAM("Initialization of a subscriber failed. Exiting.");
57  ros::shutdown();
58  exit(EXIT_FAILURE);
59  }
60 
61  // dynamic reconfiguration
62  dynamic_reconfigure::Server<pid::PidConfig> config_server;
63  dynamic_reconfigure::Server<pid::PidConfig>::CallbackType f;
64  f = boost::bind(&PidObject::reconfigureCallback, this, _1, _2);
65  config_server.setCallback(f);
66 
67  // Wait for first messages
68  while( ros::ok() && !ros::topic::waitForMessage<std_msgs::Float64>(setpoint_topic_, ros::Duration(10.)))
69  ROS_WARN_STREAM("Waiting for first setpoint message.");
70 
71  while( ros::ok() && !ros::topic::waitForMessage<std_msgs::Float64>(topic_from_plant_, ros::Duration(10.)))
72  ROS_WARN_STREAM("Waiting for first state message from the plant.");
73 
74  // Respond to inputs until shut down
75  while (ros::ok())
76  {
77  doCalcs();
78  ros::spinOnce();
79 
80  // Add a small sleep to avoid 100% CPU usage
81  ros::Duration(0.001).sleep();
82  }
83 };
84 
85 void PidObject::setpointCallback(const std_msgs::Float64& setpoint_msg)
86 {
87  setpoint_ = setpoint_msg.data;
89  new_state_or_setpt_ = true;
90 }
91 
92 void PidObject::plantStateCallback(const std_msgs::Float64& state_msg)
93 {
94  plant_state_ = state_msg.data;
95 
96  new_state_or_setpt_ = true;
97 }
98 
99 void PidObject::pidEnableCallback(const std_msgs::Bool& pid_enable_msg)
100 {
101  pid_enabled_ = pid_enable_msg.data;
102 }
103 
104 void PidObject::getParams(double in, double& value, double& scale)
105 {
106  int digits = 0;
107  value = in;
108  while (ros::ok() && ((fabs(value) > 1.0 || fabs(value) < 0.1) && (digits < 2 && digits > -1)))
109  {
110  if (fabs(value) > 1.0)
111  {
112  value /= 10.0;
113  digits++;
114  }
115  else
116  {
117  value *= 10.0;
118  digits--;
119  }
120  }
121  if (value > 1.0)
122  value = 1.0;
123  if (value < -1.0)
124  value = -1.0;
125 
126  scale = pow(10.0, digits);
127 }
128 
130 {
132  {
133  ROS_ERROR("The lower saturation limit cannot be greater than the upper "
134  "saturation limit.");
135  return (false);
136  }
137 
138  return true;
139 }
140 
142 {
143  std::cout << std::endl << "PID PARAMETERS" << std::endl << "-----------------------------------------" << std::endl;
144  std::cout << "Kp: " << Kp_ << ", Ki: " << Ki_ << ", Kd: " << Kd_ << std::endl;
145  if (cutoff_frequency_ == -1) // If the cutoff frequency was not specified by the user
146  std::cout << "LPF cutoff frequency: 1/4 of sampling rate" << std::endl;
147  else
148  std::cout << "LPF cutoff frequency: " << cutoff_frequency_ << std::endl;
149  std::cout << "pid node name: " << ros::this_node::getName() << std::endl;
150  std::cout << "Name of topic from controller: " << topic_from_controller_ << std::endl;
151  std::cout << "Name of topic from the plant: " << topic_from_plant_ << std::endl;
152  std::cout << "Name of setpoint topic: " << setpoint_topic_ << std::endl;
153  std::cout << "Integral-windup limit: " << windup_limit_ << std::endl;
154  std::cout << "Saturation limits: " << upper_limit_ << "/" << lower_limit_ << std::endl;
155  std::cout << "-----------------------------------------" << std::endl;
156 
157  return;
158 }
159 
160 void PidObject::reconfigureCallback(pid::PidConfig& config, uint32_t level)
161 {
162  if (first_reconfig_)
163  {
164  getParams(Kp_, config.Kp, config.Kp_scale);
165  getParams(Ki_, config.Ki, config.Ki_scale);
166  getParams(Kd_, config.Kd, config.Kd_scale);
167  first_reconfig_ = false;
168  return; // Ignore the first call to reconfigure which happens at startup
169  }
170 
171  Kp_ = config.Kp * config.Kp_scale;
172  Ki_ = config.Ki * config.Ki_scale;
173  Kd_ = config.Kd * config.Kd_scale;
174  ROS_INFO("Pid reconfigure request: Kp: %f, Ki: %f, Kd: %f", Kp_, Ki_, Kd_);
175 }
176 
178 {
179  // Do fresh calcs if knowledge of the system has changed.
181  {
182  if (!((Kp_ <= 0. && Ki_ <= 0. && Kd_ <= 0.) ||
183  (Kp_ >= 0. && Ki_ >= 0. && Kd_ >= 0.))) // All 3 gains should have the same sign
184  ROS_WARN("All three gains (Kp, Ki, Kd) should have the same sign for "
185  "stability.");
186 
187  error_.at(2) = error_.at(1);
188  error_.at(1) = error_.at(0);
189  error_.at(0) = setpoint_ - plant_state_; // Current error goes to slot 0
190 
191  // If the angle_error param is true, then address discontinuity in error
192  // calc.
193  // For example, this maintains an angular error between -180:180.
194  if (angle_error_)
195  {
196  while (error_.at(0) < -1.0 * angle_wrap_ / 2.0)
197  {
198  error_.at(0) += angle_wrap_;
199 
200  // The proportional error will flip sign, but the integral error
201  // won't and the filtered derivative will be poorly defined. So,
202  // reset them.
203  error_deriv_.at(2) = 0.;
204  error_deriv_.at(1) = 0.;
205  error_deriv_.at(0) = 0.;
206  error_integral_ = 0.;
207  }
208  while (error_.at(0) > angle_wrap_ / 2.0)
209  {
210  error_.at(0) -= angle_wrap_;
211 
212  // The proportional error will flip sign, but the integral error
213  // won't and the filtered derivative will be poorly defined. So,
214  // reset them.
215  error_deriv_.at(2) = 0.;
216  error_deriv_.at(1) = 0.;
217  error_deriv_.at(0) = 0.;
218  error_integral_ = 0.;
219  }
220  }
221 
222  // calculate delta_t
223  if (!prev_time_.isZero()) // Not first time through the program
224  {
227  if (0 == delta_t_.toSec())
228  {
229  ROS_ERROR("delta_t is 0, skipping this loop. Possible overloaded cpu "
230  "at time: %f",
231  ros::Time::now().toSec());
232  return;
233  }
234  }
235  else
236  {
237  ROS_INFO("prev_time is 0, doing nothing");
239  return;
240  }
241 
242  // integrate the error
243  error_integral_ += error_.at(0) * delta_t_.toSec();
244 
245  // Apply windup limit to limit the size of the integral term
246  if (error_integral_ > fabsf(windup_limit_))
248 
249  if (error_integral_ < -fabsf(windup_limit_))
250  error_integral_ = -fabsf(windup_limit_);
251 
252  // My filter reference was Julius O. Smith III, Intro. to Digital Filters
253  // With Audio Applications.
254  // See https://ccrma.stanford.edu/~jos/filters/Example_Second_Order_Butterworth_Lowpass.html
255  if (cutoff_frequency_ != -1)
256  {
257  // Check if tan(_) is really small, could cause c = NaN
258  tan_filt_ = tan((cutoff_frequency_ * 6.2832) * delta_t_.toSec() / 2);
259 
260  // Avoid tan(0) ==> NaN
261  if ((tan_filt_ <= 0.) && (tan_filt_ > -0.01))
262  tan_filt_ = -0.01;
263  if ((tan_filt_ >= 0.) && (tan_filt_ < 0.01))
264  tan_filt_ = 0.01;
265 
266  c_ = 1 / tan_filt_;
267  }
268 
269  filtered_error_.at(2) = filtered_error_.at(1);
270  filtered_error_.at(1) = filtered_error_.at(0);
271  filtered_error_.at(0) = (1 / (1 + c_ * c_ + 1.414 * c_)) * (error_.at(2) + 2 * error_.at(1) + error_.at(0) -
272  (c_ * c_ - 1.414 * c_ + 1) * filtered_error_.at(2) -
273  (-2 * c_ * c_ + 2) * filtered_error_.at(1));
274 
275  // Take derivative of error
276  // First the raw, unfiltered data:
277  error_deriv_.at(2) = error_deriv_.at(1);
278  error_deriv_.at(1) = error_deriv_.at(0);
279  error_deriv_.at(0) = (error_.at(0) - error_.at(1)) / delta_t_.toSec();
280 
283 
284  filtered_error_deriv_.at(0) =
285  (1 / (1 + c_ * c_ + 1.414 * c_)) *
286  (error_deriv_.at(2) + 2 * error_deriv_.at(1) + error_deriv_.at(0) -
287  (c_ * c_ - 1.414 * c_ + 1) * filtered_error_deriv_.at(2) - (-2 * c_ * c_ + 2) * filtered_error_deriv_.at(1));
288 
289  // calculate the control effort
294 
295  // Apply saturation limits
298  else if (control_effort_ < lower_limit_)
300 
301  // Publish the stabilizing control effort if the controller is enabled
302  if (pid_enabled_ && (setpoint_timeout_ == -1 ||
304  {
307  // Publish topic with
308  std::vector<double> pid_debug_vect { plant_state_, control_effort_, proportional_, integral_, derivative_};
309  std_msgs::Float64MultiArray pidDebugMsg;
310  pidDebugMsg.data = pid_debug_vect;
311  pid_debug_pub_.publish(pidDebugMsg);
312  }
314  {
315  ROS_WARN_ONCE("Setpoint message timed out, will stop publising control_effort_messages");
316  error_integral_ = 0.0;
317  }
318  else
319  error_integral_ = 0.0;
320  }
321 
322  new_state_or_setpt_ = false;
323 }
ros::Duration delta_t_
Definition: controller.h:64
std::string topic_from_controller_
Definition: controller.h:102
std::vector< double > filtered_error_deriv_
Definition: controller.h:98
ros::Publisher control_effort_pub_
Definition: controller.h:101
std::string pid_debug_pub_name_
Definition: pid.h:124
void publish(const boost::shared_ptr< M > &message) const
f
double max_loop_frequency_
Definition: controller.h:107
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
bool sleep() const
std::vector< double > error_deriv_
Definition: controller.h:98
ROSCPP_DECL const std::string & getName()
double plant_state_
Definition: controller.h:57
double windup_limit_
Definition: controller.h:95
#define ROS_WARN(...)
double min_loop_frequency_
Definition: controller.h:107
std::string topic_from_plant_
Definition: controller.h:102
std::string pid_enable_topic_
Definition: controller.h:102
double Ki_scale
Definition: PidConfig.h:272
std::string setpoint_topic_
Definition: controller.h:102
bool new_state_or_setpt_
Definition: controller.h:61
double setpoint_timeout_
Definition: pid.h:100
double upper_limit_
Definition: controller.h:92
double Kd_scale
Definition: PidConfig.h:278
double error_integral_
Definition: controller.h:67
bool validateParameters()
Definition: pid.cpp:129
#define ROS_WARN_ONCE(...)
double derivative_
Definition: controller.h:70
#define ROS_INFO(...)
ros::Time prev_time_
Definition: controller.h:63
void reconfigureCallback(pid::PidConfig &config, uint32_t level)
Definition: pid.cpp:160
bool param(const std::string &param_name, T &param_val, const T &default_val) const
#define ROS_ASSERT_MSG(cond,...)
double cutoff_frequency_
Definition: controller.h:81
ROSCPP_DECL bool ok()
double angle_wrap_
Definition: controller.h:77
ros::Publisher pid_debug_pub_
Definition: pid.h:121
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
double lower_limit_
Definition: controller.h:92
#define ROS_WARN_STREAM(args)
double control_effort_
Definition: controller.h:58
ros::Time last_setpoint_msg_time_
Definition: pid.h:77
void doCalcs()
Definition: pid.cpp:177
std::vector< double > filtered_error_
Definition: controller.h:98
void pidEnableCallback(const std_msgs::Bool &pid_enable_msg)
Definition: pid.cpp:99
void setpointCallback(const std_msgs::Float64 &setpoint_msg)
Definition: pid.cpp:85
double Kp_scale
Definition: PidConfig.h:266
std_msgs::Float64 control_msg_
Definition: controller.h:104
static Time now()
void printParameters()
Definition: pid.cpp:141
ROSCPP_DECL void shutdown()
#define ROS_ERROR_STREAM(args)
ROSCPP_DECL void spinOnce()
void getParams(double in, double &value, double &scale)
Definition: pid.cpp:104
#define ROS_ERROR(...)
double proportional_
Definition: controller.h:68
void plantStateCallback(const std_msgs::Float64 &state_msg)
Definition: pid.cpp:92
std::vector< double > error_
Definition: controller.h:98


pid
Author(s): Andy Zelenak , Paul Bouchier
autogenerated on Sat Jul 4 2020 03:26:03