Merge changes If50dcc0e,I17d591a5,I9c1cabda,Ib73d395b,I7e923b0d, ...

* changes:
  Only schedule OnRun callbacks right after construction in aos
  Split function scheduler out
  Fix transmit timestamp logic in SimulatedNetworkBridge
  Unit test for mismatched configs for server/client
  Convert unreachable simulated network bridge code to CHECKs.
  aos: Fix `--force_timestamp_loading` for single-node logs
  Log, replay, and solve with transmit timestamps
  Add boot info to NoncausalTimestampFilter debugging
  Logger: Pipe the monotonic_remote_transmit_time through event loop
  Remove unused kLogMessageAndDeliveryTime
  Fix starter_test under msan
  memory_mapped_queue: Use system_page_size
  Logger: Define contract to record transmit time for reliable messages
  Add note to static fbs Vector reserve() method
  Add a test reproducing a log reading failure
  Fix move ResizeableObject move constructor
  Logger: Print solve number along with candidate solution for debug info
  Call set_name() on more AOS timers
  Fix crash in message_bridge_server
  Fix typo in message_bridge_auth_client_lib.cc
  Fix Python wheel generation
  Fix LogReader destruction problem
  Make doctests only compatible with x86_64
  Fix possible data race in `aos/network/multinode_timestamp_filter.h`
  Fix handling of empty C++ comments in JSON parsing
  rename variables in aos Logger
  Fix AOS logging when using non-EventLoop configuration
  Add SendJson to AOS Sender
  Add note on size units in file_operations.h
  Reduce discrepancies between FlatbufferToJson versions
  Reduce flakeness of shm_event_loop_test
  aos: Make SetCurrentThreadAffinity errors a bit nicer
diff --git a/frc971/imu_fdcan/Dual_IMU/Core/Inc/main.h b/frc971/imu_fdcan/Dual_IMU/Core/Inc/main.h
index e2ab234..f4e0ee8 100644
--- a/frc971/imu_fdcan/Dual_IMU/Core/Inc/main.h
+++ b/frc971/imu_fdcan/Dual_IMU/Core/Inc/main.h
@@ -56,12 +56,63 @@
 

 /* USER CODE BEGIN EFP */

 // Different representations of the same four byte value. Simplifies conversion.

-union FourBytes {

+typedef union {

   float decimal;

   uint32_t four_bytes;

   uint16_t two_bytes[2];

   uint8_t byte[4];

-};

+} FourBytes;

+

+typedef enum { SPI_INIT, SPI_ZERO, SPI_START, SPI_RUN } SpiIn;

+

+typedef enum { SPI_READY, SPI_BUSY } SpiOut;

+

+typedef struct {

+  int16_t acc_x;

+  int16_t acc_y;

+  int16_t acc_z;

+  int16_t gyro_x;

+  int16_t gyro_y;

+  int16_t gyro_z;

+  int16_t temp;

+  int state;

+  int index;

+} DataRawInt16;

+

+typedef struct {

+  float acc_x;

+  float acc_y;

+  float acc_z;

+  float gyro_x;

+  float gyro_y;

+  float gyro_z;

+  float temp;

+} DataOutFloat;

+

+typedef struct {

+  uint32_t timestamp;

+  uint16_t can_counter;

+  uint16_t tdk_counter;

+  uint16_t uno_counter;

+  uint16_t due_counter;

+  float tdk_acc_x;

+  float tdk_acc_y;

+  float tdk_acc_z;

+  float tdk_gyro_x;

+  float tdk_gyro_y;

+  float tdk_gyro_z;

+  float murata_acc_x;

+  float murata_acc_y;

+  float murata_acc_z;

+  float murata_gyro_x;

+  float murata_gyro_y;

+  float murata_gyro_z;

+  uint8_t tdk_temp;

+  uint8_t uno_temp;

+  uint8_t due_temp;

+  uint8_t flags;

+} CanData;

+

 /* USER CODE END EFP */

 

 /* Private defines -----------------------------------------------------------*/

@@ -95,6 +146,10 @@
 #define T_VCP_TX_GPIO_Port GPIOC

 #define T_VCP_RX_Pin GPIO_PIN_5

 #define T_VCP_RX_GPIO_Port GPIOC

+#define PWM_RATE_Pin GPIO_PIN_0

+#define PWM_RATE_GPIO_Port GPIOB

+#define PWM_HEADING_Pin GPIO_PIN_1

+#define PWM_HEADING_GPIO_Port GPIOB

 #define CS_UNO_Pin GPIO_PIN_12

 #define CS_UNO_GPIO_Port GPIOB

 #define SCK_UNO_Pin GPIO_PIN_13

@@ -129,6 +184,9 @@
 #define FDCAN_STBY_GPIO_Port GPIOB

 

 /* USER CODE BEGIN Private defines */

+

+#define IMU_SAMPLES_PER_MS 3

+

 /* USER CODE END Private defines */

 

 #ifdef __cplusplus

diff --git a/frc971/imu_fdcan/Dual_IMU/Core/Inc/murata.h b/frc971/imu_fdcan/Dual_IMU/Core/Inc/murata.h
index a3cf626..19cddd9 100644
--- a/frc971/imu_fdcan/Dual_IMU/Core/Inc/murata.h
+++ b/frc971/imu_fdcan/Dual_IMU/Core/Inc/murata.h
@@ -15,10 +15,15 @@
 #include <stdint.h>
 #include <stdio.h>
 
+#include "math.h"
+
 #define CONV_INT16_INT8_H(a) ((int8_t)(((a) >> 8) & 0xFF))
 #define CONV_INT16_INT8_L(a) ((int8_t)(a & 0xFF))
+#define CONV_UINT8_INT16(a_H, a_L) (int16_t)((a_H << 8) | a_L)
 #define CONV_UINT16_UINT8_H(a) ((uint8_t)(((a) >> 8) & 0xFF))
 #define CONV_UINT16_UINT8_L(a) ((uint8_t)(a & 0xFF))
+#define CONV_UINT16_INT8_H(a) ((int8_t)(((a) >> 8) & 0xFF))
+#define CONV_UINT16_INT8_L(a) ((int8_t)(a & 0xFF))
 
 #define GET_SPI_DATA_INT16(a) ((int16_t)(((a) >> 8) & 0xFFFF))
 #define GET_SPI_DATA_UINT16(a) ((uint16_t)(((a) >> 8) & 0xFFFF))
@@ -26,7 +31,7 @@
 #define GET_SPI_DATA_UINT8_L(a) ((uint8_t)((a >> 8) & 0xFF))
 #define CHECK_RS_ERROR(a) (!(((a) >> 24) & 0x03))
 #define MURATA_CONV_TEMP(a) (float)((a / 30.0f) + 25)
-#define MURATA_CONV_GYRO(a) (float)(a / 80.0f)
+#define MURATA_CONV_GYRO(a) (float)(a / 80.0f) * ((float)(M_PI) / 180.0f)
 #define MURATA_CONV_ACC(a) (float)(a / 4905.0f)
 
 // Define each operation as SPI 32-bit frame. Details in datasheet page 34
@@ -40,6 +45,11 @@
 #define WRITE_FILTER_46HZ_ACC 0xE8022248   // Set 46 Hz filter
 #define WRITE_EOI_BIT 0xE000025B
 
+#define MURATA_GYRO_FILTER_ADDR 0x36
+#define MURATA_GYRO_FILTER_300HZ 0x2424
+#define MURATA_ACC_FILTER_ADDR 0xE8
+#define MURATA_ACC_FILTER_300HZ 0x0444
+
 #define READ_GYRO_X 0x040000F7
 #define READ_GYRO_Y 0x0C0000FB
 #define READ_GYRO_Z 0x040000F7
@@ -48,11 +58,6 @@
 #define READ_ACC_Z 0x180000E5
 #define READ_TEMP 0x1C0000E3
 
-#define READ_ACCELEROMETER_STATUS 0x4800009D
-#define READ_COMMON_STATUS_1 0x50000089
-#define READ_COMMON_STATUS_2 0x5400008F
-#define READ_RATE_STATUS_1 0x40000091
-#define READ_RATE_STATUS_2 0x44000097
 #define READ_SUMMARY_STATUS 0x380000D5
 #define READ_TRC_0 0x740000BF
 #define READ_TRC_1 0x780000B5
@@ -81,8 +86,8 @@
   int16_t gyro_x;    // deg/s
   int16_t gyro_y;    // deg/s
   int16_t gyro_z;    // deg/s
-  int16_t temp_due;  // C
-  int16_t temp_uno;  // C
+  int16_t due_temp;  // C
+  int16_t uno_temp;  // C
 } DataMurataRaw;
 
 // Human readable sensor data
@@ -93,8 +98,8 @@
   float gyro_x;
   float gyro_y;
   float gyro_z;
-  float temp_due;
-  float temp_uno;
+  float due_temp;
+  float uno_temp;
 } DataMurata;
 
 // Cross axis compensation values for fine tuning acc and gyro output
diff --git a/frc971/imu_fdcan/Dual_IMU/Core/Inc/stm32g4xx_it.h b/frc971/imu_fdcan/Dual_IMU/Core/Inc/stm32g4xx_it.h
index c320580..6b6817a 100644
--- a/frc971/imu_fdcan/Dual_IMU/Core/Inc/stm32g4xx_it.h
+++ b/frc971/imu_fdcan/Dual_IMU/Core/Inc/stm32g4xx_it.h
@@ -55,6 +55,10 @@
 void DebugMon_Handler(void);

 void PendSV_Handler(void);

 void SysTick_Handler(void);

