blob: 89a3c7b8aa97a2040e7857f9604e92ac6e4a5720 [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;
70 // Maximum voltage to apply when zeroing.
71 double max_zeroing_voltage;
72 // Angles where we see a positive edge from the hall effect sensors.
73 double hall_effect_start_angle[kNumZeroSensors];
74 };
75
76 // Current position data for the encoder and hall effect information.
77 struct PositionData {
78 // Current encoder position.
79 double position;
80 // Array of hall effect values.
81 bool hall_effects[kNumZeroSensors];
82 // Array of the last positive edge position for the sensors.
83 double hall_effect_positions[kNumZeroSensors];
84 };
85
86 ZeroedJoint(StateFeedbackLoop<2, 1, 1> loop)
87 : loop_(new ZeroedStateFeedbackLoop<kNumZeroSensors>(loop, this)),
88 state_(UNINITIALIZED),
89 error_count_(0),
90 zero_offset_(0.0),
91 capped_goal_(false) {
92 }
93
94 // Copies the provided configuration data locally.
95 void set_config_data(const ConfigurationData &config_data) {
96 config_data_ = config_data;
97 }
98
99 // Clips the goal to be inside the limits and returns the clipped goal.
100 // Requires the constants to have already been fetched.
101 double ClipGoal(double goal) const {
102 return ::std::min(config_data_.upper_limit,
103 std::max(config_data_.lower_limit, goal));
104 }
105
106 // Updates the loop and state machine.
107 // position is null if the position data is stale, output_enabled is true if
108 // the output will actually go to the motors, and goal_angle and goal_velocity
109 // are the goal position and velocities.
110 double Update(const ZeroedJoint<kNumZeroSensors>::PositionData *position,
111 bool output_enabled,
112 double goal_angle, double goal_velocity);
113
114 // True if the code is zeroing.
115 bool is_zeroing() const { return state_ == ZEROING; }
116
Austin Schuhe20e93c2013-03-09 19:54:16 -0800117 // True if the code is moving off the hall effect.
118 bool is_moving_off() const { return state_ == MOVING_OFF; }
119
Austin Schuh844960d2013-03-09 17:07:51 -0800120 // True if the state machine is uninitialized.
121 bool is_uninitialized() const { return state_ == UNINITIALIZED; }
122
123 // True if the state machine is ready.
124 bool is_ready() const { return state_ == READY; }
125
126 // Returns the uncapped voltage.
127 double U_uncapped() const { return loop_->U_uncapped(0, 0); }
128
129 // True if the goal was moved to avoid goal windup.
130 bool capped_goal() const { return capped_goal_; }
131
Austin Schuhe20e93c2013-03-09 19:54:16 -0800132 // Timestamp
133 static const double dt;
134
Austin Schuh844960d2013-03-09 17:07:51 -0800135 private:
136 friend class ZeroedStateFeedbackLoop<kNumZeroSensors>;
137 // Friend the wrist test cases so that they can simulate windeup.
138 friend class testing::WristTest_NoWindupPositive_Test;
139 friend class testing::WristTest_NoWindupNegative_Test;
140
141 // The state feedback control loop to talk to.
142 ::std::unique_ptr<ZeroedStateFeedbackLoop<kNumZeroSensors>> loop_;
143
144 ConfigurationData config_data_;
145
Austin Schuhe20e93c2013-03-09 19:54:16 -0800146 // Returns the index of the first active sensor, or -1 if none are active.
147 int ActiveSensorIndex(
148 const ZeroedJoint<kNumZeroSensors>::PositionData *position) {
149 if (!position) {
150 return -1;
151 }
152 int active_index = -1;
153 for (int i = 0; i < kNumZeroSensors; ++i) {
154 if (position->hall_effects[i]) {
155 if (active_index != -1) {
156 LOG(ERROR, "More than one hall effect sensor is active\n");
157 } else {
158 active_index = i;
159 }
160 }
161 }
162 return active_index;
163 }
164 // Returns true if any of the sensors are active.
165 bool AnySensorsActive(
166 const ZeroedJoint<kNumZeroSensors>::PositionData *position) {
167 return ActiveSensorIndex(position) != -1;
168 }
169
Austin Schuh844960d2013-03-09 17:07:51 -0800170 // Enum to store the state of the internal zeroing state machine.
171 enum State {
172 UNINITIALIZED,
173 MOVING_OFF,
174 ZEROING,
175 READY,
176 ESTOP
177 };
178
179 // Internal state for zeroing.
180 State state_;
181
182 // Missed position packet count.
183 int error_count_;
184 // Offset from the raw encoder value to the absolute angle.
185 double zero_offset_;
186 // Position that gets incremented when zeroing the wrist to slowly move it to
187 // the hall effect sensor.
188 double zeroing_position_;
189 // Last position at which the hall effect sensor was off.
190 double last_off_position_;
191
192 // True if the zeroing goal was capped during this cycle.
193 bool capped_goal_;
194
Austin Schuhe20e93c2013-03-09 19:54:16 -0800195 // Returns true if number is between first and second inclusive.
196 bool is_between(double first, double second, double number) {
197 if ((number >= first || number >= second) &&
198 (number <= first || number <= second)) {
199 return true;
200 }
201 return false;
202 }
203
Austin Schuh844960d2013-03-09 17:07:51 -0800204 DISALLOW_COPY_AND_ASSIGN(ZeroedJoint);
205};
206
Austin Schuhe20e93c2013-03-09 19:54:16 -0800207template <int kNumZeroSensors>
208/*static*/ const double ZeroedJoint<kNumZeroSensors>::dt = 0.01;
209
Austin Schuh844960d2013-03-09 17:07:51 -0800210// Updates the zeroed joint controller and state machine.
211template <int kNumZeroSensors>
212double ZeroedJoint<kNumZeroSensors>::Update(
213 const ZeroedJoint<kNumZeroSensors>::PositionData *position,
214 bool output_enabled,
215 double goal_angle, double goal_velocity) {
216 // Uninitialize the bot if too many cycles pass without an encoder.
217 if (position == NULL) {
218 LOG(WARNING, "no new pos given\n");
219 error_count_++;
220 } else {
221 error_count_ = 0;
222 }
223 if (error_count_ >= 4) {
224 LOG(WARNING, "err_count is %d so forcing a re-zero\n", error_count_);
225 state_ = UNINITIALIZED;
226 }
227
228 // Compute the absolute position of the wrist.
229 double absolute_position;
230 if (position) {
231 absolute_position = position->position;
232 if (state_ == READY) {
233 absolute_position -= zero_offset_;
234 }
235 loop_->Y << absolute_position;
Austin Schuhe20e93c2013-03-09 19:54:16 -0800236 if (!AnySensorsActive(position)) {
237 last_off_position_ = position->position;
Austin Schuh844960d2013-03-09 17:07:51 -0800238 }
239 } else {
240 // Dead recon for now.
241 absolute_position = loop_->X_hat(0, 0);
242 }
243
244 switch (state_) {
245 case UNINITIALIZED:
Austin Schuh844960d2013-03-09 17:07:51 -0800246 if (position) {
247 // Reset the zeroing goal.
248 zeroing_position_ = absolute_position;
249 // Clear the observer state.
250 loop_->X_hat << absolute_position, 0.0;
251 // Set the goal to here to make it so it doesn't move when disabled.
252 loop_->R = loop_->X_hat;
253 // Only progress if we are enabled.
254 if (::aos::robot_state->enabled) {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800255 if (AnySensorsActive(position)) {
256 state_ = MOVING_OFF;
257 } else {
258 state_ = ZEROING;
Austin Schuh844960d2013-03-09 17:07:51 -0800259 }
260 }
261 }
262 break;
263 case MOVING_OFF:
Austin Schuhe20e93c2013-03-09 19:54:16 -0800264 {
265 // Move off the hall effect sensor.
266 if (!::aos::robot_state->enabled) {
267 // Start over if disabled.
268 state_ = UNINITIALIZED;
269 } else if (position && !AnySensorsActive(position)) {
270 // We are now off the sensor. Time to zero now.
271 state_ = ZEROING;
272 } else {
273 // Slowly creep off the sensor.
274 zeroing_position_ -= config_data_.zeroing_speed * dt;
275 loop_->R << zeroing_position_, -config_data_.zeroing_speed;
276 break;
277 }
Austin Schuh844960d2013-03-09 17:07:51 -0800278 }
279 case ZEROING:
Austin Schuhe20e93c2013-03-09 19:54:16 -0800280 {
281 int active_sensor_index = ActiveSensorIndex(position);
282 if (!::aos::robot_state->enabled) {
283 // Start over if disabled.
284 state_ = UNINITIALIZED;
285 } else if (position && active_sensor_index != -1) {
286 state_ = READY;
287 // Verify that the calibration number is between the last off position
288 // and the current on position. If this is not true, move off and try
289 // again.
290 const double calibration =
291 position->hall_effect_positions[active_sensor_index];
292 if (!is_between(last_off_position_, position->position,
293 calibration)) {
294 LOG(ERROR, "Got a bogus calibration number. Trying again.\n");
295 LOG(ERROR,
296 "Last off position was %f, current is %f, calibration is %f\n",
297 last_off_position_, position->position,
298 position->hall_effect_positions[active_sensor_index]);
299 state_ = MOVING_OFF;
300 } else {
301 // Save the zero, and then offset the observer to deal with the
302 // phantom step change.
303 const double old_zero_offset = zero_offset_;
304 zero_offset_ =
305 position->hall_effect_positions[active_sensor_index] -
306 config_data_.hall_effect_start_angle[active_sensor_index];
307 loop_->X_hat(0, 0) += old_zero_offset - zero_offset_;
308 loop_->Y(0, 0) += old_zero_offset - zero_offset_;
309 }
Austin Schuh844960d2013-03-09 17:07:51 -0800310 } else {
Austin Schuhe20e93c2013-03-09 19:54:16 -0800311 // Slowly creep towards the sensor.
312 zeroing_position_ += config_data_.zeroing_speed * dt;
313 loop_->R << zeroing_position_, config_data_.zeroing_speed;
Austin Schuh844960d2013-03-09 17:07:51 -0800314 }
Austin Schuhe20e93c2013-03-09 19:54:16 -0800315 break;
Austin Schuh844960d2013-03-09 17:07:51 -0800316 }
Austin Schuh844960d2013-03-09 17:07:51 -0800317
318 case READY:
319 {
Austin Schuh844960d2013-03-09 17:07:51 -0800320 const double limited_goal = ClipGoal(goal_angle);
321 loop_->R << limited_goal, goal_velocity;
322 break;
323 }
324
325 case ESTOP:
Austin Schuh844960d2013-03-09 17:07:51 -0800326 LOG(WARNING, "have already given up\n");
327 return 0.0;
328 }
329
330 // Update the observer.
Austin Schuh844960d2013-03-09 17:07:51 -0800331 loop_->Update(position != NULL, !output_enabled);
Austin Schuh844960d2013-03-09 17:07:51 -0800332
333 capped_goal_ = false;
334 // Verify that the zeroing goal hasn't run away.
335 switch (state_) {
336 case UNINITIALIZED:
337 case READY:
338 case ESTOP:
339 // Not zeroing. No worries.
340 break;
341 case MOVING_OFF:
342 case ZEROING:
343 // Check if we have cliped and adjust the goal.
344 if (loop_->U_uncapped(0, 0) > config_data_.max_zeroing_voltage) {
345 double dx = (loop_->U_uncapped(0, 0) -
346 config_data_.max_zeroing_voltage) / loop_->K(0, 0);
347 zeroing_position_ -= dx;
348 capped_goal_ = true;
349 } else if(loop_->U_uncapped(0, 0) < -config_data_.max_zeroing_voltage) {
350 double dx = (loop_->U_uncapped(0, 0) +
351 config_data_.max_zeroing_voltage) / loop_->K(0, 0);
352 zeroing_position_ -= dx;
353 capped_goal_ = true;
354 }
355 break;
356 }
357 return loop_->U(0, 0);
358}
359
360} // namespace control_loops
361} // namespace frc971
362
363#endif // FRC971_CONTROL_LOOPS_ZEROED_JOINT_H_