Skip to content

Commit c406891

Browse files
committed
Add integrator discretization
1 parent b256384 commit c406891

File tree

2 files changed

+100
-24
lines changed

2 files changed

+100
-24
lines changed

control_toolbox/include/control_toolbox/pid.hpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -778,10 +778,13 @@ class Pid
778778
double d_term_last_ = 0; /** Save state for derivative filter. */
779779
double d_error_ = 0; /** Derivative of error. */
780780

781-
double i_term_ = 0; /** Integrator state. */
781+
double i_term_ = 0; /** Integrator state. */
782+
double i_term_last_ = 0; /** Last integrator state. */
783+
double aw_term_last_ = 0; /** Last anti-windup term. */
782784

783-
double cmd_ = 0; /** Command to send. */
784-
double cmd_unsat_ = 0; /** command without saturation. */
785+
double cmd_ = 0; /** Command to send. */
786+
double cmd_unsat_ = 0; /** command without saturation. */
787+
double error_last_ = 0; /** Last error. */
785788
};
786789

787790
} // namespace control_toolbox

control_toolbox/src/pid.cpp

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ void Pid::reset(bool save_i_term)
111111
d_error_ = 0.0;
112112
cmd_ = 0.0;
113113

114+
d_error_last_ = 0;
115+
d_term_last_ = 0;
116+
error_last_ = 0;
117+
aw_term_last_ = 0;
118+
114119
// Check to see if we should reset integral error here
115120
if (!save_i_term)
116121
{
@@ -121,7 +126,11 @@ void Pid::reset(bool save_i_term)
121126
gains_ = gains_box_.get();
122127
}
123128

124-
void Pid::clear_saved_iterm() { i_term_ = 0.0; }
129+
void Pid::clear_saved_iterm()
130+
{
131+
i_term_ = 0.0;
132+
i_term_last_ = 0.0;
133+
}
125134

