blob: 582d48b0a31b71fcdad023bde1ffbe11601189b1 [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
Austin Schuhc1f68892013-03-16 17:06:27 -070016// Note: Everything in this file assumes that there is a 1 cycle delay between
17// power being requested and it showing up at the motor. It assumes that
18// X_hat(2, 1) is the voltage being applied as well. It will go unstable if
19// that isn't true.
20
Austin Schuh844960d2013-03-09 17:07:51 -080021template<int kNumZeroSensors>
22class ZeroedJoint;
23
24// This class implements the CapU function correctly given all the extra
25// information that we know about from the wrist motor.
26template<int kNumZeroSensors>
Austin Schuhc1f68892013-03-16 17:06:27 -070027class ZeroedStateFeedbackLoop : public StateFeedbackLoop<3, 1, 1> {
Austin Schuh844960d2013-03-09 17:07:51 -080028 public:
Austin Schuhc1f68892013-03-16 17:06:27 -070029 ZeroedStateFeedbackLoop(StateFeedbackLoop<3, 1, 1> loop,
Austin Schuh844960d2013-03-09 17:07:51 -080030 ZeroedJoint<kNumZeroSensors> *zeroed_joint)
Austin Schuhc1f68892013-03-16 17:06:27 -070031 : StateFeedbackLoop<3, 1, 1>(loop),
32 zeroed_joint_(zeroed_joint),
33 voltage_(0.0),
34 last_voltage_(0.0) {
Austin Schuh844960d2013-03-09 17:07:51 -080035 }
36
37 // Caps U, but this time respects the state of the wrist as well.
38 virtual void CapU();
Austin Schuhc1f68892013-03-16 17:06:27 -070039
40 // Returns the accumulated voltage.
41 double voltage() const { return voltage_; }
42
43 // Returns the uncapped voltage.
44 double uncapped_voltage() const { return uncapped_voltage_; }
45
46 // Zeros the accumulator.
47 void ZeroPower() { voltage_ = 0.0; }
Austin Schuh844960d2013-03-09 17:07:51 -080048 private:
49 ZeroedJoint<kNumZeroSensors> *zeroed_joint_;
Austin Schuhc1f68892013-03-16 17:06:27 -070050
51 // The accumulated voltage to apply to the motor.
52 double voltage_;
53 double last_voltage_;
54 double uncapped_voltage_;
Austin Schuh844960d2013-03-09 17:07:51 -080055};
56
57template<int kNumZeroSensors>
58void ZeroedStateFeedbackLoop<kNumZeroSensors>::CapU() {
Austin Schuhc1f68892013-03-16 17:06:27 -070059 const double old_voltage = voltage_;
60 voltage_ += U(0, 0);
61
62 uncapped_voltage_ = voltage_;
63
64 // Do all our computations with the voltage, and then compute what the delta
65 // is to make that happen.
Austin Schuh844960d2013-03-09 17:07:51 -080066 if (zeroed_joint_->state_ == ZeroedJoint<kNumZeroSensors>::READY) {
67 if (Y(0, 0) >= zeroed_joint_->config_data_.upper_limit) {
Austin Schuhc1f68892013-03-16 17:06:27 -070068 voltage_ = std::min(0.0, voltage_);
Austin Schuh844960d2013-03-09 17:07:51 -080069 }
70 if (Y(0, 0) <= zeroed_joint_->config_data_.lower_limit) {
Austin Schuhc1f68892013-03-16 17:06:27 -070071 voltage_ = std::max(0.0, voltage_);
Austin Schuh844960d2013-03-09 17:07:51 -080072 }
73 }
74
75 const bool is_ready =
76 zeroed_joint_->state_ == ZeroedJoint<kNumZeroSensors>::READY;
77 double limit = is_ready ?
78 12.0 : zeroed_joint_->config_data_.max_zeroing_voltage;
79
Austin Schuhc1f68892013-03-16 17:06:27 -070080 voltage_ = std::min(limit, voltage_);
81 voltage_ = std::max(-limit, voltage_);
82 U(0, 0) = voltage_ - old_voltage;
83
84 // Make sure that reality and the observer can't get too far off. There is a
85 // delay by one cycle between the applied voltage and X_hat(2, 0), so compare
86 // against last cycle's voltage.
87 if (X_hat(2, 0) > last_voltage_ + 2.0) {
88 X_hat(2, 0) = last_voltage_ + 2.0;
89 } else if (X_hat(2, 0) < last_voltage_ -2.0) {
90 X_hat(2, 0) = last_voltage_ - 2.0;
91 }
92
93 last_voltage_ = voltage_;
Austin Schuh844960d2013-03-09 17:07:51 -080094}
95
96
97// Class to zero and control a joint with any number of zeroing sensors with a
98// state feedback controller.
99template<int kNumZeroSensors>
100class ZeroedJoint {
101 public:
102 // Sturcture to hold the hardware configuration information.
103 struct ConfigurationData {
104 // Angle at the lower hardware limit.
105 double lower_limit;
106 // Angle at the upper hardware limit.
107 double upper_limit;
108 // Speed (and direction) to move while zeroing.
109 double zeroing_speed;
Austin Schuhdd3bc412013-03-16 17:02:40 -0700110 // Speed (and direction) to move while moving off the sensor.
111 double zeroing_off_speed;
Austin Schuh844960d2013-03-09 17:07:51 -0800112 // Maximum voltage to apply when zeroing.
113 double max_zeroing_voltage;
114 // Angles where we see a positive edge from the hall effect sensors.
115 double hall_effect_start_angle[kNumZeroSensors];
116 };
117
118 // Current position data for the encoder and hall effect information.
119 struct PositionData {
120 // Current encoder position.
121 double position;
122 // Array of hall effect values.
123 bool hall_effects[kNumZeroSensors];
124 // Array of the last positive edge position for the sensors.
125 double hall_effect_positions[kNumZeroSensors];
126 };
127
Austin Schuhc1f68892013-03-16 17:06:27 -0700128 ZeroedJoint(StateFeedbackLoop<3, 1, 1> loop)
Austin Schuh844960d2013-03-09 17:07:51 -0800129 : loop_(new ZeroedStateFeedbackLoop<kNumZeroSensors>(loop, this)),
130 state_(UNINITIALIZED),
131 error_count_(0),
132 zero_offset_(0.0),
133 capped_goal_(false) {
134 }
135
136 // Copies the provided configuration data locally.
137 void set_config_data(const ConfigurationData &config_data) {
138 config_data_ = config_data;
139 }
140
141 // Clips the goal to be inside the limits and returns the clipped goal.
142 // Requires the constants to have already been fetched.
143 double ClipGoal(double goal) const {
144 return ::std::min(config_data_.upper_limit,
145 std::max(config_data_.lower_limit, goal));
146 }
147
148 // Updates the loop and state machine.
149 // position is null if the position data is stale, output_enabled is true if
150 // the output will actually go to the motors, and goal_angle and goal_velocity
151 // are the goal position and velocities.
152 double Update(const ZeroedJoint<kNumZeroSensors>::PositionData *position,
153 bool output_enabled,
154 double goal_angle, double goal_velocity);
155
156 // True if the code is zeroing.
157 bool is_zeroing() const { return state_ == ZEROING; }
158
Austin Schuhe20e93c2013-03-09 19:54:16 -0800159 // True if the code is moving off the hall effect.
160 bool is_moving_off() const { return state_ == MOVING_OFF; }
161
Austin Schuh844960d2013-03-09 17:07:51 -0800162 // True if the state machine is uninitialized.
163 bool is_uninitialized() const { return state_ == UNINITIALIZED; }
164
165 // True if the state machine is ready.
166 bool is_ready() const { return state_ == READY; }
167
168 // Returns the uncapped voltage.
169 double U_uncapped() const { return loop_->U_uncapped(0, 0); }
170
171 // True if the goal was moved to avoid goal windup.
172 bool capped_goal() const { return capped_goal_; }
173
Austin Schuhe20e93c2013-03-09 19:54:16 -0800174 // Timestamp
175 static const double dt;
176
Brian Silverman893ba072013-03-16 14:03:50 -0700177 double absolute_position() const { return loop_->X_hat(0, 0); }
178
Austin Schuh844960d2013-03-09 17:07:51 -0800179 private:
180 friend class ZeroedStateFeedbackLoop<kNumZeroSensors>;
181 // Friend the wrist test cases so that they can simulate windeup.
182 friend class testing::WristTest_NoWindupPositive_Test;
183 friend class testing::WristTest_NoWindupNegative_Test;
184
185 // The state feedback control loop to talk to.
186 ::std::unique_ptr<ZeroedStateFeedbackLoop<kNumZeroSensors>> loop_;
187
188 ConfigurationData config_data_;
189
Austin Schuhe20e93c2013-03-09 19:54:16 -0800190 // Returns the index of the first active sensor, or -1 if none are active.
191 int ActiveSensorIndex(
192 const ZeroedJoint<kNumZeroSensors>::PositionData *position) {
193 if (!position) {
194 return -1;
195 }
196 int active_index = -1;
197 for (int i = 0; i < kNumZeroSensors; ++i) {
198 if (position->hall_effects[i]) {
199 if (active_index != -1) {
200 LOG(ERROR, "More than one hall effect sensor is active\n");
201 } else {
202 active_index = i;
203 }
204 }
205 }
206 return active_index;
207 }
208 // Returns true if any of the sensors are active.
209 bool AnySensorsActive(
210 const ZeroedJoint<kNumZeroSensors>::PositionData *position) {
211 return ActiveSensorIndex(position) != -1;
212 }
213
Austin Schuh844960d2013-03-09 17:07:51 -0800214 // Enum to store the state of the internal zeroing state machine.
215 enum State {
216 UNINITIALIZED,
217 MOVING_OFF,
218 ZEROING,
219 READY,
220 ESTOP
221 };
222
223 // Internal state for zeroing.
224 State state_;
225
226 // Missed position packet count.
227 int error_count_;
228 // Offset from the raw encoder value to the absolute angle.
229 double zero_offset_;
230 // Position that gets incremented when zeroing the wrist to slowly move it to
231 // the hall effect sensor.
232 double zeroing_position_;
233 // Last position at which the hall effect sensor was off.
234 double last_off_position_;
235
236 // True if the zeroing goal was capped during this cycle.
237 bool capped_goal_;
238
Austin Schuhe20e93c2013-03-09 19:54:16 -0800239 // Returns true if number is between first and second inclusive.
240 bool is_between(double first, double second, double number) {
241 if ((number >= first || number >= second) &&
242 (number <= first || number <= second)) {
243 return true;
244 }
245 return false;
246 }
247
Austin Schuh844960d2013-03-09 17:07:51 -0800248 DISALLOW_COPY_AND_ASSIGN(ZeroedJoint);
249};
250
Austin Schuhe20e93c2013-03-09 19:54:16 -0800251template <int kNumZeroSensors>
252/*static*/ const double ZeroedJoint<kNumZeroSensors>::dt = 0.01;
253
Austin Schuh844960d2013-03-09 17:07:51 -0800254// Updates the zeroed joint controller and state machine.
255template <int kNumZeroSensors>
256double ZeroedJoint<kNumZeroSensors>::Update(
257 const ZeroedJoint<kNumZeroSensors>::PositionData *position,
258 bool output_enabled,
259 double goal_angle, double goal_velocity) {
260 // Uninitialize the bot if too many cycles pass without an encoder.
261 if (position == NULL) {
262 LOG(WARNING, "no new pos given\n");
263 error_count_++;
264 } else {
265 error_count_ = 0;
266 }
267 if (error_count_ >= 4) {
268 LOG(WARNING, "err_count is %d so forcing a re-zero\n", error_count_);
269 state_ = UNINITIALIZED;
270 }
271
272 // Compute the absolute position of the wrist.
273 double absolute_position;
274 if (position) {
275 absolute_position = position->position;
276 if (state_ == READY) {
277 absolute_position -= zero_offset_;
278 }
279 loop_->Y << absolute_position;
Austin Schuhe20e93c2013-03-09 19:54:16 -0800280 if (!AnySensorsActive(position)) {
281 last_off_position_ = position->position;
Austin Schuh844960d2013-03-09 17:07:51 -0800282 }
283 } else {
284 // Dead recon for now.
285 absolute_position = loop_->X_hat(0, 0);
286 }
287
288 switch (state_) {
289 case UNINITIALIZED:
Austin Schuhb5191b92013-03-10 18:22:24 -0700290 LOG(DEBUG, "UNINITIALIZED\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800291 if (position) {
292 // Reset the zeroing goal.
293 zeroing_position_ = absolute_position;
294 // Clear the observer state.
Austin Schuhc1f68892013-03-16 17:06:27 -0700295 loop_->X_hat << absolute_position, 0.0, 0.0;
296 loop_->ZeroPower();
Austin Schuh844960d2013-03-09 17:07:51 -0800297 // Set the goal to here to make it so it doesn't move when disabled.
298 loop_->R = loop_->X_hat;
299 // Only progress if we are enabled.
300 if (::aos::robot_state->enabled) {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800301 if (AnySensorsActive(position)) {
302 state_ = MOVING_OFF;
303 } else {
304 state_ = ZEROING;
Austin Schuh844960d2013-03-09 17:07:51 -0800305 }
306 }
307 }
308 break;
309 case MOVING_OFF:
Austin Schuhb5191b92013-03-10 18:22:24 -0700310 LOG(DEBUG, "MOVING_OFF\n");
Austin Schuhe20e93c2013-03-09 19:54:16 -0800311 {
312 // Move off the hall effect sensor.
313 if (!::aos::robot_state->enabled) {
314 // Start over if disabled.
315 state_ = UNINITIALIZED;
316 } else if (position && !AnySensorsActive(position)) {
317 // We are now off the sensor. Time to zero now.
318 state_ = ZEROING;
319 } else {
320 // Slowly creep off the sensor.
Austin Schuhdd3bc412013-03-16 17:02:40 -0700321 zeroing_position_ -= config_data_.zeroing_off_speed * dt;
Austin Schuhc1f68892013-03-16 17:06:27 -0700322 loop_->R << zeroing_position_, -config_data_.zeroing_off_speed, 0.0;
Austin Schuhe20e93c2013-03-09 19:54:16 -0800323 break;
324 }
Austin Schuh844960d2013-03-09 17:07:51 -0800325 }
326 case ZEROING:
Austin Schuhb5191b92013-03-10 18:22:24 -0700327 LOG(DEBUG, "ZEROING\n");
Austin Schuhe20e93c2013-03-09 19:54:16 -0800328 {
329 int active_sensor_index = ActiveSensorIndex(position);
330 if (!::aos::robot_state->enabled) {
331 // Start over if disabled.
332 state_ = UNINITIALIZED;
333 } else if (position && active_sensor_index != -1) {
334 state_ = READY;
335 // Verify that the calibration number is between the last off position
336 // and the current on position. If this is not true, move off and try
337 // again.
338 const double calibration =
339 position->hall_effect_positions[active_sensor_index];
340 if (!is_between(last_off_position_, position->position,
341 calibration)) {
342 LOG(ERROR, "Got a bogus calibration number. Trying again.\n");
343 LOG(ERROR,
344 "Last off position was %f, current is %f, calibration is %f\n",
345 last_off_position_, position->position,
346 position->hall_effect_positions[active_sensor_index]);
347 state_ = MOVING_OFF;
348 } else {
349 // Save the zero, and then offset the observer to deal with the
350 // phantom step change.
351 const double old_zero_offset = zero_offset_;
352 zero_offset_ =
353 position->hall_effect_positions[active_sensor_index] -
354 config_data_.hall_effect_start_angle[active_sensor_index];
355 loop_->X_hat(0, 0) += old_zero_offset - zero_offset_;
356 loop_->Y(0, 0) += old_zero_offset - zero_offset_;
357 }
Austin Schuh844960d2013-03-09 17:07:51 -0800358 } else {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800359 // Slowly creep towards the sensor.
360 zeroing_position_ += config_data_.zeroing_speed * dt;
Austin Schuhc1f68892013-03-16 17:06:27 -0700361 loop_->R << zeroing_position_, config_data_.zeroing_speed, 0.0;
Austin Schuh844960d2013-03-09 17:07:51 -0800362 }
Austin Schuhe20e93c2013-03-09 19:54:16 -0800363 break;
Austin Schuh844960d2013-03-09 17:07:51 -0800364 }
Austin Schuh844960d2013-03-09 17:07:51 -0800365
366 case READY:
Austin Schuhb5191b92013-03-10 18:22:24 -0700367 LOG(DEBUG, "READY\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800368 {
Austin Schuh844960d2013-03-09 17:07:51 -0800369 const double limited_goal = ClipGoal(goal_angle);
Austin Schuhc1f68892013-03-16 17:06:27 -0700370 loop_->R << limited_goal, goal_velocity, 0.0;
Austin Schuh844960d2013-03-09 17:07:51 -0800371 break;
372 }
373
374 case ESTOP:
Austin Schuhb5191b92013-03-10 18:22:24 -0700375 LOG(DEBUG, "ESTOP\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800376 LOG(WARNING, "have already given up\n");
377 return 0.0;
378 }
379
380 // Update the observer.
Austin Schuh844960d2013-03-09 17:07:51 -0800381 loop_->Update(position != NULL, !output_enabled);
Austin Schuh844960d2013-03-09 17:07:51 -0800382
383 capped_goal_ = false;
384 // Verify that the zeroing goal hasn't run away.
385 switch (state_) {
386 case UNINITIALIZED:
387 case READY:
388 case ESTOP:
389 // Not zeroing. No worries.
390 break;
391 case MOVING_OFF:
392 case ZEROING:
393 // Check if we have cliped and adjust the goal.
Austin Schuhc1f68892013-03-16 17:06:27 -0700394 if (loop_->uncapped_voltage() > config_data_.max_zeroing_voltage) {
395 double dx = (loop_->uncapped_voltage() -
Austin Schuh844960d2013-03-09 17:07:51 -0800396 config_data_.max_zeroing_voltage) / loop_->K(0, 0);
397 zeroing_position_ -= dx;
398 capped_goal_ = true;
Austin Schuhc1f68892013-03-16 17:06:27 -0700399 } else if(loop_->uncapped_voltage() < -config_data_.max_zeroing_voltage) {
400 double dx = (loop_->uncapped_voltage() +
Austin Schuh844960d2013-03-09 17:07:51 -0800401 config_data_.max_zeroing_voltage) / loop_->K(0, 0);
402 zeroing_position_ -= dx;
403 capped_goal_ = true;
404 }
405 break;
406 }
Austin Schuhc1f68892013-03-16 17:06:27 -0700407 return loop_->voltage();
Austin Schuh844960d2013-03-09 17:07:51 -0800408}
409
410} // namespace control_loops
411} // namespace frc971
412
413#endif // FRC971_CONTROL_LOOPS_ZEROED_JOINT_H_