
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2022 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stm32f0xx_hal_adc_ex.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
// turn on the watchdog timer
#define WATCHDOG

/// \brief Enumeration of heater states
typedef enum
{
  HEAT_OFF,        // heater is off
  HEAT_PENDING,    // heater request is pending
  HEAT_ON,         // heater is switched on
  HEAT_ON_LOW_VOLT // heater timer running, voltage is low
} heaterControl;

/// \brief state for heater channel

#pragma pack(push, 1)
typedef struct
{
  heaterControl control; ///< control state
  uint16_t LEDintensity; ///< current LED intensity
  uint16_t LEDtarget;    ///< current LED target intensity
  uint32_t heatTimer;    /// < tick time counter for heater channel
  uint8_t buttonCount;   /// < debounce counter
  uint8_t tick;          /// < a value that increments 
  uint16_t checkSum;     /// < checksum used in post-reset validation
} heaterStatus;
#pragma pack(pop)

/// \brief enumeration of LED intensities for each case
typedef enum
{
  INTENSITY_OFF = 1,          // dim glow
  INTENSITY_STBY_DIM = 8,     // flashing waiting for batttery voltage - dim
  INTENSITY_STBY_BRIGHT = 32, // flashing waiting for battery voltage -bright
  INTENSITY_ON_LOW = 64,      // night time intensity - dash lighting on
  INTENSITY_ON = 256          // daytime intensity  - dash lighting off
} ledIntensities;

/// \brief Enumeration of active ADC channels
typedef enum
{
  IGNITION_VOLT_CHAN = 0,
  DASHBOARD_VOLT_CHAN,
  TEMPERATURE_CHAN,
  VREF_CHAN
} adcChannels;

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/// \brief LED intensities :
/// multiply by INTENSITY_STEP / (INTENSITY_STEP-1) to fade up  intensity
/// multiply by (INTENSITY_STEP-1) / INTENSITY_STEP to fade down intensity
#define INTENSITY_STEP 12

/// \brief ADC filtering parameters
#define ADC_TMPGRP_BUF_DEPTH 4
#define ADC_TEMPGRP_NUM_CHANNELS 4

/// \brief ADC scaling for power supply measurement
///  Resistor ladder is 47k top 10k bottom :ratio expressed as 1000 times value
#define IGN_ADC_SCALE 5556 // should be 5700 , but resistor ratio is 5.749 not

// Battery voltage * 1000
// alternator charging
#define HEATER_ON_VOLTAGE 13500
// battery OK under load
#define HEATER_OFF_VOLTAGE 11500

// if the dashboard/backlight power is over 5 volts, consider dimming LEDS
#define DASH_ON_VOLTAGE 5000

// temperature which is regarded as cold
#define COLD_TEMPERATURE 3

// Default timer run time in milli seconds
#define WARM_TIMER_RUN_TICKS 400000L

#define COLD_TIMER_RUN_TICKS 600000L

/*
 * Register addresses were taken from DM00088500 (STM32F030 datasheet)
 * For non-STM32F030 microcontrollers register addresses
 * might need to be modified according to the respective datasheet.
 */
// Temperature sensor raw value at 30 degrees C, VDDA=3.3V
#define TEMP30_CAL_ADDR ((uint16_t *)((uint32_t)0x1FFFF7B8))
// Internal voltage reference raw value at 30 degrees C, VDDA=3.3V
#define VREFINT_CAL_ADDR ((uint16_t *)((uint32_t)0x1FFFF7BA))
// internal temperature sensor : 1000 times ADC slope per degree C
#define AVG_SLOPE (5336L)
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc;
DMA_HandleTypeDef hdma_adc;

TIM_HandleTypeDef htim3;
TIM_HandleTypeDef htim14;

WWDG_HandleTypeDef hwwdg;

/* USER CODE BEGIN PV */

// storage for heater status

heaterStatus const ResetHeater = {HEAT_OFF, 0, 0, 0, 0, 0};

heaterStatus HeaterLeft = ResetHeater;
heaterStatus HeaterRight = ResetHeater;

#define BACKUP_COPIES 2