+void TIM1_UP_TIM16_IRQHandler(void);

+void SPI1_IRQHandler(void);

+void SPI2_IRQHandler(void);

+void SPI3_IRQHandler(void);

 /* USER CODE BEGIN EFP */

 

 /* USER CODE END EFP */

diff --git a/frc971/imu_fdcan/Dual_IMU/Core/Inc/tdk.h b/frc971/imu_fdcan/Dual_IMU/Core/Inc/tdk.h
index 4346810..6b29184 100644
--- a/frc971/imu_fdcan/Dual_IMU/Core/Inc/tdk.h
+++ b/frc971/imu_fdcan/Dual_IMU/Core/Inc/tdk.h
@@ -11,12 +11,19 @@
 #ifndef INC_TDK_H_
 #define INC_TDK_H_
 
-#define CONV_UINT8_INT16(a_H, a_L) (int16_t)((a_H << 8) | a_L)
+#include "math.h"
+
+// TDK Range
+#define TDK_ACC_MAG_MAX (float)(16)  // +/- 16 g's
+#define TDK_GYRO_MAG_MAX \
+  (float)(2000.0f *      \
+          ((float)(M_PI) / 180.0f))  // +/- 2000 dps to rad/s ~85 rad/s
 
 // Convert raw sensor data to human readable format
-#define TDK_CONV_TEMP(a) (float)((a / 326.8f) + 25)  // output in Celsius
-#define TDK_CONV_GYRO(a) (float)(a / 16.4f)          // output in deg/s
-#define TDK_CONV_ACC(a) (float)(a / 2048.0f)         // output in g
+#define TDK_CONV_TEMP(a) (float)((a / 326.8f) + 25.0f)  // output in Celsius
+#define TDK_CONV_GYRO(a) \
+  (float)(a / 16.4f) * ((float)(M_PI) / 180.0f)  // output in deg/s
+#define TDK_CONV_ACC(a) (float)(a / 2048.0f)     // output in g
 
 // Register map
 #define SMPLRT_DIV 0x19
@@ -41,6 +48,7 @@
 #define GYRO_ZOUT_L 0x48
 #define USER_CTRL 0x6A
 #define PWR_MGMT_1 0x6B
+#define PWR_MGMT_2 0x6C
 #define FIFO_COUNT_H 0x72
 #define FIFO_COUNT_L 0x73
 #define FIFO_R_W 0x74
@@ -63,6 +71,17 @@
   uint8_t temp_H;
 } DataTdkRaw;
 
+// Combined version of raw data
+typedef struct {
+  int16_t acc_x;
+  int16_t acc_y;
+  int16_t acc_z;
+  int16_t gyro_x;
+  int16_t gyro_y;
+  int16_t gyro_z;
+  int16_t temp;
+} DataTdkRawCombined;
+
 // Human readable sensor data
 typedef struct {
   float acc_x;   // g
diff --git a/frc971/imu_fdcan/Dual_IMU/Core/Src/main.c b/frc971/imu_fdcan/Dual_IMU/Core/Src/main.c
index 98b61d2..c0758f8 100644
--- a/frc971/imu_fdcan/Dual_IMU/Core/Src/main.c
+++ b/frc971/imu_fdcan/Dual_IMU/Core/Src/main.c
@@ -24,6 +24,7 @@
 #include <stdbool.h>

 #include <stdio.h>

 #include <string.h>

+#include <time.h>

 

 #include "murata.h"

 #include "tdk.h"

@@ -36,8 +37,6 @@
 

 /* Private define ------------------------------------------------------------*/

 /* USER CODE BEGIN PD */

-#define MURATA_ACC_FILTER WRITE_FILTER_46HZ_ACC

-#define MURATA_GYRO_FILTER WRITE_FILTER_46HZ_GYRO

 /* USER CODE END PD */

 

 /* Private macro -------------------------------------------------------------*/

@@ -54,6 +53,8 @@
 SPI_HandleTypeDef hspi2;

 SPI_HandleTypeDef hspi3;

 

+TIM_HandleTypeDef htim1;

+TIM_HandleTypeDef htim2;

 TIM_HandleTypeDef htim3;

 

 UART_HandleTypeDef huart1;

@@ -75,13 +76,35 @@
 

 FDCAN_FilterTypeDef sFilterConfig;

 FDCAN_TxHeaderTypeDef tx_header;

+FDCAN_RxHeaderTypeDef rx_header;

 

 static CrossAxisCompMurata

     cac_murata;  // Cross-axis compensation. Details on datasheet page 11

-static DataMurataRaw data_murata_raw;

 static DataMurata data_murata;

-static DataTdkRaw data_tdk_raw;

 static DataTdk data_tdk;

+static uint8_t can_tx[64];

+static int can_tx_packet_counter;

+static CanData can_out;

+static int timer_index;

+

+static DataMurata murata_averaged;

+static DataTdk tdk_averaged;

+

+static DataRawInt16 uno_data[IMU_SAMPLES_PER_MS];

+static DataRawInt16 due_data[IMU_SAMPLES_PER_MS];

+static DataRawInt16 tdk_data[IMU_SAMPLES_PER_MS];

+

+static SpiOut uno_state;

+static SpiOut due_state;

+static SpiOut tdk_state;

+

+static uint8_t spi1_rx[2];

+static uint8_t spi1_tx[2];

+static FourBytes spi2_rx;

+static FourBytes spi2_tx;

+static FourBytes spi3_rx;

+static FourBytes spi3_tx;

+static FourBytes spi_murata_rx;

 

 /* USER CODE END PV */

 

@@ -96,10 +119,12 @@
 static void MX_USART1_UART_Init(void);

 static void MX_TIM3_Init(void);

 static void MX_USB_PCD_Init(void);

+static void MX_TIM2_Init(void);

+static void MX_TIM1_Init(void);

 /* USER CODE BEGIN PFP */

 

 static void EnableLeds(void);

-static void EnableSpiTdk(void);

+static void PowerTdk(void);

 static void ConvertEndianMurata(uint8_t *result, uint32_t original);

 static uint8_t MurataCRC8(uint8_t bit_value,

                           uint8_t redundancy);  // For checksum calculation.

@@ -114,12 +139,19 @@
 static uint8_t SendSpiTdk(uint8_t address, uint8_t data, bool read);

 static bool InitMurata(void);

 static void InitTdk(void);

-static void ReadDataMurata(void);

-static void ReadDataTdk(void);

-static void ConvertDataMurata(void);

-static void ConvertDataTdk(void);

-static void InitCan(FDCAN_TxHeaderTypeDef *tx_header, uint8_t id);

-

+static void InitCan(FDCAN_TxHeaderTypeDef *tx_header, uint32_t id);

+static void PWMSend(TIM_HandleTypeDef *htim, uint32_t channel, float data);

+static void ConstructCanfdPacket(uint8_t *tx, DataMurata *murata, DataTdk *tdk);

+static void AverageData(void);

+static void ComposeData(void);

+static void RealignData(void);

+static void SpiReadIt(SPI_HandleTypeDef *hspix, uint32_t reg);

+static void SpiCsStart(SPI_HandleTypeDef *hspix);

+static void SpiCsEnd(SPI_HandleTypeDef *hspix);

+static void SpiTdk(DataRawInt16 *data, SPI_HandleTypeDef *hspix, SpiOut *res,

+                   SpiIn call);

+static void SpiMurata(DataRawInt16 *data, SPI_HandleTypeDef *hspix, SpiOut *res,

+                      SpiIn call);

 /* USER CODE END PFP */

 

 /* Private user code ---------------------------------------------------------*/

@@ -163,12 +195,19 @@
   MX_USART1_UART_Init();

   MX_TIM3_Init();

   MX_USB_PCD_Init();

+  MX_TIM2_Init();

+  MX_TIM1_Init();

   /* USER CODE BEGIN 2 */

 

-  EnableLeds();  // Set LEDs to red

-  InitTdk();

-  InitMurata();

-  InitCan(&tx_header, 0xAA);

+  EnableLeds();               // Set LEDs to red

+  InitCan(&tx_header, 0x01);  // Initialize the CAN module

+  InitMurata();  // Run the Murata power up sequence (see pg 30 of datasheet)

+  InitTdk();     // Run the TDK power up sequence (see pg 22 of datasheet)

+  HAL_TIM_Base_Start_IT(&htim2);  // Start 1 us timer

+  HAL_TIM_Base_Start_IT(&htim1);  // Start 1 ms timer

+

+  HAL_TIM_PWM_Start_IT(&htim3, TIM_CHANNEL_3);

+  HAL_TIM_PWM_Start_IT(&htim3, TIM_CHANNEL_4);

 

   /* USER CODE END 2 */

 

@@ -179,30 +218,6 @@
     /* USER CODE END WHILE */

 

     /* USER CODE BEGIN 3 */

-    ReadDataMurata();

-    ConvertDataMurata();

-    printf(

-        "MUR IMU:\tX ACC: %.8f \tY ACC: %.8f \tZ ACC: %.8f \tX GYRO: %.8f \tY "

-        "GYRO: %.8f \tZ GYRO: %.8f \n\r",

-        data_murata.acc_x, data_murata.acc_y, data_murata.acc_z,

-        data_murata.gyro_x, data_murata.gyro_y, data_murata.gyro_z);

-

-    ReadDataTdk();

-    ConvertDataTdk();

-    printf(

-        "TDK IMU:\t\tX ACC: %.8f \tY ACC: %.8f \tZ ACC: %.8f \tX GYRO: %.8f "

-        "\tY GYRO: %.8f \tZ GYRO: %.8f \n\r",

-        data_tdk.acc_x, data_tdk.acc_y, data_tdk.acc_z, data_tdk.gyro_x,

-        data_tdk.gyro_y, data_tdk.gyro_z);

-

-    uint8_t tx_data[8] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};

