Austin Schuh | dc1c84a | 2013-02-23 16:33:10 -0800 | [diff] [blame^] | 1 | #include "frc971/control_loops/wrist.h" |
| 2 | |
| 3 | #include <stdio.h> |
| 4 | |
| 5 | #include <algorithm> |
| 6 | |
| 7 | #include "aos/aos_core.h" |
| 8 | |
| 9 | #include "aos/common/messages/RobotState.q.h" |
| 10 | #include "aos/common/control_loop/control_loops.q.h" |
| 11 | #include "aos/common/logging/logging.h" |
| 12 | |
| 13 | #include "frc971/constants.h" |
| 14 | #include "frc971/control_loops/wrist_motor_plant.h" |
| 15 | |
| 16 | namespace frc971 { |
| 17 | namespace control_loops { |
| 18 | |
| 19 | static const double MIN_POS = -0.80; |
| 20 | static const double MAX_POS = 1.77; |
| 21 | static const double positive_deadband_power = 0.15 * 12.0; |
| 22 | static const double negative_deadband_power = 0.09 * 12.0; |
| 23 | // Final units is radians/iteration. |
| 24 | static const double MAX_SPEED = 19300.0/*free RPM*/ * 12.0/*pinions*/ / 60.0 * 14.0 / 50.0 * 14.0 / 48.0 / 60.0/*60 sec/min*/ / 100.0/*100 cycles/sec*/ * (2.0 * M_PI)/*rotations to radians*/; |
| 25 | // The minimum magnitude for X_hat[1] to be sure of the direction it's moving so |
| 26 | // that the code can get a zero. X_hat[1] is the angular velocity of the wrist |
| 27 | // predicted by the observer, and it can be wrong when the wrist is just |
| 28 | // switching direction and/or it is moving very slowly in/around the victor |
| 29 | // deadband. Using this threshold instead of just 0 helps avoid falsely zeroing |
| 30 | // on the back edge of the magnet instead of the front one. |
| 31 | static const double kX_hatThreshold = 0.2; |
| 32 | // The maximum amount that the calibration value can be off for it to be |
| 33 | // accepted. |
| 34 | // MAX_SPEED / 10 is the amount it would be off if it was read 1ms earlier than |
| 35 | // the actual encoder position, which is a lot more than it should ever be. |
| 36 | static const double kMaxCalibrationError = MAX_SPEED / 10; |
| 37 | |
| 38 | WristMotor::WristMotor(control_loops::WristLoop *my_wrist) |
| 39 | : aos::control_loops::ControlLoop<control_loops::WristLoop>(my_wrist), |
| 40 | loop_(new StateFeedbackLoop<2, 1, 1>(MakeWristLoop())), |
| 41 | stop_(false), |
| 42 | error_count_(0), |
| 43 | zeroed_(false), |
| 44 | zero_offset_(0.0) { |
| 45 | } |
| 46 | |
| 47 | // down is positive |
| 48 | void WristMotor::RunIteration( |
| 49 | const ::aos::control_loops::Goal *goal, |
| 50 | const control_loops::WristLoop::Position *position, |
| 51 | ::aos::control_loops::Output *output, |
| 52 | ::aos::control_loops::Status * /*status*/) { |
| 53 | |
| 54 | if (output) { |
| 55 | output->voltage = 0; |
| 56 | } |
| 57 | |
| 58 | if (stop_) { |
| 59 | LOG(WARNING, "have already given up\n"); |
| 60 | return; |
| 61 | } |
| 62 | |
| 63 | if (position == NULL) { |
| 64 | LOG(WARNING, "no new pos given\n"); |
| 65 | error_count_++; |
| 66 | } else { |
| 67 | error_count_ = 0; |
| 68 | } |
| 69 | |
| 70 | double horizontal; |
| 71 | if (!constants::horizontal_offset(&horizontal)) { |
| 72 | LOG(ERROR, "Failed to fetch the horizontal offset constant.\n"); |
| 73 | return; |
| 74 | } |
| 75 | |
| 76 | static bool good_zeroed = false; |
| 77 | static bool first = true; |
| 78 | if (error_count_ >= 4) { |
| 79 | LOG(WARNING, "err_count is %d so forcing a re-zero\n", error_count_); |
| 80 | first = true; |
| 81 | zeroed_ = false; |
| 82 | good_zeroed = false; |
| 83 | } |
| 84 | |
| 85 | const double limited_goal = std::min(MAX_POS, std::max(MIN_POS, goal->goal)); |
| 86 | |
| 87 | static double zero_goal; |
| 88 | static bool old_cal = false; |
| 89 | static double old_pos = -5000000; |
| 90 | bool bad_encoder = false; |
| 91 | if (!first) { |
| 92 | if (old_cal != position->hall_effect) { |
| 93 | LOG(INFO, "old=%s X_hat=%f\n", old_cal ? "true" : "false", loop_->X_hat[1]); |
| 94 | // Don't want to miss the first one if it starts really close to the edge |
| 95 | // of the magnet so isn't moving very fast when it passes. |
| 96 | const double X_hatThreshold = zeroed_ ? kX_hatThreshold : 0; |
| 97 | if ((old_cal && loop_->X_hat[1] < -X_hatThreshold) || |
| 98 | (!old_cal && loop_->X_hat[1] > X_hatThreshold)) { |
| 99 | LOG(INFO, "zeroing at %f (pos=%f, old_pos=%f) (used to be %f)\n", |
| 100 | position->calibration, position->pos, old_pos, zero_offset_); |
| 101 | // If ((old_pos - 0.02) <= position->calibration <= position->pos) or |
| 102 | // the other way around. Checks if position->calibration is between |
| 103 | // old_pos and position->pos if the wrist is moving either way, with a |
| 104 | // slight tweak to |
| 105 | // old_pos in case it was triggered while the sensor packet was being |
| 106 | // sent or something. |
| 107 | if (!((old_pos - kMaxCalibrationError <= position->calibration && |
| 108 | position->calibration <= position->pos) || |
| 109 | (old_pos + kMaxCalibrationError >= position->calibration && |
| 110 | position->calibration >= position->pos))) { |
| 111 | if (!good_zeroed) { |
| 112 | LOG(WARNING, "using encoder pos because " |
| 113 | "calibration sensor pos doesn't make sense\n"); |
| 114 | zero_offset_ = position->pos; |
| 115 | } else { |
| 116 | LOG(INFO, "already had a good zero, " |
| 117 | "so not using inaccurate zero value\n"); |
| 118 | } |
| 119 | } else { |
| 120 | good_zeroed = true; |
| 121 | zero_offset_ = position->calibration; |
| 122 | } |
| 123 | zeroed_ = true; |
| 124 | } else { |
| 125 | LOG(INFO, "hit back edge at %f\n", position->calibration); |
| 126 | } |
| 127 | old_cal = position->hall_effect; |
| 128 | } |
| 129 | if (std::abs(position->pos - old_pos) > MAX_SPEED * 1.5) { |
| 130 | bad_encoder = true; |
| 131 | LOG(WARNING, "encoder value changed by %f which is more than MAX_SPEED(%f)\n", |
| 132 | std::abs(position->pos - old_pos), MAX_SPEED); |
| 133 | } |
| 134 | } // !first |
| 135 | |
| 136 | old_pos = position->pos; |
| 137 | const double absolute_position = position->pos - zero_offset_ - horizontal; |
| 138 | if (first) { |
| 139 | first = false; |
| 140 | old_cal = position->hall_effect; |
| 141 | zero_goal = absolute_position; |
| 142 | } |
| 143 | |
| 144 | loop_->Y << absolute_position; |
| 145 | if (!zeroed_) { |
| 146 | loop_->R << zero_goal, 0.0; |
| 147 | if (aos::robot_state->enabled) { |
| 148 | if (!position->hall_effect) { |
| 149 | zero_goal += 0.010; |
| 150 | } else { |
| 151 | zero_goal -= 0.010; |
| 152 | } |
| 153 | } |
| 154 | } else { |
| 155 | loop_->R << limited_goal, 0.0; |
| 156 | } |
| 157 | loop_->Update(!bad_encoder, bad_encoder || output == NULL); |
| 158 | double output_voltage = loop_->U[0]; |
| 159 | //LOG(DEBUG, "fancy math gave %f\n", status->pwm); |
| 160 | |
| 161 | if (output_voltage > 0) { |
| 162 | output_voltage += positive_deadband_power; |
| 163 | } |
| 164 | if (output_voltage < 0) { |
| 165 | output_voltage -= negative_deadband_power; |
| 166 | } |
| 167 | |
| 168 | if (zeroed_) { |
| 169 | if (absolute_position >= MAX_POS) { |
| 170 | output_voltage = std::min(0.0, output_voltage); |
| 171 | } |
| 172 | if (absolute_position <= MIN_POS) { |
| 173 | output_voltage = std::max(0.0, output_voltage); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | double limit = zeroed_ ? 1.0 : 0.5; |
| 178 | //limit = std::min(0.3, limit); |
| 179 | output_voltage = std::min(limit, output_voltage); |
| 180 | output_voltage = std::max(-limit, output_voltage); |
| 181 | LOG(DEBUG, "pos=%f zero=%f horizontal=%f currently %f hall: %s\n", |
| 182 | position->pos, zero_offset_, horizontal, absolute_position, |
| 183 | position->hall_effect ? "true" : "false"); |
| 184 | if (output) { |
| 185 | output->voltage = output_voltage; |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | } // namespace control_loops |
| 190 | } // namespace frc971 |