heaterStatus __attribute__((section(".persistent"))) BackupLeft[BACKUP_COPIES];
heaterStatus __attribute__((section(".persistent"))) BackupRight[BACKUP_COPIES];

// storage for ADC DMA'd samples

uint16_t ADC_Samples[ADC_TMPGRP_BUF_DEPTH * ADC_TEMPGRP_NUM_CHANNELS];

// see https://techoverflow.net/2015/01/13/reading-stm32f0-internal-temperature-and-voltage-using-chibios/
typedef struct
{
  int32_t temperature;
  int32_t vdda;
  int32_t batteryVoltage;
  int32_t dashVoltage;
} analogReadings;

analogReadings vals = {0, 0, 0, 0};

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC_Init(void);
static void MX_TIM3_Init(void);
static void MX_TIM14_Init(void);
static void MX_WWDG_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

void setLEDLeft(uint16_t brightness)
{
  __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_4, brightness);
}

void setLEDRight(uint16_t brightness)
{
  __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, brightness);
}

void setLEDEval(uint16_t brightness)
{
  __HAL_TIM_SET_COMPARE(&htim14, TIM_CHANNEL_1, brightness);
}

void setRelayLeft(heaterControl control)
{
  HAL_GPIO_WritePin(RelayLeft_GPIO_Port, RelayLeft_Pin, control == HEAT_ON ? GPIO_PIN_SET : GPIO_PIN_RESET);
}

void setRelayRight(heaterControl control)
{
  HAL_GPIO_WritePin(RelayRight_GPIO_Port, RelayRight_Pin, control == HEAT_ON ? GPIO_PIN_SET : GPIO_PIN_RESET);
}

// return 1 when button pressed (using NC button)
uint8_t getButtonLeft()
{
  return HAL_GPIO_ReadPin(PushLeft_GPIO_Port, PushLeft_Pin) == GPIO_PIN_SET;
}

// return 1 when button pressed (using NC button)
uint8_t getButtonRight()
{
  return HAL_GPIO_ReadPin(PushRight_GPIO_Port, PushRight_Pin) == GPIO_PIN_SET;
}

void readTemperatureVDDA(void)
{
  // NOTE: Computation is performed in 32 bits, but result is converted to 16 bits later.s
  /**
   * Compute average of temperature sensor raw output
   * and vrefint raw output
   */

  int32_t tempAvg = 0;
  int32_t vrefintAvg = 0;
  int32_t batteryAvg = 0;
  int32_t dashAvg = 0;
  // Samples are alternating: ignition, temp, vrefint, ignition, temp, vrefint, ...
  for (int i = 0; i < (ADC_TMPGRP_BUF_DEPTH * ADC_TEMPGRP_NUM_CHANNELS); i += ADC_TEMPGRP_NUM_CHANNELS)
  {
    batteryAvg += ADC_Samples[i + IGNITION_VOLT_CHAN];
    tempAvg += ADC_Samples[i + TEMPERATURE_CHAN];
    vrefintAvg += ADC_Samples[i + VREF_CHAN];
    dashAvg += ADC_Samples[i + DASHBOARD_VOLT_CHAN];
  }
  tempAvg /= ADC_TMPGRP_BUF_DEPTH;
  vrefintAvg /= ADC_TMPGRP_BUF_DEPTH;
  batteryAvg /= ADC_TMPGRP_BUF_DEPTH;
  dashAvg /= ADC_TMPGRP_BUF_DEPTH;

  /**
   * Compute temperature in celsius
   *
   * Note that we need to normalize the value first by applying
   * the (actual VDDA / VDDARef) ratio.
   *
   * Note: VDDA_Actual = 3.3V * VREFINT_CAL / vrefintAvg
   * Therefore, the ratio mentioned above is equal to
   * q = VREFINT_CAL / vrefintAvg
   */
  int32_t temperature = ((int32_t)*TEMP30_CAL_ADDR - tempAvg) * 1000;

  temperature = temperature / AVG_SLOPE;
  temperature = temperature + 30L;

  vals.temperature = temperature;
  vals.vdda = (3300 * (*VREFINT_CAL_ADDR)) / vrefintAvg;
  vals.batteryVoltage = (IGN_ADC_SCALE * batteryAvg) / 4096 * vals.vdda / 1000; //* 3300 * (*VREFINT_CAL_ADDR)) / batteryAvg);
  vals.dashVoltage = (IGN_ADC_SCALE * dashAvg) / 4096 * vals.vdda / 1000;
}
uint16_t getBatteryVoltage()
{
  return vals.batteryVoltage;
}

