blob: 32306221e826f6698f780694f90a3285752c7a7b [file] [log] [blame]
Austin Schuh844960d2013-03-09 17:07:51 -08001#ifndef FRC971_CONTROL_LOOPS_ZEROED_JOINT_H_
2#define FRC971_CONTROL_LOOPS_ZEROED_JOINT_H_
3
4#include <memory>
5
6#include "aos/common/control_loop/ControlLoop.h"
7#include "frc971/control_loops/state_feedback_loop.h"
Austin Schuh844960d2013-03-09 17:07:51 -08008
9namespace frc971 {
10namespace control_loops {
11namespace testing {
12class WristTest_NoWindupPositive_Test;
13class WristTest_NoWindupNegative_Test;
14};
15
16template<int kNumZeroSensors>
17class ZeroedJoint;
18
19// This class implements the CapU function correctly given all the extra
20// information that we know about from the wrist motor.
21template<int kNumZeroSensors>
22class ZeroedStateFeedbackLoop : public StateFeedbackLoop<2, 1, 1> {
23 public:
24 ZeroedStateFeedbackLoop(StateFeedbackLoop<2, 1, 1> loop,
25 ZeroedJoint<kNumZeroSensors> *zeroed_joint)
26 : StateFeedbackLoop<2, 1, 1>(loop),
27 zeroed_joint_(zeroed_joint) {
28 }
29
30 // Caps U, but this time respects the state of the wrist as well.
31 virtual void CapU();
32 private:
33 ZeroedJoint<kNumZeroSensors> *zeroed_joint_;
34};
35
36template<int kNumZeroSensors>
37void ZeroedStateFeedbackLoop<kNumZeroSensors>::CapU() {
38 if (zeroed_joint_->state_ == ZeroedJoint<kNumZeroSensors>::READY) {
39 if (Y(0, 0) >= zeroed_joint_->config_data_.upper_limit) {
40 U(0, 0) = std::min(0.0, U(0, 0));
41 }
42 if (Y(0, 0) <= zeroed_joint_->config_data_.lower_limit) {
43 U(0, 0) = std::max(0.0, U(0, 0));
44 }
45 }
46
47 const bool is_ready =
48 zeroed_joint_->state_ == ZeroedJoint<kNumZeroSensors>::READY;
49 double limit = is_ready ?
50 12.0 : zeroed_joint_->config_data_.max_zeroing_voltage;
51
52 U(0, 0) = std::min(limit, U(0, 0));
53 U(0, 0) = std::max(-limit, U(0, 0));
54}
55
56
57// Class to zero and control a joint with any number of zeroing sensors with a
58// state feedback controller.
59template<int kNumZeroSensors>
60class ZeroedJoint {
61 public:
62 // Sturcture to hold the hardware configuration information.
63 struct ConfigurationData {
64 // Angle at the lower hardware limit.
65 double lower_limit;
66 // Angle at the upper hardware limit.
67 double upper_limit;
68 // Speed (and direction) to move while zeroing.
69 double zeroing_speed;
Austin Schuhdd3bc412013-03-16 17:02:40 -070070 // Speed (and direction) to move while moving off the sensor.
71 double zeroing_off_speed;
Austin Schuh844960d2013-03-09 17:07:51 -080072 // Maximum voltage to apply when zeroing.
73 double max_zeroing_voltage;
74 // Angles where we see a positive edge from the hall effect sensors.
75 double hall_effect_start_angle[kNumZeroSensors];
76 };
77
78 // Current position data for the encoder and hall effect information.
79 struct PositionData {
80 // Current encoder position.
81 double position;
82 // Array of hall effect values.
83 bool hall_effects[kNumZeroSensors];
84 // Array of the last positive edge position for the sensors.
85 double hall_effect_positions[kNumZeroSensors];
86 };
87
88 ZeroedJoint(StateFeedbackLoop<2, 1, 1> loop)
89 : loop_(new ZeroedStateFeedbackLoop<kNumZeroSensors>(loop, this)),
90 state_(UNINITIALIZED),
91 error_count_(0),
92 zero_offset_(0.0),
93 capped_goal_(false) {
94 }
95
96 // Copies the provided configuration data locally.
97 void set_config_data(const ConfigurationData &config_data) {
98 config_data_ = config_data;
99 }
100
101 // Clips the goal to be inside the limits and returns the clipped goal.
102 // Requires the constants to have already been fetched.
103 double ClipGoal(double goal) const {
104 return ::std::min(config_data_.upper_limit,
105 std::max(config_data_.lower_limit, goal));
106 }
107
108 // Updates the loop and state machine.
109 // position is null if the position data is stale, output_enabled is true if
110 // the output will actually go to the motors, and goal_angle and goal_velocity
111 // are the goal position and velocities.
112 double Update(const ZeroedJoint<kNumZeroSensors>::PositionData *position,
113 bool output_enabled,
114 double goal_angle, double goal_velocity);
115
116 // True if the code is zeroing.
117 bool is_zeroing() const { return state_ == ZEROING; }
118
Austin Schuhe20e93c2013-03-09 19:54:16 -0800119 // True if the code is moving off the hall effect.
120 bool is_moving_off() const { return state_ == MOVING_OFF; }
121
Austin Schuh844960d2013-03-09 17:07:51 -0800122 // True if the state machine is uninitialized.
123 bool is_uninitialized() const { return state_ == UNINITIALIZED; }
124
125 // True if the state machine is ready.
126 bool is_ready() const { return state_ == READY; }
127
128 // Returns the uncapped voltage.
129 double U_uncapped() const { return loop_->U_uncapped(0, 0); }
130
131 // True if the goal was moved to avoid goal windup.
132 bool capped_goal() const { return capped_goal_; }
133
Austin Schuhe20e93c2013-03-09 19:54:16 -0800134 // Timestamp
135 static const double dt;
136
Austin Schuh844960d2013-03-09 17:07:51 -0800137 private:
138 friend class ZeroedStateFeedbackLoop<kNumZeroSensors>;
139 // Friend the wrist test cases so that they can simulate windeup.
140 friend class testing::WristTest_NoWindupPositive_Test;
141 friend class testing::WristTest_NoWindupNegative_Test;
142
143 // The state feedback control loop to talk to.
144 ::std::unique_ptr<ZeroedStateFeedbackLoop<kNumZeroSensors>> loop_;
145
146 ConfigurationData config_data_;
147
Austin Schuhe20e93c2013-03-09 19:54:16 -0800148 // Returns the index of the first active sensor, or -1 if none are active.
149 int ActiveSensorIndex(
150 const ZeroedJoint<kNumZeroSensors>::PositionData *position) {
151 if (!position) {
152 return -1;
153 }
154 int active_index = -1;
155 for (int i = 0; i < kNumZeroSensors; ++i) {
156 if (position->hall_effects[i]) {
157 if (active_index != -1) {
158 LOG(ERROR, "More than one hall effect sensor is active\n");
159 } else {
160 active_index = i;
161 }
162 }
163 }
164 return active_index;
165 }
166 // Returns true if any of the sensors are active.
167 bool AnySensorsActive(
168 const ZeroedJoint<kNumZeroSensors>::PositionData *position) {
169 return ActiveSensorIndex(position) != -1;
170 }
171
Austin Schuh844960d2013-03-09 17:07:51 -0800172 // Enum to store the state of the internal zeroing state machine.
173 enum State {
174 UNINITIALIZED,
175 MOVING_OFF,
176 ZEROING,
177 READY,
178 ESTOP
179 };
180
181 // Internal state for zeroing.
182 State state_;
183
184 // Missed position packet count.
185 int error_count_;
186 // Offset from the raw encoder value to the absolute angle.
187 double zero_offset_;
188 // Position that gets incremented when zeroing the wrist to slowly move it to
189 // the hall effect sensor.
190 double zeroing_position_;
191 // Last position at which the hall effect sensor was off.
192 double last_off_position_;
193
194 // True if the zeroing goal was capped during this cycle.
195 bool capped_goal_;
196
Austin Schuhe20e93c2013-03-09 19:54:16 -0800197 // Returns true if number is between first and second inclusive.
198 bool is_between(double first, double second, double number) {
199 if ((number >= first || number >= second) &&
200 (number <= first || number <= second)) {
201 return true;
202 }
203 return false;
204 }
205
Austin Schuh844960d2013-03-09 17:07:51 -0800206 DISALLOW_COPY_AND_ASSIGN(ZeroedJoint);
207};
208
Austin Schuhe20e93c2013-03-09 19:54:16 -0800209template <int kNumZeroSensors>
210/*static*/ const double ZeroedJoint<kNumZeroSensors>::dt = 0.01;
211
Austin Schuh844960d2013-03-09 17:07:51 -0800212// Updates the zeroed joint controller and state machine.
213template <int kNumZeroSensors>
214double ZeroedJoint<kNumZeroSensors>::Update(
215 const ZeroedJoint<kNumZeroSensors>::PositionData *position,
216 bool output_enabled,
217 double goal_angle, double goal_velocity) {
218 // Uninitialize the bot if too many cycles pass without an encoder.
219 if (position == NULL) {
220 LOG(WARNING, "no new pos given\n");
221 error_count_++;
222 } else {
223 error_count_ = 0;
224 }
225 if (error_count_ >= 4) {
226 LOG(WARNING, "err_count is %d so forcing a re-zero\n", error_count_);
227 state_ = UNINITIALIZED;
228 }
229
230 // Compute the absolute position of the wrist.
231 double absolute_position;
232 if (position) {
233 absolute_position = position->position;
234 if (state_ == READY) {
235 absolute_position -= zero_offset_;
236 }
237 loop_->Y << absolute_position;
Austin Schuhe20e93c2013-03-09 19:54:16 -0800238 if (!AnySensorsActive(position)) {
239 last_off_position_ = position->position;
Austin Schuh844960d2013-03-09 17:07:51 -0800240 }
241 } else {
242 // Dead recon for now.
243 absolute_position = loop_->X_hat(0, 0);
244 }
245
246 switch (state_) {
247 case UNINITIALIZED:
Austin Schuhb5191b92013-03-10 18:22:24 -0700248 LOG(DEBUG, "UNINITIALIZED\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800249 if (position) {
250 // Reset the zeroing goal.
251 zeroing_position_ = absolute_position;
252 // Clear the observer state.
253 loop_->X_hat << absolute_position, 0.0;
254 // Set the goal to here to make it so it doesn't move when disabled.
255 loop_->R = loop_->X_hat;
256 // Only progress if we are enabled.
257 if (::aos::robot_state->enabled) {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800258 if (AnySensorsActive(position)) {
259 state_ = MOVING_OFF;
260 } else {
261 state_ = ZEROING;
Austin Schuh844960d2013-03-09 17:07:51 -0800262 }
263 }
264 }
265 break;
266 case MOVING_OFF:
Austin Schuhb5191b92013-03-10 18:22:24 -0700267 LOG(DEBUG, "MOVING_OFF\n");
Austin Schuhe20e93c2013-03-09 19:54:16 -0800268 {
269 // Move off the hall effect sensor.
270 if (!::aos::robot_state->enabled) {
271 // Start over if disabled.
272 state_ = UNINITIALIZED;
273 } else if (position && !AnySensorsActive(position)) {
274 // We are now off the sensor. Time to zero now.
275 state_ = ZEROING;
276 } else {
277 // Slowly creep off the sensor.
Austin Schuhdd3bc412013-03-16 17:02:40 -0700278 zeroing_position_ -= config_data_.zeroing_off_speed * dt;
279 loop_->R << zeroing_position_, -config_data_.zeroing_off_speed;
Austin Schuhe20e93c2013-03-09 19:54:16 -0800280 break;
281 }
Austin Schuh844960d2013-03-09 17:07:51 -0800282 }
283 case ZEROING:
Austin Schuhb5191b92013-03-10 18:22:24 -0700284 LOG(DEBUG, "ZEROING\n");
Austin Schuhe20e93c2013-03-09 19:54:16 -0800285 {
286 int active_sensor_index = ActiveSensorIndex(position);
287 if (!::aos::robot_state->enabled) {
288 // Start over if disabled.
289 state_ = UNINITIALIZED;
290 } else if (position && active_sensor_index != -1) {
291 state_ = READY;
292 // Verify that the calibration number is between the last off position
293 // and the current on position. If this is not true, move off and try
294 // again.
295 const double calibration =
296 position->hall_effect_positions[active_sensor_index];
297 if (!is_between(last_off_position_, position->position,
298 calibration)) {
299 LOG(ERROR, "Got a bogus calibration number. Trying again.\n");
300 LOG(ERROR,
301 "Last off position was %f, current is %f, calibration is %f\n",
302 last_off_position_, position->position,
303 position->hall_effect_positions[active_sensor_index]);
304 state_ = MOVING_OFF;
305 } else {
306 // Save the zero, and then offset the observer to deal with the
307 // phantom step change.
308 const double old_zero_offset = zero_offset_;
309 zero_offset_ =
310 position->hall_effect_positions[active_sensor_index] -
311 config_data_.hall_effect_start_angle[active_sensor_index];
312 loop_->X_hat(0, 0) += old_zero_offset - zero_offset_;
313 loop_->Y(0, 0) += old_zero_offset - zero_offset_;
314 }
Austin Schuh844960d2013-03-09 17:07:51 -0800315 } else {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800316 // Slowly creep towards the sensor.
317 zeroing_position_ += config_data_.zeroing_speed * dt;
318 loop_->R << zeroing_position_, config_data_.zeroing_speed;
Austin Schuh844960d2013-03-09 17:07:51 -0800319 }
Austin Schuhe20e93c2013-03-09 19:54:16 -0800320 break;
Austin Schuh844960d2013-03-09 17:07:51 -0800321 }
Austin Schuh844960d2013-03-09 17:07:51 -0800322
323 case READY:
Austin Schuhb5191b92013-03-10 18:22:24 -0700324 LOG(DEBUG, "READY\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800325 {
Austin Schuh844960d2013-03-09 17:07:51 -0800326 const double limited_goal = ClipGoal(goal_angle);
327 loop_->R << limited_goal, goal_velocity;
328 break;
329 }
330
331 case ESTOP:
Austin Schuhb5191b92013-03-10 18:22:24 -0700332 LOG(DEBUG, "ESTOP\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800333 LOG(WARNING, "have already given up\n");
334 return 0.0;
335 }
336
337 // Update the observer.
Austin Schuh844960d2013-03-09 17:07:51 -0800338 loop_->Update(position != NULL, !output_enabled);
Austin Schuh844960d2013-03-09 17:07:51 -0800339
340 capped_goal_ = false;
341 // Verify that the zeroing goal hasn't run away.
342 switch (state_) {
343 case UNINITIALIZED:
344 case READY:
345 case ESTOP:
346 // Not zeroing. No worries.
347 break;
348 case MOVING_OFF:
349 case ZEROING:
350 // Check if we have cliped and adjust the goal.
351 if (loop_->U_uncapped(0, 0) > config_data_.max_zeroing_voltage) {
352 double dx = (loop_->U_uncapped(0, 0) -
353 config_data_.max_zeroing_voltage) / loop_->K(0, 0);
354 zeroing_position_ -= dx;
355 capped_goal_ = true;
356 } else if(loop_->U_uncapped(0, 0) < -config_data_.max_zeroing_voltage) {
357 double dx = (loop_->U_uncapped(0, 0) +
358 config_data_.max_zeroing_voltage) / loop_->K(0, 0);
359 zeroing_position_ -= dx;
360 capped_goal_ = true;
361 }
362 break;
363 }
364 return loop_->U(0, 0);
365}
366
367} // namespace control_loops
368} // namespace frc971
369
370#endif // FRC971_CONTROL_LOOPS_ZEROED_JOINT_H_