-

-    if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan2, &tx_header, tx_data) !=

-        HAL_OK) {

-      Error_Handler();

-    }

-

-    HAL_Delay(100);

   }

   /* USER CODE END 3 */

 }

@@ -453,6 +468,86 @@
 }

 

 /**

+ * @brief TIM1 Initialization Function

+ * @param None

+ * @retval None

+ */

+static void MX_TIM1_Init(void) {

+  /* USER CODE BEGIN TIM1_Init 0 */

+

+  /* USER CODE END TIM1_Init 0 */

+

+  TIM_ClockConfigTypeDef sClockSourceConfig = {0};

+  TIM_MasterConfigTypeDef sMasterConfig = {0};

+

+  /* USER CODE BEGIN TIM1_Init 1 */

+

+  /* USER CODE END TIM1_Init 1 */

+  htim1.Instance = TIM1;

+  htim1.Init.Prescaler = 168 - 1;

+  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;

+  htim1.Init.Period = 333;

+  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

+  htim1.Init.RepetitionCounter = 0;

+  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;

+  if (HAL_TIM_Base_Init(&htim1) != HAL_OK) {

+    Error_Handler();

+  }

+  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;

+  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) {

+    Error_Handler();

+  }

+  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;

+  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;

+  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

+  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) {

+    Error_Handler();

+  }

+  /* USER CODE BEGIN TIM1_Init 2 */

+

+  /* USER CODE END TIM1_Init 2 */

+}

+

+/**

+ * @brief TIM2 Initialization Function

+ * @param None

+ * @retval None

+ */

+static void MX_TIM2_Init(void) {

+  /* USER CODE BEGIN TIM2_Init 0 */

+

+  /* USER CODE END TIM2_Init 0 */

+

+  TIM_ClockConfigTypeDef sClockSourceConfig = {0};

+  TIM_MasterConfigTypeDef sMasterConfig = {0};

+

+  /* USER CODE BEGIN TIM2_Init 1 */

+

+  /* USER CODE END TIM2_Init 1 */

+  htim2.Instance = TIM2;

+  htim2.Init.Prescaler = 168 - 1;

+  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;

+  htim2.Init.Period = 4.294967295E9;

+  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

+  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;

+  if (HAL_TIM_Base_Init(&htim2) != HAL_OK) {

+    Error_Handler();

+  }

+  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;

+  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) {

+    Error_Handler();

+  }

+  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;

+  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

+  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) {

+    Error_Handler();

+  }

+  /* USER CODE BEGIN TIM2_Init 2 */

+

+  /* USER CODE END TIM2_Init 2 */

+}

+

+/**

  * @brief TIM3 Initialization Function

  * @param None

  * @retval None

@@ -470,9 +565,9 @@
 

   /* USER CODE END TIM3_Init 1 */

   htim3.Instance = TIM3;

-  htim3.Init.Prescaler = 2563;

+  htim3.Init.Prescaler = 24 - 1;

   htim3.Init.CounterMode = TIM_COUNTERMODE_UP;

-  htim3.Init.Period = 65535;

+  htim3.Init.Period = 35000;

   htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

   htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

   if (HAL_TIM_Base_Init(&htim3) != HAL_OK) {

@@ -678,7 +773,7 @@
   HAL_GPIO_WritePin(GPIOC, LED2_RED_Pin, GPIO_PIN_RESET);

 }

 