int8_t getTemperature()
{
  return vals.temperature;
}

uint16_t getDashVoltage()
{
  return vals.dashVoltage;
}

uint16_t checkSum(heaterStatus *status)
{
  uint16_t sum = 0xFFFF;
  uint8_t *ptr = (uint8_t *)(status);
  for (uint8_t *p = ptr; p < ptr + sizeof(heaterStatus) - sizeof(uint16_t); p++)
  {
    sum *= 41;
    sum += *p;
  }
  return sum;
}

/// @brief  Periodic status save into RAM.
/// @param status status to save
/// @param saveStatus address of array to save status in
/// @param iter  iteration of saving
/// @return new iteratiom
uint8_t saveStatus(heaterStatus *status, heaterStatus *saveStatus, uint8_t iter)
{
  status->checkSum = checkSum(status);
  if (iter >= BACKUP_COPIES)
    iter = 0;
  saveStatus[iter] = *status;

  iter++;
  return iter;
}

/// @brief Recover status from RAM after reset/crash
/// @param saveStatus pointer to array of saved status
/// @return pointer to valid saved or reset status
heaterStatus const *recoverStatus(heaterStatus *saveStatus)
{
  for (int i = 0; i < BACKUP_COPIES; i++)
  {
    if (saveStatus[i].checkSum == checkSum(saveStatus + i))
      return saveStatus + i;
  };
  // default return a reset state
  return &ResetHeater;
}

void process(heaterStatus *status, uint8_t button,  int8_t temperature, uint16_t battery, uint16_t dashboard)
{
  // deal with button debounce
    uint8_t buttonPressed = 0;
    uint8_t longButtonPressed = 0;

    // deal with intensity
    uint16_t intensity; 
    status->tick++; 
    if ((status->tick % 128) < 64)
    {
      intensity = INTENSITY_STBY_DIM;
    }
    else{
      intensity = INTENSITY_STBY_BRIGHT;
    }
    
  if (status->buttonCount >=100)
    status->buttonCount=100;
  if (button && status->buttonCount < 100)
  {
    status->buttonCount++;
    if (status->buttonCount == 10)
      buttonPressed = 1;
    if (status->buttonCount == 100)
      longButtonPressed = 1;
  }
  if (!button)
    status->buttonCount = 0;
  

  // deal with LED brightness control

  if (status->LEDintensity < status->LEDtarget)
  {
    // do an exponential fade up
    uint16_t tmp = status->LEDintensity;
    tmp *= INTENSITY_STEP;
    tmp /= INTENSITY_STEP - 1;
    // if nothing happened increment
    status->LEDintensity = tmp == status->LEDintensity ? tmp + 1 : tmp;
    // handle overshoot
    if (status->LEDintensity > status->LEDtarget)
      status->LEDintensity = status->LEDtarget;
  }
  if (status->LEDintensity > status->LEDtarget)
  {
    // do an exponential fade down
    uint16_t tmp = status->LEDintensity;
    tmp *= INTENSITY_STEP - 1;
    tmp /= INTENSITY_STEP;
    // if nothing happened, decrement
    status->LEDintensity = tmp == status->LEDintensity ? tmp - 1 : tmp;
    // handle undershoot
    if (status->LEDintensity < status->LEDtarget)
      status->LEDintensity = status->LEDtarget;
  }

  // deal with state machine
  switch (status->control)
  {
  case HEAT_OFF: // heater is off
    status->LEDtarget = INTENSITY_OFF;
    if (buttonPressed)
      status->control = HEAT_PENDING;
    break;
  case HEAT_PENDING: // heater request is pending
    status->LEDtarget = intensity;
    if (buttonPressed)
    {
      status->control = HEAT_OFF;
      break;
    }
    if (battery > HEATER_ON_VOLTAGE)
    {
      // start the timer
      status->heatTimer = HAL_GetTick();
      status->control = HEAT_ON;
      break;
    }
    break;
  case HEAT_ON: // heater is switched on
  case HEAT_ON_LOW_VOLT:
    // specific conditions
    if (status->control == HEAT_ON)
    {
      if (battery < HEATER_OFF_VOLTAGE)
        status->control = HEAT_ON_LOW_VOLT;

      status->LEDtarget = (dashboard > DASH_ON_VOLTAGE) ? INTENSITY_ON : INTENSITY_ON_LOW;
    }
    if (status->control == HEAT_ON_LOW_VOLT)
    {
      if (battery > HEATER_ON_VOLTAGE)
        status->control = HEAT_ON;

      status->LEDtarget = intensity;
    }

    // common code
    // press and hold to turn off

    if (longButtonPressed)
    {
      status->control = HEAT_OFF;
      break;
    }
    // press button to extend time
    if (buttonPressed)
    {
      // restart the timer
      status->heatTimer = HAL_GetTick();
      break;
    }
    //  respond to temperature input
    uint32_t timeLimit = (temperature < COLD_TEMPERATURE) ? COLD_TIMER_RUN_TICKS : WARM_TIMER_RUN_TICKS;

    if ((HAL_GetTick() - status->heatTimer) > timeLimit)
    {
      status->control = HEAT_OFF;
      break;
    }

    break;

    // check timer value here
  }
}

