blob: f56c84ac701738cdcb44791f0da097b677389231 [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"
8#include "frc971/control_loops/wrist/wrist_motor.q.h"
9#include "frc971/control_loops/wrist/wrist_motor_plant.h"
10
11namespace frc971 {
12namespace control_loops {
13namespace testing {
14class WristTest_NoWindupPositive_Test;
15class WristTest_NoWindupNegative_Test;
16};
17
18template<int kNumZeroSensors>
19class ZeroedJoint;
20
21// This class implements the CapU function correctly given all the extra
22// information that we know about from the wrist motor.
23template<int kNumZeroSensors>
24class ZeroedStateFeedbackLoop : public StateFeedbackLoop<2, 1, 1> {
25 public:
26 ZeroedStateFeedbackLoop(StateFeedbackLoop<2, 1, 1> loop,
27 ZeroedJoint<kNumZeroSensors> *zeroed_joint)
28 : StateFeedbackLoop<2, 1, 1>(loop),
29 zeroed_joint_(zeroed_joint) {
30 }
31
32 // Caps U, but this time respects the state of the wrist as well.
33 virtual void CapU();
34 private:
35 ZeroedJoint<kNumZeroSensors> *zeroed_joint_;
36};
37
38template<int kNumZeroSensors>
39void ZeroedStateFeedbackLoop<kNumZeroSensors>::CapU() {
40 if (zeroed_joint_->state_ == ZeroedJoint<kNumZeroSensors>::READY) {
41 if (Y(0, 0) >= zeroed_joint_->config_data_.upper_limit) {
42 U(0, 0) = std::min(0.0, U(0, 0));
43 }
44 if (Y(0, 0) <= zeroed_joint_->config_data_.lower_limit) {
45 U(0, 0) = std::max(0.0, U(0, 0));
46 }
47 }
48
49 const bool is_ready =
50 zeroed_joint_->state_ == ZeroedJoint<kNumZeroSensors>::READY;
51 double limit = is_ready ?
52 12.0 : zeroed_joint_->config_data_.max_zeroing_voltage;
53
54 U(0, 0) = std::min(limit, U(0, 0));
55 U(0, 0) = std::max(-limit, U(0, 0));
56}
57
58
59// Class to zero and control a joint with any number of zeroing sensors with a
60// state feedback controller.
61template<int kNumZeroSensors>
62class ZeroedJoint {
63 public:
64 // Sturcture to hold the hardware configuration information.
65 struct ConfigurationData {
66 // Angle at the lower hardware limit.
67 double lower_limit;
68 // Angle at the upper hardware limit.
69 double upper_limit;
70 // Speed (and direction) to move while zeroing.
71 double zeroing_speed;
72 // 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
119 // True if the state machine is uninitialized.
120 bool is_uninitialized() const { return state_ == UNINITIALIZED; }
121
122 // True if the state machine is ready.
123 bool is_ready() const { return state_ == READY; }
124
125 // Returns the uncapped voltage.
126 double U_uncapped() const { return loop_->U_uncapped(0, 0); }
127
128 // True if the goal was moved to avoid goal windup.
129 bool capped_goal() const { return capped_goal_; }
130
131 private:
132 friend class ZeroedStateFeedbackLoop<kNumZeroSensors>;
133 // Friend the wrist test cases so that they can simulate windeup.
134 friend class testing::WristTest_NoWindupPositive_Test;
135 friend class testing::WristTest_NoWindupNegative_Test;
136
137 // The state feedback control loop to talk to.
138 ::std::unique_ptr<ZeroedStateFeedbackLoop<kNumZeroSensors>> loop_;
139
140 ConfigurationData config_data_;
141
142 // Enum to store the state of the internal zeroing state machine.
143 enum State {
144 UNINITIALIZED,
145 MOVING_OFF,
146 ZEROING,
147 READY,
148 ESTOP
149 };
150
151 // Internal state for zeroing.
152 State state_;
153
154 // Missed position packet count.
155 int error_count_;
156 // Offset from the raw encoder value to the absolute angle.
157 double zero_offset_;
158 // Position that gets incremented when zeroing the wrist to slowly move it to
159 // the hall effect sensor.
160 double zeroing_position_;
161 // Last position at which the hall effect sensor was off.
162 double last_off_position_;
163
164 // True if the zeroing goal was capped during this cycle.
165 bool capped_goal_;
166
167 DISALLOW_COPY_AND_ASSIGN(ZeroedJoint);
168};
169
170// Updates the zeroed joint controller and state machine.
171template <int kNumZeroSensors>
172double ZeroedJoint<kNumZeroSensors>::Update(
173 const ZeroedJoint<kNumZeroSensors>::PositionData *position,
174 bool output_enabled,
175 double goal_angle, double goal_velocity) {
176 // Uninitialize the bot if too many cycles pass without an encoder.
177 if (position == NULL) {
178 LOG(WARNING, "no new pos given\n");
179 error_count_++;
180 } else {
181 error_count_ = 0;
182 }
183 if (error_count_ >= 4) {
184 LOG(WARNING, "err_count is %d so forcing a re-zero\n", error_count_);
185 state_ = UNINITIALIZED;
186 }
187
188 // Compute the absolute position of the wrist.
189 double absolute_position;
190 if (position) {
191 absolute_position = position->position;
192 if (state_ == READY) {
193 absolute_position -= zero_offset_;
194 }
195 loop_->Y << absolute_position;
196 for (int i = 0; i < kNumZeroSensors; ++i) {
197 if (!position->hall_effects[i]) {
198 last_off_position_ = position->position;
199 }
200 }
201 } else {
202 // Dead recon for now.
203 absolute_position = loop_->X_hat(0, 0);
204 }
205
206 switch (state_) {
207 case UNINITIALIZED:
208 printf("uninit\n");
209 if (position) {
210 // Reset the zeroing goal.
211 zeroing_position_ = absolute_position;
212 // Clear the observer state.
213 loop_->X_hat << absolute_position, 0.0;
214 // Set the goal to here to make it so it doesn't move when disabled.
215 loop_->R = loop_->X_hat;
216 // Only progress if we are enabled.
217 if (::aos::robot_state->enabled) {
218 state_ = ZEROING;
219 for (int i = 0; i < kNumZeroSensors; ++i) {
220 if (position->hall_effects[i]) {
221 state_ = MOVING_OFF;
222 }
223 }
224 }
225 }
226 break;
227 case MOVING_OFF:
228 printf("moving off\n");
229 // Move off the hall effect sensor.
230 if (!::aos::robot_state->enabled) {
231 // Start over if disabled.
232 state_ = UNINITIALIZED;
233 } else if (position && !position->hall_effects[0]) {
234 // We are now off the sensor. Time to zero now.
235 state_ = ZEROING;
236 } else {
237 // Slowly creep off the sensor.
238 zeroing_position_ -= config_data_.zeroing_speed / 100;
239 loop_->R << zeroing_position_, -config_data_.zeroing_speed;
240 break;
241 }
242 case ZEROING:
243 printf("zeroing\n");
244 if (!::aos::robot_state->enabled) {
245 // Start over if disabled.
246 state_ = UNINITIALIZED;
247 } else if (position && position->hall_effects[0]) {
248 state_ = READY;
249 // Verify that the calibration number is between the last off position
250 // and the current on position. If this is not true, move off and try
251 // again.
252 if (position->hall_effect_positions[0] <= last_off_position_ ||
253 position->hall_effect_positions[0] > position->position) {
254 LOG(ERROR, "Got a bogus calibration number. Trying again.\n");
255 LOG(ERROR,
256 "Last off position was %f, current is %f, calibration is %f\n",
257 last_off_position_, position->position,
258 position->hall_effect_positions[0]);
259 state_ = MOVING_OFF;
260 } else {
261 // Save the zero, and then offset the observer to deal with the
262 // phantom step change.
263 const double old_zero_offset = zero_offset_;
264 zero_offset_ = (position->hall_effect_positions[0] -
265 config_data_.hall_effect_start_angle[0]);
266 loop_->X_hat(0, 0) += old_zero_offset - zero_offset_;
267 loop_->Y(0, 0) += old_zero_offset - zero_offset_;
268 }
269 } else {
270 // Slowly creep towards the sensor.
271 zeroing_position_ += config_data_.zeroing_speed / 100;
272 loop_->R << zeroing_position_, config_data_.zeroing_speed;
273 }
274 break;
275
276 case READY:
277 {
278 printf("ready\n");
279 const double limited_goal = ClipGoal(goal_angle);
280 loop_->R << limited_goal, goal_velocity;
281 break;
282 }
283
284 case ESTOP:
285 printf("estop\n");
286 LOG(WARNING, "have already given up\n");
287 return 0.0;
288 }
289
290 // Update the observer.
291 printf("Output is enabled %d\n", output_enabled);
292 loop_->Update(position != NULL, !output_enabled);
293 printf("Voltage %f\n", loop_->U(0, 0));
294
295 capped_goal_ = false;
296 // Verify that the zeroing goal hasn't run away.
297 switch (state_) {
298 case UNINITIALIZED:
299 case READY:
300 case ESTOP:
301 // Not zeroing. No worries.
302 break;
303 case MOVING_OFF:
304 case ZEROING:
305 // Check if we have cliped and adjust the goal.
306 if (loop_->U_uncapped(0, 0) > config_data_.max_zeroing_voltage) {
307 double dx = (loop_->U_uncapped(0, 0) -
308 config_data_.max_zeroing_voltage) / loop_->K(0, 0);
309 zeroing_position_ -= dx;
310 capped_goal_ = true;
311 } else if(loop_->U_uncapped(0, 0) < -config_data_.max_zeroing_voltage) {
312 double dx = (loop_->U_uncapped(0, 0) +
313 config_data_.max_zeroing_voltage) / loop_->K(0, 0);
314 zeroing_position_ -= dx;
315 capped_goal_ = true;
316 }
317 break;
318 }
319 return loop_->U(0, 0);
320}
321
322} // namespace control_loops
323} // namespace frc971
324
325#endif // FRC971_CONTROL_LOOPS_ZEROED_JOINT_H_