-static void EnableSpiTdk(void) {

+static void PowerTdk(void) {

   // TDK_PWR_EN starts high, must be set low before reading

   HAL_GPIO_WritePin(TDK_PWR_EN_GPIO_Port, TDK_PWR_EN_Pin, GPIO_PIN_RESET);

   // TDK_EN starts high, must be set low after a delay from TDK_PWR_EN

@@ -728,7 +823,7 @@
 static uint32_t MakeSpiMsgMurata(uint8_t address, uint16_t data) {

   // Constructs SPI read/write frame

   // Details on datasheet page 32-34

-  uint32_t message = (uint32_t)((((address << 2) | 0x01) << 24) | data << 8);

+  uint32_t message = (uint32_t)(((address << 2) << 24) | data << 8);

   uint8_t redundancy = GetChecksumMurata(message);

   return (uint32_t)(message | redundancy);

 }

@@ -740,9 +835,9 @@
 

 static uint32_t TransmitSpiMurata(uint32_t value, GPIO_TypeDef *cs_port,

                                   uint16_t cs_pin, SPI_HandleTypeDef *hspix) {

-  union FourBytes rx_data_raw;

-  union FourBytes rx_data;

-  union FourBytes tx_data;

+  FourBytes rx_data_raw;

+  FourBytes rx_data;

+  FourBytes tx_data;

   ConvertEndianMurata(tx_data.byte, value);

   HAL_GPIO_WritePin(cs_port, cs_pin, GPIO_PIN_RESET);

   HAL_SPI_TransmitReceive(hspix, tx_data.byte, rx_data_raw.byte,

@@ -772,10 +867,6 @@
   return spi_rx[0];

 }

 

-static uint8_t ReadSpiTdk(uint8_t address) {

-  return SendSpiTdk(address, 0x00, true);

-}

-

 static bool InitMurata(void) {

   int num_attempts = 0;

   uint32_t response_due = 0;

@@ -794,6 +885,13 @@
 

   HAL_Delay(25);

 

+  SendSpiDue(MakeSpiReadMsgMurata(ACC_DC1_ADDRESS));  // cxx_cxy address

+

+  SendSpiDue(WRITE_OP_MODE_NORMAL);

+  SendSpiDue(WRITE_OP_MODE_NORMAL);

+  SendSpiUno(WRITE_OP_MODE_NORMAL);

+  HAL_Delay(70);

+

   SendSpiDue(WRITE_MODE_ASM_010);

   SendSpiDue(READ_MODE);

   SendSpiDue(WRITE_MODE_ASM_001);

@@ -864,8 +962,9 @@
   SendSpiUno(WRITE_OP_MODE_NORMAL);

   HAL_Delay(70);

 

-  SendSpiUno(MURATA_GYRO_FILTER);

-  SendSpiUno(MURATA_ACC_FILTER);

+  SendSpiUno(

+      MakeSpiMsgMurata(MURATA_GYRO_FILTER_ADDR, MURATA_GYRO_FILTER_300HZ));

+  SendSpiUno(MakeSpiMsgMurata(MURATA_ACC_FILTER_ADDR, MURATA_ACC_FILTER_300HZ));

 

   HAL_GPIO_WritePin(RESET_DUE_GPIO_Port, RESET_DUE_Pin,

                     GPIO_PIN_RESET);  // Reset DUE

@@ -877,7 +976,8 @@
   SendSpiDue(WRITE_OP_MODE_NORMAL);

   HAL_Delay(1);

 

-  SendSpiDue(MURATA_GYRO_FILTER);

+  SendSpiDue(

+      MakeSpiMsgMurata(MURATA_GYRO_FILTER_ADDR, MURATA_GYRO_FILTER_300HZ));

 

   for (num_attempts = 0; num_attempts < 2; num_attempts++) {

     HAL_Delay(405);

@@ -915,9 +1015,12 @@
       SendSpiUno(WRITE_OP_MODE_NORMAL);

       HAL_Delay(50);

 

-      SendSpiUno(MURATA_GYRO_FILTER);

-      SendSpiUno(MURATA_ACC_FILTER);

-      SendSpiDue(MURATA_GYRO_FILTER);

+      SendSpiUno(

+          MakeSpiMsgMurata(MURATA_GYRO_FILTER_ADDR, MURATA_GYRO_FILTER_300HZ));

+      SendSpiUno(

+          MakeSpiMsgMurata(MURATA_ACC_FILTER_ADDR, MURATA_ACC_FILTER_300HZ));

+      SendSpiDue(

+          MakeSpiMsgMurata(MURATA_GYRO_FILTER_ADDR, MURATA_GYRO_FILTER_300HZ));

       HAL_Delay(45);

     } else {

       break;

@@ -931,31 +1034,216 @@
   return true;

 }

 

-// Todo (Zach): separate the steps of reading data and realigning axis

-static void ReadDataMurata(void) {

-  SendSpiUno(READ_ACC_Z);

-  data_murata_raw.acc_z = -GET_SPI_DATA_INT16(SendSpiUno(READ_ACC_X));

-  data_murata_raw.acc_y = GET_SPI_DATA_INT16(SendSpiUno(READ_ACC_Y));

-  data_murata_raw.acc_x = GET_SPI_DATA_INT16(SendSpiUno(READ_GYRO_X));

-  data_murata_raw.gyro_y = -GET_SPI_DATA_INT16(SendSpiUno(READ_TEMP));

-  data_murata_raw.temp_uno = GET_SPI_DATA_INT16(SendSpiUno(READ_ACC_Z));

+static void InitTdk(void) {

+  // Power up sequence. See datasheet p22 for details

+  // The chip must receive power prior to SPI

+  PowerTdk();

 

-  SendSpiDue(READ_GYRO_Z);

-  data_murata_raw.gyro_z = GET_SPI_DATA_INT16(SendSpiDue(READ_GYRO_Y));

-  data_murata_raw.gyro_x = -GET_SPI_DATA_INT16(SendSpiDue(READ_TEMP));

-  data_murata_raw.temp_due = GET_SPI_DATA_INT16(SendSpiDue(READ_GYRO_Z));

+  // Send 0x81 to PWR_MGMT to initialize SPI

+  SendSpiTdk(PWR_MGMT_1, 0x81, false);

+  HAL_Delay(100);

+

+  // Setting the sample rates for TDK

+  SendSpiTdk(USER_CTRL, 0x55, false);

+  SendSpiTdk(ACCEL_CONFIG, 0x18, false);

+  SendSpiTdk(ACCEL_CONFIG_2, 0x08, false);

+  SendSpiTdk(GYRO_CONFIG, 0x1A, false);

+  SendSpiTdk(FIFO_EN, 0x00, false);

+  SendSpiTdk(CONFIG, 0x00, false);

+  SendSpiTdk(SMPLRT_DIV, 0x00, false);

 }

 

-static void ConvertDataMurata(void) {

-  data_murata.acc_x = MURATA_CONV_ACC(data_murata_raw.acc_x);

-  data_murata.acc_y = MURATA_CONV_ACC(data_murata_raw.acc_y);

-  data_murata.acc_z = MURATA_CONV_ACC(data_murata_raw.acc_z);

-  data_murata.gyro_x = MURATA_CONV_GYRO(data_murata_raw.gyro_x);

-  data_murata.gyro_y = MURATA_CONV_GYRO(data_murata_raw.gyro_y);

-  data_murata.gyro_z = MURATA_CONV_GYRO(data_murata_raw.gyro_z);

-  data_murata.temp_uno = MURATA_CONV_TEMP(data_murata_raw.temp_uno);

-  data_murata.temp_due = MURATA_CONV_TEMP(data_murata_raw.temp_due);

+static void InitCan(FDCAN_TxHeaderTypeDef *tx_header, uint32_t id) {

+  // Initialize the Header

+  tx_header->Identifier = id;

+  tx_header->IdType = FDCAN_STANDARD_ID;

+  tx_header->TxFrameType = FDCAN_DATA_FRAME;

+  tx_header->DataLength = FDCAN_DLC_BYTES_8;

+  tx_header->ErrorStateIndicator = FDCAN_ESI_ACTIVE;

+  tx_header->BitRateSwitch = FDCAN_BRS_OFF;

+  tx_header->FDFormat = FDCAN_CLASSIC_CAN;

+  tx_header->TxEventFifoControl = FDCAN_NO_TX_EVENTS;

+  tx_header->MessageMarker = 0x0;  // Ignore because FDCAN_NO_TX_EVENTS

 

+  // Pull standby pin low

+  HAL_GPIO_WritePin(FDCAN_STBY_GPIO_Port, FDCAN_STBY_Pin, GPIO_PIN_RESET);

+

+  // Start the FDCAN module

+  if (HAL_FDCAN_Start(&hfdcan2) != HAL_OK) {

+    Error_Handler();

+  }

+

+  // Initialize can_out to zeros

+  memset(&can_out, 0, sizeof(can_out));

+}

+

+static void PWMSend(TIM_HandleTypeDef *htim, uint32_t channel, float data) {

+  if (htim == &htim3) {

+    // 10% pwm == min, 90% pwm == max

+    float pwm_period = (float)(htim->Init.Period);

+    float range;

+    float scale;

+    float translated;

+    uint32_t result;

+

+    switch (channel) {

+      case TIM_CHANNEL_3:

+        // Sends TDK Z-gyro data over PWM_Rate port

+        range = 2 * TDK_GYRO_MAG_MAX;

+        scale = (pwm_period * .8f) / range;

+        translated = data + range * .5f;

+        result = (uint32_t)(scale * translated + pwm_period * .1f);

+        if (result < pwm_period * .1f) {

+          result = pwm_period * .1f;

+        }

+        if (result > pwm_period * .9f) {

+          result = pwm_period * .9f;

+        }

+        TIM3->CCR3 = result;

+        break;

+

+      case TIM_CHANNEL_4:

+        // Unused PWM_Heading port

+        // TODO: (Zach) figure out if we keep it with the

+        // same as CH3 or if we add something else

+        range = 2 * TDK_GYRO_MAG_MAX;

+        scale = (pwm_period * .8f) / range;

+        translated = data + range * .5f;

+        result = (uint32_t)(scale * translated + pwm_period * .1f);

+        if (result < pwm_period * .1f) {

+          result = pwm_period * .1f;

+        }

+        if (result > pwm_period * .9f) {

+          result = pwm_period * .9f;

+        }

+        TIM3->CCR4 = result;

+        break;

+

+      default:

+        break;

+    }

+  }

+}

+

+/* 	CAN_FD Tx data packet specs:

+ *  https://docs.google.com/document/d/12AJUruW7DZ2pIrDzTyPC0qqFoia4QOSVlax6Jd7m4H0

+ */

+

+static void ConstructCanfdPacket(uint8_t *tx, DataMurata *murata,

+                                 DataTdk *tdk) {

+  // Clear the CAN packet

+  memset(tx, 0, 64 * sizeof(*tx));

+

+  // Write in the struct data

+  can_out.tdk_acc_x = tdk->acc_x;

+  can_out.tdk_acc_y = tdk->acc_y;

+  can_out.tdk_acc_z = tdk->acc_z;

+  can_out.tdk_gyro_x = tdk->gyro_x;

+  can_out.tdk_gyro_y = tdk->gyro_y;

+  can_out.tdk_gyro_z = tdk->gyro_z;

+

+  can_out.murata_acc_x = murata->acc_x;

+  can_out.murata_acc_y = murata->acc_y;

+  can_out.murata_acc_z = murata->acc_z;

+  can_out.murata_gyro_x = murata->gyro_x;

+  can_out.murata_gyro_y = murata->gyro_y;

+  can_out.murata_gyro_z = murata->gyro_z;

+

+  if (tdk->temp < 0) {

+    can_out.tdk_temp = 0;

+  } else if (tdk->temp > 255) {

+    can_out.tdk_temp = 255;

+  } else {

+    can_out.tdk_temp = (uint8_t)(tdk->temp);

+  }

+

+  if (murata->uno_temp < 0) {

+    can_out.uno_temp = 0;

+  } else if (murata->uno_temp > 255) {

+    can_out.uno_temp = 255;

+  } else {

+    can_out.uno_temp = (uint8_t)(murata->uno_temp);

+  }

+

+  if (murata->due_temp < 0) {

+    can_out.due_temp = 0;

+  } else if (murata->due_temp > 255) {

+    can_out.due_temp = 255;

+  } else {

+    can_out.due_temp = (uint8_t)(murata->due_temp);

+  }

+

+  can_out.timestamp = __HAL_TIM_GetCounter(&htim2);

+

+  memcpy(&tx[0], &can_out, sizeof(can_out));

+}

+

+static void AverageData(void) {

+  // Clear the float data fields

+  memset(&tdk_averaged, 0, sizeof(tdk_averaged));

+  memset(&murata_averaged, 0, sizeof(murata_averaged));

+

+  for (int i = 0; i < IMU_SAMPLES_PER_MS; i++) {

+    tdk_averaged.acc_x += tdk_data[i].acc_x;

+    tdk_averaged.acc_y += tdk_data[i].acc_y;

+    tdk_averaged.acc_z += tdk_data[i].acc_z;

+    tdk_averaged.gyro_x += tdk_data[i].gyro_x;

+    tdk_averaged.gyro_y += tdk_data[i].gyro_y;

+    tdk_averaged.gyro_z += tdk_data[i].gyro_z;

+    tdk_averaged.temp += tdk_data[i].temp;

+

+    murata_averaged.acc_x += uno_data[i].acc_x;

+    murata_averaged.acc_y += uno_data[i].acc_y;

+    murata_averaged.acc_z += uno_data[i].acc_z;

+    murata_averaged.gyro_x += uno_data[i].gyro_x;

+    murata_averaged.gyro_y += due_data[i].gyro_y;

+    murata_averaged.gyro_z += due_data[i].gyro_z;

+    murata_averaged.uno_temp += uno_data[i].temp;

+    murata_averaged.due_temp += due_data[i].temp;

+  }

+

+  tdk_averaged.acc_x /= IMU_SAMPLES_PER_MS;

+  tdk_averaged.acc_y /= IMU_SAMPLES_PER_MS;

+  tdk_averaged.acc_z /= IMU_SAMPLES_PER_MS;

+  tdk_averaged.gyro_x /= IMU_SAMPLES_PER_MS;

+  tdk_averaged.gyro_y /= IMU_SAMPLES_PER_MS;

+  tdk_averaged.gyro_z /= IMU_SAMPLES_PER_MS;

+  tdk_averaged.temp /= IMU_SAMPLES_PER_MS;

+

+  murata_averaged.acc_x /= IMU_SAMPLES_PER_MS;

+  murata_averaged.acc_y /= IMU_SAMPLES_PER_MS;

+  murata_averaged.acc_z /= IMU_SAMPLES_PER_MS;

+  murata_averaged.gyro_x /= IMU_SAMPLES_PER_MS;

+  murata_averaged.gyro_y /= IMU_SAMPLES_PER_MS;

+  murata_averaged.gyro_z /= IMU_SAMPLES_PER_MS;

+  murata_averaged.uno_temp /= IMU_SAMPLES_PER_MS;

+  murata_averaged.due_temp /= IMU_SAMPLES_PER_MS;

+}

+

+static void ComposeData(void) {

+  // Clear the float data fields

+  memset(&data_tdk, 0, sizeof(data_tdk));

+  memset(&data_murata, 0, sizeof(data_murata));

+

+  // Assign the converted binary

+  data_tdk.acc_x = TDK_CONV_ACC(tdk_averaged.acc_x);

+  data_tdk.acc_y = TDK_CONV_ACC(tdk_averaged.acc_y);

+  data_tdk.acc_z = TDK_CONV_ACC(tdk_averaged.acc_z);

+  data_tdk.gyro_x = TDK_CONV_GYRO(tdk_averaged.gyro_x);

+  data_tdk.gyro_y = TDK_CONV_GYRO(tdk_averaged.gyro_y);

+  data_tdk.gyro_z = TDK_CONV_GYRO(tdk_averaged.gyro_z);

+  data_tdk.temp = TDK_CONV_TEMP(tdk_averaged.temp);

+

+  data_murata.acc_x = MURATA_CONV_ACC(murata_averaged.acc_x);

+  data_murata.acc_y = MURATA_CONV_ACC(murata_averaged.acc_y);

+  data_murata.acc_z = MURATA_CONV_ACC(murata_averaged.acc_z);

+  data_murata.gyro_x = MURATA_CONV_GYRO(murata_averaged.gyro_x);

+  data_murata.gyro_y = MURATA_CONV_GYRO(murata_averaged.gyro_y);

+  data_murata.gyro_z = MURATA_CONV_GYRO(murata_averaged.gyro_z);

+  data_murata.uno_temp = MURATA_CONV_TEMP(murata_averaged.uno_temp);

+  data_murata.due_temp = MURATA_CONV_TEMP(murata_averaged.due_temp);

+

+  // Apply the murata cross-axis compensation

   data_murata.acc_x = (cac_murata.bxx * data_murata.acc_x) +

                       (cac_murata.bxy * data_murata.acc_y) +

                       (cac_murata.bxz * data_murata.acc_z);

@@ -977,98 +1265,363 @@
                        (cac_murata.czz * data_murata.gyro_z);

 }

 

-static void ReadDataTdk(void) {

-  data_tdk_raw.acc_x_H = ReadSpiTdk(ACCEL_XOUT_H);

-  data_tdk_raw.acc_x_L = ReadSpiTdk(ACCEL_XOUT_L);

-  data_tdk_raw.acc_y_H = ReadSpiTdk(ACCEL_YOUT_H);

-  data_tdk_raw.acc_y_L = ReadSpiTdk(ACCEL_YOUT_L);

-  data_tdk_raw.acc_z_H = ReadSpiTdk(ACCEL_ZOUT_H);

-  data_tdk_raw.acc_z_L = ReadSpiTdk(ACCEL_ZOUT_L);

-  data_tdk_raw.gyro_x_H = ReadSpiTdk(GYRO_XOUT_H);

-  data_tdk_raw.gyro_x_L = ReadSpiTdk(GYRO_XOUT_L);

-  data_tdk_raw.gyro_y_H = ReadSpiTdk(GYRO_YOUT_H);

-  data_tdk_raw.gyro_y_L = ReadSpiTdk(GYRO_YOUT_L);

-  data_tdk_raw.gyro_z_H = ReadSpiTdk(GYRO_ZOUT_H);

-  data_tdk_raw.gyro_z_L = ReadSpiTdk(GYRO_ZOUT_L);

-  data_tdk_raw.temp_H = ReadSpiTdk(TEMP_OUT_H);

-  data_tdk_raw.temp_L = ReadSpiTdk(TEMP_OUT_L);

+static void RealignData(void) {

+  // Rotate the axis based on observed behavior

+  float f = data_tdk.acc_x;

+  data_tdk.acc_x = -data_tdk.acc_y;

+  data_tdk.acc_y = -f;

+  f = data_tdk.gyro_x;

+  data_tdk.acc_z = -data_tdk.acc_z;

+  data_tdk.gyro_x = -data_tdk.gyro_y;

+  data_tdk.gyro_y = -f;

+  data_tdk.gyro_z = -data_tdk.gyro_z;

+

+  f = data_murata.acc_x;

+  data_murata.acc_x = -data_murata.acc_y;

+  data_murata.acc_y = -f;

+  f = data_murata.gyro_x;

+  data_murata.gyro_x = -data_murata.gyro_y;

+  data_murata.gyro_y = -f;

 }

 

-static void ConvertDataTdk(void) {

-  data_tdk.acc_x = TDK_CONV_ACC(

-      CONV_UINT8_INT16(data_tdk_raw.acc_y_H, data_tdk_raw.acc_y_L));

-  data_tdk.acc_y = TDK_CONV_ACC(

-      CONV_UINT8_INT16(data_tdk_raw.acc_x_H, data_tdk_raw.acc_x_L));

-  data_tdk.acc_z = TDK_CONV_ACC(

-      CONV_UINT8_INT16(data_tdk_raw.acc_z_H, data_tdk_raw.acc_z_L));

-  data_tdk.gyro_x = -TDK_CONV_GYRO(

-      CONV_UINT8_INT16(data_tdk_raw.gyro_y_H, data_tdk_raw.gyro_y_L));

-  data_tdk.gyro_y = -TDK_CONV_GYRO(

-      CONV_UINT8_INT16(data_tdk_raw.gyro_x_H, data_tdk_raw.gyro_x_L));

-  data_tdk.gyro_z = -TDK_CONV_GYRO(

-      CONV_UINT8_INT16(data_tdk_raw.gyro_z_H, data_tdk_raw.gyro_z_L));

-  data_tdk.temp =

-      TDK_CONV_TEMP(CONV_UINT8_INT16(data_tdk_raw.temp_H, data_tdk_raw.temp_L));

+// Read SPI in interrupt mode

+static void SpiReadIt(SPI_HandleTypeDef *hspix, uint32_t reg) {

+  if (hspix == &hspi1) {

+    spi1_tx[0] = 0x00;

+    spi1_tx[1] = (uint8_t)(reg & 0xFF) | 0x80;

+    memset(spi1_rx, 0, sizeof(*spi1_rx));

+

+    HAL_SPI_TransmitReceive_IT(hspix, spi1_tx, spi1_rx, sizeof(spi1_tx) / 2);

+  } else if (hspix == &hspi2 || hspix == &hspi3) {

+    ConvertEndianMurata((hspix == &hspi2) ? (spi2_tx.byte) : (spi3_tx.byte),

+                        reg);

+    memset((hspix == &hspi2) ? spi2_rx.byte : spi3_rx.byte, 0, 4);

+

+    HAL_SPI_TransmitReceive_IT(

+        hspix, (hspix == &hspi2) ? spi2_tx.byte : spi3_tx.byte,

+        (hspix == &hspi2) ? spi2_rx.byte : spi3_rx.byte, 2);

+  }

 }

 

-static void InitTdk(void) {

-  // The chip must receive power prior to SPI

-  EnableSpiTdk();

-

-  // Send 0x81 to PWR_MGMT to initialize SPI

-  SendSpiTdk(PWR_MGMT_1, 0x81, false);

-  HAL_Delay(100);

-

-  // Setting the sample rates for TDK

-  SendSpiTdk(SMPLRT_DIV, 0x00, false);

-  SendSpiTdk(CONFIG, 0x00, false);

-  SendSpiTdk(GYRO_CONFIG, 0x1A, false);

-  SendSpiTdk(ACCEL_CONFIG, 0x18, false);

-  SendSpiTdk(ACCEL_CONFIG_2, 0x08, false);

-  SendSpiTdk(FIFO_EN, 0xF8, false);

-  SendSpiTdk(USER_CTRL, 0x55, false);

+static void SpiCsStart(SPI_HandleTypeDef *hspix) {

+  if (hspix == &hspi1) {

+    HAL_GPIO_WritePin(CS_TDK_ST_GPIO_Port, CS_TDK_ST_Pin,

+                      GPIO_PIN_RESET);  // CS low at start of transmission

+  } else if (hspix == &hspi2) {

+    HAL_GPIO_WritePin(CS_UNO_GPIO_Port, CS_UNO_Pin,

+                      GPIO_PIN_RESET);  // CS low at start of transmission

+  } else if (hspix == &hspi3) {

+    HAL_GPIO_WritePin(CS_DUE_GPIO_Port, CS_DUE_Pin,

+                      GPIO_PIN_RESET);  // CS low at start of transmission

+  }

 }

 

-/* 	CAN_FD Tx data packet:

- *

- * 	BYTES		Contents		Bits of content

- * 	[0..3]		murata_acc_x	[0..31]

- * 	[4..7]		murata_acc_y	[0..31]

- * 	[8..11]		murata_acc_z	[0..31]

- * 	[12..15]	murata_gyro_x	[0..31]

- * 	[16..19]	murata_gyro_y	[0..31]

- * 	[20..23]	murata_gyro_z	[0..31]

- * 	[24..27]	murata_due_temp	[0..31]

- * 	[28..31]	murata_uno_temp	[0..31]

- * 	[32..35]	tdk_acc_x		[0..31]

- * 	[36..39]	tdk_acc_y		[0..31]

- * 	[40..43]	tdk_acc_z		[0..31]

- * 	[44..47]	tdk_gyro_x		[0..31]

- * 	[48..51]	tdk_gyro_y		[0..31]

- * 	[52..55]	tdk_gyro_z		[0..31]

- * 	[56..59]	tdk_temp		[0..31]

- * 	[60..63]	murata_acc_x	[0..31]

- *

- */

+static void SpiCsEnd(SPI_HandleTypeDef *hspix) {

+  if (hspix == &hspi1) {

+    HAL_GPIO_WritePin(CS_TDK_ST_GPIO_Port, CS_TDK_ST_Pin,

+                      GPIO_PIN_SET);  // CS high at end of transmission

+  } else if (hspix == &hspi2) {

+    HAL_GPIO_WritePin(CS_UNO_GPIO_Port, CS_UNO_Pin,

+                      GPIO_PIN_SET);  // CS high at end of transmission

+  } else if (hspix == &hspi3) {

+    HAL_GPIO_WritePin(CS_DUE_GPIO_Port, CS_DUE_Pin,

+                      GPIO_PIN_SET);  // CS high at end of transmission

+  }

+}

 

-static void InitCan(FDCAN_TxHeaderTypeDef *tx_header, uint8_t id) {

-  // Initialize the Header

-  tx_header->Identifier = id;

-  tx_header->IdType = FDCAN_STANDARD_ID;

-  tx_header->TxFrameType = FDCAN_DATA_FRAME;

-  tx_header->DataLength = FDCAN_DLC_BYTES_8;

-  tx_header->ErrorStateIndicator = FDCAN_ESI_ACTIVE;

-  tx_header->BitRateSwitch = FDCAN_BRS_OFF;

-  tx_header->FDFormat = FDCAN_CLASSIC_CAN;

-  tx_header->TxEventFifoControl = FDCAN_NO_TX_EVENTS;

-  tx_header->MessageMarker = 0x0;  // Ignore because FDCAN_NO_TX_EVENTS

+static void SpiTdk(DataRawInt16 *data, SPI_HandleTypeDef *hspix, SpiOut *res,

+                   SpiIn call) {

+  switch (call) {

+    case SPI_INIT:

+      InitTdk();

+      break;

+    case SPI_ZERO:

+      memset(data, 0, IMU_SAMPLES_PER_MS * sizeof(*data));

+      break;

+    case SPI_START:

+      data[timer_index].state = 0;

+      data[timer_index].index = 0;

+      *res = SPI_READY;

+      break;

 

-  // Pull standby pin low

-  HAL_GPIO_WritePin(FDCAN_STBY_GPIO_Port, FDCAN_STBY_Pin, GPIO_PIN_RESET);

+    case SPI_RUN:

+      *res = SPI_BUSY;

 

-  // Start the FDCAN module

-  if (HAL_FDCAN_Start(&hfdcan2) != HAL_OK) {

-    Error_Handler();

+      SpiCsStart(hspix);

+

+      switch (data[timer_index].state) {

+        case 0:

+          SpiReadIt(hspix, ACCEL_XOUT_H);  // REG + acc_x_h

+          break;

+        case 1:

+          SpiReadIt(hspix, 0x00);  // acc_x_l + acc_y_h

+          break;

+        case 2:

+          SpiReadIt(hspix, 0x00);  // acc_y_l + acc_z_h

+          break;

+        case 3:

+          SpiReadIt(hspix, 0x00);  // acc_z_l + temp_h

+          break;

+        case 4:

+          SpiReadIt(hspix, 0x00);  // temp_l + gyro_x_h

+          break;

+        case 5:

+          SpiReadIt(hspix, 0x00);  // gyro_x_l + gyro_y_h

+          break;

+        case 6:

+          SpiReadIt(hspix, 0x00);  // gyro_y_l + gyro_z_h

+          break;

+        case 7:

+          SpiReadIt(hspix, 0x00);  // gyro_z_l + UNUSED

+          break;

+      }

+

+      *res = SPI_READY;

+      break;

+  }

+}

+

+static void SpiMurata(DataRawInt16 *data, SPI_HandleTypeDef *hspix, SpiOut *res,

+                      SpiIn call) {

+  switch (call) {

+    case SPI_INIT:

+      InitMurata();

+      break;

+    case SPI_ZERO:

+      memset(data, 0, IMU_SAMPLES_PER_MS * sizeof(*data));

+      break;

+    case SPI_START:

+      data[timer_index].state = (hspix == &hspi3) ? 4 : 0;

+      data[timer_index].index = 0;

+      *res = SPI_READY;

+      break;

+

+    case SPI_RUN:

+      *res = SPI_BUSY;

+

+      SpiCsStart(hspix);

+

+      switch (data[timer_index].state) {

+        case 0:

+          SpiReadIt(hspix, READ_ACC_X);

+          break;

+        case 1:

+          SpiReadIt(hspix, READ_ACC_Y);

+          break;

+        case 2:

+          SpiReadIt(hspix, READ_ACC_Z);

+          break;

+        case 3:

+          SpiReadIt(hspix, READ_GYRO_X);

+          break;

+        case 4:

+          SpiReadIt(hspix, READ_TEMP);

+          break;

+        case 5:

+          SpiReadIt(hspix, READ_GYRO_Y);

+          break;

+        case 6:

+          SpiReadIt(hspix, READ_GYRO_Z);

+          break;

+      }

+

+      *res = SPI_READY;

+      break;

+  }

+}

+

+void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {

+  if (htim == &htim1) {

+    timer_index++;

+    timer_index %= IMU_SAMPLES_PER_MS;

+

+    if (timer_index == 0) {

+      can_tx_packet_counter = 0;

+      AverageData();

+      ComposeData();

+      RealignData();

+

+      ConstructCanfdPacket(can_tx, &data_murata, &data_tdk);

+      can_out.can_counter++;

+      can_out.can_counter =

+          (can_out.can_counter == 0xFFFF) ? 0 : can_out.can_counter;

+

+      PWMSend(&htim3, TIM_CHANNEL_3, (data_murata.gyro_z));

+      PWMSend(&htim3, TIM_CHANNEL_4, (data_murata.gyro_z));

+

+      SpiTdk(tdk_data, &hspi1, &tdk_state, SPI_ZERO);

+      SpiMurata(uno_data, &hspi2, &uno_state, SPI_ZERO);

+      SpiMurata(due_data, &hspi3, &due_state, SPI_ZERO);

+    }

+

+    SpiTdk(tdk_data, &hspi1, &tdk_state, SPI_START);

+    SpiMurata(uno_data, &hspi2, &uno_state, SPI_START);

+    SpiMurata(due_data, &hspi3, &due_state, SPI_START);

+

+    if (tdk_state == SPI_READY && uno_state == SPI_READY &&

+        due_state == SPI_READY) {

+      for (int j = 0; j < 3; j++) {

+        tx_header.Identifier = can_tx_packet_counter + j + 1;

+        if (tx_header.Identifier == 9) {

+          break;

+        }

+        while (HAL_FDCAN_AddMessageToTxFifoQ(

+                   &hfdcan2, &tx_header,

+                   can_tx + (can_tx_packet_counter + j) * 8) != HAL_OK) {

+          // Error Handler will stop execution

+        }

+      }

+      can_tx_packet_counter += 3;

+

+      SpiTdk(tdk_data, &hspi1, &tdk_state, SPI_RUN);

+      SpiMurata(uno_data, &hspi2, &uno_state, SPI_RUN);

+      SpiMurata(due_data, &hspi3, &due_state, SPI_RUN);

+    }

+  }

+}

+

+void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {

+  if (hspi == &hspi1) {

+    switch (tdk_data[timer_index].state) {

+      case 0:

+        tdk_data[timer_index].acc_x = (int16_t)(spi1_rx[0] << 8);

+        break;

+      case 1:

+        tdk_data[timer_index].acc_x |= (int16_t)spi1_rx[1];

+        tdk_data[timer_index].acc_y = (int16_t)(spi1_rx[0] << 8);

+        break;

+      case 2:

+        tdk_data[timer_index].acc_y |= (int16_t)spi1_rx[1];

+        tdk_data[timer_index].acc_z = (int16_t)(spi1_rx[0] << 8);

+        break;

+      case 3:

+        tdk_data[timer_index].acc_z |= (int16_t)spi1_rx[1];

+        tdk_data[timer_index].temp = (int16_t)(spi1_rx[0] << 8);

+        break;

+      case 4:

+        tdk_data[timer_index].temp |= (int16_t)spi1_rx[1];

+        tdk_data[timer_index].gyro_x = (int16_t)(spi1_rx[0] << 8);

+        break;

+      case 5:

+        tdk_data[timer_index].gyro_x |= (int16_t)spi1_rx[1];

+        tdk_data[timer_index].gyro_y = (int16_t)(spi1_rx[0] << 8);

+        break;

+      case 6:

+        tdk_data[timer_index].gyro_y |= (int16_t)spi1_rx[1];

+        tdk_data[timer_index].gyro_z = (int16_t)(spi1_rx[0] << 8);

+        break;

+      case 7:

+        tdk_data[timer_index].gyro_z |= (int16_t)spi1_rx[1];

+        break;

+      default:

+        SpiCsEnd(hspi);

+        return;

+    }

+

+    tdk_data[timer_index].state++;

+    if (tdk_data[timer_index].state == 8) {

+      SpiCsEnd(hspi);

+      tdk_data[timer_index].state = 0;

+      can_out.tdk_counter++;

+      can_out.tdk_counter =

+          (can_out.tdk_counter == 0xFFFF) ? 0 : can_out.tdk_counter;

+      return;

+    }

+

+    SpiTdk(tdk_data, hspi, &tdk_state, SPI_RUN);

+    return;

+

+  } else if (hspi == &hspi2 || hspi == &hspi3) {

+    SpiCsEnd(hspi);

+    ConvertEndianMurata(spi_murata_rx.byte, (hspi == &hspi2)

+                                                ? spi2_rx.four_bytes

+                                                : spi3_rx.four_bytes);

+

+    if (hspi == &hspi2) {

+      switch (uno_data[timer_index].state) {

+        case 0:

+          uno_data[timer_index].temp =

+              GET_SPI_DATA_INT16(spi_murata_rx.four_bytes);

+          break;

+        case 1:

+          uno_data[timer_index].acc_x =

+              GET_SPI_DATA_INT16(spi_murata_rx.four_bytes);

+          break;

+        case 2:

+          uno_data[timer_index].acc_y =

+              GET_SPI_DATA_INT16(spi_murata_rx.four_bytes);

+          break;

+        case 3:

+          uno_data[timer_index].acc_z =

+              GET_SPI_DATA_INT16(spi_murata_rx.four_bytes);

+          break;

+        case 4:

+          uno_data[timer_index].gyro_x =

+              GET_SPI_DATA_INT16(spi_murata_rx.four_bytes);

+          break;

+        default:

+          return;

+      }

+

+      if (uno_data[timer_index].index == 1) {

+        uno_data[timer_index].index = 0;

+

+        can_out.uno_counter++;

+        can_out.uno_counter =

+            (can_out.uno_counter == 0xFFFF) ? 0 : can_out.uno_counter;

+

+        return;

+      }

+

+      uno_data[timer_index].state++;

+

+      if (uno_data[timer_index].state == 5) {

+        uno_data[timer_index].state = 0;

+        uno_data[timer_index].index++;

+      }

+

+      SpiMurata(uno_data, hspi, &uno_state, SPI_RUN);

+

+      return;

+    } else if (hspi == &hspi3) {

+      switch (due_data[timer_index].state - 4) {

+        case 0:

+          due_data[timer_index].gyro_z =

+              GET_SPI_DATA_INT16(spi_murata_rx.four_bytes);

+          break;

+        case 1:

+          due_data[timer_index].temp =

+              GET_SPI_DATA_INT16(spi_murata_rx.four_bytes);

+          break;

+        case 2:

+          due_data[timer_index].gyro_y =

+              GET_SPI_DATA_INT16(spi_murata_rx.four_bytes);

+          break;

+        default:

+          return;

+      }

+

+      if (due_data[timer_index].index == 1) {

+        due_data[timer_index].index = 0;

+

+        can_out.due_counter++;

+        can_out.due_counter =

+            (can_out.due_counter == 0xFFFF) ? 0 : can_out.due_counter;

+

+        return;

+      }

+

+      due_data[timer_index].state++;

+

+      if (due_data[timer_index].state == 7) {

+        due_data[timer_index].state = 4;

+        due_data[timer_index].index++;

+      }

+

+      SpiMurata(due_data, hspi, &due_state, SPI_RUN);

+

+      return;

+    }

   }

 }

 

diff --git a/frc971/imu_fdcan/Dual_IMU/Core/Src/stm32g4xx_hal_msp.c b/frc971/imu_fdcan/Dual_IMU/Core/Src/stm32g4xx_hal_msp.c
index b4a4229..f315dfa 100644
--- a/frc971/imu_fdcan/Dual_IMU/Core/Src/stm32g4xx_hal_msp.c
+++ b/frc971/imu_fdcan/Dual_IMU/Core/Src/stm32g4xx_hal_msp.c
@@ -256,6 +256,9 @@
     GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;

     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

 

+    /* SPI1 interrupt Init */

+    HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);

+    HAL_NVIC_EnableIRQ(SPI1_IRQn);

     /* USER CODE BEGIN SPI1_MspInit 1 */

 

     /* USER CODE END SPI1_MspInit 1 */

@@ -279,6 +282,9 @@
     GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;

     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

 

+    /* SPI2 interrupt Init */

+    HAL_NVIC_SetPriority(SPI2_IRQn, 0, 0);

+    HAL_NVIC_EnableIRQ(SPI2_IRQn);

     /* USER CODE BEGIN SPI2_MspInit 1 */

 

     /* USER CODE END SPI2_MspInit 1 */

@@ -302,6 +308,9 @@
     GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;

     HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

 

+    /* SPI3 interrupt Init */

+    HAL_NVIC_SetPriority(SPI3_IRQn, 0, 0);

+    HAL_NVIC_EnableIRQ(SPI3_IRQn);

     /* USER CODE BEGIN SPI3_MspInit 1 */

 

     /* USER CODE END SPI3_MspInit 1 */

@@ -329,6 +338,8 @@
     */

     HAL_GPIO_DeInit(GPIOA, SCK_TDK_ST_Pin | MISO_TDK_Pin | MOSI_TDK_ST_Pin);

 

+    /* SPI1 interrupt DeInit */

+    HAL_NVIC_DisableIRQ(SPI1_IRQn);

     /* USER CODE BEGIN SPI1_MspDeInit 1 */

 

     /* USER CODE END SPI1_MspDeInit 1 */

@@ -346,6 +357,8 @@
     */

     HAL_GPIO_DeInit(GPIOB, SCK_UNO_Pin | MISO_UNO_Pin | MOSI_UNO_Pin);

 

+    /* SPI2 interrupt DeInit */

+    HAL_NVIC_DisableIRQ(SPI2_IRQn);

     /* USER CODE BEGIN SPI2_MspDeInit 1 */

 

     /* USER CODE END SPI2_MspDeInit 1 */

@@ -363,6 +376,8 @@
     */

     HAL_GPIO_DeInit(GPIOC, SCK_DUE_Pin | MISO_DUE_Pin | MOSI_DUE_Pin);

 

+    /* SPI3 interrupt DeInit */

+    HAL_NVIC_DisableIRQ(SPI3_IRQn);

     /* USER CODE BEGIN SPI3_MspDeInit 1 */

 

     /* USER CODE END SPI3_MspDeInit 1 */

@@ -376,7 +391,28 @@
  * @retval None

  */

 void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim_base) {

-  if (htim_base->Instance == TIM3) {

+  if (htim_base->Instance == TIM1) {

+    /* USER CODE BEGIN TIM1_MspInit 0 */

+

+    /* USER CODE END TIM1_MspInit 0 */

+    /* Peripheral clock enable */

+    __HAL_RCC_TIM1_CLK_ENABLE();

+    /* TIM1 interrupt Init */

+    HAL_NVIC_SetPriority(TIM1_UP_TIM16_IRQn, 0, 0);

+    HAL_NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn);

+    /* USER CODE BEGIN TIM1_MspInit 1 */

+

+    /* USER CODE END TIM1_MspInit 1 */

+  } else if (htim_base->Instance == TIM2) {

+    /* USER CODE BEGIN TIM2_MspInit 0 */

+

+    /* USER CODE END TIM2_MspInit 0 */

+    /* Peripheral clock enable */

+    __HAL_RCC_TIM2_CLK_ENABLE();

+    /* USER CODE BEGIN TIM2_MspInit 1 */

+

+    /* USER CODE END TIM2_MspInit 1 */

+  } else if (htim_base->Instance == TIM3) {

     /* USER CODE BEGIN TIM3_MspInit 0 */

 

     /* USER CODE END TIM3_MspInit 0 */

@@ -400,7 +436,7 @@
     PB0     ------> TIM3_CH3

     PB1     ------> TIM3_CH4

     */

-    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;

+    GPIO_InitStruct.Pin = PWM_RATE_Pin | PWM_HEADING_Pin;

     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;

     GPIO_InitStruct.Pull = GPIO_NOPULL;

     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;

@@ -419,7 +455,28 @@
  * @retval None

  */

 void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *htim_base) {

-  if (htim_base->Instance == TIM3) {

+  if (htim_base->Instance == TIM1) {

+    /* USER CODE BEGIN TIM1_MspDeInit 0 */

+

+    /* USER CODE END TIM1_MspDeInit 0 */

+    /* Peripheral clock disable */

+    __HAL_RCC_TIM1_CLK_DISABLE();

+

+    /* TIM1 interrupt DeInit */

+    HAL_NVIC_DisableIRQ(TIM1_UP_TIM16_IRQn);

+    /* USER CODE BEGIN TIM1_MspDeInit 1 */

+

+    /* USER CODE END TIM1_MspDeInit 1 */

+  } else if (htim_base->Instance == TIM2) {

+    /* USER CODE BEGIN TIM2_MspDeInit 0 */

+

+    /* USER CODE END TIM2_MspDeInit 0 */

+    /* Peripheral clock disable */

+    __HAL_RCC_TIM2_CLK_DISABLE();

+    /* USER CODE BEGIN TIM2_MspDeInit 1 */

+

+    /* USER CODE END TIM2_MspDeInit 1 */

+  } else if (htim_base->Instance == TIM3) {

     /* USER CODE BEGIN TIM3_MspDeInit 0 */

 

     /* USER CODE END TIM3_MspDeInit 0 */

diff --git a/frc971/imu_fdcan/Dual_IMU/Core/Src/stm32g4xx_it.c b/frc971/imu_fdcan/Dual_IMU/Core/Src/stm32g4xx_it.c
index a160264..f320906 100644
--- a/frc971/imu_fdcan/Dual_IMU/Core/Src/stm32g4xx_it.c
+++ b/frc971/imu_fdcan/Dual_IMU/Core/Src/stm32g4xx_it.c
@@ -56,7 +56,10 @@
 /* USER CODE END 0 */

 

 /* External variables --------------------------------------------------------*/

-

+extern SPI_HandleTypeDef hspi1;

+extern SPI_HandleTypeDef hspi2;

+extern SPI_HandleTypeDef hspi3;

+extern TIM_HandleTypeDef htim1;

 /* USER CODE BEGIN EV */

 

 /* USER CODE END EV */

@@ -185,6 +188,59 @@
 /* please refer to the startup file (startup_stm32g4xx.s).                    */

 /******************************************************************************/

 

+/**

+ * @brief This function handles TIM1 update interrupt and TIM16 global

+ * interrupt.

+ */

+void TIM1_UP_TIM16_IRQHandler(void) {

+  /* USER CODE BEGIN TIM1_UP_TIM16_IRQn 0 */

+

+  /* USER CODE END TIM1_UP_TIM16_IRQn 0 */

+  HAL_TIM_IRQHandler(&htim1);

+  /* USER CODE BEGIN TIM1_UP_TIM16_IRQn 1 */

+

+  /* USER CODE END TIM1_UP_TIM16_IRQn 1 */

+}

+

+/**

+ * @brief This function handles SPI1 global interrupt.

+ */

+void SPI1_IRQHandler(void) {

+  /* USER CODE BEGIN SPI1_IRQn 0 */

+

+  /* USER CODE END SPI1_IRQn 0 */

+  HAL_SPI_IRQHandler(&hspi1);

+  /* USER CODE BEGIN SPI1_IRQn 1 */

+

+  /* USER CODE END SPI1_IRQn 1 */

+}

+

+/**

+ * @brief This function handles SPI2 global interrupt.

+ */

+void SPI2_IRQHandler(void) {

+  /* USER CODE BEGIN SPI2_IRQn 0 */

+

+  /* USER CODE END SPI2_IRQn 0 */

+  HAL_SPI_IRQHandler(&hspi2);

+  /* USER CODE BEGIN SPI2_IRQn 1 */

+

+  /* USER CODE END SPI2_IRQn 1 */

+}

+

+/**

+ * @brief This function handles SPI3 global interrupt.

+ */

+void SPI3_IRQHandler(void) {

+  /* USER CODE BEGIN SPI3_IRQn 0 */

+

+  /* USER CODE END SPI3_IRQn 0 */

+  HAL_SPI_IRQHandler(&hspi3);

+  /* USER CODE BEGIN SPI3_IRQn 1 */

+

+  /* USER CODE END SPI3_IRQn 1 */

+}

+

 /* USER CODE BEGIN 1 */

 

 /* USER CODE END 1 */

diff --git a/frc971/imu_fdcan/Dual_IMU/Dual_IMU.ioc b/frc971/imu_fdcan/Dual_IMU/Dual_IMU.ioc
index dd4a2c8..ffc0648 100644
--- a/frc971/imu_fdcan/Dual_IMU/Dual_IMU.ioc
+++ b/frc971/imu_fdcan/Dual_IMU/Dual_IMU.ioc
@@ -28,16 +28,18 @@
 Mcu.Family=STM32G4
 Mcu.IP0=ADC5
 Mcu.IP1=FDCAN2
-Mcu.IP10=USB
+Mcu.IP10=TIM3
+Mcu.IP11=USART1
+Mcu.IP12=USB
 Mcu.IP2=NVIC
 Mcu.IP3=RCC
 Mcu.IP4=SPI1
 Mcu.IP5=SPI2
 Mcu.IP6=SPI3
 Mcu.IP7=SYS
-Mcu.IP8=TIM3
-Mcu.IP9=USART1
-Mcu.IPNb=11
+Mcu.IP8=TIM1
+Mcu.IP9=TIM2
+Mcu.IPNb=13
 Mcu.Name=STM32G473R(B-C-E)Tx
 Mcu.Package=LQFP64
 Mcu.Pin0=PC13
@@ -89,12 +91,14 @@
 Mcu.Pin50=PB9
 Mcu.Pin51=VP_SYS_VS_Systick
 Mcu.Pin52=VP_SYS_VS_DBSignals
-Mcu.Pin53=VP_TIM3_VS_ClockSourceINT
+Mcu.Pin53=VP_TIM1_VS_ClockSourceINT
+Mcu.Pin54=VP_TIM2_VS_ClockSourceINT
+Mcu.Pin55=VP_TIM3_VS_ClockSourceINT
 Mcu.Pin6=PC1
 Mcu.Pin7=PC2
 Mcu.Pin8=PC3
 Mcu.Pin9=PA0
-Mcu.PinsNb=54
+Mcu.PinsNb=56
 Mcu.ThirdPartyNb=0
 Mcu.UserConstants=
 Mcu.UserName=STM32G473RETx
@@ -108,8 +112,12 @@
 NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
 NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
 NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4
+NVIC.SPI1_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true
+NVIC.SPI2_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true
+NVIC.SPI3_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true
 NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
 NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false
+NVIC.TIM1_UP_TIM16_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true
 NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
 PA0.GPIOParameters=GPIO_Label
 PA0.GPIO_Label=INT_TDK
@@ -176,8 +184,12 @@
 PA8.Signal=ADC5_IN1
 PA9.Locked=true
 PA9.Signal=GPIO_Output
+PB0.GPIOParameters=GPIO_Label
+PB0.GPIO_Label=PWM_RATE
 PB0.Locked=true
 PB0.Signal=S_TIM3_CH3
+PB1.GPIOParameters=GPIO_Label
+PB1.GPIO_Label=PWM_HEADING
 PB1.Locked=true
 PB1.Signal=S_TIM3_CH4
 PB10.Locked=true
@@ -326,7 +338,7 @@
 ProjectManager.UAScriptAfterPath=
 ProjectManager.UAScriptBeforePath=
 ProjectManager.UnderRoot=true
-ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_FDCAN2_Init-FDCAN2-false-HAL-true,4-MX_ADC5_Init-ADC5-false-HAL-true,5-MX_SPI1_Init-SPI1-false-HAL-true,6-MX_SPI2_Init-SPI2-false-HAL-true,7-MX_SPI3_Init-SPI3-false-HAL-true,8-MX_USART1_UART_Init-USART1-false-HAL-true,9-MX_TIM3_Init-TIM3-false-HAL-true,10-MX_USB_PCD_Init-USB-false-HAL-true
+ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_FDCAN2_Init-FDCAN2-false-HAL-true,4-MX_ADC5_Init-ADC5-false-HAL-true,5-MX_SPI1_Init-SPI1-false-HAL-true,6-MX_SPI2_Init-SPI2-false-HAL-true,7-MX_SPI3_Init-SPI3-false-HAL-true,8-MX_USART1_UART_Init-USART1-false-HAL-true,9-MX_TIM3_Init-TIM3-false-HAL-true,10-MX_USB_PCD_Init-USB-false-HAL-true,11-MX_TIM2_Init-TIM2-false-HAL-true,12-MX_TIM1_Init-TIM1-false-HAL-true
 RCC.ADC12Freq_Value=168000000
 RCC.ADC345Freq_Value=168000000
 RCC.AHBFreq_Value=168000000
@@ -409,16 +421,31 @@
 SPI3.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler,DataSize,CLKPhase,CLKPolarity
 SPI3.Mode=SPI_MODE_MASTER
 SPI3.VirtualType=VM_MASTER
+TIM1.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_ENABLE
+TIM1.IPParameters=Prescaler,PeriodNoDither,AutoReloadPreload
+TIM1.PeriodNoDither=333
+TIM1.Prescaler=168-1
+TIM2.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_ENABLE
+TIM2.IPParameters=Prescaler,AutoReloadPreload,PeriodNoDither
+TIM2.PeriodNoDither=4294967295
+TIM2.Prescaler=168-1
 TIM3.Channel-PWM\ Generation3\ CH3=TIM_CHANNEL_3
 TIM3.Channel-PWM\ Generation4\ CH4=TIM_CHANNEL_4
-TIM3.IPParameters=Channel-PWM Generation3 CH3,Channel-PWM Generation4 CH4,Prescaler
-TIM3.Prescaler=2563
+TIM3.IPParameters=Channel-PWM Generation3 CH3,Channel-PWM Generation4 CH4,Prescaler,PeriodNoDither
+TIM3.PeriodNoDither=35000
+TIM3.Prescaler=24-1
 USART1.IPParameters=VirtualMode-Asynchronous
 USART1.VirtualMode-Asynchronous=VM_ASYNC
+USB.IPParameters=Sof_enable
+USB.Sof_enable=DISABLE
 VP_SYS_VS_DBSignals.Mode=DisableDeadBatterySignals
 VP_SYS_VS_DBSignals.Signal=SYS_VS_DBSignals
 VP_SYS_VS_Systick.Mode=SysTick
 VP_SYS_VS_Systick.Signal=SYS_VS_Systick
+VP_TIM1_VS_ClockSourceINT.Mode=Internal
+VP_TIM1_VS_ClockSourceINT.Signal=TIM1_VS_ClockSourceINT
+VP_TIM2_VS_ClockSourceINT.Mode=Internal
+VP_TIM2_VS_ClockSourceINT.Signal=TIM2_VS_ClockSourceINT
 VP_TIM3_VS_ClockSourceINT.Mode=Internal
 VP_TIM3_VS_ClockSourceINT.Signal=TIM3_VS_ClockSourceINT
 VREF+.Locked=true