blob: 22e3b3d23edb9f92542ee50c37ef90c64cc5c5c5 [file] [log] [blame]
Adam Snaiderc4b3c192015-02-01 01:30:39 +00001#include "frc971/zeroing/zeroing.h"
Adam Snaiderb4119252015-02-15 01:30:57 +00002
Brian Silvermanb691f5e2015-08-02 11:37:55 -07003#include <cmath>
Adam Snaiderc4b3c192015-02-01 01:30:39 +00004#include <vector>
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -08005#include <algorithm>
Adam Snaiderc4b3c192015-02-01 01:30:39 +00006
Austin Schuh5f01f152017-02-11 21:34:08 -08007#include "frc971/zeroing/wrap.h"
8
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -08009
Adam Snaiderc4b3c192015-02-01 01:30:39 +000010namespace frc971 {
11namespace zeroing {
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -080012namespace {
13
14bool compare_encoder(const PotAndAbsolutePosition &left,
15 const PotAndAbsolutePosition &right) {
16 return left.encoder < right.encoder;
17}
18
19} // namespace
Adam Snaiderc4b3c192015-02-01 01:30:39 +000020
Tyler Chatowf8f03112017-02-05 14:31:34 -080021void PopulateEstimatorState(
22 const zeroing::PotAndIndexPulseZeroingEstimator &estimator,
23 EstimatorState *state) {
Daniel Pettiab274232015-02-16 19:15:34 -080024 state->error = estimator.error();
25 state->zeroed = estimator.zeroed();
26 state->position = estimator.position();
Austin Schuhbe133ed2016-03-11 21:23:34 -080027 state->pot_position = estimator.filtered_position();
Daniel Pettiab274232015-02-16 19:15:34 -080028}
29
Austin Schuh5f01f152017-02-11 21:34:08 -080030void PopulateEstimatorState(
31 const zeroing::PotAndAbsEncoderZeroingEstimator &estimator,
32 AbsoluteEstimatorState *state) {
33 state->error = estimator.error();
34 state->zeroed = estimator.zeroed();
35
36 state->position = estimator.position();
37 state->pot_position = estimator.filtered_position();
38}
39
Tyler Chatowf8f03112017-02-05 14:31:34 -080040PotAndIndexPulseZeroingEstimator::PotAndIndexPulseZeroingEstimator(
Austin Schuh5f01f152017-02-11 21:34:08 -080041 const constants::PotAndIndexPulseZeroingConstants &constants)
42 : constants_(constants) {
43 start_pos_samples_.reserve(constants_.average_filter_size);
Adam Snaiderb4119252015-02-15 01:30:57 +000044 Reset();
Austin Schuh703b8d42015-02-01 14:56:34 -080045}
46
Tyler Chatowf8f03112017-02-05 14:31:34 -080047void PotAndIndexPulseZeroingEstimator::Reset() {
Adam Snaiderc4b3c192015-02-01 01:30:39 +000048 samples_idx_ = 0;
Adam Snaiderb4119252015-02-15 01:30:57 +000049 start_pos_ = 0;
50 start_pos_samples_.clear();
51 zeroed_ = false;
Philipp Schrader41d82912015-02-15 03:44:23 +000052 wait_for_index_pulse_ = true;
Philipp Schradere828be72015-02-15 07:07:37 +000053 last_used_index_pulse_count_ = 0;
Adam Snaider3cd11c52015-02-16 02:16:09 +000054 first_start_pos_ = 0.0;
Philipp Schrader53f4b6d2015-02-15 22:32:08 +000055 error_ = false;
56}
57
Tyler Chatowf8f03112017-02-05 14:31:34 -080058void PotAndIndexPulseZeroingEstimator::TriggerError() {
Philipp Schrader53f4b6d2015-02-15 22:32:08 +000059 if (!error_) {
60 LOG(ERROR, "Manually triggered zeroing error.\n");
61 error_ = true;
62 }
Philipp Schradere828be72015-02-15 07:07:37 +000063}
64
Tyler Chatowf8f03112017-02-05 14:31:34 -080065double PotAndIndexPulseZeroingEstimator::CalculateStartPosition(
66 double start_average, double latched_encoder) const {
Philipp Schradere828be72015-02-15 07:07:37 +000067 // We calculate an aproximation of the value of the last index position.
68 // Also account for index pulses not lining up with integer multiples of the
69 // index_diff.
Austin Schuh5f01f152017-02-11 21:34:08 -080070 double index_pos =
71 start_average + latched_encoder - constants_.measured_index_position;
Philipp Schradere828be72015-02-15 07:07:37 +000072 // We round index_pos to the closest valid value of the index.
Austin Schuh5f01f152017-02-11 21:34:08 -080073 double accurate_index_pos = (round(index_pos / constants_.index_difference)) *
74 constants_.index_difference;
Philipp Schradere828be72015-02-15 07:07:37 +000075 // Now we reverse the first calculation to get the accurate start position.
Austin Schuh5f01f152017-02-11 21:34:08 -080076 return accurate_index_pos - latched_encoder +
77 constants_.measured_index_position;
Adam Snaiderc4b3c192015-02-01 01:30:39 +000078}
79
Tyler Chatowf8f03112017-02-05 14:31:34 -080080void PotAndIndexPulseZeroingEstimator::UpdateEstimate(
81 const PotAndIndexPosition &info) {
Philipp Schrader41d82912015-02-15 03:44:23 +000082 // We want to make sure that we encounter at least one index pulse while
83 // zeroing. So we take the index pulse count from the first sample after
84 // reset and wait for that count to change before we consider ourselves
85 // zeroed.
86 if (wait_for_index_pulse_) {
Philipp Schradere828be72015-02-15 07:07:37 +000087 last_used_index_pulse_count_ = info.index_pulses;
Philipp Schrader41d82912015-02-15 03:44:23 +000088 wait_for_index_pulse_ = false;
89 }
90
Austin Schuh5f01f152017-02-11 21:34:08 -080091 if (start_pos_samples_.size() < constants_.average_filter_size) {
Adam Snaiderc4b3c192015-02-01 01:30:39 +000092 start_pos_samples_.push_back(info.pot - info.encoder);
93 } else {
94 start_pos_samples_[samples_idx_] = info.pot - info.encoder;
95 }
Adam Snaiderb4119252015-02-15 01:30:57 +000096
97 // Drop the oldest sample when we run this function the next time around.
Austin Schuh5f01f152017-02-11 21:34:08 -080098 samples_idx_ = (samples_idx_ + 1) % constants_.average_filter_size;
Adam Snaiderc4b3c192015-02-01 01:30:39 +000099
Adam Snaiderb4119252015-02-15 01:30:57 +0000100 double sample_sum = 0.0;
101
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000102 for (size_t i = 0; i < start_pos_samples_.size(); ++i) {
Adam Snaiderb4119252015-02-15 01:30:57 +0000103 sample_sum += start_pos_samples_[i];
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000104 }
105
106 // Calculates the average of the starting position.
Adam Snaiderb4119252015-02-15 01:30:57 +0000107 double start_average = sample_sum / start_pos_samples_.size();
108
109 // If there are no index pulses to use or we don't have enough samples yet to
110 // have a well-filtered starting position then we use the filtered value as
111 // our best guess.
Austin Schuh7485dbb2016-02-08 00:21:58 -0800112 if (!zeroed_ &&
113 (info.index_pulses == last_used_index_pulse_count_ || !offset_ready())) {
Adam Snaiderb4119252015-02-15 01:30:57 +0000114 start_pos_ = start_average;
Philipp Schradere828be72015-02-15 07:07:37 +0000115 } else if (!zeroed_ || last_used_index_pulse_count_ != info.index_pulses) {
116 // Note the accurate start position and the current index pulse count so
117 // that we only run this logic once per index pulse. That should be more
118 // resilient to corrupted intermediate data.
119 start_pos_ = CalculateStartPosition(start_average, info.latched_encoder);
120 last_used_index_pulse_count_ = info.index_pulses;
Austin Schuh7485dbb2016-02-08 00:21:58 -0800121
122 // TODO(austin): Reject encoder positions which have x% error rather than
123 // rounding to the closest index pulse.
124
Adam Snaider3cd11c52015-02-16 02:16:09 +0000125 // Save the first starting position.
126 if (!zeroed_) {
127 first_start_pos_ = start_pos_;
128 LOG(INFO, "latching start position %f\n", first_start_pos_);
129 }
Adam Snaiderb4119252015-02-15 01:30:57 +0000130
131 // Now that we have an accurate starting position we can consider ourselves
132 // zeroed.
Austin Schuh703b8d42015-02-01 14:56:34 -0800133 zeroed_ = true;
Adam Snaider3cd11c52015-02-16 02:16:09 +0000134 // Throw an error if first_start_pos is bigger/smaller than
Austin Schuh5f01f152017-02-11 21:34:08 -0800135 // constants_.allowable_encoder_error * index_diff + start_pos.
Adam Snaider3cd11c52015-02-16 02:16:09 +0000136 if (::std::abs(first_start_pos_ - start_pos_) >
Austin Schuh5f01f152017-02-11 21:34:08 -0800137 constants_.allowable_encoder_error * constants_.index_difference) {
Adam Snaider3cd11c52015-02-16 02:16:09 +0000138 if (!error_) {
139 LOG(ERROR,
140 "Encoder ticks out of range since last index pulse. first start "
Austin Schuh1c85bc82016-04-03 21:36:31 -0700141 "position: %f recent starting position: %f, allowable error: %f\n",
142 first_start_pos_, start_pos_,
Austin Schuh5f01f152017-02-11 21:34:08 -0800143 constants_.allowable_encoder_error * constants_.index_difference);
Adam Snaider3cd11c52015-02-16 02:16:09 +0000144 error_ = true;
145 }
146 }
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000147 }
Adam Snaiderb4119252015-02-15 01:30:57 +0000148
Austin Schuh5f01f152017-02-11 21:34:08 -0800149 position_ = start_pos_ + info.encoder;
Austin Schuhbe133ed2016-03-11 21:23:34 -0800150 filtered_position_ = start_average + info.encoder;
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000151}
152
Austin Schuh5f01f152017-02-11 21:34:08 -0800153PotAndAbsEncoderZeroingEstimator::PotAndAbsEncoderZeroingEstimator(
154 const constants::PotAndAbsoluteEncoderZeroingConstants &constants)
155 : constants_(constants) {
156 relative_to_absolute_offset_samples_.reserve(constants_.average_filter_size);
157 offset_samples_.reserve(constants_.average_filter_size);
158 Reset();
159}
160
161void PotAndAbsEncoderZeroingEstimator::Reset() {
162 zeroed_ = false;
163 relative_to_absolute_offset_samples_.clear();
164 offset_samples_.clear();
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800165 buffered_samples_.clear();
Austin Schuh5f01f152017-02-11 21:34:08 -0800166}
167
168// So, this needs to be a multistep process. We need to first estimate the
169// offset between the absolute encoder and the relative encoder. That process
170// should get us an absolute number which is off by integer multiples of the
171// distance/rev. In parallel, we can estimate the offset between the pot and
172// encoder. When both estimates have converged, we can then compute the offset
173// in a cycle, and which cycle, which gives us the accurate global offset.
174//
175// It's tricky to compute the offset between the absolute and relative encoder.
176// We need to compute this inside 1 revolution. The easiest way to do this
177// would be to wrap the encoder, subtract the two of them, and then average the
178// result. That will struggle when they are off by PI. Instead, we need to
179// wrap the number to +- PI from the current averaged offset.
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800180//
181// To guard against the robot moving while updating estimates, buffer a number
182// of samples and check that the buffered samples are not different than the
183// zeroing threshold. At any point that the samples differ too much, do not
184// update estimates based on those samples.
Austin Schuh5f01f152017-02-11 21:34:08 -0800185void PotAndAbsEncoderZeroingEstimator::UpdateEstimate(
186 const PotAndAbsolutePosition &info) {
Neil Balch16275e32017-02-18 16:38:45 -0800187 // Check for Abs Encoder NaN value that would mess up the rest of the zeroing
188 // code below. NaN values are given when the Absolute Encoder is disconnected.
189 if (::std::isnan(info.absolute_encoder)) {
190 error_ = true;
191 return;
192 }
193
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800194 bool moving = true;
195 if (buffered_samples_.size() < constants_.moving_buffer_size) {
196 // Not enough samples to start determining if the robot is moving or not,
197 // don't use the samples yet.
198 buffered_samples_.push_back(info);
Austin Schuh5f01f152017-02-11 21:34:08 -0800199 } else {
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800200 // Have enough samples to start determining if the robot is moving or not.
201 buffered_samples_[buffered_samples_idx_] = info;
202 auto max_value =
203 ::std::max_element(buffered_samples_.begin(), buffered_samples_.end(),
204 compare_encoder)
205 ->encoder;
206 auto min_value =
207 ::std::min_element(buffered_samples_.begin(), buffered_samples_.end(),
208 compare_encoder)
209 ->encoder;
210 if (::std::abs(max_value - min_value) < constants_.zeroing_threshold) {
211 // Robot isn't moving, use middle sample to determine offsets.
212 moving = false;
213 }
214 }
215 buffered_samples_idx_ =
216 (buffered_samples_idx_ + 1) % constants_.moving_buffer_size;
217
218 if (!moving) {
219 // The robot is not moving, use the middle sample to determine offsets.
220 const int middle_index =
221 (buffered_samples_idx_ + (constants_.moving_buffer_size - 1) / 2) %
222 constants_.moving_buffer_size;
223
224 // Compute the sum of all the offset samples.
225 double relative_to_absolute_offset_sum = 0.0;
226 for (size_t i = 0; i < relative_to_absolute_offset_samples_.size(); ++i) {
227 relative_to_absolute_offset_sum +=
228 relative_to_absolute_offset_samples_[i];
229 }
230
231 // Compute the average offset between the absolute encoder and relative
232 // encoder. If we have 0 samples, assume it is 0.
233 double average_relative_to_absolute_offset =
234 relative_to_absolute_offset_samples_.size() == 0
235 ? 0.0
236 : relative_to_absolute_offset_sum /
237 relative_to_absolute_offset_samples_.size();
238
239 // Now, compute the nearest absolute encoder value to the offset relative
240 // encoder position.
241 const double adjusted_absolute_encoder =
242 Wrap(buffered_samples_[middle_index].encoder +
243 average_relative_to_absolute_offset,
244 buffered_samples_[middle_index].absolute_encoder -
245 constants_.measured_absolute_position,
246 constants_.one_revolution_distance);
247
248 const double relative_to_absolute_offset =
249 adjusted_absolute_encoder - buffered_samples_[middle_index].encoder;
250
251 // Add the sample and update the average with the new reading.
252 const size_t relative_to_absolute_offset_samples_size =
253 relative_to_absolute_offset_samples_.size();
254 if (relative_to_absolute_offset_samples_size <
255 constants_.average_filter_size) {
256 average_relative_to_absolute_offset =
257 (average_relative_to_absolute_offset *
258 relative_to_absolute_offset_samples_size +
259 relative_to_absolute_offset) /
260 (relative_to_absolute_offset_samples_size + 1);
261
262 relative_to_absolute_offset_samples_.push_back(
263 relative_to_absolute_offset);
264 } else {
265 average_relative_to_absolute_offset -=
266 relative_to_absolute_offset_samples_[samples_idx_] /
267 relative_to_absolute_offset_samples_size;
268 relative_to_absolute_offset_samples_[samples_idx_] =
269 relative_to_absolute_offset;
270 average_relative_to_absolute_offset +=
271 relative_to_absolute_offset /
272 relative_to_absolute_offset_samples_size;
273 }
274
275 // Now compute the offset between the pot and relative encoder.
276 if (offset_samples_.size() < constants_.average_filter_size) {
277 offset_samples_.push_back(buffered_samples_[middle_index].pot -
278 buffered_samples_[middle_index].encoder);
279 } else {
280 offset_samples_[samples_idx_] = buffered_samples_[middle_index].pot -
281 buffered_samples_[middle_index].encoder;
282 }
283
284 // Drop the oldest sample when we run this function the next time around.
285 samples_idx_ = (samples_idx_ + 1) % constants_.average_filter_size;
286
287 double pot_relative_encoder_offset_sum = 0.0;
288 for (size_t i = 0; i < offset_samples_.size(); ++i) {
289 pot_relative_encoder_offset_sum += offset_samples_[i];
290 }
291 pot_relative_encoder_offset_ =
292 pot_relative_encoder_offset_sum / offset_samples_.size();
293
294 offset_ = Wrap(buffered_samples_[middle_index].encoder +
295 pot_relative_encoder_offset_,
296 average_relative_to_absolute_offset +
297 buffered_samples_[middle_index].encoder,
298 constants_.one_revolution_distance) -
299 buffered_samples_[middle_index].encoder;
300 if (offset_ready()) {
301 zeroed_ = true;
302 }
Austin Schuh5f01f152017-02-11 21:34:08 -0800303 }
304
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800305 // Update the position.
306 filtered_position_ = pot_relative_encoder_offset_ + info.encoder;
Austin Schuh5f01f152017-02-11 21:34:08 -0800307 position_ = offset_ + info.encoder;
308}
309
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000310} // namespace zeroing
311} // namespace frc971