blob: 153b84ff05d9ec746d1d1a5771bc74893b754d2e [file] [log] [blame]
James Kuszmaul9f2f53c2023-02-19 14:08:18 -08001#include "frc971/imu_reader/imu_watcher.h"
2
3#include "frc971/wpilib/imu_batch_generated.h"
4
5namespace frc971::controls {
6namespace {
7// Return the amount of distance that the drivetrain can travel before the
8// encoders will wrap. Necessary because the pico only sends over the encoders
9// in 16-bit counters, which will wrap relatively readily.
10double EncoderWrapDistance(double drivetrain_distance_per_encoder_tick) {
11 return drivetrain_distance_per_encoder_tick * (1 << 16);
12}
13} // namespace
14ImuWatcher::ImuWatcher(
15 aos::EventLoop *event_loop,
16 const control_loops::drivetrain::DrivetrainConfig<double> &dt_config,
17 const double drivetrain_distance_per_encoder_tick,
18 std::function<
19 void(aos::monotonic_clock::time_point, aos::monotonic_clock::time_point,
20 std::optional<Eigen::Vector2d>, Eigen::Vector3d, Eigen::Vector3d)>
James Kuszmaul54fe9d02023-03-23 20:32:40 -070021 callback,
22 TimestampSource timestamp_source)
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080023 : dt_config_(dt_config),
24 callback_(std::move(callback)),
25 zeroer_(zeroing::ImuZeroer::FaultBehavior::kTemporary),
26 left_encoder_(
27 -EncoderWrapDistance(drivetrain_distance_per_encoder_tick) / 2.0,
28 EncoderWrapDistance(drivetrain_distance_per_encoder_tick)),
29 right_encoder_(
30 -EncoderWrapDistance(drivetrain_distance_per_encoder_tick) / 2.0,
31 EncoderWrapDistance(drivetrain_distance_per_encoder_tick)) {
James Kuszmaul723e66e2023-04-05 21:22:57 -070032 event_loop->MakeWatcher("/localizer", [this, timestamp_source, event_loop](
James Kuszmaul54fe9d02023-03-23 20:32:40 -070033 const IMUValuesBatch &values) {
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080034 CHECK(values.has_readings());
35 for (const IMUValues *value : *values.readings()) {
36 zeroer_.InsertAndProcessMeasurement(*value);
37 if (zeroer_.Faulted()) {
38 if (value->checksum_failed()) {
39 imu_fault_tracker_.pico_to_pi_checksum_mismatch++;
40 } else if (value->previous_reading_diag_stat()->checksum_mismatch()) {
41 imu_fault_tracker_.imu_to_pico_checksum_mismatch++;
42 } else {
43 imu_fault_tracker_.other_zeroing_faults++;
44 }
45 } else {
46 if (!first_valid_data_counter_.has_value()) {
47 first_valid_data_counter_ = value->data_counter();
48 }
49 }
James Kuszmaul723e66e2023-04-05 21:22:57 -070050 int messages_dropped_this_cycle = 0;
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080051 if (first_valid_data_counter_.has_value()) {
52 total_imu_messages_received_++;
53 // Only update when we have good checksums, since the data counter
54 // could get corrupted.
55 if (!zeroer_.Faulted()) {
56 if (value->data_counter() < last_data_counter_) {
57 data_counter_offset_ += 1 << 16;
58 }
James Kuszmaul723e66e2023-04-05 21:22:57 -070059 const int total_dropped =
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080060 (1 + value->data_counter() + data_counter_offset_ -
61 first_valid_data_counter_.value()) -
62 total_imu_messages_received_;
James Kuszmaul723e66e2023-04-05 21:22:57 -070063 messages_dropped_this_cycle =
64 total_dropped - imu_fault_tracker_.missed_messages;
65 imu_fault_tracker_.missed_messages = total_dropped;
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080066 last_data_counter_ = value->data_counter();
67 }
68 }
69 // Set encoders to nullopt if we are faulted at all (faults may include
70 // checksum mismatches).
71 const std::optional<Eigen::Vector2d> encoders =
72 zeroer_.Faulted()
73 ? std::nullopt
74 : std::make_optional(Eigen::Vector2d{
75 left_encoder_.Unwrap(value->left_encoder()),
76 right_encoder_.Unwrap(value->right_encoder())});
77 {
James Kuszmaul04a343c2023-02-20 16:38:22 -080078 const aos::monotonic_clock::time_point pi_read_timestamp =
79 aos::monotonic_clock::time_point(
80 std::chrono::nanoseconds(value->monotonic_timestamp_ns()));
James Kuszmaul54fe9d02023-03-23 20:32:40 -070081 // If we can't trust the imu reading, just naively increment the
82 // pico timestamp.
83 const aos::monotonic_clock::time_point pico_timestamp =
84 timestamp_source == TimestampSource::kPi
85 ? pi_read_timestamp
86 : (zeroer_.Faulted()
87 ? (last_pico_timestamp_.has_value()
88 ? last_pico_timestamp_.value() + kNominalDt
89 : aos::monotonic_clock::epoch())
90 : aos::monotonic_clock::time_point(
91 std::chrono::microseconds(
92 value->pico_timestamp_us())));
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080093 // TODO(james): If we get large enough drift off of the pico,
94 // actually do something about it.
95 if (!pico_offset_.has_value()) {
James Kuszmaul04a343c2023-02-20 16:38:22 -080096 pico_offset_ = pi_read_timestamp - pico_timestamp;
James Kuszmaul9f2f53c2023-02-19 14:08:18 -080097 last_pico_timestamp_ = pico_timestamp;
98 }
James Kuszmaul723e66e2023-04-05 21:22:57 -070099 // The pico will sleep for a minimum of 3 seconds when resetting the
100 // IMU. Any absence of messages for less than that time is probably not
101 // an actual reset.
James Kuszmaul9f2f53c2023-02-19 14:08:18 -0800102 if (pico_timestamp < last_pico_timestamp_) {
103 pico_offset_.value() += std::chrono::microseconds(1ULL << 32);
104 }
105 const aos::monotonic_clock::time_point sample_timestamp =
106 pico_offset_.value() + pico_timestamp;
James Kuszmaul04a343c2023-02-20 16:38:22 -0800107 pico_offset_error_ = pi_read_timestamp - sample_timestamp;
James Kuszmaul723e66e2023-04-05 21:22:57 -0700108
109 if (last_imu_message_.has_value()) {
110 constexpr std::chrono::seconds kMinimumImuResetTime(3);
111 const std::chrono::nanoseconds message_time_gap =
112 last_imu_message_.value() -
113 event_loop->context().monotonic_event_time;
114 if (message_time_gap > kMinimumImuResetTime) {
115 bool assigned_fault = false;
116 // The IMU has probably reset; attempt to determine whether just the
117 // IMU was reset or if the IMU and pico reset.
118 if (std::chrono::abs(pico_offset_error_) >
119 std::chrono::seconds(1)) {
120 // If we have this big of a gap, then everything downstream of
121 // this is going to complain anyways, so reset the offset.
122 pico_offset_.reset();
123
124 imu_fault_tracker_.probable_pico_reset_count++;
125 assigned_fault = true;
126 }
127 // See if we dropped the "right" number of messages for the gap in
128 // question.
129 if (std::chrono::abs(messages_dropped_this_cycle * kNominalDt -
130 message_time_gap) >
131 std::chrono::milliseconds(100)) {
132 imu_fault_tracker_.probable_imu_reset_count++;
133 assigned_fault = true;
134 }
135
136 if (!assigned_fault) {
137 imu_fault_tracker_.unassignable_reset_count++;
138 }
139 }
140 }
James Kuszmaul9f2f53c2023-02-19 14:08:18 -0800141 const bool zeroed = zeroer_.Zeroed();
142
143 // When not zeroed, we aim to approximate zero acceleration by doing a
144 // zero-order hold on the gyro and setting the accelerometer readings to
145 // gravity.
James Kuszmaul04a343c2023-02-20 16:38:22 -0800146 callback_(sample_timestamp, pi_read_timestamp, encoders,
147 zeroed ? zeroer_.ZeroedGyro().value() : last_gyro_,
James Kuszmaul9f2f53c2023-02-19 14:08:18 -0800148 zeroed ? zeroer_.ZeroedAccel().value()
149 : dt_config_.imu_transform.transpose() *
150 Eigen::Vector3d::UnitZ());
151
152 if (zeroed) {
153 last_gyro_ = zeroer_.ZeroedGyro().value();
154 }
155 last_pico_timestamp_ = pico_timestamp;
156 }
James Kuszmaul723e66e2023-04-05 21:22:57 -0700157 last_imu_message_ = event_loop->context().monotonic_event_time;
James Kuszmaul9f2f53c2023-02-19 14:08:18 -0800158 }
159 });
160}
161} // namespace frc971::controls