Do hysteresis in both directions on IMU blender

This also changes the algorithm a bit such that we don't strictly need X
saturated readings *in a row* to consider ourselves saturated.

Change-Id: I8298d4867e259b39af0cd000c74bade47bcd286d
Signed-off-by: James Kuszmaul <jabukuszmaul+collab@gmail.com>
diff --git a/frc971/imu_fdcan/dual_imu_blender_lib.cc b/frc971/imu_fdcan/dual_imu_blender_lib.cc
index 2627739..6099844 100644
--- a/frc971/imu_fdcan/dual_imu_blender_lib.cc
+++ b/frc971/imu_fdcan/dual_imu_blender_lib.cc
@@ -14,7 +14,7 @@
 // Coefficient to multiply the saturation values by to give some room on where
 // we switch to tdk.
 static constexpr double kSaturationCoeff = 0.9;
-static constexpr size_t kSaturationCounterThreshold = 20;
+static constexpr int kSaturationCounterThreshold = 20;
 
 using frc971::imu_fdcan::DualImuBlender;
 
@@ -46,6 +46,12 @@
   imu_values->set_pico_timestamp_us(dual_imu->board_timestamp_us());
   imu_values->set_monotonic_timestamp_ns(dual_imu->kernel_timestamp());
   imu_values->set_data_counter(dual_imu->packet_counter());
+  // Notes on saturation strategy:
+  // We use the TDK to detect saturation because we presume that if the Murata
+  // is saturated then it may produce poor or undefined behavior (including
+  // potentially producing values that make it look like it is not saturated).
+  // In practice, the Murata does seem to behave reasonably under saturation (it
+  // just maxes out its outputs at the given value).
 
   if (std::abs(dual_imu->tdk()->gyro_x()) >=
       kSaturationCoeff * kMurataGyroSaturation) {
@@ -65,14 +71,24 @@
     imu_values->set_gyro_y(dual_imu->murata()->gyro_y());
   }
 
+  // TODO(james): Currently we only do hysteresis for the gyro Z axis because
+  // this is the only axis that is particularly critical. We should do something
+  // like this for all axes.
   if (std::abs(dual_imu->tdk()->gyro_z()) >=
       kSaturationCoeff * kMurataGyroSaturation) {
     ++saturated_counter_;
   } else {
-    saturated_counter_ = 0;
+    --saturated_counter_;
+  }
+  if (saturated_counter_ <= -kSaturationCounterThreshold) {
+    is_saturated_ = false;
+    saturated_counter_ = -kSaturationCounterThreshold;
+  } else if (saturated_counter_ >= kSaturationCounterThreshold) {
+    is_saturated_ = true;
+    saturated_counter_ = kSaturationCounterThreshold;
   }
 
-  if (saturated_counter_ > kSaturationCounterThreshold) {
+  if (is_saturated_) {
     dual_imu_blender_status_builder->set_gyro_z(imu::ImuType::TDK);
     imu_values->set_gyro_z(dual_imu->tdk()->gyro_z());
   } else {
diff --git a/frc971/imu_fdcan/dual_imu_blender_lib.h b/frc971/imu_fdcan/dual_imu_blender_lib.h
index b3aa5c0..223f3ed 100644
--- a/frc971/imu_fdcan/dual_imu_blender_lib.h
+++ b/frc971/imu_fdcan/dual_imu_blender_lib.h
@@ -20,7 +20,8 @@
  private:
   aos::Sender<IMUValuesBatchStatic> imu_values_batch_sender_;
   aos::Sender<imu::DualImuBlenderStatusStatic> dual_imu_blender_status_sender_;
-  size_t saturated_counter_ = 0;
+  int saturated_counter_ = 0;
+  bool is_saturated_ = false;
 };
 
 }  // namespace frc971::imu_fdcan