libstp
Robotics library for Botball
Loading...
Searching...
No Matches
profiled_pid_controller.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5
6#include "foundation/pid.hpp"
9
10namespace libstp::motion
11{
21 {
22 public:
25
26 struct Config
27 {
29 double velocity_ff{1.0};
30 };
31
32 ProfiledPIDController(Config config, Constraints profile_constraints)
33 : cfg_(config)
34 , constraints_(profile_constraints)
35 , pid_(config.pid)
36 {
37 }
38
39 void setGoal(double position) { goal_ = {position, 0.0}; }
40 void setGoal(State goal) { goal_ = goal; }
41 [[nodiscard]] State getGoal() const { return goal_; }
42 [[nodiscard]] State getSetpoint() const { return setpoint_; }
43 [[nodiscard]] const Constraints& getConstraints() const { return constraints_; }
44
45 void setConstraints(Constraints constraints) { constraints_ = constraints; }
46
47 void enableContinuousInput(double min_input, double max_input)
48 {
49 continuous_input_ = true;
50 min_input_ = min_input;
51 max_input_ = max_input;
52 }
53
54 void disableContinuousInput() { continuous_input_ = false; }
55
56 void reset(double position, double velocity = 0.0)
57 {
58 setpoint_ = {position, velocity};
59 pid_.reset();
60 }
61
62 void reset(State state) { reset(state.position, state.velocity); }
63
71 double calculate(double measurement, double dt)
72 {
73 if (dt <= 0.0) return 0.0;
74
75 // Handle continuous input wrapping (e.g., angles)
76 if (continuous_input_)
77 {
78 const double error_bound = (max_input_ - min_input_) / 2.0;
79 const double goal_min = inputModulus(
80 goal_.position - measurement, -error_bound, error_bound);
81 const double setpoint_min = inputModulus(
82 setpoint_.position - measurement, -error_bound, error_bound);
83
84 goal_.position = goal_min + measurement;
85 setpoint_.position = setpoint_min + measurement;
86 }
87
88 // Advance profile: setpoint moves toward goal
89 setpoint_ = TrapezoidalProfile::calculate(dt, setpoint_, goal_, constraints_);
90
91 // Tracking error: where profile says we should be vs where we are
92 double error = setpoint_.position - measurement;
93
94 // Wrap tracking error for continuous input
95 if (continuous_input_)
96 {
97 const double error_bound = (max_input_ - min_input_) / 2.0;
98 error = inputModulus(error, -error_bound, error_bound);
99 }
100
101 const double pid_output = pid_.update(error, dt);
102
103 // Feedforward from profile velocity + PID correction
104 return cfg_.velocity_ff * setpoint_.velocity + pid_output;
105 }
106
110 [[nodiscard]] bool profileComplete() const
111 {
112 return std::abs(goal_.position - setpoint_.position) < kMotionEpsilon
113 && std::abs(goal_.velocity - setpoint_.velocity) < kMotionEpsilon;
114 }
115
116 private:
117 static double inputModulus(double input, double min, double max)
118 {
119 const double range = max - min;
120 double mod = std::fmod(input - min, range);
121 if (mod < 0.0) mod += range;
122 return mod + min;
123 }
124
125 Config cfg_;
126 Constraints constraints_;
128 State goal_{};
129 State setpoint_{};
130
131 bool continuous_input_{false};
132 double min_input_{0.0};
133 double max_input_{0.0};
134 };
135}
Stateful PID controller with integral clamping and a low-pass filtered derivative term.
Definition pid.hpp:69
TrapezoidalProfile::Constraints Constraints
double calculate(double measurement, double dt)
void enableContinuousInput(double min_input, double max_input)
ProfiledPIDController(Config config, Constraints profile_constraints)
void reset(double position, double velocity=0.0)
static State calculate(double dt, const State &current, const State &goal, const Constraints &constraints)
constexpr double kMotionEpsilon