/* USER CODE END 0 */

/**
 * @brief  The application entry point.
 * @retval int
 */
int main(void)
{
  /* USER CODE BEGIN 1 */
  uint8_t saveChannel = 0;

  HeaterLeft = *recoverStatus(BackupLeft);
  HeaterRight = *recoverStatus(BackupRight);

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC_Init();
  MX_TIM3_Init();
  MX_TIM14_Init();
#if defined WATCHDOG
  MX_WWDG_Init();
#endif

  /* USER CODE BEGIN 2 */

  HAL_ADC_MspInit(&hadc);

  HAL_ADC_Start_DMA(&hadc, (uint32_t *)ADC_Samples, ADC_TMPGRP_BUF_DEPTH * ADC_TEMPGRP_NUM_CHANNELS);

  HAL_ADC_Start(&hadc);

  // turn on temperature sensor and VREF
  ADC->CCR |= ADC_CCR_TSEN | ADC_CCR_VREFEN;

  // initialise all the STMCubeMX stuff
  HAL_TIM_Base_MspInit(&htim3);
  HAL_TIM_Base_MspInit(&htim14);
  // Start the counter
  HAL_TIM_Base_Start(&htim3);
  HAL_TIM_Base_Start(&htim14);

  HAL_TIM_OC_Start(&htim3, TIM_CHANNEL_2);
  HAL_TIM_OC_Start(&htim3, TIM_CHANNEL_4);

  HAL_TIM_OC_Start(&htim14, TIM_CHANNEL_1);

  HeaterLeft.LEDtarget = INTENSITY_OFF;
  HeaterRight.LEDtarget = INTENSITY_OFF;

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    readTemperatureVDDA();

    int8_t temperature = getTemperature();
    uint16_t batteryVoltage = getBatteryVoltage();
    uint16_t dashVoltage = getDashVoltage();

    setLEDLeft(HeaterLeft.LEDintensity);
    setLEDRight(HeaterRight.LEDintensity);
    setLEDEval(HeaterLeft.LEDintensity);

    setRelayLeft(HeaterLeft.control);
    setRelayRight(HeaterRight.control);
    
    // generate different intensity targets for LED pulsation effect

    process(&HeaterLeft, getButtonLeft(),  temperature, batteryVoltage, dashVoltage);
    process(&HeaterRight, getButtonRight(),  temperature, batteryVoltage, dashVoltage);

    /* note the WWDG configuration also needs to be updated if this delay is changed */
    HAL_Delay(10);
#if defined WATCHDOG
    HAL_WWDG_Refresh(&hwwdg);
#endif
    saveStatus(&HeaterLeft, BackupLeft, saveChannel);
    saveChannel = saveStatus(&HeaterRight, BackupRight, saveChannel);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
   * in the RCC_OscInitTypeDef structure.
   */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
   */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
 * @brief ADC Initialization Function
 * @param None
 * @retval None
 */
static void MX_ADC_Init(void)
{

  /* USER CODE BEGIN ADC_Init 0 */

  /* USER CODE END ADC_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC_Init 1 */

  /* USER CODE END ADC_Init 1 */

  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
   */
  hadc.Instance = ADC1;
  hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
  hadc.Init.Resolution = ADC_RESOLUTION_12B;
  hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
  hadc.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  hadc.Init.LowPowerAutoWait = DISABLE;
  hadc.Init.LowPowerAutoPowerOff = DISABLE;
  hadc.Init.ContinuousConvMode = DISABLE;
  hadc.Init.DiscontinuousConvMode = DISABLE;
  hadc.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;
  hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc.Init.DMAContinuousRequests = ENABLE;
  hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel to be converted.
   */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel to be converted.
   */
  sConfig.Channel = ADC_CHANNEL_1;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel to be converted.
   */
  sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel to be converted.
   */
  sConfig.Channel = ADC_CHANNEL_VREFINT;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC_Init 2 */

  /* USER CODE END ADC_Init 2 */
}

/**
 * @brief TIM3 Initialization Function
 * @param None
 * @retval None
 */
static void MX_TIM3_Init(void)
{

  /* USER CODE BEGIN TIM3_Init 0 */

  /* USER CODE END TIM3_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 79;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 255;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 1;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.Pulse = 64;
  if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_4) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM3_Init 2 */

  /* USER CODE END TIM3_Init 2 */
  HAL_TIM_MspPostInit(&htim3);
}

/**
 * @brief TIM14 Initialization Function
 * @param None
 * @retval None
 */
static void MX_TIM14_Init(void)
{

  /* USER CODE BEGIN TIM14_Init 0 */

  /* USER CODE END TIM14_Init 0 */

  TIM_OC_InitTypeDef sConfigOC = {0};

  /* USER CODE BEGIN TIM14_Init 1 */

  /* USER CODE END TIM14_Init 1 */
  htim14.Instance = TIM14;
  htim14.Init.Prescaler = 79;
  htim14.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim14.Init.Period = 255;
  htim14.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim14.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim14) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim14) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM2;
  sConfigOC.Pulse = 128;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim14, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM14_Init 2 */

  /* USER CODE END TIM14_Init 2 */
  HAL_TIM_MspPostInit(&htim14);
}