126135
void Pid::get_gains(
127136
double & p, double & i, double & d, double & u_max, double & u_min,
@@ -332,22 +341,16 @@ double Pid::compute_command(double error, double error_dot, const double & dt_s)
332341
else if (gains_.tf_ > 0.0 && gains_.d_method_ == "trapezoidal")
333342
{
334343
// Derivative filter is on
335-
d_term = ((2 * gains_.tf_ + dt_s) * d_term_last_ +
344+
d_term = ((2 * gains_.tf_ - dt_s) * d_term_last_ +
336345
(gains_.d_gain_ * dt_s * (d_error_ + d_error_last_))) /
337346
(2 * gains_.tf_ + dt_s);
338347
}
339-
else if (
340-
gains_.tf_ == 0.0 &&
341-
(gains_.d_method_ == "forward_euler" || gains_.d_method_ == "backward_euler"))
348+
else if (gains_.tf_ == 0.0)
342349
{
343-
// Derivative filter is off. Since forward doesn't exist for tf=0, use backward
350+
// Derivative filter is off. Since forward doesn't exist for tf=0, use backward.
351+
// To avoid artificial states and amplify noise, trapezoidal also falls back to backward.
344352
d_term = gains_.d_gain_ * d_error_;
345353
}
346-
else if (gains_.tf_ == 0.0 && gains_.d_method_ == "trapezoidal")
347-
{
348-
// Derivative filter is off
349-
d_term = (-dt_s * d_term_last_ + (gains_.d_gain_ * dt_s * (d_error_ + d_error_last_))) / (dt_s);
350-
}
351354

352355
if (gains_.antiwindup_strat_.type == AntiWindupStrategy::UNDEFINED)
353356
{
@@ -358,6 +361,60 @@ double Pid::compute_command(double error, double error_dot, const double & dt_s)
358361
const bool is_error_in_deadband_zone =
359362
control_toolbox::is_zero(error, gains_.antiwindup_strat_.error_deadband);
360363

364+
if (gains_.i_method_ == "forward_euler")
365+
{
366+
i_term_ = gains_.i_gain_ * dt_s * error_last_;
367+
}
368+
else if (gains_.i_method_ == "backward_euler")
369+
{
370+
i_term_ = gains_.i_gain_ * dt_s * error;
371+
}
372+
else if (gains_.i_method_ == "trapezoidal")
373+
{
374+
i_term_ = gains_.i_gain_ * (dt_s * 0.5) * (error + error_last_);
375+
}
376+
else
377+
{
378+
throw std::runtime_error("Pid: invalid integral method");
379+
}
380+
381+
double i_proposed = i_term_last_ + i_term_;
382+
if (gains_.antiwindup_strat_.type == AntiWindupStrategy::CONDITIONAL_INTEGRATION)
383+
{
384+
// testa se o incremento empurra a saturação
385+
const bool have_limits = std::isfinite(gains_.u_min_) || std::isfinite(gains_.u_max_);
386+
if (have_limits)
387+
{
388+
// commando não-saturado candidato usando i_proposed
389+
const double cmd_unsat_candidate = p_term + d_term + i_proposed;
390+
391+
const bool sat_high = std::isfinite(gains_.u_max_) && (cmd_unsat_candidate > gains_.u_max_);
392+
const bool sat_low = std::isfinite(gains_.u_min_) && (cmd_unsat_candidate < gains_.u_min_);
393+
394+
const bool pushing_high = sat_high && (i_term_ > 0.0);
395+
const bool pushing_low = sat_low && (i_term_ < 0.0);
396+
397+
if (pushing_high || pushing_low)
398+
{
399+
i_term_ = i_term_last_; // congela
400+
}
401+
else
402+
{
403+
i_term_ = i_proposed; // aceita
404+
}
405+
}
406+
else
407+
{
408+
i_term_ = i_proposed; // sem limites -> aceita
409+
}
410+
}
411+
else
412+
{
413+
i_term_ = i_proposed; // sem CI -> integra normalmente (AW vem depois)
414+
}
415+
416+
i_term_ = std::clamp(i_term_, gains_.i_min_, gains_.i_max_);
417+
361418
// Compute the command
362419
cmd_unsat_ = p_term + i_term_ + d_term;
363420

@@ -381,10 +438,30 @@ double Pid::compute_command(double error, double error_dot, const double & dt_s)
381438
{
382439
if (
383440
gains_.antiwindup_strat_.type == AntiWindupStrategy::BACK_CALCULATION &&
384-
!is_zero(gains_.i_gain_))
441+
!is_zero(gains_.i_gain_) && gains_.i_method_ == "forward_euler")
442+
{
443+
i_term_ += dt_s * 1 / gains_.antiwindup_strat_.tracking_time_constant * (cmd_ - cmd_unsat_);
444+
}
445+
else if (
446+
gains_.antiwindup_strat_.type == AntiWindupStrategy::BACK_CALCULATION &&
447+
!is_zero(gains_.i_gain_) && gains_.i_method_ == "backward_euler")
385448
{
386-
i_term_ += dt_s * (gains_.i_gain_ * error +
387-
1 / gains_.antiwindup_strat_.tracking_time_constant * (cmd_ - cmd_unsat_));
449+
i_term_ = i_term_ / (1 + dt_s / gains_.antiwindup_strat_.tracking_time_constant) +
450+
dt_s / gains_.antiwindup_strat_.tracking_time_constant * (cmd_ - p_term - d_term) /
451+
(1 + dt_s / gains_.antiwindup_strat_.tracking_time_constant);
452+
}
453+
else if (
454+
gains_.antiwindup_strat_.type == AntiWindupStrategy::BACK_CALCULATION &&
455+
!is_zero(gains_.i_gain_) && gains_.i_method_ == "trapezoidal")
456+
{
457+
const double num_i_last =
458+
(1.0 - dt_s / (2.0 * gains_.antiwindup_strat_.tracking_time_constant)) * i_term_last_;
459+
const double trap_inc = gains_.i_gain_ * (dt_s * 0.5) * (p_error_ + error_last_);
460+
const double aw_sum = (cmd_ - (p_term + d_term)) + aw_term_last_;
461+
const double denom = (1.0 + dt_s / (2.0 * gains_.antiwindup_strat_.tracking_time_constant));
462+
i_term_ = (num_i_last + trap_inc +
463+
(dt_s / (2.0 * gains_.antiwindup_strat_.tracking_time_constant)) * aw_sum) /
464+
denom;
388465
}
389466
else if (gains_.antiwindup_strat_.type == AntiWindupStrategy::CONDITIONAL_INTEGRATION)
390467
{
@@ -393,17 +470,13 @@ double Pid::compute_command(double error, double error_dot, const double & dt_s)
393470
i_term_ += dt_s * gains_.i_gain_ * error;
394471
}
395472
}
396-
else if (gains_.antiwindup_strat_.type == AntiWindupStrategy::NONE)
397-
{
398-
// No anti-windup strategy, so just integrate the error
399-
i_term_ += dt_s * gains_.i_gain_ * error;
400-
}
401473
}
402474

403-
i_term_ = std::clamp(i_term_, gains_.i_min_, gains_.i_max_);
404-
405475
d_term_last_ = d_term;
406476
d_error_last_ = d_error_;
477+
i_term_last_ = i_term_;
478+
aw_term_last_ = cmd_ - p_term - d_term;
479+
error_last_ = error;
407480

408481
return cmd_;
409482
}

0 commit comments

Comments
 (0)