blob: 6dd6f420bc7359fee38839e9a6ce4adbc96949a8 [file] [log] [blame]
Adam Snaiderc4b3c192015-02-01 01:30:39 +00001#include "frc971/zeroing/zeroing.h"
Adam Snaiderb4119252015-02-15 01:30:57 +00002
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -08003#include <algorithm>
Isaac Wilcove0851ffd2017-02-16 04:13:14 +00004#include <cmath>
5#include <limits>
6#include <vector>
Adam Snaiderc4b3c192015-02-01 01:30:39 +00007
Austin Schuh5f01f152017-02-11 21:34:08 -08008#include "frc971/zeroing/wrap.h"
9
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -080010
Adam Snaiderc4b3c192015-02-01 01:30:39 +000011namespace frc971 {
12namespace zeroing {
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -080013namespace {
14
15bool compare_encoder(const PotAndAbsolutePosition &left,
16 const PotAndAbsolutePosition &right) {
17 return left.encoder < right.encoder;
18}
19
20} // namespace
Adam Snaiderc4b3c192015-02-01 01:30:39 +000021
Tyler Chatowf8f03112017-02-05 14:31:34 -080022void PopulateEstimatorState(
23 const zeroing::PotAndIndexPulseZeroingEstimator &estimator,
24 EstimatorState *state) {
Daniel Pettiab274232015-02-16 19:15:34 -080025 state->error = estimator.error();
26 state->zeroed = estimator.zeroed();
27 state->position = estimator.position();
Austin Schuhbe133ed2016-03-11 21:23:34 -080028 state->pot_position = estimator.filtered_position();
Daniel Pettiab274232015-02-16 19:15:34 -080029}
30
Austin Schuh5f01f152017-02-11 21:34:08 -080031void PopulateEstimatorState(
32 const zeroing::PotAndAbsEncoderZeroingEstimator &estimator,
33 AbsoluteEstimatorState *state) {
34 state->error = estimator.error();
35 state->zeroed = estimator.zeroed();
36
37 state->position = estimator.position();
38 state->pot_position = estimator.filtered_position();
39}
40
Tyler Chatowf8f03112017-02-05 14:31:34 -080041PotAndIndexPulseZeroingEstimator::PotAndIndexPulseZeroingEstimator(
Austin Schuh5f01f152017-02-11 21:34:08 -080042 const constants::PotAndIndexPulseZeroingConstants &constants)
43 : constants_(constants) {
44 start_pos_samples_.reserve(constants_.average_filter_size);
Adam Snaiderb4119252015-02-15 01:30:57 +000045 Reset();
Austin Schuh703b8d42015-02-01 14:56:34 -080046}
47
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000048
Tyler Chatowf8f03112017-02-05 14:31:34 -080049void PotAndIndexPulseZeroingEstimator::Reset() {
Adam Snaiderc4b3c192015-02-01 01:30:39 +000050 samples_idx_ = 0;
Isaac Wilcove0851ffd2017-02-16 04:13:14 +000051 offset_ = 0;
Adam Snaiderb4119252015-02-15 01:30:57 +000052 start_pos_samples_.clear();
53 zeroed_ = false;
Philipp Schrader41d82912015-02-15 03:44:23 +000054 wait_for_index_pulse_ = true;
Philipp Schradere828be72015-02-15 07:07:37 +000055 last_used_index_pulse_count_ = 0;
Adam Snaider3cd11c52015-02-16 02:16:09 +000056 first_start_pos_ = 0.0;
Philipp Schrader53f4b6d2015-02-15 22:32:08 +000057 error_ = false;
58}
59
Tyler Chatowf8f03112017-02-05 14:31:34 -080060void PotAndIndexPulseZeroingEstimator::TriggerError() {
Philipp Schrader53f4b6d2015-02-15 22:32:08 +000061 if (!error_) {
62 LOG(ERROR, "Manually triggered zeroing error.\n");
63 error_ = true;
64 }
Philipp Schradere828be72015-02-15 07:07:37 +000065}
66
Tyler Chatowf8f03112017-02-05 14:31:34 -080067double PotAndIndexPulseZeroingEstimator::CalculateStartPosition(
68 double start_average, double latched_encoder) const {
Philipp Schradere828be72015-02-15 07:07:37 +000069 // We calculate an aproximation of the value of the last index position.
70 // Also account for index pulses not lining up with integer multiples of the
71 // index_diff.
Austin Schuh5f01f152017-02-11 21:34:08 -080072 double index_pos =
73 start_average + latched_encoder - constants_.measured_index_position;
Philipp Schradere828be72015-02-15 07:07:37 +000074 // We round index_pos to the closest valid value of the index.
Austin Schuh5f01f152017-02-11 21:34:08 -080075 double accurate_index_pos = (round(index_pos / constants_.index_difference)) *
76 constants_.index_difference;
Philipp Schradere828be72015-02-15 07:07:37 +000077 // Now we reverse the first calculation to get the accurate start position.
Austin Schuh5f01f152017-02-11 21:34:08 -080078 return accurate_index_pos - latched_encoder +
79 constants_.measured_index_position;
Adam Snaiderc4b3c192015-02-01 01:30:39 +000080}
81
Tyler Chatowf8f03112017-02-05 14:31:34 -080082void PotAndIndexPulseZeroingEstimator::UpdateEstimate(
83 const PotAndIndexPosition &info) {
Philipp Schrader41d82912015-02-15 03:44:23 +000084 // We want to make sure that we encounter at least one index pulse while
85 // zeroing. So we take the index pulse count from the first sample after
86 // reset and wait for that count to change before we consider ourselves
87 // zeroed.
88 if (wait_for_index_pulse_) {
Philipp Schradere828be72015-02-15 07:07:37 +000089 last_used_index_pulse_count_ = info.index_pulses;
Philipp Schrader41d82912015-02-15 03:44:23 +000090 wait_for_index_pulse_ = false;
91 }
92
Austin Schuh5f01f152017-02-11 21:34:08 -080093 if (start_pos_samples_.size() < constants_.average_filter_size) {
Adam Snaiderc4b3c192015-02-01 01:30:39 +000094 start_pos_samples_.push_back(info.pot - info.encoder);
95 } else {
96 start_pos_samples_[samples_idx_] = info.pot - info.encoder;
97 }
Adam Snaiderb4119252015-02-15 01:30:57 +000098
99 // Drop the oldest sample when we run this function the next time around.
Austin Schuh5f01f152017-02-11 21:34:08 -0800100 samples_idx_ = (samples_idx_ + 1) % constants_.average_filter_size;
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000101
Adam Snaiderb4119252015-02-15 01:30:57 +0000102 double sample_sum = 0.0;
103
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000104 for (size_t i = 0; i < start_pos_samples_.size(); ++i) {
Adam Snaiderb4119252015-02-15 01:30:57 +0000105 sample_sum += start_pos_samples_[i];
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000106 }
107
108 // Calculates the average of the starting position.
Adam Snaiderb4119252015-02-15 01:30:57 +0000109 double start_average = sample_sum / start_pos_samples_.size();
110
111 // If there are no index pulses to use or we don't have enough samples yet to
112 // have a well-filtered starting position then we use the filtered value as
113 // our best guess.
Austin Schuh7485dbb2016-02-08 00:21:58 -0800114 if (!zeroed_ &&
115 (info.index_pulses == last_used_index_pulse_count_ || !offset_ready())) {
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000116 offset_ = start_average;
Philipp Schradere828be72015-02-15 07:07:37 +0000117 } else if (!zeroed_ || last_used_index_pulse_count_ != info.index_pulses) {
118 // Note the accurate start position and the current index pulse count so
119 // that we only run this logic once per index pulse. That should be more
120 // resilient to corrupted intermediate data.
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000121 offset_ = CalculateStartPosition(start_average, info.latched_encoder);
Philipp Schradere828be72015-02-15 07:07:37 +0000122 last_used_index_pulse_count_ = info.index_pulses;
Austin Schuh7485dbb2016-02-08 00:21:58 -0800123
124 // TODO(austin): Reject encoder positions which have x% error rather than
125 // rounding to the closest index pulse.
126
Adam Snaider3cd11c52015-02-16 02:16:09 +0000127 // Save the first starting position.
128 if (!zeroed_) {
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000129 first_start_pos_ = offset_;
Adam Snaider3cd11c52015-02-16 02:16:09 +0000130 LOG(INFO, "latching start position %f\n", first_start_pos_);
131 }
Adam Snaiderb4119252015-02-15 01:30:57 +0000132
133 // Now that we have an accurate starting position we can consider ourselves
134 // zeroed.
Austin Schuh703b8d42015-02-01 14:56:34 -0800135 zeroed_ = true;
Adam Snaider3cd11c52015-02-16 02:16:09 +0000136 // Throw an error if first_start_pos is bigger/smaller than
Austin Schuh5f01f152017-02-11 21:34:08 -0800137 // constants_.allowable_encoder_error * index_diff + start_pos.
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000138 if (::std::abs(first_start_pos_ - offset_) >
Austin Schuh5f01f152017-02-11 21:34:08 -0800139 constants_.allowable_encoder_error * constants_.index_difference) {
Adam Snaider3cd11c52015-02-16 02:16:09 +0000140 if (!error_) {
141 LOG(ERROR,
142 "Encoder ticks out of range since last index pulse. first start "
Austin Schuh1c85bc82016-04-03 21:36:31 -0700143 "position: %f recent starting position: %f, allowable error: %f\n",
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000144 first_start_pos_, offset_,
Austin Schuh5f01f152017-02-11 21:34:08 -0800145 constants_.allowable_encoder_error * constants_.index_difference);
Adam Snaider3cd11c52015-02-16 02:16:09 +0000146 error_ = true;
147 }
148 }
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000149 }
Adam Snaiderb4119252015-02-15 01:30:57 +0000150
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000151 position_ = offset_ + info.encoder;
Austin Schuhbe133ed2016-03-11 21:23:34 -0800152 filtered_position_ = start_average + info.encoder;
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000153}
154
Austin Schuh5f01f152017-02-11 21:34:08 -0800155PotAndAbsEncoderZeroingEstimator::PotAndAbsEncoderZeroingEstimator(
156 const constants::PotAndAbsoluteEncoderZeroingConstants &constants)
157 : constants_(constants) {
158 relative_to_absolute_offset_samples_.reserve(constants_.average_filter_size);
159 offset_samples_.reserve(constants_.average_filter_size);
160 Reset();
161}
162
163void PotAndAbsEncoderZeroingEstimator::Reset() {
164 zeroed_ = false;
165 relative_to_absolute_offset_samples_.clear();
166 offset_samples_.clear();
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800167 buffered_samples_.clear();
Austin Schuh5f01f152017-02-11 21:34:08 -0800168}
169
170// So, this needs to be a multistep process. We need to first estimate the
171// offset between the absolute encoder and the relative encoder. That process
172// should get us an absolute number which is off by integer multiples of the
173// distance/rev. In parallel, we can estimate the offset between the pot and
174// encoder. When both estimates have converged, we can then compute the offset
175// in a cycle, and which cycle, which gives us the accurate global offset.
176//
177// It's tricky to compute the offset between the absolute and relative encoder.
178// We need to compute this inside 1 revolution. The easiest way to do this
179// would be to wrap the encoder, subtract the two of them, and then average the
180// result. That will struggle when they are off by PI. Instead, we need to
181// wrap the number to +- PI from the current averaged offset.
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800182//
183// To guard against the robot moving while updating estimates, buffer a number
184// of samples and check that the buffered samples are not different than the
185// zeroing threshold. At any point that the samples differ too much, do not
186// update estimates based on those samples.
Austin Schuh5f01f152017-02-11 21:34:08 -0800187void PotAndAbsEncoderZeroingEstimator::UpdateEstimate(
188 const PotAndAbsolutePosition &info) {
Neil Balch16275e32017-02-18 16:38:45 -0800189 // Check for Abs Encoder NaN value that would mess up the rest of the zeroing
190 // code below. NaN values are given when the Absolute Encoder is disconnected.
191 if (::std::isnan(info.absolute_encoder)) {
192 error_ = true;
193 return;
194 }
195
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800196 bool moving = true;
197 if (buffered_samples_.size() < constants_.moving_buffer_size) {
198 // Not enough samples to start determining if the robot is moving or not,
199 // don't use the samples yet.
200 buffered_samples_.push_back(info);
Austin Schuh5f01f152017-02-11 21:34:08 -0800201 } else {
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800202 // Have enough samples to start determining if the robot is moving or not.
203 buffered_samples_[buffered_samples_idx_] = info;
204 auto max_value =
205 ::std::max_element(buffered_samples_.begin(), buffered_samples_.end(),
206 compare_encoder)
207 ->encoder;
208 auto min_value =
209 ::std::min_element(buffered_samples_.begin(), buffered_samples_.end(),
210 compare_encoder)
211 ->encoder;
212 if (::std::abs(max_value - min_value) < constants_.zeroing_threshold) {
213 // Robot isn't moving, use middle sample to determine offsets.
214 moving = false;
215 }
216 }
217 buffered_samples_idx_ =
218 (buffered_samples_idx_ + 1) % constants_.moving_buffer_size;
219
220 if (!moving) {
221 // The robot is not moving, use the middle sample to determine offsets.
222 const int middle_index =
223 (buffered_samples_idx_ + (constants_.moving_buffer_size - 1) / 2) %
224 constants_.moving_buffer_size;
225
226 // Compute the sum of all the offset samples.
227 double relative_to_absolute_offset_sum = 0.0;
228 for (size_t i = 0; i < relative_to_absolute_offset_samples_.size(); ++i) {
229 relative_to_absolute_offset_sum +=
230 relative_to_absolute_offset_samples_[i];
231 }
232
233 // Compute the average offset between the absolute encoder and relative
234 // encoder. If we have 0 samples, assume it is 0.
235 double average_relative_to_absolute_offset =
236 relative_to_absolute_offset_samples_.size() == 0
237 ? 0.0
238 : relative_to_absolute_offset_sum /
239 relative_to_absolute_offset_samples_.size();
240
241 // Now, compute the nearest absolute encoder value to the offset relative
242 // encoder position.
243 const double adjusted_absolute_encoder =
244 Wrap(buffered_samples_[middle_index].encoder +
245 average_relative_to_absolute_offset,
246 buffered_samples_[middle_index].absolute_encoder -
247 constants_.measured_absolute_position,
248 constants_.one_revolution_distance);
249
250 const double relative_to_absolute_offset =
251 adjusted_absolute_encoder - buffered_samples_[middle_index].encoder;
252
253 // Add the sample and update the average with the new reading.
254 const size_t relative_to_absolute_offset_samples_size =
255 relative_to_absolute_offset_samples_.size();
256 if (relative_to_absolute_offset_samples_size <
257 constants_.average_filter_size) {
258 average_relative_to_absolute_offset =
259 (average_relative_to_absolute_offset *
260 relative_to_absolute_offset_samples_size +
261 relative_to_absolute_offset) /
262 (relative_to_absolute_offset_samples_size + 1);
263
264 relative_to_absolute_offset_samples_.push_back(
265 relative_to_absolute_offset);
266 } else {
267 average_relative_to_absolute_offset -=
268 relative_to_absolute_offset_samples_[samples_idx_] /
269 relative_to_absolute_offset_samples_size;
270 relative_to_absolute_offset_samples_[samples_idx_] =
271 relative_to_absolute_offset;
272 average_relative_to_absolute_offset +=
273 relative_to_absolute_offset /
274 relative_to_absolute_offset_samples_size;
275 }
276
277 // Now compute the offset between the pot and relative encoder.
278 if (offset_samples_.size() < constants_.average_filter_size) {
279 offset_samples_.push_back(buffered_samples_[middle_index].pot -
280 buffered_samples_[middle_index].encoder);
281 } else {
282 offset_samples_[samples_idx_] = buffered_samples_[middle_index].pot -
283 buffered_samples_[middle_index].encoder;
284 }
285
286 // Drop the oldest sample when we run this function the next time around.
287 samples_idx_ = (samples_idx_ + 1) % constants_.average_filter_size;
288
289 double pot_relative_encoder_offset_sum = 0.0;
290 for (size_t i = 0; i < offset_samples_.size(); ++i) {
291 pot_relative_encoder_offset_sum += offset_samples_[i];
292 }
293 pot_relative_encoder_offset_ =
294 pot_relative_encoder_offset_sum / offset_samples_.size();
295
296 offset_ = Wrap(buffered_samples_[middle_index].encoder +
297 pot_relative_encoder_offset_,
298 average_relative_to_absolute_offset +
299 buffered_samples_[middle_index].encoder,
300 constants_.one_revolution_distance) -
301 buffered_samples_[middle_index].encoder;
302 if (offset_ready()) {
303 zeroed_ = true;
304 }
Austin Schuh5f01f152017-02-11 21:34:08 -0800305 }
306
Diana Vandenberg8fea6ea2017-02-18 17:24:45 -0800307 // Update the position.
308 filtered_position_ = pot_relative_encoder_offset_ + info.encoder;
Austin Schuh5f01f152017-02-11 21:34:08 -0800309 position_ = offset_ + info.encoder;
310}
311
Isaac Wilcove0851ffd2017-02-16 04:13:14 +0000312void PulseIndexZeroingEstimator::Reset() {
313 max_index_position_ = ::std::numeric_limits<double>::lowest();
314 min_index_position_ = ::std::numeric_limits<double>::max();
315 offset_ = 0;
316 last_used_index_pulse_count_ = 0;
317 zeroed_ = false;
318 error_ = false;
319}
320
321void PulseIndexZeroingEstimator::StoreIndexPulseMaxAndMin(
322 const IndexPosition &info) {
323 // If we have a new index pulse.
324 if (last_used_index_pulse_count_ != info.index_pulses) {
325 // If the latest pulses's position is outside the range we've currently
326 // seen, record it appropriately.
327 if (info.latched_encoder > max_index_position_) {
328 max_index_position_ = info.latched_encoder;
329 }
330 if (info.latched_encoder < min_index_position_) {
331 min_index_position_ = info.latched_encoder;
332 }
333 last_used_index_pulse_count_ = info.index_pulses;
334 }
335}
336
337int PulseIndexZeroingEstimator::IndexPulseCount() {
338 if (min_index_position_ > max_index_position_) {
339 // This condition means we haven't seen a pulse yet.
340 return 0;
341 }
342
343 // Calculate the number of pulses encountered so far.
344 return 1 + static_cast<int>(
345 ::std::round((max_index_position_ - min_index_position_) /
346 constants_.index_difference));
347}
348
349void PulseIndexZeroingEstimator::UpdateEstimate(const IndexPosition &info) {
350 StoreIndexPulseMaxAndMin(info);
351 const int index_pulse_count = IndexPulseCount();
352 if (index_pulse_count > constants_.index_pulse_count) {
353 error_ = true;
354 }
355
356 // TODO(austin): Detect if the encoder or index pulse is unplugged.
357 // TODO(austin): Detect missing counts.
358
359 if (index_pulse_count == constants_.index_pulse_count && !zeroed_) {
360 offset_ = constants_.measured_index_position -
361 constants_.known_index_pulse * constants_.index_difference -
362 min_index_position_;
363 zeroed_ = true;
364 }
365 if (zeroed_) {
366 position_ = info.encoder + offset_;
367 }
368}
369
Adam Snaiderc4b3c192015-02-01 01:30:39 +0000370} // namespace zeroing
371} // namespace frc971