]> git.leonardobizzoni.com Git - pioneer-stm32/commitdiff
Error checking and reporting via uart
authorLeonardoBizzoni <leo2002714@gmail.com>
Wed, 21 Jan 2026 11:23:31 +0000 (12:23 +0100)
committerLeonardoBizzoni <leo2002714@gmail.com>
Wed, 21 Jan 2026 11:23:31 +0000 (12:23 +0100)
Haven't tested it yet

pioneer_controller/Core/Inc/firmware/fmw_core.h
pioneer_controller/Core/Inc/firmware/fmw_messages.h
pioneer_controller/Core/Inc/main.h
pioneer_controller/Core/Src/main.c

index bb20fb060e6d359c98d8db8720bd8cd890ce7735..bd09523ca82d07695eef64f8955d5ec495df4b94 100644 (file)
@@ -60,25 +60,30 @@ typedef struct {
   FMW_LedState state;
 } FMW_Led;
 
-void fmw_motor_init(FMW_Motor motors[FMW_MOTOR_COUNT]);
-void fmw_motor_set_speed(FMW_Motor *motor, int32_t duty_cycle);
-void fmw_motor_brake(FMW_Motor *motor);
-void fmw_motor_enable(FMW_Motor * motor);
-void fmw_motor_disable(FMW_Motor * motor);
+void fmw_motor_init(FMW_Motor motors[FMW_MOTOR_COUNT])                  __attribute__((nonnull));
+void fmw_motor_set_speed(FMW_Motor *motor, int32_t duty_cycle)          __attribute__((nonnull));
+void fmw_motor_brake(FMW_Motor *motor)                                  __attribute__((nonnull));
+void fmw_motor_enable(FMW_Motor * motor)                                __attribute__((nonnull));
+void fmw_motor_disable(FMW_Motor * motor)                               __attribute__((nonnull));
 
-void fmw_encoder_init(FMW_Encoder encoders[FMW_ENCODER_COUNT]);
-void fmw_encoder_update(FMW_Encoder *encoder);
-float fmw_encoder_get_linear_velocity(const FMW_Encoder *encoder);
-void fmw_encoder_count_reset(FMW_Encoder *encoder);
-int32_t fmw_encoder_count_get(const FMW_Encoder *encoder);
+void fmw_encoder_init(FMW_Encoder encoders[FMW_ENCODER_COUNT])          __attribute__((nonnull));
+void fmw_encoder_update(FMW_Encoder *encoder)                           __attribute__((nonnull));
+float fmw_encoder_get_linear_velocity(const FMW_Encoder *encoder)       __attribute__((warn_unused_result, nonnull));
+void fmw_encoder_count_reset(FMW_Encoder *encoder)                      __attribute__((nonnull));
+int32_t fmw_encoder_count_get(const FMW_Encoder *encoder)               __attribute__((warn_unused_result, nonnull));
 
-int32_t fmw_pid_update(FMW_PidController *pid, float measure);
+int32_t fmw_pid_update(FMW_PidController *pid, float measure)           __attribute__((warn_unused_result, nonnull));
 
-void fmw_led_init(FMW_Led *led);
-void fmw_led_update(FMW_Led *led);
+void fmw_led_init(FMW_Led *led)                                         __attribute__((nonnull));
+void fmw_led_update(FMW_Led *led)                                       __attribute__((nonnull));
 
-void fmw_report_handler(FMW_Error error_code, const char *filename, int32_t filename_length, int32_t line);
-void fmw_message_handle(FMW_State state, UART_HandleTypeDef *huart, int32_t wait_ms);
+void fmw_result_log_uart(UART_HandleTypeDef *huart, FMW_Result result,
+                         const char *filename, int16_t filename_length,
+                         int32_t line)                                  __attribute__((nonnull));
+
+FMW_Result fmw_message_receive_uart(UART_HandleTypeDef *huart,
+                                    int32_t wait_ms, FMW_Message *msg)  __attribute__((warn_unused_result, nonnull));
+FMW_Result fmw_message_handle(FMW_State state, const FMW_Message *msg)  __attribute__((warn_unused_result, nonnull));
 
 #define FMW_LED_UPDATE_PERIOD 200
 #define FMW_DEBOUNCE_DELAY 200