/**
 * @brief WWDG Initialization Function
 * @param None
 * @retval None
 */
static void MX_WWDG_Init(void)
{

  /* USER CODE BEGIN WWDG_Init 0 */

  /* USER CODE END WWDG_Init 0 */

  /* USER CODE BEGIN WWDG_Init 1 */

  /* USER CODE END WWDG_Init 1 */
  hwwdg.Instance = WWDG;
  hwwdg.Init.Prescaler = WWDG_PRESCALER_1;
  hwwdg.Init.Window = 83;
  hwwdg.Init.Counter = 93;
  hwwdg.Init.EWIMode = WWDG_EWI_DISABLE;
  if (HAL_WWDG_Init(&hwwdg) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN WWDG_Init 2 */

  /* USER CODE END WWDG_Init 2 */
}

/**
 * Enable DMA controller clock
 */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

/**
 * @brief GPIO Initialization Function
 * @param None
 * @retval None
 */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, RelayRight_Pin | RelayLeft_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pins : PA2 PA3 PushLeft_Pin */
  GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3 | PushLeft_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pins : RelayRight_Pin RelayLeft_Pin */
  GPIO_InitStruct.Pin = RelayRight_Pin | RelayLeft_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /*Configure GPIO pin : PushRight_Pin */
  GPIO_InitStruct.Pin = PushRight_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(PushRight_GPIO_Port, &GPIO_InitStruct);
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
 * @brief  This function is executed in case of error occurrence.
 * @retval None
 */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef USE_FULL_ASSERT
/**
 * @brief  Reports the name of the source file and the source line number
 *         where the assert_param error has occurred.
 * @param  file: pointer to the source file name
 * @param  line: assert_param error line source number
 * @retval None
 */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */