blob: e1a256becc47065806565b7029ee90420f4b05c9 [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
Briana6553ed2014-04-02 21:26:46 -07006#include "aos/common/controls/control_loop.h"
Austin Schuh844960d2013-03-09 17:07:51 -08007#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 // Make sure that reality and the observer can't get too far off. There is a
81 // delay by one cycle between the applied voltage and X_hat(2, 0), so compare
82 // against last cycle's voltage.
83 if (X_hat(2, 0) > last_voltage_ + 2.0) {
Brian Silvermanae9d4b72013-03-16 20:11:29 -070084 //X_hat(2, 0) = last_voltage_ + 2.0;
85 voltage_ -= X_hat(2, 0) - (last_voltage_ + 2.0);
86 LOG(DEBUG, "X_hat(2, 0) = %f\n", X_hat(2, 0));
Austin Schuhc1f68892013-03-16 17:06:27 -070087 } else if (X_hat(2, 0) < last_voltage_ -2.0) {
Brian Silvermanae9d4b72013-03-16 20:11:29 -070088 //X_hat(2, 0) = last_voltage_ - 2.0;
89 voltage_ += X_hat(2, 0) - (last_voltage_ - 2.0);
90 LOG(DEBUG, "X_hat(2, 0) = %f\n", X_hat(2, 0));
Austin Schuhc1f68892013-03-16 17:06:27 -070091 }
92
Brian Silvermanae9d4b72013-03-16 20:11:29 -070093 voltage_ = std::min(limit, voltage_);
94 voltage_ = std::max(-limit, voltage_);
95 U(0, 0) = voltage_ - old_voltage;
96 LOG(DEBUG, "abc %f\n", X_hat(2, 0) - voltage_);
97 LOG(DEBUG, "error %f\n", X_hat(0, 0) - R(0, 0));
98
Austin Schuhc1f68892013-03-16 17:06:27 -070099 last_voltage_ = voltage_;
Austin Schuh844960d2013-03-09 17:07:51 -0800100}
101
102
103// Class to zero and control a joint with any number of zeroing sensors with a
104// state feedback controller.
105template<int kNumZeroSensors>
106class ZeroedJoint {
107 public:
108 // Sturcture to hold the hardware configuration information.
109 struct ConfigurationData {
110 // Angle at the lower hardware limit.
111 double lower_limit;
112 // Angle at the upper hardware limit.
113 double upper_limit;
114 // Speed (and direction) to move while zeroing.
115 double zeroing_speed;
Austin Schuhdd3bc412013-03-16 17:02:40 -0700116 // Speed (and direction) to move while moving off the sensor.
117 double zeroing_off_speed;
Austin Schuh844960d2013-03-09 17:07:51 -0800118 // Maximum voltage to apply when zeroing.
119 double max_zeroing_voltage;
Austin Schuh039d4f92013-04-04 05:52:03 +0000120 // Deadband voltage.
121 double deadband_voltage;
Austin Schuh844960d2013-03-09 17:07:51 -0800122 // Angles where we see a positive edge from the hall effect sensors.
123 double hall_effect_start_angle[kNumZeroSensors];
124 };
125
126 // Current position data for the encoder and hall effect information.
127 struct PositionData {
128 // Current encoder position.
129 double position;
130 // Array of hall effect values.
131 bool hall_effects[kNumZeroSensors];
132 // Array of the last positive edge position for the sensors.
133 double hall_effect_positions[kNumZeroSensors];
134 };
135
Austin Schuhc1f68892013-03-16 17:06:27 -0700136 ZeroedJoint(StateFeedbackLoop<3, 1, 1> loop)
Austin Schuh844960d2013-03-09 17:07:51 -0800137 : loop_(new ZeroedStateFeedbackLoop<kNumZeroSensors>(loop, this)),
Brian Silvermanede2d782013-03-22 18:11:34 -0700138 last_good_time_(0, 0),
Austin Schuh844960d2013-03-09 17:07:51 -0800139 state_(UNINITIALIZED),
140 error_count_(0),
141 zero_offset_(0.0),
142 capped_goal_(false) {
143 }
144
145 // Copies the provided configuration data locally.
146 void set_config_data(const ConfigurationData &config_data) {
147 config_data_ = config_data;
148 }
149
150 // Clips the goal to be inside the limits and returns the clipped goal.
151 // Requires the constants to have already been fetched.
152 double ClipGoal(double goal) const {
153 return ::std::min(config_data_.upper_limit,
154 std::max(config_data_.lower_limit, goal));
155 }
156
157 // Updates the loop and state machine.
158 // position is null if the position data is stale, output_enabled is true if
159 // the output will actually go to the motors, and goal_angle and goal_velocity
160 // are the goal position and velocities.
161 double Update(const ZeroedJoint<kNumZeroSensors>::PositionData *position,
162 bool output_enabled,
163 double goal_angle, double goal_velocity);
164
165 // True if the code is zeroing.
166 bool is_zeroing() const { return state_ == ZEROING; }
167
Austin Schuhe20e93c2013-03-09 19:54:16 -0800168 // True if the code is moving off the hall effect.
169 bool is_moving_off() const { return state_ == MOVING_OFF; }
170
Austin Schuh844960d2013-03-09 17:07:51 -0800171 // True if the state machine is uninitialized.
172 bool is_uninitialized() const { return state_ == UNINITIALIZED; }
173
174 // True if the state machine is ready.
175 bool is_ready() const { return state_ == READY; }
176
177 // Returns the uncapped voltage.
178 double U_uncapped() const { return loop_->U_uncapped(0, 0); }
179
180 // True if the goal was moved to avoid goal windup.
181 bool capped_goal() const { return capped_goal_; }
182
Austin Schuhe20e93c2013-03-09 19:54:16 -0800183 // Timestamp
184 static const double dt;
185
Brian Silverman893ba072013-03-16 14:03:50 -0700186 double absolute_position() const { return loop_->X_hat(0, 0); }
187
Austin Schuh844960d2013-03-09 17:07:51 -0800188 private:
189 friend class ZeroedStateFeedbackLoop<kNumZeroSensors>;
190 // Friend the wrist test cases so that they can simulate windeup.
191 friend class testing::WristTest_NoWindupPositive_Test;
192 friend class testing::WristTest_NoWindupNegative_Test;
193
Brian Silvermanede2d782013-03-22 18:11:34 -0700194 static const ::aos::time::Time kRezeroTime;
195
Austin Schuh844960d2013-03-09 17:07:51 -0800196 // The state feedback control loop to talk to.
197 ::std::unique_ptr<ZeroedStateFeedbackLoop<kNumZeroSensors>> loop_;
198
199 ConfigurationData config_data_;
200
Brian Silvermanede2d782013-03-22 18:11:34 -0700201 ::aos::time::Time last_good_time_;
202
Austin Schuhe20e93c2013-03-09 19:54:16 -0800203 // Returns the index of the first active sensor, or -1 if none are active.
204 int ActiveSensorIndex(
205 const ZeroedJoint<kNumZeroSensors>::PositionData *position) {
206 if (!position) {
207 return -1;
208 }
209 int active_index = -1;
210 for (int i = 0; i < kNumZeroSensors; ++i) {
211 if (position->hall_effects[i]) {
212 if (active_index != -1) {
213 LOG(ERROR, "More than one hall effect sensor is active\n");
214 } else {
215 active_index = i;
216 }
217 }
218 }
219 return active_index;
220 }
221 // Returns true if any of the sensors are active.
222 bool AnySensorsActive(
223 const ZeroedJoint<kNumZeroSensors>::PositionData *position) {
224 return ActiveSensorIndex(position) != -1;
225 }
226
Austin Schuh844960d2013-03-09 17:07:51 -0800227 // Enum to store the state of the internal zeroing state machine.
228 enum State {
229 UNINITIALIZED,
230 MOVING_OFF,
231 ZEROING,
232 READY,
233 ESTOP
234 };
235
236 // Internal state for zeroing.
237 State state_;
238
239 // Missed position packet count.
240 int error_count_;
241 // Offset from the raw encoder value to the absolute angle.
242 double zero_offset_;
243 // Position that gets incremented when zeroing the wrist to slowly move it to
244 // the hall effect sensor.
245 double zeroing_position_;
246 // Last position at which the hall effect sensor was off.
247 double last_off_position_;
248
249 // True if the zeroing goal was capped during this cycle.
250 bool capped_goal_;
251
Austin Schuhe20e93c2013-03-09 19:54:16 -0800252 // Returns true if number is between first and second inclusive.
253 bool is_between(double first, double second, double number) {
254 if ((number >= first || number >= second) &&
255 (number <= first || number <= second)) {
256 return true;
257 }
258 return false;
259 }
260
Austin Schuh844960d2013-03-09 17:07:51 -0800261 DISALLOW_COPY_AND_ASSIGN(ZeroedJoint);
262};
263
Austin Schuhe20e93c2013-03-09 19:54:16 -0800264template <int kNumZeroSensors>
Brian Silvermanede2d782013-03-22 18:11:34 -0700265const ::aos::time::Time ZeroedJoint<kNumZeroSensors>::kRezeroTime =
Brian Silverman96d9cea2013-11-12 21:10:50 -0800266 ::aos::time::Time::InSeconds(2);
Brian Silvermanede2d782013-03-22 18:11:34 -0700267
268template <int kNumZeroSensors>
Austin Schuhe20e93c2013-03-09 19:54:16 -0800269/*static*/ const double ZeroedJoint<kNumZeroSensors>::dt = 0.01;
270
Austin Schuh844960d2013-03-09 17:07:51 -0800271// Updates the zeroed joint controller and state machine.
272template <int kNumZeroSensors>
273double ZeroedJoint<kNumZeroSensors>::Update(
274 const ZeroedJoint<kNumZeroSensors>::PositionData *position,
275 bool output_enabled,
276 double goal_angle, double goal_velocity) {
277 // Uninitialize the bot if too many cycles pass without an encoder.
278 if (position == NULL) {
279 LOG(WARNING, "no new pos given\n");
280 error_count_++;
Austin Schuh844960d2013-03-09 17:07:51 -0800281 }
282 if (error_count_ >= 4) {
Austin Schuh261c4052013-03-19 03:29:54 +0000283 output_enabled = false;
284 LOG(WARNING, "err_count is %d so disabling\n", error_count_);
Brian Silverman7992d6e2013-03-24 19:20:54 -0700285
286 if ((::aos::time::Time::Now() - last_good_time_) > kRezeroTime) {
287 LOG(WARNING, "err_count is %d (or 1st time) so forcing a re-zero\n",
288 error_count_);
289 state_ = UNINITIALIZED;
290 loop_->Reset();
291 }
Austin Schuh844960d2013-03-09 17:07:51 -0800292 }
Brian Silverman7992d6e2013-03-24 19:20:54 -0700293 if (position != NULL) {
Brian Silvermanede2d782013-03-22 18:11:34 -0700294 last_good_time_ = ::aos::time::Time::Now();
Brian Silverman7992d6e2013-03-24 19:20:54 -0700295 error_count_ = 0;
Brian Silvermanede2d782013-03-22 18:11:34 -0700296 }
Austin Schuh844960d2013-03-09 17:07:51 -0800297
298 // Compute the absolute position of the wrist.
299 double absolute_position;
300 if (position) {
301 absolute_position = position->position;
302 if (state_ == READY) {
303 absolute_position -= zero_offset_;
304 }
305 loop_->Y << absolute_position;
Austin Schuhe20e93c2013-03-09 19:54:16 -0800306 if (!AnySensorsActive(position)) {
307 last_off_position_ = position->position;
Austin Schuh844960d2013-03-09 17:07:51 -0800308 }
309 } else {
310 // Dead recon for now.
311 absolute_position = loop_->X_hat(0, 0);
312 }
313
314 switch (state_) {
315 case UNINITIALIZED:
Austin Schuhb5191b92013-03-10 18:22:24 -0700316 LOG(DEBUG, "UNINITIALIZED\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800317 if (position) {
318 // Reset the zeroing goal.
319 zeroing_position_ = absolute_position;
320 // Clear the observer state.
Austin Schuhc1f68892013-03-16 17:06:27 -0700321 loop_->X_hat << absolute_position, 0.0, 0.0;
322 loop_->ZeroPower();
Austin Schuh844960d2013-03-09 17:07:51 -0800323 // Set the goal to here to make it so it doesn't move when disabled.
324 loop_->R = loop_->X_hat;
325 // Only progress if we are enabled.
326 if (::aos::robot_state->enabled) {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800327 if (AnySensorsActive(position)) {
328 state_ = MOVING_OFF;
329 } else {
330 state_ = ZEROING;
Austin Schuh844960d2013-03-09 17:07:51 -0800331 }
332 }
333 }
334 break;
335 case MOVING_OFF:
Austin Schuhb5191b92013-03-10 18:22:24 -0700336 LOG(DEBUG, "MOVING_OFF\n");
Austin Schuhe20e93c2013-03-09 19:54:16 -0800337 {
338 // Move off the hall effect sensor.
339 if (!::aos::robot_state->enabled) {
340 // Start over if disabled.
341 state_ = UNINITIALIZED;
342 } else if (position && !AnySensorsActive(position)) {
343 // We are now off the sensor. Time to zero now.
344 state_ = ZEROING;
345 } else {
346 // Slowly creep off the sensor.
Austin Schuhdd3bc412013-03-16 17:02:40 -0700347 zeroing_position_ -= config_data_.zeroing_off_speed * dt;
Austin Schuhc1f68892013-03-16 17:06:27 -0700348 loop_->R << zeroing_position_, -config_data_.zeroing_off_speed, 0.0;
Austin Schuhe20e93c2013-03-09 19:54:16 -0800349 break;
350 }
Austin Schuh844960d2013-03-09 17:07:51 -0800351 }
352 case ZEROING:
Austin Schuhb5191b92013-03-10 18:22:24 -0700353 LOG(DEBUG, "ZEROING\n");
Austin Schuhe20e93c2013-03-09 19:54:16 -0800354 {
355 int active_sensor_index = ActiveSensorIndex(position);
356 if (!::aos::robot_state->enabled) {
357 // Start over if disabled.
358 state_ = UNINITIALIZED;
359 } else if (position && active_sensor_index != -1) {
360 state_ = READY;
361 // Verify that the calibration number is between the last off position
362 // and the current on position. If this is not true, move off and try
363 // again.
364 const double calibration =
365 position->hall_effect_positions[active_sensor_index];
366 if (!is_between(last_off_position_, position->position,
367 calibration)) {
368 LOG(ERROR, "Got a bogus calibration number. Trying again.\n");
369 LOG(ERROR,
370 "Last off position was %f, current is %f, calibration is %f\n",
371 last_off_position_, position->position,
372 position->hall_effect_positions[active_sensor_index]);
373 state_ = MOVING_OFF;
374 } else {
375 // Save the zero, and then offset the observer to deal with the
376 // phantom step change.
377 const double old_zero_offset = zero_offset_;
378 zero_offset_ =
379 position->hall_effect_positions[active_sensor_index] -
380 config_data_.hall_effect_start_angle[active_sensor_index];
381 loop_->X_hat(0, 0) += old_zero_offset - zero_offset_;
382 loop_->Y(0, 0) += old_zero_offset - zero_offset_;
383 }
Austin Schuh844960d2013-03-09 17:07:51 -0800384 } else {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800385 // Slowly creep towards the sensor.
386 zeroing_position_ += config_data_.zeroing_speed * dt;
Austin Schuhc1f68892013-03-16 17:06:27 -0700387 loop_->R << zeroing_position_, config_data_.zeroing_speed, 0.0;
Austin Schuh844960d2013-03-09 17:07:51 -0800388 }
Austin Schuhe20e93c2013-03-09 19:54:16 -0800389 break;
Austin Schuh844960d2013-03-09 17:07:51 -0800390 }
Austin Schuh844960d2013-03-09 17:07:51 -0800391
392 case READY:
Austin Schuhb5191b92013-03-10 18:22:24 -0700393 LOG(DEBUG, "READY\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800394 {
Austin Schuh844960d2013-03-09 17:07:51 -0800395 const double limited_goal = ClipGoal(goal_angle);
Austin Schuhc1f68892013-03-16 17:06:27 -0700396 loop_->R << limited_goal, goal_velocity, 0.0;
Austin Schuh844960d2013-03-09 17:07:51 -0800397 break;
398 }
399
400 case ESTOP:
Austin Schuhb5191b92013-03-10 18:22:24 -0700401 LOG(DEBUG, "ESTOP\n");
Austin Schuh844960d2013-03-09 17:07:51 -0800402 LOG(WARNING, "have already given up\n");
403 return 0.0;
404 }
405
406 // Update the observer.
Austin Schuh844960d2013-03-09 17:07:51 -0800407 loop_->Update(position != NULL, !output_enabled);
Austin Schuh844960d2013-03-09 17:07:51 -0800408
Brian Silvermanae9d4b72013-03-16 20:11:29 -0700409 LOG(DEBUG, "X_hat={%f, %f, %f}\n",
410 loop_->X_hat(0, 0), loop_->X_hat(1, 0), loop_->X_hat(2, 0));
411
Austin Schuh844960d2013-03-09 17:07:51 -0800412 capped_goal_ = false;
413 // Verify that the zeroing goal hasn't run away.
414 switch (state_) {
415 case UNINITIALIZED:
416 case READY:
417 case ESTOP:
418 // Not zeroing. No worries.
419 break;
420 case MOVING_OFF:
421 case ZEROING:
422 // Check if we have cliped and adjust the goal.
Austin Schuhc1f68892013-03-16 17:06:27 -0700423 if (loop_->uncapped_voltage() > config_data_.max_zeroing_voltage) {
424 double dx = (loop_->uncapped_voltage() -
Austin Schuh844960d2013-03-09 17:07:51 -0800425 config_data_.max_zeroing_voltage) / loop_->K(0, 0);
426 zeroing_position_ -= dx;
427 capped_goal_ = true;
Austin Schuhc1f68892013-03-16 17:06:27 -0700428 } else if(loop_->uncapped_voltage() < -config_data_.max_zeroing_voltage) {
429 double dx = (loop_->uncapped_voltage() +
Austin Schuh844960d2013-03-09 17:07:51 -0800430 config_data_.max_zeroing_voltage) / loop_->K(0, 0);
431 zeroing_position_ -= dx;
432 capped_goal_ = true;
433 }
434 break;
435 }
Austin Schuh261c4052013-03-19 03:29:54 +0000436 if (output_enabled) {
Austin Schuh039d4f92013-04-04 05:52:03 +0000437 double voltage = loop_->voltage();
438 if (voltage > 0) {
439 voltage += config_data_.deadband_voltage;
440 } else if (voltage < 0) {
441 voltage -= config_data_.deadband_voltage;
442 }
443 if (voltage > 12.0) {
444 voltage = 12.0;
445 } else if (voltage < -12.0) {
446 voltage = -12.0;
447 }
448 return voltage;
Austin Schuh261c4052013-03-19 03:29:54 +0000449 } else {
450 return 0.0;
451 }
Austin Schuh844960d2013-03-09 17:07:51 -0800452}
453
454} // namespace control_loops
455} // namespace frc971
456
457#endif // FRC971_CONTROL_LOOPS_ZEROED_JOINT_H_