@@ -90,16 +95,7 @@ void fmw_message_handle(FMW_State state, UART_HandleTypeDef *huart, int32_t wait
 #define FMW_METERS_FROM_TICKS(Ticks, WheelCircumference, TicksPerRevolution) \
   ((Ticks * WheelCircumference) / TicksPerRevolution)
 
-#define FMW_REPORT_UNLESS(Cond, MessageStatusCode)        \
-  do {                                                    \
-    if (!(Cond)) {                                        \
-      fmw_report_handler(MessageStatusCode, __FILE__,     \
-                          ARRLENGTH(__FILE__), __LINE__); \
-    }                                                     \
-  } while (0)
-
-#define FMW_REPORT(MessageStatusCode)                \
-  fmw_report_handler(MessageStatusCode, __FILE__,    \
-                      ARRLENGTH(__FILE__), __LINE__)
+#define FMW_RESULT_LOG_UART(HUART_PTR, FMW_RESULT) \
+  fmw_result_log_uart((HUART_PTR), (FMW_RESULT), __FILE__, ARRLENGTH(__FILE__), __LINE__)
 
 #endif
index 4f971cd02ec28f6dd83cd31545f5c2cf5da0383e..79e87e4e3aa76eff4047218785bccd7003900443 100644 (file)
@@ -1,6 +1,30 @@
 #ifndef FMW_MESSAGE_H
 #define FMW_MESSAGE_H
 
+// TODO(lb): fill with sensible values
+#define FMW_MIN_ACCEPTABLE_TICKS_PER_REVOLUTION 1
+#define FMW_MAX_ACCEPTABLE_TICKS_PER_REVOLUTION 10000
+
+// TODO(lb): fill with sensible values
+#define FMW_MIN_ACCEPTABLE_PID_PROPORTIONAL -1.f
+#define FMW_MAX_ACCEPTABLE_PID_PROPORTIONAL  1.f
+
+// TODO(lb): fill with sensible values
+#define FMW_MIN_ACCEPTABLE_PID_INTEGRAL -1.f
+#define FMW_MAX_ACCEPTABLE_PID_INTEGRAL  1.f
+
+// TODO(lb): fill with sensible values
+#define FMW_MIN_ACCEPTABLE_PID_DERIVATIVE -1.f
+#define FMW_MAX_ACCEPTABLE_PID_DERIVATIVE  1.f
+
+// TODO(lb): fill with sensible values
+#define FMW_MIN_ACCEPTABLE_LED_VOLTAGE 0.f
+#define FMW_MAX_ACCEPTABLE_LED_VOLTAGE 1.f
+
+// TODO(lb): fill with sensible values
+#define FMW_MIN_ACCEPTABLE_LED_UPDATE_PERIOD 1
+#define FMW_MAX_ACCEPTABLE_LED_UPDATE_PERIOD 10000
+
 typedef union {
   struct {
     float proportional;
@@ -10,46 +34,69 @@ typedef union {
   float values[3];
 } FMW_PidConstants;
 
-typedef uint16_t MessageStatusCode;
+typedef uint16_t FMW_MessageStatusCode;
 enum {
-  MessageStatusCode_Waiting4Config = 0,
-  MessageStatusCode_Running        = 1,
-  MessageStatusCode_Error_Config   = 2,
-  MessageStatusCode_Error_Velocity = 3,
-  MessageStatusCode_Fault_HBridge  = 4,
+  FMW_MessageStatusCode_None,
+
+  FMW_MessageStatusCode_Waiting4Config = 0,
+  FMW_MessageStatusCode_Running        = 1,
+  FMW_MessageStatusCode_Error_Config   = 2,
+  FMW_MessageStatusCode_Error_Velocity = 3,
+  FMW_MessageStatusCode_Fault_HBridge  = 4,
+
+  FMW_MessageStatusCode_COUNT,
 };
 
 typedef uint8_t FMW_State;
 enum {
+  FMW_State_None,
+
   FMW_State_Init,
   FMW_State_Error,
   FMW_State_Running,
+
+  FMW_State_COUNT,
 };
 
 typedef uint8_t FMW_MessageType;
 enum {
-  FMW_MessageType_Error,
+  FMW_MessageType_None,
 
+  FMW_MessageType_Error,
   FMW_MessageType_Run,
   FMW_MessageType_Config_Robot,
   FMW_MessageType_Config_PID,
   FMW_MessageType_Config_LED,
-
   FMW_MessageType_Status,
   FMW_MessageType_Velocity,
 
   FMW_MessageType_COUNT,
 };
 
-typedef uint8_t FMW_Error;
-enum {
-  FMW_Error_Unknown = 0,
+#define FMW_RESULT_VARIANTS(X)                                                  \
+  X(FMW_Result_Ok)                                                              \
+  X(FMW_Result_Error_NilMessage)                                                \
+  X(FMW_Result_Error_UART_Crc)                                                  \
+  X(FMW_Result_Error_UART_NilHandle)                                            \
+  X(FMW_Result_Error_UART_NegativeTimeout)                                      \
+  X(FMW_Result_Error_UART_ReceiveTimeoutElapsed)                                \
+  X(FMW_Result_Error_MessageHandler_InvalidState)                               \
+  X(FMW_Result_Error_MessageHandler_Init_NonPositiveBaseline)                   \
+  X(FMW_Result_Error_MessageHandler_Init_NonPositiveWheelCircumference)         \
+  X(FMW_Result_Error_MessageHandler_Init_InvalidTicksPerRevolution)             \
+  X(FMW_Result_Error_MessageHandler_Init_InvalidPIDProportionalConstant)        \
+  X(FMW_Result_Error_MessageHandler_Init_InvalidPIDIntegralConstant)            \
+  X(FMW_Result_Error_MessageHandler_Init_InvalidPIDDerivativeConstant)          \
+  X(FMW_Result_Error_MessageHandler_Init_InvalidLEDVoltage)                     \
+  X(FMW_Result_Error_Command_NotRecognized)                                     \
+  X(FMW_Result_Error_Command_NotAvailable)                                      \
+  X(FMW_Result_COUNT)
 
-  FMW_Error_UART_Crc,
-  FMW_Error_UART_ReceiveTimeoutElapsed,
-
-  FMW_Error_Command_NotRecognized,
-  FMW_Error_Command_NotAvailable,
+typedef uint8_t FMW_Result;
+enum {
+#define X(Variant) Variant,
+  FMW_RESULT_VARIANTS(X)
+#undef X
 };
 
 #pragma pack(push, 1)
@@ -61,18 +108,18 @@ typedef struct {
 
   union {
     struct {
-      FMW_Error reason;
+      const char *filename;
       int32_t line;
       int32_t filename_size;
-      const char *filename;
+      FMW_Result reason;
     } error;
 
     struct {
       float baseline;
-      float left_wheel_circumference;
-      uint32_t left_ticks_per_revolution;
-      float right_wheel_circumference;
-      uint32_t right_ticks_per_revolution;
+      float wheel_circumference_left;
+      float wheel_circumference_right;
+      uint32_t ticks_per_revolution_left;
+      uint32_t ticks_per_revolution_right;
     } config_robot;
     struct {
       FMW_PidConstants left;
@@ -87,7 +134,7 @@ typedef struct {
     } config_led;
 
     struct {
-      MessageStatusCode status_code;
+      FMW_MessageStatusCode status_code;
       uint16_t delta_millis;
       int32_t left_ticks;
       int32_t right_ticks;
index 4dba0dc01940c31a7501ae79d0b99017afa8fc2a..2ffcb2e21302bcb4d6a60c595f861fa5b270ef72 100644 (file)
@@ -32,7 +32,7 @@ extern "C" {
 \r
 /* Private includes ----------------------------------------------------------*/\r
 /* USER CODE BEGIN Includes */\r
-\r
+#include <stdio.h>\r
 /* USER CODE END Includes */\r
 \r
 /* Exported types ------------------------------------------------------------*/\r
@@ -62,6 +62,8 @@ extern int32_t pid_min;
 \r
 #define ARRLENGTH(Arr) (sizeof((Arr)) / sizeof(*(Arr)))\r
 \r
+void start(void) __attribute__((noreturn));\r
+\r
 /* USER CODE END EM */\r
 \r
 void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim);\r
index 2eb5b46b32e2aeeacc9126f8ce9e8268e328cbb4..72dc7d8d449573a96f761d0b088a0982254035a6 100644 (file)
@@ -176,7 +176,7 @@ static void MX_TIM3_Init(void);
 static void MX_TIM6_Init(void);\r
 static void MX_CRC_Init(void);\r
 /* USER CODE BEGIN PFP */\r
-__attribute__((noreturn)) void start(void);\r
+\r
 /* USER CODE END PFP */\r
 \r
 /* Private user code ---------------------------------------------------------*/\r
@@ -184,9 +184,9 @@ __attribute__((noreturn)) void start(void);
 /* USER CODE END 0 */\r
 \r
 /**\r
 * @brief  The application entry point.\r
 * @retval int\r
 */\r
+ * @brief  The application entry point.\r
+ * @retval int\r
+ */\r
 int main(void)\r
 {\r
 \r
@@ -236,26 +236,26 @@ int main(void)
 }\r
 \r
 /**\r
 * @brief System Clock Configuration\r
 * @retval None\r
 */\r
+ * @brief System Clock Configuration\r
+ * @retval None\r
+ */\r
 void SystemClock_Config(void)\r
 {\r
   RCC_OscInitTypeDef RCC_OscInitStruct = {0};\r
   RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};\r
 \r
   /** Configure LSE Drive Capability\r
-  */\r
+   */\r
   HAL_PWR_EnableBkUpAccess();\r
 \r
   /** Configure the main internal regulator output voltage\r
-  */\r
+   */\r
   __HAL_RCC_PWR_CLK_ENABLE();\r
   __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE3);\r
 \r
   /** Initializes the RCC Oscillators according to the specified parameters\r
-  * in the RCC_OscInitTypeDef structure.\r
-  */\r
+   * in the RCC_OscInitTypeDef structure.\r
+   */\r
   RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;\r
   RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;\r
   RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;\r
@@ -266,37 +266,37 @@ void SystemClock_Config(void)
   RCC_OscInitStruct.PLL.PLLQ = 4;\r
   RCC_OscInitStruct.PLL.PLLR = 2;\r
   if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
 \r
   /** Activate the Over-Drive mode\r
-  */\r
+   */\r
   if (HAL_PWREx_EnableOverDrive() != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
 \r
   /** Initializes the CPU, AHB and APB buses clocks\r
-  */\r
+   */\r
   RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK\r
-                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;\r
+                                |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;\r
   RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;\r
   RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;\r
   RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;\r
   RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;\r
 \r
   if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
 }\r
 \r
 /**\r
 * @brief ADC1 Initialization Function\r
 * @param None\r
 * @retval None\r
 */\r
+ * @brief ADC1 Initialization Function\r
+ * @param None\r
+ * @retval None\r
+ */\r
 static void MX_ADC1_Init(void)\r
 {\r
 \r
@@ -311,7 +311,7 @@ static void MX_ADC1_Init(void)
   /* USER CODE END ADC1_Init 1 */\r
 \r
   /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)\r
-  */\r
+   */\r
   hadc1.Instance = ADC1;\r
   hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;\r
   hadc1.Init.Resolution = ADC_RESOLUTION_12B;\r
@@ -325,19 +325,19 @@ static void MX_ADC1_Init(void)
   hadc1.Init.DMAContinuousRequests = DISABLE;\r
   hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;\r
   if (HAL_ADC_Init(&hadc1) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
 \r
   /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.\r
-  */\r
+   */\r
   sConfig.Channel = ADC_CHANNEL_2;\r
   sConfig.Rank = ADC_REGULAR_RANK_1;\r
   sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES;\r
   if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   /* USER CODE BEGIN ADC1_Init 2 */\r
 \r
   /* USER CODE END ADC1_Init 2 */\r
@@ -345,10 +345,10 @@ static void MX_ADC1_Init(void)
 }\r
 \r
 /**\r
 * @brief CRC Initialization Function\r
 * @param None\r
 * @retval None\r
 */\r
+ * @brief CRC Initialization Function\r
+ * @param None\r
+ * @retval None\r
+ */\r
 static void MX_CRC_Init(void)\r
 {\r
 \r
@@ -366,9 +366,9 @@ static void MX_CRC_Init(void)
   hcrc.Init.OutputDataInversionMode = CRC_OUTPUTDATA_INVERSION_DISABLE;\r
   hcrc.InputDataFormat = CRC_INPUTDATA_FORMAT_BYTES;\r
   if (HAL_CRC_Init(&hcrc) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   /* USER CODE BEGIN CRC_Init 2 */\r
 \r
   /* USER CODE END CRC_Init 2 */\r
@@ -376,10 +376,10 @@ static void MX_CRC_Init(void)
 }\r
 \r
 /**\r
 * @brief TIM1 Initialization Function\r
 * @param None\r
 * @retval None\r
 */\r
+ * @brief TIM1 Initialization Function\r
+ * @param None\r
+ * @retval None\r
+ */\r
 static void MX_TIM1_Init(void)\r
 {\r
 \r
@@ -402,16 +402,16 @@ static void MX_TIM1_Init(void)
   htim1.Init.RepetitionCounter = 0;\r
   htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;\r
   if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;\r
   sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;\r
   sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;\r
   if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   sConfigOC.OCMode = TIM_OCMODE_PWM1;\r
   sConfigOC.Pulse = 0;\r
   sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;\r
@@ -420,13 +420,13 @@ static void MX_TIM1_Init(void)
   sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;\r
   sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;\r
   if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;\r
   sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;\r
   sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;\r
@@ -439,9 +439,9 @@ static void MX_TIM1_Init(void)
   sBreakDeadTimeConfig.Break2Filter = 0;\r
   sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;\r
   if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   /* USER CODE BEGIN TIM1_Init 2 */\r
 \r
   /* USER CODE END TIM1_Init 2 */\r
@@ -450,10 +450,10 @@ static void MX_TIM1_Init(void)
 }\r
 \r
 /**\r
 * @brief TIM2 Initialization Function\r
 * @param None\r
 * @retval None\r
 */\r
+ * @brief TIM2 Initialization Function\r
+ * @param None\r
+ * @retval None\r
+ */\r
 static void MX_TIM2_Init(void)\r
 {\r
 \r
@@ -483,15 +483,15 @@ static void MX_TIM2_Init(void)
   sConfig.IC2Prescaler = TIM_ICPSC_DIV1;\r
   sConfig.IC2Filter = 0;\r
   if (HAL_TIM_Encoder_Init(&htim2, &sConfig) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;\r
   sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;\r
   if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   /* USER CODE BEGIN TIM2_Init 2 */\r
 \r
   /* USER CODE END TIM2_Init 2 */\r
@@ -499,10 +499,10 @@ static void MX_TIM2_Init(void)
 }\r
 \r
 /**\r
 * @brief TIM3 Initialization Function\r
 * @param None\r
 * @retval None\r
 */\r
+ * @brief TIM3 Initialization Function\r
+ * @param None\r
+ * @retval None\r
+ */\r
 static void MX_TIM3_Init(void)\r
 {\r
 \r
@@ -532,15 +532,15 @@ static void MX_TIM3_Init(void)
   sConfig.IC2Prescaler = TIM_ICPSC_DIV1;\r
   sConfig.IC2Filter = 0;\r
   if (HAL_TIM_Encoder_Init(&htim3, &sConfig) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;\r
   sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;\r
   if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   /* USER CODE BEGIN TIM3_Init 2 */\r
 \r
   /* USER CODE END TIM3_Init 2 */\r
@@ -548,10 +548,10 @@ static void MX_TIM3_Init(void)
 }\r
 \r
 /**\r
 * @brief TIM4 Initialization Function\r
 * @param None\r
 * @retval None\r
 */\r
+ * @brief TIM4 Initialization Function\r
+ * @param None\r
+ * @retval None\r
+ */\r
 static void MX_TIM4_Init(void)\r
 {\r
 \r
@@ -572,27 +572,27 @@ static void MX_TIM4_Init(void)
   htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;\r
   htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;\r
   if (HAL_TIM_PWM_Init(&htim4) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;\r
   sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;\r
   if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   sConfigOC.OCMode = TIM_OCMODE_PWM1;\r
   sConfigOC.Pulse = 0;\r
   sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;\r
   sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;\r
   if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   if (HAL_TIM_PWM_ConfigChannel(&htim4, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   /* USER CODE BEGIN TIM4_Init 2 */\r
 \r
   /* USER CODE END TIM4_Init 2 */\r
@@ -601,10 +601,10 @@ static void MX_TIM4_Init(void)
 }\r
 \r
 /**\r
 * @brief TIM6 Initialization Function\r
 * @param None\r
 * @retval None\r
 */\r
+ * @brief TIM6 Initialization Function\r
+ * @param None\r
+ * @retval None\r
+ */\r
 static void MX_TIM6_Init(void)\r
 {\r
 \r
@@ -623,15 +623,15 @@ static void MX_TIM6_Init(void)
   htim6.Init.Period = 65535;\r
   htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;\r
   if (HAL_TIM_Base_Init(&htim6) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;\r
   sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;\r
   if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   /* USER CODE BEGIN TIM6_Init 2 */\r
 \r
   /* USER CODE END TIM6_Init 2 */\r
@@ -639,10 +639,10 @@ static void MX_TIM6_Init(void)
 }\r
 \r
 /**\r
 * @brief USART3 Initialization Function\r
 * @param None\r
 * @retval None\r
 */\r
+ * @brief USART3 Initialization Function\r
+ * @param None\r
+ * @retval None\r
+ */\r
 static void MX_USART3_UART_Init(void)\r
 {\r
 \r
@@ -664,9 +664,9 @@ static void MX_USART3_UART_Init(void)
   huart3.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;\r
   huart3.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;\r
   if (HAL_UART_Init(&huart3) != HAL_OK)\r
-  {\r
-    Error_Handler();\r
-  }\r
+    {\r
+      Error_Handler();\r
+    }\r
   /* USER CODE BEGIN USART3_Init 2 */\r
 \r
   /* USER CODE END USART3_Init 2 */\r
@@ -674,10 +674,10 @@ static void MX_USART3_UART_Init(void)
 }\r
 \r
 /**\r
 * @brief GPIO Initialization Function\r
 * @param None\r
 * @retval None\r
 */\r
+ * @brief GPIO Initialization Function\r
+ * @param None\r
+ * @retval None\r
+ */\r
 static void MX_GPIO_Init(void)\r
 {\r
   GPIO_InitTypeDef GPIO_InitStruct = {0};\r
@@ -775,10 +775,21 @@ static void MX_GPIO_Init(void)
 }\r
 \r
 /* USER CODE BEGIN 4 */\r
-__attribute__((noreturn)) void start(void) {\r
+void start(void) {\r
+  FMW_Message msg = {0};\r
   do {\r
-    fmw_message_handle(FMW_State_Init, &huart3, 180 * 1000);\r
-  } while(fmw_state != FMW_State_Running);\r
+    FMW_Result res = fmw_message_receive_uart(&huart3, 180 * 1000, &msg);\r
+    if (res != FMW_Result_Ok) {\r
+      FMW_RESULT_LOG_UART(&huart3, res);\r
+      continue;\r
+    }\r
+\r
+    res = fmw_message_handle(FMW_State_Init, &msg);\r
+    if (res != FMW_Result_Ok) {\r
+      FMW_RESULT_LOG_UART(&huart3, res);\r
+      continue;\r
+    }\r
+  } while(fmw_state == FMW_State_Init);\r
 \r
   fmw_encoder_init(encoders.values);\r
   fmw_motor_init(motors.values);\r
@@ -805,61 +816,172 @@ __attribute__((noreturn)) void start(void) {
   }\r
 }\r
 \r
-void fmw_message_handle(FMW_State state, UART_HandleTypeDef *huart, int32_t wait_ms) {\r
-  FMW_Message msg = {0};\r
-  HAL_StatusTypeDef uart_packet_status = HAL_UART_Receive(huart, (uint8_t*)&msg, sizeof(msg), wait_ms);\r
-  FMW_REPORT_UNLESS(uart_packet_status == HAL_OK, FMW_Error_UART_ReceiveTimeoutElapsed);\r
-  FMW_REPORT_UNLESS(msg.header.type < FMW_MessageType_COUNT, FMW_Error_Command_NotRecognized);\r
+FMW_Result fmw_message_receive_uart(UART_HandleTypeDef *huart, int32_t wait_ms, FMW_Message *msg) {\r
+  if (!(wait_ms >= 0))  { return FMW_Result_Error_UART_NegativeTimeout; }\r
+\r
+  memset(msg, 0, sizeof *msg);\r
+\r
+  HAL_StatusTypeDef uart_packet_status = HAL_UART_Receive(huart, (uint8_t*)msg, sizeof(msg), wait_ms);\r
+  if (!(uart_packet_status == HAL_OK)) { return FMW_Result_Error_UART_ReceiveTimeoutElapsed; }\r
+  if (!(msg->header.type > FMW_MessageType_None && msg->header.type < FMW_MessageType_COUNT)) {\r
+    return FMW_Result_Error_Command_NotRecognized;\r
+  }\r
+  return FMW_Result_Ok;\r
+}\r
 \r
-  uint32_t crc_received = msg.header.crc;\r
-  msg.header.crc = 0;\r
+FMW_Result fmw_message_handle(FMW_State state, const FMW_Message *msg) {\r
+  if (!(state > FMW_State_None && state < FMW_State_COUNT)) { return FMW_Result_Error_MessageHandler_InvalidState; }\r
+\r
+  // NOTE(lb): the `msg->header.crc != -1` checks are just because i haven't\r
+  //           implemented CRC into the program that sends these messages.\r
   switch (state) {\r
   case FMW_State_Init: {\r
-    switch (msg.header.type) {\r
+    switch (msg->header.type) {\r
     case FMW_MessageType_Run: {\r
-      uint32_t crc_computed = HAL_CRC_Calculate(&hcrc, (uint32_t*)&msg, sizeof msg.header);\r
-      FMW_REPORT_UNLESS(crc_received == -1 || crc_computed == crc_received, FMW_Error_UART_Crc);\r
+      if (msg->header.crc != -1) {\r
+        uint32_t crc_computed = HAL_CRC_Calculate(&hcrc, (uint32_t*)msg, sizeof msg->header.type);\r
+        if (!(crc_computed == msg->header.crc)) { return FMW_Result_Error_UART_Crc; }\r
+      }\r
       fmw_state = FMW_State_Running;\r
     } break;\r
     case FMW_MessageType_Config_Robot: {\r
-      uint32_t crc_computed = HAL_CRC_Calculate(&hcrc, (uint32_t*)&msg, sizeof msg.header.type + sizeof msg.config_robot);\r
-      FMW_REPORT_UNLESS(crc_received == -1 || crc_computed == crc_received, FMW_Error_UART_Crc);\r
-\r
-      odometry.baseline = msg.config_robot.baseline;\r
-      encoders.left.wheel_circumference   = msg.config_robot.left_wheel_circumference;\r
-      encoders.left.ticks_per_revolution  = msg.config_robot.left_ticks_per_revolution;\r
-      encoders.right.wheel_circumference  = msg.config_robot.right_wheel_circumference;\r
-      encoders.right.ticks_per_revolution = msg.config_robot.right_ticks_per_revolution;\r
+      if (msg->header.crc != -1) {\r
+        uint32_t crc_computed = HAL_CRC_Calculate(&hcrc, (uint32_t*)msg, sizeof msg->header.type + sizeof msg->config_robot);\r
+        if (!(crc_computed == msg->header.crc)) { return FMW_Result_Error_UART_Crc; }\r
+      }\r
+\r
+      if (!(msg->config_robot.baseline > 0.f)) {\r
+        return FMW_Result_Error_MessageHandler_Init_NonPositiveBaseline;\r
+      }\r
+      if (!(msg->config_robot.wheel_circumference_left > 0.f ||\r
+            msg->config_robot.wheel_circumference_right > 0.f)) {\r
+        return FMW_Result_Error_MessageHandler_Init_NonPositiveWheelCircumference;\r
+      }\r
+      if (!(msg->config_robot.ticks_per_revolution_left >= FMW_MIN_ACCEPTABLE_TICKS_PER_REVOLUTION &&\r
+            msg->config_robot.ticks_per_revolution_left < FMW_MAX_ACCEPTABLE_TICKS_PER_REVOLUTION)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidTicksPerRevolution;\r
+      }\r
+      if (!(msg->config_robot.ticks_per_revolution_right >= FMW_MIN_ACCEPTABLE_TICKS_PER_REVOLUTION &&\r
+            msg->config_robot.ticks_per_revolution_right < FMW_MAX_ACCEPTABLE_TICKS_PER_REVOLUTION)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidTicksPerRevolution;\r
+      }\r
+\r
+      odometry.baseline = msg->config_robot.baseline;\r
+      encoders.left.wheel_circumference   = msg->config_robot.wheel_circumference_left;\r
+      encoders.left.ticks_per_revolution  = msg->config_robot.ticks_per_revolution_left;\r
+      encoders.right.wheel_circumference  = msg->config_robot.wheel_circumference_right;\r
+      encoders.right.ticks_per_revolution = msg->config_robot.ticks_per_revolution_right;\r
     } break;\r
     case FMW_MessageType_Config_PID: {\r
-      uint32_t crc_computed = HAL_CRC_Calculate(&hcrc, (uint32_t*)&msg, sizeof msg.header.type + sizeof msg.config_pid);\r
-      FMW_REPORT_UNLESS(crc_received == -1 || crc_computed == crc_received, FMW_Error_UART_Crc);\r
+      if (msg->header.crc != -1) {\r
+        uint32_t crc_computed = HAL_CRC_Calculate(&hcrc, (uint32_t*)msg, sizeof msg->header.type + sizeof msg->config_pid);\r
+        if (!(crc_computed == msg->header.crc)) { return FMW_Result_Error_UART_Crc; }\r
+      }\r
 \r
-      memcpy(&pid_left.ks,  &msg.config_pid.left,  sizeof pid_left.ks);\r
-      memcpy(&pid_right.ks, &msg.config_pid.right, sizeof pid_right.ks);\r
-      memcpy(&pid_cross.ks, &msg.config_pid.cross, sizeof pid_cross.ks);\r
+      if (!(msg->config_pid.left.proportional >= FMW_MIN_ACCEPTABLE_PID_PROPORTIONAL &&\r
+            msg->config_pid.left.proportional < FMW_MAX_ACCEPTABLE_PID_PROPORTIONAL)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidPIDProportionalConstant;\r
+      }\r
+      if (!(msg->config_pid.cross.proportional >= FMW_MIN_ACCEPTABLE_PID_PROPORTIONAL &&\r
+            msg->config_pid.cross.proportional < FMW_MAX_ACCEPTABLE_PID_PROPORTIONAL)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidPIDProportionalConstant;\r
+      }\r
+      if (!(msg->config_pid.right.proportional >= FMW_MIN_ACCEPTABLE_PID_PROPORTIONAL &&\r
+            msg->config_pid.right.proportional < FMW_MAX_ACCEPTABLE_PID_PROPORTIONAL)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidPIDProportionalConstant;\r
+      }\r
+\r
+      if (!(msg->config_pid.left.integral >= FMW_MIN_ACCEPTABLE_PID_INTEGRAL &&\r
+            msg->config_pid.left.integral < FMW_MAX_ACCEPTABLE_PID_INTEGRAL)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidPIDIntegralConstant;\r
+      }\r
+      if (!(msg->config_pid.cross.integral >= FMW_MIN_ACCEPTABLE_PID_INTEGRAL &&\r
+            msg->config_pid.cross.integral < FMW_MAX_ACCEPTABLE_PID_INTEGRAL)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidPIDIntegralConstant;\r
+      }\r
+      if (!(msg->config_pid.right.integral >= FMW_MIN_ACCEPTABLE_PID_INTEGRAL &&\r
+            msg->config_pid.right.integral < FMW_MAX_ACCEPTABLE_PID_INTEGRAL)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidPIDIntegralConstant;\r
+      }\r
+\r
+      if (!(msg->config_pid.left.derivative >= FMW_MIN_ACCEPTABLE_PID_DERIVATIVE &&\r
+            msg->config_pid.left.derivative < FMW_MAX_ACCEPTABLE_PID_DERIVATIVE)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidPIDDerivativeConstant;\r
+      }\r
+      if (!(msg->config_pid.cross.derivative >= FMW_MIN_ACCEPTABLE_PID_DERIVATIVE &&\r
+            msg->config_pid.cross.derivative < FMW_MAX_ACCEPTABLE_PID_DERIVATIVE)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidPIDDerivativeConstant;\r
+      }\r
+      if (!(msg->config_pid.right.derivative >= FMW_MIN_ACCEPTABLE_PID_DERIVATIVE &&\r
+            msg->config_pid.right.derivative < FMW_MAX_ACCEPTABLE_PID_DERIVATIVE)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidPIDDerivativeConstant;\r
+      }\r
+\r
+      memcpy(&pid_left.ks,  &msg->config_pid.left,  sizeof pid_left.ks);\r
+      memcpy(&pid_right.ks, &msg->config_pid.right, sizeof pid_right.ks);\r
+      memcpy(&pid_cross.ks, &msg->config_pid.cross, sizeof pid_cross.ks);\r
     } break;\r
     case FMW_MessageType_Config_LED: {\r
-      uint32_t crc_computed = HAL_CRC_Calculate(&hcrc, (uint32_t*)&msg, sizeof msg.header.type + sizeof msg.config_led);\r
-      FMW_REPORT_UNLESS(crc_received == -1 || crc_computed == crc_received, FMW_Error_UART_Crc);\r
+      if (msg->header.crc != -1) {\r
+        uint32_t crc_computed = HAL_CRC_Calculate(&hcrc, (uint32_t*)msg, sizeof msg->header.type + sizeof msg->config_led);\r
+        if (!(crc_computed == msg->header.crc)) { return FMW_Result_Error_UART_Crc; }\r
+      }\r
 \r
-      pled.voltage_red = msg.config_led.voltage_red;\r
-      pled.voltage_orange = msg.config_led.voltage_orange;\r
-      pled.voltage_hysteresis = msg.config_led.voltage_hysteresis;\r
-      led_update_period = msg.config_led.update_period;\r
+      if (!(msg->config_led.voltage_red >= FMW_MIN_ACCEPTABLE_LED_VOLTAGE &&\r
+            msg->config_led.voltage_red < FMW_MAX_ACCEPTABLE_LED_VOLTAGE)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidLEDVoltage;\r
+      }\r
+      if (!(msg->config_led.voltage_orange >= FMW_MIN_ACCEPTABLE_LED_VOLTAGE &&\r
+            msg->config_led.voltage_orange < FMW_MAX_ACCEPTABLE_LED_VOLTAGE)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidLEDVoltage;\r
+      }\r
+      if (!(msg->config_led.voltage_hysteresis >= FMW_MIN_ACCEPTABLE_LED_VOLTAGE &&\r
+            msg->config_led.voltage_hysteresis < FMW_MAX_ACCEPTABLE_LED_VOLTAGE)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidLEDVoltage;\r
+      }\r
+      if (!(msg->config_led.update_period >= FMW_MIN_ACCEPTABLE_LED_UPDATE_PERIOD &&\r
+            msg->config_led.update_period < FMW_MAX_ACCEPTABLE_LED_UPDATE_PERIOD)) {\r
+        return FMW_Result_Error_MessageHandler_Init_InvalidLEDVoltage;\r
+      }\r
+\r
+      pled.voltage_red = msg->config_led.voltage_red;\r
+      pled.voltage_orange = msg->config_led.voltage_orange;\r
+      pled.voltage_hysteresis = msg->config_led.voltage_hysteresis;\r
+      led_update_period = msg->config_led.update_period;\r
     } break;\r
     }\r
   } break;\r
   }\r
+\r
+  return FMW_Result_Ok;\r
 }\r
 \r
-void fmw_report_handler(FMW_Error error_code, const char *filename, int32_t filename_length, int32_t line) {\r
-  // TODO(lb): send error message via UART,\r
-  //           go back to some sort of "initialization" state\r
-  fmw_motor_brake(&motors.left);\r
-  fmw_motor_brake(&motors.right);\r
-  fmw_state = FMW_State_Error;\r
-  for (;;) {}\r
+int32_t fmw_result_format(char buffer[], size_t buffer_size,\r
+                          const char *filename, int16_t filename_length,\r
+                          int32_t line, FMW_Result result) {\r
+  static const char* const variant_strings[] = {\r
+    #define X(Variant) #Variant,\r
+    FMW_RESULT_VARIANTS(X)\r
+    #undef X\r
+  };\r
+\r
+  return snprintf(buffer, buffer_size,\r
+                  "[%.*s:%ld] failed with result: %s\r\n",\r
+                  filename_length, filename, line,\r
+                  variant_strings[result]);\r
+}\r
+\r
+void fmw_result_log_uart(UART_HandleTypeDef *huart, FMW_Result result,\r
+                         const char *filename, int16_t filename_length,\r
+                         int32_t line) {\r
+  if (fmw_state != FMW_State_Init) {\r
+    fmw_motor_brake(&motors.left);\r
+    fmw_motor_brake(&motors.right);\r
+  }\r
+\r
+  char buff[512] = {0};\r
+  int32_t length = fmw_result_format(buff, sizeof buff, filename, filename_length, line, result);\r
+  HAL_UART_Transmit(huart, (uint8_t*)buff, length, HAL_MAX_DELAY);\r
 }\r
 \r
 // TODO(lb): move to fmw. maybe create a FMW_Buzzer?\r
@@ -1025,9 +1147,9 @@ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
 /* USER CODE END 4 */\r
 \r
 /**\r
 * @brief  This function is executed in case of error occurrence.\r
 * @retval None\r
 */\r
+ * @brief  This function is executed in case of error occurrence.\r
+ * @retval None\r
+ */\r
 void Error_Handler(void)\r
 {\r
   /* USER CODE BEGIN Error_Handler_Debug */\r
@@ -1037,12 +1159,12 @@ void Error_Handler(void)
 }\r
 #ifdef USE_FULL_ASSERT\r
 /**\r
 * @brief  Reports the name of the source file and the source line number\r
 *         where the assert_param error has occurred.\r
 * @param  file: pointer to the source file name\r
 * @param  line: assert_param error line source number\r
 * @retval None\r
 */\r
+ * @brief  Reports the name of the source file and the source line number\r
+ *         where the assert_param error has occurred.\r
+ * @param  file: pointer to the source file name\r
+ * @param  line: assert_param error line source number\r
+ * @retval None\r
+ */\r
 void assert_failed(uint8_t *file, uint32_t line)\r
 {\r
   /* USER CODE BEGIN 6 */\r