/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2020 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "crc.h"
#include "dac.h"
#include "dma.h"
#include "iwdg.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

#include <stdio.h>
#include <math.h>

#include "log.h"
#include "sysdata.h"
#include "modbus.h"
#include "SEGGER_RTT.h"
#include "feeprom.h"
#include "tast.h"
#include "string.h"
#include "precharge.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

typedef enum LOGIC {LOGIC_POSITIV, LOGIC_NEGATIV} logic_t;

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

#define TAG "MAIN"

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

#ifdef USE_RAM_FUNC
uint8_t vectorTableInRAM[192] __attribute__ ((aligned (256)));
#endif

          modbus_t    modbusData;
          sys_data_t  sys_data;
volatile  uint16_t    ADC_values[ADC_CHANNELS];
volatile  int32_t	  rawMOSFETsVoltageDrop;
volatile  int32_t	  rawContactVoltageDropPlus;
volatile  int32_t	  rawContactVoltageDropMinus;
          int         command_parser_is_enabled;
volatile  int		  overcurrent_shutdown_is_active = 0;
volatile  int		  overload_shutdown_is_active = 0;
          int		  low_bat_shutdown_is_active = 0;
          int		  temperature_shutdown_is_active = 0;
          int		  mosfets_voltagedrop_shutdown_is_active = 0;
          void		  (*MOSFETS_Management)(void);					// Function pointer that is called in ADC interrupt, depending on the states of LVP&OVP
          void		  (*LVP_OVP[LVP_OVP_EVENT_NUM])(void);			// Function pointers array that contains "what to do" functions for every combination of LVP&OVP
          void		  (*AUTO_Mode)(uint32_t, int);					// Function pointer that contains function that is executed when gSwitch is in AUTO mode (depends on DIP switches)
          logic_t	  LVP_OVP_logic = LOGIC_NEGATIV;				// Default logic is negative
          int		  manual_overdrive_is_enabled;
          uint32_t	  swdioConnection = UINT32_MAX;					// Special variable that contains non-zero number, if SWD-debugger is connected to target
          void		  (*InternalGreenLED_Management)(void);					// Function pointer that controls Green LED
          void		  (*InternalBlueLED_Management)(void);					// Function pointer that controls Blue LED
          void		  (*InternalRedLED_Management)(void);			// Function pointer that controls internal Red LED
          void		  (*ExternalGreenLED_Management)(void);			// Function pointer that controls external Green LED, which is located inside of the button
          void		  (*ExternalRedLED_Management)(void);			// Function pointer that controls external Red LED, which is located inside of the button
          int		  RS485ActiveMode = 1;							// RS485 transsiver is active
          int		  auto_recover_from_temp_shutdown_is_enabled;	// Automatic reconnect after overtemperature disconnect
          //uint16_t  i_samples[I_RMS_SAMPLES_COUNT] __attribute__((aligned(4)));
          //uint16_t  d_samples[I_RMS_SAMPLES_COUNT];
          //uint16_t  u_samples[I_RMS_SAMPLES_COUNT];
//volatile  int32_t	  i_samples_counter = 0;
		  uint16_t	  savedLockKey;
volatile  uint32_t	  overcurrent_shutdown_time = 0xFFFFE0C0;
volatile  uint32_t	  overload_shutdown_time = 0xFFFFE0C0;
		  void		  (*Callibration)(void);
		  //void		  (*Callibration_2)(void);
		  //void		  (*ActuelKeyManagement)(void);
		  uint16_t	  keyAccepted = 0;
		  uint16_t	  savedLockKey;
		  int		  statDataChanged = 0;
volatile  uint32_t	  maxIntegral = UINT32_MAX;
		  void		  (*InrushCurrentManagement)(void);

//#include "raccess.c"
extern accessMode_t accessModeTable[ MAX_ADRESS+1 ];

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
void SYSDATA_Init(void);
void StartUpSequence(void);
#ifdef USE_RAM_FUNC
void CopyingVectorTableToRAM(void);
#endif
void DoNothing();
void OpenBothMOSFETSVeryFast(void);
void CloseBothMOSFETSVeryFast(void);
void OVP_not_present__LVP_not_present(void);
void OVP_present__LVP_not_present(void);
void OVP_not_present__LVP_present(void);
void OVP_present__LVP_present(void);
void ADC_OVP_not_present__LVP_not_present(void);
void ADC_OVP_present__LVP_not_present(void);
void ADC_OVP_not_present__LVP_present(void);
void ADC_OVP_present__LVP_present(void);
void ShowSlaveAddressOnLED(uint16_t address, GPIO_TypeDef *port, uint16_t pin);
inline __attribute__((always_inline)) void MODBUS_Management(void);
void DEBUG_print(uint32_t ticks);
void HeavyCalculations(uint32_t ticks);
void Keys_Management(void);
void AUTO_LVP_OVP_Management(uint32_t ticks, int reset);
void AUTO_LVP_Management(uint32_t ticks, int reset);
void AUTO_OVP_Management(uint32_t ticks, int reset);
void DIP_Switches(void);
void OVP_ignored__LVP_not_present(void);
void OVP_ignored__LVP_present(void);
void ADC_OVP_ignored__LVP_not_present(void);
void ADC_OVP_ignored__LVP_present(void);
void OVP_not_present__LVP_ignored(void);
void OVP_present__LVP_ignored(void);
void ADC_OVP_not_present__LVP_ignored(void);
void ADC_OVP_present__LVP_ignored(void);
void ADC_Close_Both_MOSFETs(void);
void ADC_Open_Both_MOSFETs(void);
void SystemClock_Decrease(void);
void EnterPowerSavingMode(void);
void ExitPowerSavingMode(void);
void StartOffMode(int reset);
void LEDs_Management(void);
void BlueLEDShortBlinking(void);
void TurnBlueLEDOn(void);
void TurnExternalGreenLEDOn(void);
void TurnExternalGreenLEDOff(void);
//void ExternalRedLEDShortBlinking(void);
//void ExternalRedLEDVeryShortBlinking(void);
void ExternalGreenLEDShortBlinking(void);
void OVP_Management_NoAutoreconnect(uint32_t new_time, int reset);
void OVP_present__LVP_ignored_NoAutoreconnect(void);
void LVP_Management_NoAutoreconnect(uint32_t new_time, int reset);
void OVP_ignored__LVP_present_NoAutoreconnect(void);
void LVP_OVP_Management_NoAutoreconnect(uint32_t new_time, int reset);
void ShortCutCheck(void);
void TrueRMSCurrentCalculation(void);
void TrueRMSCurrentOfflineCalculation(void);
void CurrentOfflineCalculation(void);
void CallibrateVoltageDropABMiddlePointOffset(void);
void CallibrateControlCurrentVoltageDropOnContactBB(void);
void CallibrateCurrentSensorZeroOffsetOnContactBB(void);
void ABVoltageDropCalculation(void);
inline __attribute__((always_inline)) void OpenBothMOSFETS(void);
inline __attribute__((always_inline)) void CloseBothMOSFETS(void);
void InrushCurrentDetected(void);
void ExternalRedLED1ShortOnThenLongPauseBlinking(void);
void ExternalRedLED2ShortOnThenLongPauseBlinking(void);
void ExternalRedLED3ShortOnThenLongPauseBlinking(void);
void ExternalRedLED4ShortOnThenLongPauseBlinking(void);
void ExternalRedLED5ShortOnThenLongPauseBlinking(void);
void ExternalRedLED6ShortOnThenLongPauseBlinking(void);
void ExternalRedLED2ShortOnThen2LongOnThenLongPauseBlinking(void);
void RS485DisableButtonManagement(uint32_t new_time);

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
#ifdef DEBUG
  __HAL_RCC_DBGMCU_CLK_ENABLE();
  DBG->APBFZ1 |= (DBG_APB_FZ1_DBG_TIM2_STOP | DBG_APB_FZ1_DBG_TIM6_STOP | DBG_APB_FZ1_DBG_TIM7_STOP);
  DBG->APBFZ2 |= (DBG_APB_FZ2_DBG_TIM14_STOP | DBG_APB_FZ2_DBG_TIM15_STOP | DBG_APB_FZ2_DBG_TIM16_STOP | DBG_APB_FZ2_DBG_TIM17_STOP);
#endif
  command_parser_is_enabled = 1;
  uint32_t new_time;
  uint32_t old_time = 0;
  //uint32_t MIN_MAX_CALCULATION_DELAY = 3000;
  uint32_t stat_last_time_checked = 0;
  /* 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 */
#define MX_IWDG_Init  DoNothing	  // This line helps to eliminate early start of WatchDog timer and keep code reconfigurable by CubeMx
  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  MX_CRC_Init();
  MX_DAC1_Init();
  MX_TIM17_Init();
  MX_IWDG_Init();
  MX_TIM16_Init();
  MX_TIM14_Init();
  MX_TIM7_Init();
  MX_TIM6_Init();
  MX_TIM2_Init();
  MX_TIM15_Init();
  /* USER CODE BEGIN 2 */
#undef MX_IWDG_Init	  // Removing previous definition, that helped not to start a watchdog

//#ifdef DEBUG
  //RCC->APBENR1 |= RCC_APBENR1_DBGEN;
  //DBG->APBFZ1 |= DBG_APB_FZ1_DBG_TIM7_STOP | DBG_APB_FZ1_DBG_TIM2_STOP | DBG_APB_FZ1_DBG_TIM6_STOP;
  //DBG->APBFZ2 |= DBG_APB_FZ2_DBG_TIM14_STOP | DBG_APB_FZ2_DBG_TIM16_STOP | DBG_APB_FZ2_DBG_TIM17_STOP;
//#endif

  SYSDATA_Init();

  if (HAL_TIM_Base_Start(&htim2) != HAL_OK) LOG_E(TAG, "Cannot start TIMER2!");

  SEGGER_RTT_printf(0, RTT_CTRL_CLEAR);
  LOG_I(TAG, "Program started.");
  switch(DBG->IDCODE & 0xFFF)
  {
	  case 0x467: LOG_I(TAG, "Device ID: STM32G0B1 or STM32G0C1"); break;
	  case 0x460: LOG_I(TAG, "Device ID: STM32G071 or STM32G081"); break;
	  case 0x456: LOG_I(TAG, "Device ID: STM32G051 or STM32G061"); break;
	  case 0x466: LOG_I(TAG, "Device ID: STM32G031 or STM32G041"); break;
	  default: LOG_I(TAG, "Device ID: unknown");
  }
  SEGGER_RTT_printf(0, "%s: Revision number: 0x%4X\n", TAG, DBG->IDCODE>>16);
  SEGGER_RTT_printf(0, "Free space for cofiguration in fake EEPROM: %u bytes\n", FEEPROM_ConfigFreeBytes());
  SEGGER_RTT_printf(0, "Free space for statistics in fake EEPROM: %u bytes\n", FEEPROM_StatFreeBytes());
  SEGGER_RTT_printf(0, "MAX_POSSIBLE_DIFF_TO_MEASURE: %u\n", MAX_POSSIBLE_DIFF_TO_MEASURE);
  SEGGER_RTT_printf(0, "CPU Freq: %u Hz\n", HAL_RCC_GetSysClockFreq());

  if (HAL_RCC_GetSysClockFreq() < 64000000)
  {
	  LOG_E(TAG, "CPU speed is not 64MHz!");
	  LOG_E(TAG, "Trying to restart.");
	  HAL_NVIC_SystemReset();
  }

  StartUpSequence();

#ifdef USE_RAM_FUNC
  CopyingVectorTableToRAM();
#endif

  if(FEEPROM_isFirstStart())
  {
	  LOG_W(TAG, "First start! Writing default configuration!");
	  FEEPROM_fullRestore(/*&sys_data*/);
	  FEEPROM_ResetLogData();
  }

  // Fetching configuration from FLASH
  if (FEEPROM_readConfig(&sys_data)) LOG_E(TAG, "Cannot read configuration from FLASH memory!");
  if (FEEPROM_ReadLogData(&sys_data)) LOG_E(TAG, "Cannot read statistcal data from FLASH memory!");

  sys_data.s.startup_cnt++;	  // Start-up counter
  statDataChanged = 1;
  maxIntegral = sys_data.s.inrush_max_current_in_adc * sys_data.s.inrush_curr_integral_steps;
  sys_data.s.copper_v_drop_adc_limit = (sys_data.s.copper_v_drop_adc * 110) / 100;

  ShowSlaveAddressOnLED(sys_data.s.slave_address, LED_ERROR_GPIO_Port, LED_ERROR_Pin);

  // Modbus Initialisierung
  if (sys_data.s.parity_mode == 'e') mbInit(&modbusData, sys_data.s.baudrate, MODBUS_UART_PARITY_EVEN, &huart1, accessModeTable, &keyAccepted);
  else if (sys_data.s.parity_mode == 'o') mbInit(&modbusData, sys_data.s.baudrate, MODBUS_UART_PARITY_ODD, &huart1, accessModeTable, &keyAccepted);
  else mbInit(&modbusData, sys_data.s.baudrate, MODBUS_UART_PARITY_NONE, &huart1, accessModeTable, &keyAccepted);

  // ADC self-calibration
  if (HAL_ADC_Stop(&hadc1) == HAL_OK) // Stopping ADC
  {
      if (HAL_ADCEx_Calibration_Start (&hadc1) == HAL_OK)
      {
          uint32_t calibration_value = HAL_ADCEx_Calibration_GetValue(&hadc1);
          SEGGER_RTT_printf(0, "%s%s: ADC Calibration value: %u\n", RTT_CTRL_TEXT_BRIGHT_GREEN,TAG, calibration_value | 0x3F);
      }
      else LOG_E(TAG, "ADC calibration error!");
  }
  else LOG_E(TAG, "Cannot stop ADC!");

  // DAC calibration
  uint32_t calib_1 = HAL_DACEx_GetTrimOffset(&hdac1, MOSFET_CHANNEL_A);
  uint32_t calib_2 = HAL_DACEx_GetTrimOffset(&hdac1, MOSFET_CHANNEL_B);
  SEGGER_RTT_printf(0, "%s: DAC Calibration value for channel 1: %u\n", TAG, calib_1);
  SEGGER_RTT_printf(0, "%s: DAC Calibration value for channel 2: %u\n", TAG, calib_2);

  // Before ADC start we must initialize our MOSFETs management function pointer
  StartOffMode(1);

  //MOSFETS_Management = &ADC_Open_Both_MOSFETs;								// & is optional, but it clearly refers to pointer initialization
  //sys_data.s.user_button_mode = SWITCH_OFF;									// Initial state of the switch
  if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_values, ADC_CHANNELS) != HAL_OK) LOG_E(TAG, "Cannot start ADC in DMA mode!");

  DMA1_Channel1->CCR &= ~DMA_CCR_HTIE;											// Disabling Half-Transfer interrupt, because we don't need it

  // Starting DAC
  HAL_DAC_Start(&hdac1, MOSFET_CHANNEL_A);
  HAL_DAC_Start(&hdac1, MOSFET_CHANNEL_B);
  // Setting new values
  HAL_DAC_SetValue(&hdac1, MOSFET_CHANNEL_A, DAC_ALIGN_12B_R, DAC_0V);
  HAL_DAC_SetValue(&hdac1, MOSFET_CHANNEL_B, DAC_ALIGN_12B_R, DAC_0V);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */

  DIP_Switches();

  // Initializing independant watchdog timer (cannot be disabled)
  //MX_IWDG_Init();

  // Due to the fact, that gSwitch always start in OFF state, after start-up
  // we can enter low power mode, because nothing dangerous happens in OFF mode
  //EnterPowerSavingMode();

  InternalGreenLED_Management = &DoNothing;
  InternalBlueLED_Management = &BlueLEDShortBlinking;
  InternalRedLED_Management = &DoNothing;
  ExternalGreenLED_Management = &DoNothing;
  ExternalRedLED_Management = &DoNothing;
  Callibration = &DoNothing;
  InrushCurrentManagement = &InrushCurrentDetected;

  while (1)																		// Main loop
  {
	  ABVoltageDropCalculation();

	  //TIM2->CNT = 0;
	  //CurrentOfflineCalculation();
	  //TrueRMSCurrentOfflineCalculation();	// Max execution time is
	  //SEGGER_RTT_printf(0, "Function <TrueRMSCurrentCalculation> lasts %u cycles\n", TIM2->CNT);

	  MODBUS_Management();														// This function does not rely on time event

	  Keys_Management();

	  new_time = HAL_GetTick();													// Saving current time
	  if (new_time == old_time) continue;										// If tick value hasn't changed since last time, then it is useless to check the rest of conditions below
	  old_time = new_time;														// Saving current time value

	  //HAL_IWDG_Refresh(&hiwdg);	// 0.5s RESET

	  Callibration();

 	  LEDs_Management();

      // Printing DEBUG messages
	  // If SWD connector is not connected, we do not waste time for printing
	  swdioConnection <<= 1;										// Preparing space for 1 bit
	  swdioConnection |= HAL_GPIO_ReadPin(SWCLK_Port, SWCLK_Pin);	// Sampling SWCLK pin
	  if (swdioConnection) DEBUG_print(new_time);					// If debugger is connected, then we show debug messages

	  // Some not extremely urgent values calculations
	  HeavyCalculations(new_time);	  // Max execution time is 74µs

	  RS485DisableButtonManagement(new_time);

	  //Speicher Log Daten maximal jede Stunde, aber auch nur dann, wenn siche Werte, seit dem letztem mal geändert haben
	  if (new_time - stat_last_time_checked > HOUR_TIME_INTERVALL)
	  {
		  stat_last_time_checked = new_time;
		  LOG_I(TAG, "It is time to save statistical data in Flash memory.");

		  if (statDataChanged)
		  {
			  FEEPROM_StoreLogData(&sys_data);
			  statDataChanged = false;
		  }
	  }


	  static int restartAutoMode = 0;
	  // Checking switch state (Can be changed via Modbus or external button)
	  switch(sys_data.s.user_button_mode)
	  {
		  case SWITCH_OFF:			// If button is set to OFF, then we disconnect both MOSFETS
			  break;

		  case SWITCH_ON:			// If button is set to ON, then we connect both MOSFETS (Danger!!!)
			  // Checking whether temperature, current and battery voltage are within limits
			  if (temperature_shutdown_is_active == 1)
			  {
				  // If so, opening both MOSFETs (i.e. disconnecting load/charger from battery)
#ifdef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF
				  DisableShortCutDetection();
#endif
				  HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
				  MOSFETS_Management = &ADC_Open_Both_MOSFETs;
				  sys_data.s.relay_status = RELAY_IS_OPENED;
				  HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);
				  ExternalRedLED_Management = &ExternalRedLED1ShortOnThenLongPauseBlinking;
			  }
			  else if (overcurrent_shutdown_is_active == 1)			ExternalRedLED_Management = &ExternalRedLED2ShortOnThenLongPauseBlinking;
			  else if (mosfets_voltagedrop_shutdown_is_active == 1)	ExternalRedLED_Management = &ExternalRedLED3ShortOnThenLongPauseBlinking;
			  else if (overload_shutdown_is_active == 1)			ExternalRedLED_Management = &ExternalRedLED4ShortOnThenLongPauseBlinking;
			  break;

		  case SWITCH_AUTO:
			  // Checking whether temperature, current and battery voltage are within limits
			  if ((temperature_shutdown_is_active == 1) || (low_bat_shutdown_is_active == 1))
			  {
				  if (!restartAutoMode)
				  {
					  // If so, opening both MOSFETs (i.e. disconnecting load/charger from battery)
#ifdef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF
					  DisableShortCutDetection();
#endif
					  HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
					  MOSFETS_Management = &ADC_Open_Both_MOSFETs;
					  sys_data.s.relay_status = RELAY_IS_OPENED;
					  HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);
					  if (temperature_shutdown_is_active == 1) ExternalRedLED_Management = &ExternalRedLED1ShortOnThenLongPauseBlinking;
					  if (low_bat_shutdown_is_active == 1) ExternalRedLED_Management = &ExternalRedLED5ShortOnThenLongPauseBlinking;
					  restartAutoMode = 1;
				  }
			  }
			  else if (overcurrent_shutdown_is_active == 1)
			  {
				  if (!restartAutoMode) { ExternalRedLED_Management = &ExternalRedLED2ShortOnThenLongPauseBlinking;  restartAutoMode = 1; }
			  }
			  else if (mosfets_voltagedrop_shutdown_is_active == 1)
			  {
				  if (!restartAutoMode) { ExternalRedLED_Management = &ExternalRedLED3ShortOnThenLongPauseBlinking;  restartAutoMode = 1; }
			  }
			  else if (overload_shutdown_is_active == 1)
			  {
				  if (!restartAutoMode) { ExternalRedLED_Management = &ExternalRedLED4ShortOnThenLongPauseBlinking;  restartAutoMode = 1; }
			  }
			  else
			  {
				  // Normal operations

				  /** This is a main function that is called in AUTO mode.
					  Which one function is executed depends on DIP switch state.
					  In general, here can be executed 3 functions:
						1. AUTO_LVP_Management (OVP is ignored)
						2. AUTO_OVP_Management (LVP is ignored)
						3. AUTO_LVP_OVP_Management
					  As long as device is in AUTO mode, this function is called
					  periodically.
				  */
				  AUTO_Mode(new_time, restartAutoMode);
				  restartAutoMode = 0;
			  }
			  break;
      }

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

  } /* while (1) */

  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSI
                              |RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV8;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1;
  RCC_OscInitStruct.PLL.PLLN = 16;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  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_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }

  /** Enables the Clock Security System
  */
  HAL_RCC_EnableCSS();
}

/* USER CODE BEGIN 4 */

//-----------------------------------------------------------------------------

void RS485DisableButtonManagement(uint32_t new_time)
{
	static uint32_t btn_last_time_checked = 0;
	uint32_t BTN_SCAN_PERIOD = 25;
	static uint8_t btn_state = 0;
	static int transition = 1;

	// Scanning special button on the board, which disables RS485 MODBUS interface
	if (new_time - btn_last_time_checked > BTN_SCAN_PERIOD)
	{
		btn_last_time_checked = new_time;
		BTN_SCAN_PERIOD = 25;

		btn_state <<= 1;

		// If this special button is pressed
		if (HAL_GPIO_ReadPin(BTN1_GPIO_Port, BTN1_Pin) == BTN_IS_PRESSED)
		{
			//BTN_SCAN_PERIOD = 1500;
			btn_state |= 1;

			if (btn_state & 0xFF)
			{
				//btn_state = 0;
    			// If MODBUS RS485 interface is enabled
				if (transition)
				{
					transition = 0;
        			if (RS485ActiveMode)
        			{
        				// We disable it
        				InternalBlueLED_Management = &TurnBlueLEDOn;
        				RS485ActiveMode = 0;
        			}
        			else
        			{
        				// We enable it
        				InternalBlueLED_Management = &BlueLEDShortBlinking;
        				RS485ActiveMode = 1;
        			}
				}
			}
		}
		else
		{
			btn_state |= 0;
			if (btn_state == 0) transition = 1;
		}
	}
}

//-----------------------------------------------------------------------------

void ABVoltageDropCalculation(void)
{
	static int32_t ursense_voltage_accum = 0;
	static volatile uint32_t last_time_UabCalculated = 0;
	static int positive_pulse_found = 0;

	static volatile uint32_t new_time;
	new_time = HAL_GetTick();

	if (new_time - last_time_UabCalculated > 1)
	{
		last_time_UabCalculated = new_time;
		// Calculating real voltage drop between contacts B and A in mV
		sys_data.s.ab_raw_adc_value_with_offset = rawMOSFETsVoltageDrop + sys_data.s.ab_middle_point_offset;
		int32_t temp = ((sys_data.s.ab_raw_adc_value_with_offset) * 2 * MAX_POSSIBLE_DIFF_TO_MEASURE) / ADC_MAX_VALUE - MAX_POSSIBLE_DIFF_TO_MEASURE;
		// Calculating averaged value for real voltage drop between contacts B and A in mV
		ursense_voltage_accum -= sys_data.s.ursense_voltage;
		ursense_voltage_accum += temp;
		sys_data.s.ursense_voltage = ursense_voltage_accum /16384;

		if ((sys_data.s.relay_status != RELAY_IS_OPENED)/* || (sys_data.s.relay_status == ONLY_BA_OPENED) || (sys_data.s.relay_status == ONLY_AB_OPENED)*/)
		{
			if (!positive_pulse_found)
			{
				positive_pulse_found = 1;
				ursense_voltage_accum = 0;
				sys_data.s.ursense_voltage = 0;
				return;
			}

			if (sys_data.s.relay_status == ONLY_BA_OPENED)
			{
				// Here we can start tracking the voltage drop on MOSFETs
				if (sys_data.s.ursense_voltage < -(MAX_ALLOWED_MOSFETS_V_DROP + MAX_ALLOWED_MOSFETS_V_DROP_DELTA))
				{
					if (mosfets_voltagedrop_shutdown_is_active == 0)
					{
						HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
						OpenBothMOSFETSVeryFast();
						MOSFETS_Management = &DoNothing;
						HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);
						mosfets_voltagedrop_shutdown_is_active = 1;
						//sys_data.s.device_status |= (1 << ABBA_VOLTAGE_ERROR);
						sys_data.s.mosfets_voltagedrop_error_cnt++;
						statDataChanged = 1;
					}
				}
			}
			else if (sys_data.s.relay_status == ONLY_AB_OPENED)
			{
				// Here we can start tracking the voltage drop on MOSFETs
				if (sys_data.s.ursense_voltage > (MAX_ALLOWED_MOSFETS_V_DROP + MAX_ALLOWED_MOSFETS_V_DROP_DELTA))
				{
					if (mosfets_voltagedrop_shutdown_is_active == 0)
					{
						HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
						OpenBothMOSFETSVeryFast();
						MOSFETS_Management = &DoNothing;
						HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);
						mosfets_voltagedrop_shutdown_is_active = 1;
						//sys_data.s.device_status |= (1 << ABBA_VOLTAGE_ERROR);
						sys_data.s.mosfets_voltagedrop_error_cnt++;
						statDataChanged = 1;
					}
				}
			}
		}
		else positive_pulse_found = 0;

		//SEGGER_RTT_printf(0, "Uab = %4d    Rstatus = %d\n", sys_data.s.ursense_voltage, sys_data.s.relay_status);
	}
}

//-----------------------------------------------------------------------------

void mb_save_lock_key(void)
{
  if (sys_data.s.lockKey == savedLockKey)
  {
    FEEPROM_storeConfig(&sys_data, false);
    savedLockKey = sys_data.s.newLockKey;
    sys_data.s.lockKey = savedLockKey;
  }

  if (savedLockKey != 0)
  {
    sys_data.s.writeLocked = 1;
  }
  else
  {
    sys_data.s.writeLocked = 0;
  }
}

//-----------------------------------------------------------------------------

void SYSDATA_Init(void)
{
  memset(&sys_data, 0, sizeof(sys_data));

  sys_data.s.device_type_id = DEVICE_TYPE_ID;
  sys_data.s.fw_major		= FW_VERSION_MAJOR;
  sys_data.s.fw_minor		= FW_VERSION_MINOR;
  sys_data.s.fw_revision	= FW_VERSION_REVISION;

  sys_data.s.command = 0;
  sys_data.s.device_status = 0;
}

//-----------------------------------------------------------------------------

void CurrentOfflineCalculation(void)
{
	static uint32_t last_time = 0;

	uint32_t new_time = HAL_GetTick();

	if (new_time - last_time >= 1)
	{
		last_time = new_time;

		// Sliding average calculation of discharge current
		//current_temperature = (((MAX_TEMP - MIN_TEMP)*((int)ADC_values[TEMP_CHANNEL] - TEMP_SENSOR_ADC_AT_MINUS30))/(TEMP_SENSOR_ADC_AT_PLUS100 - TEMP_SENSOR_ADC_AT_MINUS30)) + MIN_TEMP;
		//temperature_accum -= sys_data.s.temperature;
		//temperature_accum += current_temperature;
		//sys_data.s.temperature = temperature_accum / 32;//>> 5;

	}

}

//-----------------------------------------------------------------------------

void TrueRMSCurrentOfflineCalculation(void)
{
	static uint32_t last_time = 0;
	static uint32_t samples_cnt = 0;
	static uint64_t sum = 0;
	const uint32_t N = 8;

	uint32_t new_time = HAL_GetTick();

	if (new_time - last_time >= 1)
	{
		last_time = new_time;

		// rawContactVoltageDrop is in the range  [-4095, 4095]
		// Saving it in temporary variable, to prevent corruption of its value in ISR
		int32_t tmp = rawContactVoltageDropPlus;
		sum += (tmp * tmp);
		samples_cnt++;
		//SEGGER_RTT_printf(0, "[%3u] %5d %8u\n", samples_cnt, tmp, sum);
	}

	if (samples_cnt > (CONTROL_CURRENT_A*N))	  // Number of samples is picked to correspond to CONTROL_CURRENT_A value. Must be CONTROL_CURRENT_A * N, where N is [1,2,3...]
	{
		samples_cnt = 0;
		//SEGGER_RTT_printf(0, "%u\n", sum);

		if (sys_data.s.ursense_voltage >= 0)
		{
			sys_data.s.current = sqrtf((sum*CONTROL_CURRENT_A)/(sys_data.s.copper_v_drop_adc * sys_data.s.copper_v_drop_adc * N));
		}
		else
		{
			sys_data.s.current = -sqrtf((sum*CONTROL_CURRENT_A)/(sys_data.s.copper_v_drop_adc * sys_data.s.copper_v_drop_adc * N));
		}

		//SEGGER_RTT_printf(0, "%d\n", sys_data.s.current);
		sum = 0;
	}

}

//-----------------------------------------------------------------------------

/*void TrueRMSCurrentCalculation(void)
{
	static uint64_t total_sum = 0;
	static uint32_t total_sum_cnt = 0;

	// Executing this if ISR filled all I_RMS_SAMPLES_COUNT values into i_samples array
	if (i_samples_counter == I_RMS_SAMPLES_COUNT)
	{
#ifdef DEBUG
		static uint64_t sum = 0;
#else
		uint64_t sum = 0;
		//int is_negative;
#endif
		for (int i = 0; i < I_RMS_SAMPLES_COUNT; i++)
		{
#ifdef DEBUG
			static int32_t shifted_value = 0;
			shifted_value = i_samples[i] - (ADC_MAX_VALUE>>1);
			static uint32_t zero_based_value;
#else
			static volatile int32_t shifted_value;
			shifted_value = i_samples[i] - (ADC_MAX_VALUE>>1);	// Delta from middle point of 2047
			//uint32_t zero_based_value;
#endif
			//if (shifted_value >= 0)
			//{
			//	zero_based_value = shifted_value;
			//	//is_negative = 0;
			//}
			//else
			//{
			//	zero_based_value = - shifted_value;
			//	//is_negative = 1;
			//}

			//static float divider;
			//divider = ADC_MAX_VALUE * sys_data.s.copper_v_drop * (1 + (COPPER_TEMP_COEFFICIENT * (sys_data.s.temperature - 200))/10);

			//current_value = (zero_based_value * CONTROL_CURRENT_A * ADC_VREF) / divider;

			//float divider = ADC_MAX_VALUE * sys_data.s.copper_v_drop * (1 + (COPPER_TEMP_COEFFICIENT * (sys_data.s.temperature - sys_data.s.copper_v_drop_temp))/10);
			//float divider = sys_data.s.copper_v_drop_adc * (1 + (COPPER_TEMP_COEFFICIENT * (sys_data.s.temperature - sys_data.s.copper_v_drop_temp))/10);
			static volatile int32_t current_value;
			current_value = (shifted_value * CONTROL_CURRENT_A) / sys_data.s.copper_v_drop_adc;
			if (current_value < 0)
			{
				if (-current_value > sys_data.s.max_discharge_current) sys_data.s.max_discharge_current = -current_value;
				else if (-current_value < sys_data.s.min_discharge_current) sys_data.s.min_discharge_current = -current_value;
			}
			else
			{
				if (current_value > sys_data.s.max_charge_current) sys_data.s.max_charge_current = current_value;
				else if (current_value < sys_data.s.min_charge_current) sys_data.s.min_charge_current = current_value;
			}

			sum += (shifted_value * shifted_value);
			//char div[50];
			//sprintf(div, "%f", divider);
			//SEGGER_RTT_printf(0, "%u, %u\n", u_samples[i], d_samples[i]);
		}

		i_samples_counter = 0;

		if (total_sum_cnt < I_RMS_SAMPLES_SUM_COUNT)
		{
			total_sum += sum;
			total_sum_cnt++;
		}
		else
		{
			total_sum_cnt = 0;
			//SEGGER_RTT_printf(0, "Sum: %d\tTotal: %d\n", sum, total_sum);
			total_sum = (total_sum * CONTROL_CURRENT_A * CONTROL_CURRENT_A) / (sys_data.s.copper_v_drop_adc * sys_data.s.copper_v_drop_adc);
			if (sys_data.s.ursense_voltage >= 0) sys_data.s.current = sqrtf(total_sum/(I_RMS_SAMPLES_COUNT * I_RMS_SAMPLES_SUM_COUNT));
			else sys_data.s.current = -sqrtf(total_sum/(I_RMS_SAMPLES_COUNT * I_RMS_SAMPLES_SUM_COUNT));
			//else sys_data.s.current = sqrtf(total_sum/(I_RMS_SAMPLES_COUNT * I_RMS_SAMPLES_SUM_COUNT));
			total_sum = 0;
		}
	}
}*/

//-----------------------------------------------------------------------------

/*#ifdef USE_RAM_FUNC
__RAM_FUNC void ShortCutCheck(void)
#else
void ShortCutCheck(void)
#endif
{
	static uint32_t last_time_checked = 0;
	static int current_integral_calc_started = 0;
	static uint32_t current_integral = 0;

	uint32_t current_time = HAL_GetTick();
	int32_t current_adc_value = abs(rawContactVoltageDrop - (ADC_MAX_VALUE>>1));

	if (current_time - last_time_checked > 1)
	{
		last_time_checked = current_time;

		if (current_adc_value > ADC_VALUE_DELTA_AT_CONTROL_CURRENT_A)
		{
			if (current_integral_calc_started)
			{
				current_integral += current_adc_value;
				if (current_integral > (ADC_VALUE_DELTA_AT_INRUSH_CURRENT * 5)) overcurrent_shutdown_is_active = 1;
				SEGGER_RTT_printf(0, "\t%d\t%d\n", current_adc_value, current_integral);
			}
			else
			{
				current_integral = 0;
				current_integral += current_adc_value;
				current_integral_calc_started = 1;
				SEGGER_RTT_printf(0, "Overcurrent: %d\n", current_adc_value);
				SEGGER_RTT_printf(0, "Starting integral calculation:\n");
				SEGGER_RTT_printf(0, "\t%d\t%d\n", current_adc_value, current_integral);
			}
		}
		else
		{
			if (current_integral_calc_started)
			{
				SEGGER_RTT_printf(0, "Stopping integral calculation:\n");
				SEGGER_RTT_printf(0, "\t%d\t%d\n", current_adc_value, current_integral);
				current_integral = 0;
				current_integral_calc_started = 0;
			}
		}
	}

}*/

//-----------------------------------------------------------------------------

#ifdef USE_RAM_FUNC
void CopyingVectorTableToRAM(void)
{
	uint32_t SrcAddress = SCB->VTOR;
	uint32_t DstAddress = (uint32_t)vectorTableInRAM;

	if (HAL_DMA_Start(&hdma_memtomem_dma1_channel2, SrcAddress, DstAddress, 192/4) != HAL_OK)
	{
		LOG_E(TAG, "Cannot copy Vector Table from FLASH to RAM! DMA is not ready!");
		while(1);
	}
	else LOG_I(TAG, "Starting Vector Table copying from FLASH to RAM...");

	if (HAL_DMA_PollForTransfer(&hdma_memtomem_dma1_channel2, HAL_DMA_FULL_TRANSFER, 1000) != HAL_OK)
	{
		LOG_E(TAG, "Cannot finish copying Vector Table from FLASH to RAM!");
		while(1);
	}
	else LOG_I(TAG, "Vector Table has been copied from FLASH to RAM.");
	SCB->VTOR = DstAddress;
}
#endif

//------------------------------------------------------------------------------

void LEDBlink(uint32_t *local_prev_on_time,	  // last tick counter value
			  unsigned *local_step,			  // current number of led ignitions
			  unsigned *local_subStep,		  // corresponds to current phase of led blinking process
			  unsigned local_totalStages,	  // how many times led should blink
			  uint16_t *local_turnOnTime,	  // array that contains times for how long led should stay in on state
			  uint16_t *local_turnOffTime,	  // array that contains times for how long led should stay in off state
			  GPIO_TypeDef *GPIOx,			  // port name
			  uint16_t GPIO_Pin)			  // pin number
{
	uint32_t new_time = HAL_GetTick();

	if (*local_step < local_totalStages)
	{
		switch((*local_subStep))
		{
			case 0:	// Phase 0 - turning led on
				HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET);
				*local_prev_on_time = new_time;
				(*local_subStep)++;
				break;

			case 1:	// Phase 1 - turning led off, if it is time
				if (new_time - *local_prev_on_time > local_turnOnTime[*local_step])
				{
					HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET);
					*local_prev_on_time = new_time;
					(*local_subStep)++;
				}
				break;

			case 2:	// Phase 2 - waiting before returning to phase 0
				if (new_time - *local_prev_on_time > local_turnOffTime[*local_step])
				{
					*local_prev_on_time = new_time;
					*local_subStep = 0;
					(*local_step)++;
				}
				break;
		}
	}
	else *local_step = 0;
}

//------------------------------------------------------------------------------

void RedLEDBlink(uint16_t *turnOnPeriods, uint16_t *turnOffPeriods, unsigned totalStages)
{
	static uint32_t RedLEDLastTickTime = 0;
	static unsigned stage = 0;
	static unsigned subStage = 0;

	LEDBlink(&RedLEDLastTickTime, &stage, &subStage, totalStages, turnOnPeriods, turnOffPeriods, LED_ERROR_GPIO_Port, LED_ERROR_Pin);
}

//------------------------------------------------------------------------------

void ExternalRedLEDBlink(uint16_t *turnOnPeriods, uint16_t *turnOffPeriods, unsigned totalStages)
{
	static uint32_t RedLEDLastTickTime = 0;
	static unsigned stage = 0;
	static unsigned subStage = 0;

	LEDBlink(&RedLEDLastTickTime, &stage, &subStage, totalStages, turnOnPeriods, turnOffPeriods, LED_SW_ERROR_GPIO_Port, LED_SW_ERROR_Pin);
}

//------------------------------------------------------------------------------

void ExternalRedLED2ShortOnThen2LongOnThenLongPauseBlinking(void)
{
	const unsigned stages = 4;
	uint16_t turnOnTimes[stages];
	uint16_t turnOffTimes[stages];

	turnOnTimes[0] = 200;
	turnOffTimes[0] = 200;

	turnOnTimes[1] = 200;
	turnOffTimes[1] = 500;

	turnOnTimes[2] = 700;
	turnOffTimes[2] = 500;

	turnOnTimes[stages - 1] = 700;
	turnOffTimes[stages - 1] = 2500;

	ExternalRedLEDBlink(turnOnTimes, turnOffTimes, stages);
	RedLEDBlink(turnOnTimes, turnOffTimes, stages);
}

//------------------------------------------------------------------------------

void ExternalRedLED6ShortOnThenLongPauseBlinking(void)
{
	const unsigned stages = 6;
	uint16_t turnOnTimes[stages];
	uint16_t turnOffTimes[stages];

	for (unsigned i = 0; i < stages - 1; i++)
	{
		turnOnTimes[i] = 200;
		turnOffTimes[i] = 200;
	}

	turnOnTimes[stages - 1] = 200;
	turnOffTimes[stages - 1] = 2500;

	ExternalRedLEDBlink(turnOnTimes, turnOffTimes, stages);
	RedLEDBlink(turnOnTimes, turnOffTimes, stages);
}

//------------------------------------------------------------------------------

void ExternalRedLED5ShortOnThenLongPauseBlinking(void)
{
	const unsigned stages = 5;
	uint16_t turnOnTimes[stages];
	uint16_t turnOffTimes[stages];

	for (unsigned i = 0; i < stages - 1; i++)
	{
		turnOnTimes[i] = 200;
		turnOffTimes[i] = 200;
	}

	turnOnTimes[stages - 1] = 200;
	turnOffTimes[stages - 1] = 2500;

	ExternalRedLEDBlink(turnOnTimes, turnOffTimes, stages);
	RedLEDBlink(turnOnTimes, turnOffTimes, stages);
}

//------------------------------------------------------------------------------

void ExternalRedLED4ShortOnThenLongPauseBlinking(void)
{
	const unsigned stages = 4;
	uint16_t turnOnTimes[stages];
	uint16_t turnOffTimes[stages];

	for (unsigned i = 0; i < stages - 1; i++)
	{
		turnOnTimes[i] = 200;
		turnOffTimes[i] = 200;
	}

	turnOnTimes[stages - 1] = 200;
	turnOffTimes[stages - 1] = 2500;

	ExternalRedLEDBlink(turnOnTimes, turnOffTimes, stages);
	RedLEDBlink(turnOnTimes, turnOffTimes, stages);
}

//------------------------------------------------------------------------------

void ExternalRedLED3ShortOnThenLongPauseBlinking(void)
{
	const unsigned stages = 3;
	uint16_t turnOnTimes[stages];
	uint16_t turnOffTimes[stages];

	for (unsigned i = 0; i < stages - 1; i++)
	{
		turnOnTimes[i] = 200;
		turnOffTimes[i] = 200;
	}

	turnOnTimes[stages - 1] = 200;
	turnOffTimes[stages - 1] = 2500;

	ExternalRedLEDBlink(turnOnTimes, turnOffTimes, stages);
	RedLEDBlink(turnOnTimes, turnOffTimes, stages);
}

//------------------------------------------------------------------------------

void ExternalRedLED2ShortOnThenLongPauseBlinking(void)
{
	const unsigned stages = 2;
	uint16_t turnOnTimes[stages];
	uint16_t turnOffTimes[stages];

	turnOnTimes[0] = 200;
	turnOffTimes[0] = 200;

	turnOnTimes[stages - 1] = 200;
	turnOffTimes[stages - 1] = 2500;

	ExternalRedLEDBlink(turnOnTimes, turnOffTimes, stages);
	RedLEDBlink(turnOnTimes, turnOffTimes, stages);
}

//------------------------------------------------------------------------------

void ExternalRedLED1ShortOnThenLongPauseBlinking(void)
{
	const unsigned stages = 1;
	uint16_t turnOnTimes[stages];
	uint16_t turnOffTimes[stages];

	turnOnTimes[stages - 1] = 200;
	turnOffTimes[stages - 1] = 2500;

	ExternalRedLEDBlink(turnOnTimes, turnOffTimes, stages);
	RedLEDBlink(turnOnTimes, turnOffTimes, stages);
}

//------------------------------------------------------------------------------

/*void ExternalRedLEDVeryShortBlinking(void)
{
	static uint32_t old_on_time = 0;
	static uint32_t led_is_turned_on = 0;
	uint32_t new_time;

	new_time = HAL_GetTick();
	if (!led_is_turned_on && (new_time - old_on_time > 200))
	{
		HAL_GPIO_WritePin(LED_SW_ERROR_GPIO_Port, LED_SW_ERROR_Pin, GPIO_PIN_SET);	// External Red LED on button
		led_is_turned_on = 1;
		old_on_time = new_time;
	}
	else if (led_is_turned_on && (new_time - old_on_time > 200))
	{
		HAL_GPIO_WritePin(LED_SW_ERROR_GPIO_Port, LED_SW_ERROR_Pin, GPIO_PIN_RESET);
		led_is_turned_on = 0;
		old_on_time = new_time;
	}
}*/

//------------------------------------------------------------------------------

/*void ExternalRedLEDShortBlinking(void)
{
	static uint32_t old_on_time = 0;
	static uint32_t led_is_turned_on = 0;
	uint32_t new_time;

	new_time = HAL_GetTick();
	if (!led_is_turned_on && (new_time - old_on_time > 800))
	{
		HAL_GPIO_WritePin(LED_SW_ERROR_GPIO_Port, LED_SW_ERROR_Pin, GPIO_PIN_SET);	// External Red LED on button
		led_is_turned_on = 1;
		old_on_time = new_time;
	}
	else if (led_is_turned_on && (new_time - old_on_time > 200))
	{
		HAL_GPIO_WritePin(LED_SW_ERROR_GPIO_Port, LED_SW_ERROR_Pin, GPIO_PIN_RESET);
		led_is_turned_on = 0;
		old_on_time = new_time;
	}
}*/

//------------------------------------------------------------------------------

void TurnExternalRedLEDOff(void)
{
	HAL_GPIO_WritePin(LED_SW_ERROR_GPIO_Port, LED_SW_ERROR_Pin, GPIO_PIN_RESET);
	HAL_GPIO_WritePin(LED_ERROR_GPIO_Port, LED_ERROR_Pin, GPIO_PIN_RESET);
	ExternalRedLED_Management = &DoNothing;
}

//------------------------------------------------------------------------------

void ExternalGreenLEDShortBlinking(void)
{
	static uint32_t old_on_time = 0;
	static uint32_t led_is_turned_on = 0;
	uint32_t new_time;

	new_time = HAL_GetTick();
	if (!led_is_turned_on && (new_time - old_on_time > 800))
	{
		HAL_GPIO_WritePin(LED_SW_STATE_GPIO_Port, LED_SW_STATE_Pin, GPIO_PIN_SET);	// External Green LED on button
		led_is_turned_on = 1;
		old_on_time = new_time;
	}
	else if (led_is_turned_on && (new_time - old_on_time > 200))
	{
		HAL_GPIO_WritePin(LED_SW_STATE_GPIO_Port, LED_SW_STATE_Pin, GPIO_PIN_RESET);
		led_is_turned_on = 0;
		old_on_time = new_time;
	}
}

//------------------------------------------------------------------------------

void TurnExternalGreenLEDOff(void)
{
	HAL_GPIO_WritePin(LED_SW_STATE_GPIO_Port, LED_SW_STATE_Pin, GPIO_PIN_RESET);
	ExternalGreenLED_Management = &DoNothing;
}

//------------------------------------------------------------------------------

void TurnExternalGreenLEDOn(void)
{
	HAL_GPIO_WritePin(LED_SW_STATE_GPIO_Port, LED_SW_STATE_Pin, GPIO_PIN_SET);
	ExternalGreenLED_Management = &DoNothing;
}

//------------------------------------------------------------------------------

void GreenLEDShortBlinking(void)
{
	static uint32_t old_on_time = 0;
	static uint32_t led_is_turned_on = 0;
	uint32_t new_time;

	// Blue LED blinking (950ms - OFF, 50ms - ON)
	new_time = HAL_GetTick();
	if (!led_is_turned_on && (new_time - old_on_time > 950))
	{
		HAL_GPIO_WritePin(LED_STATE_GPIO_Port, LED_STATE_Pin, GPIO_PIN_SET);
		led_is_turned_on = 1;
		old_on_time = new_time;
	}
	else if (led_is_turned_on && (new_time - old_on_time > 50))
	{
		HAL_GPIO_WritePin(LED_STATE_GPIO_Port, LED_STATE_Pin, GPIO_PIN_RESET);
		led_is_turned_on = 0;
		old_on_time = new_time;
	}
}

//------------------------------------------------------------------------------

void TurnGreenLEDOff(void)
{
	HAL_GPIO_WritePin(LED_STATE_GPIO_Port, LED_STATE_Pin, GPIO_PIN_RESET);
	InternalGreenLED_Management = &DoNothing;
}

//------------------------------------------------------------------------------

void TurnGreenLEDOn(void)
{
	HAL_GPIO_WritePin(LED_STATE_GPIO_Port, LED_STATE_Pin, GPIO_PIN_SET);
	InternalGreenLED_Management = &DoNothing;
}

//------------------------------------------------------------------------------

void TurnBlueLEDOn(void)
{
	HAL_GPIO_WritePin(LED_FUNCTION_GPIO_Port, LED_FUNCTION_Pin, GPIO_PIN_SET);
	InternalBlueLED_Management = &DoNothing;
}

//------------------------------------------------------------------------------

void BlueLEDShortBlinking(void)
{
	static uint32_t old_on_time = 0;
	static uint32_t led_is_turned_on = 0;
	uint32_t new_time;

	// Blue LED blinking (950ms - OFF, 50ms - ON)
	new_time = HAL_GetTick();
	if (!led_is_turned_on && (new_time - old_on_time > 950))
	{
		HAL_GPIO_WritePin(LED_FUNCTION_GPIO_Port, LED_FUNCTION_Pin, GPIO_PIN_SET);
		led_is_turned_on = 1;
		old_on_time = new_time;
	}
	else if (led_is_turned_on && (new_time - old_on_time > 50))
	{
		HAL_GPIO_WritePin(LED_FUNCTION_GPIO_Port, LED_FUNCTION_Pin, GPIO_PIN_RESET);
		led_is_turned_on = 0;
		old_on_time = new_time;
	}
}

//------------------------------------------------------------------------------

void LEDs_Management(void)
{
	InternalGreenLED_Management();
	InternalBlueLED_Management();
	InternalRedLED_Management();
	ExternalGreenLED_Management();
	ExternalRedLED_Management();
}

//------------------------------------------------------------------------------

void ExitPowerSavingMode(void)
{
	//HAL_GPIO_WritePin(DISABLE_VBOOST_GPIO_Port, DISABLE_VBOOST_Pin, GPIO_PIN_SET);	// Turning VBOOST voltage generator on

	//HAL_PWREx_DisableLowPowerRunMode();
	//HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
	//SystemClock_Config();

	/*modbusData.uart->Instance->PRESC = UART_PRESCALER_DIV8;
	// Modbus Initialisierung
	if (sys_data.s.parity_mode == 'e') mbInit(&modbusData, sys_data.s.baudrate, MODBUS_UART_PARITY_EVEN, &huart1);
	else if (sys_data.s.parity_mode == 'o') mbInit(&modbusData, sys_data.s.baudrate, MODBUS_UART_PARITY_ODD, &huart1);
	else mbInit(&modbusData, sys_data.s.baudrate, MODBUS_UART_PARITY_NONE, &huart1);*/

}

//------------------------------------------------------------------------------

void EnterPowerSavingMode(void)
{
	//SystemClock_Decrease();														// Reduce the System clock to 2 MHz
	//HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE2);				// Set regulator voltage to scale 2
	//HAL_PWREx_EnableLowPowerRunMode();											// Enter LP RUN Mode
}

//------------------------------------------------------------------------------

void SystemClock_Decrease(void)
{
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};

  /* Select HSI as system clock source */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
  if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    /* Initialization Error */
    Error_Handler();
  }

  /* Modify HSI to HSI DIV8 */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV8;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_OFF;
  if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    /* Initialization Error */
    Error_Handler();
  }
}

//------------------------------------------------------------------------------

void DIP_Switches(void)
{
	// Checking positions of DIP switches
	if ((HAL_GPIO_ReadPin(DIP0_GPIO_Port, DIP0_Pin) == DIP_IS_OFF) &&
		(HAL_GPIO_ReadPin(DIP1_GPIO_Port, DIP1_Pin) == DIP_IS_OFF) &&
	    (HAL_GPIO_ReadPin(DIP2_GPIO_Port, DIP2_Pin) == DIP_IS_OFF))
	{
		// Auto-reconnect DIP-switch
		if (HAL_GPIO_ReadPin(DIP3_GPIO_Port, DIP3_Pin) == DIP_IS_ON)
		{
			// Modus 0 (LVP only. Active level - LOW)
			LOG_I(TAG, "Mode 0 is selected. Auto-reconnect is ON.");

			AUTO_Mode = &AUTO_LVP_Management;
			// Assigning default functions to function pointers array, depending on LVP state
			LVP_OVP[0] = &OVP_ignored__LVP_not_present;		//  0
			LVP_OVP[1] = &OVP_ignored__LVP_present;			//  1
			LVP_OVP[2] = LVP_OVP[3] = &DoNothing;			// Not used
			//sys_data.s.ovp_state = 2;						// Ignored state
			LVP_OVP_logic = LOGIC_NEGATIV;
			sys_data.s.dip_mode = 0 | (1 << 3);
		}
		else
		{
			// Modus 0 (LVP only. Active level - LOW)
			LOG_I(TAG, "Mode 0 is selected. Auto-reconnect is OFF.");

			AUTO_Mode = &LVP_Management_NoAutoreconnect;
			// Assigning default functions to function pointers array, depending on LVP state
			LVP_OVP[0] = &OVP_ignored__LVP_not_present;				//  0
			LVP_OVP[1] = &OVP_ignored__LVP_present_NoAutoreconnect;	//  1
			LVP_OVP[2] = LVP_OVP[3] = &DoNothing;			// Not used
			//sys_data.s.ovp_state = 2;						// Ignored state
			LVP_OVP_logic = LOGIC_NEGATIV;
			sys_data.s.dip_mode = 0;
		}
	}
	else if ((HAL_GPIO_ReadPin(DIP0_GPIO_Port, DIP0_Pin) == DIP_IS_ON) &&
			 (HAL_GPIO_ReadPin(DIP1_GPIO_Port, DIP1_Pin) == DIP_IS_OFF) &&
			 (HAL_GPIO_ReadPin(DIP2_GPIO_Port, DIP2_Pin) == DIP_IS_OFF))
	{
		// Auto-reconnect DIP-switch
		if (HAL_GPIO_ReadPin(DIP3_GPIO_Port, DIP3_Pin) == DIP_IS_ON)
		{
			// Modus 1 (OVP only. Active level - LOW)
			LOG_I(TAG, "Mode 1 is selected. Auto-reconnect is ON.");

			AUTO_Mode = &AUTO_OVP_Management;
			// Assigning default functions to function pointers array, depending on OVP state
			LVP_OVP[0] = &OVP_not_present__LVP_ignored;		//  0
			LVP_OVP[1] = &OVP_present__LVP_ignored;			//  1
			LVP_OVP[2] = LVP_OVP[3] = &DoNothing;			// Not used
			//sys_data.s.lvp_state = 2;						// Ignored state
			LVP_OVP_logic = LOGIC_NEGATIV;
			sys_data.s.dip_mode = 1 | (1 << 3);
		}
		else
		{
			// Modus 1 (OVP only. Active level - LOW)
			LOG_I(TAG, "Mode 1 is selected. Auto-reconnect is OFF.");

			AUTO_Mode = &OVP_Management_NoAutoreconnect;
			// Assigning default functions to function pointers array, depending on OVP state
			LVP_OVP[0] = &OVP_not_present__LVP_ignored;				//  0
			LVP_OVP[1] = &OVP_present__LVP_ignored_NoAutoreconnect;	//  1
			LVP_OVP[2] = LVP_OVP[3] = &DoNothing;			// Not used
			//sys_data.s.lvp_state = 2;						// Ignored state
			LVP_OVP_logic = LOGIC_NEGATIV;
			sys_data.s.dip_mode = 1;
		}
	}
	else if ((HAL_GPIO_ReadPin(DIP0_GPIO_Port, DIP0_Pin) == DIP_IS_OFF) &&
			 (HAL_GPIO_ReadPin(DIP1_GPIO_Port, DIP1_Pin) == DIP_IS_ON) &&
			 (HAL_GPIO_ReadPin(DIP2_GPIO_Port, DIP2_Pin) == DIP_IS_OFF))
	{
		// Auto-reconnect DIP-switch
		if (HAL_GPIO_ReadPin(DIP3_GPIO_Port, DIP3_Pin) == DIP_IS_ON)
		{
			// Modus 2 (LVP only. Active level - HIGH)
			LOG_I(TAG, "Mode 2 is selected. Auto-reconnect is ON.");

			AUTO_Mode = &AUTO_LVP_Management;
			// Assigning default functions to function pointers array, depending on LVP state
			LVP_OVP[0] = &OVP_ignored__LVP_not_present;		//  0
			LVP_OVP[1] = &OVP_ignored__LVP_present;			//  1
			LVP_OVP[2] = LVP_OVP[3] = &DoNothing;			// Not used
			//sys_data.s.ovp_state = 2;						// Ignored state
			LVP_OVP_logic = LOGIC_POSITIV;
			sys_data.s.dip_mode = 2 | (1 << 3);
		}
		else
		{
			// Modus 2 (LVP only. Active level - HIGH)
			LOG_I(TAG, "Mode 2 is selected. Auto-reconnect is OFF.");

			AUTO_Mode = &LVP_Management_NoAutoreconnect;
			// Assigning default functions to function pointers array, depending on LVP state
			LVP_OVP[0] = &OVP_ignored__LVP_not_present;				//  0
			LVP_OVP[1] = &OVP_ignored__LVP_present_NoAutoreconnect;	//  1
			LVP_OVP[2] = LVP_OVP[3] = &DoNothing;					// Not used
			//sys_data.s.ovp_state = 2;								// Ignored state
			LVP_OVP_logic = LOGIC_POSITIV;
			sys_data.s.dip_mode = 2;
		}
	}
	else if ((HAL_GPIO_ReadPin(DIP0_GPIO_Port, DIP0_Pin) == DIP_IS_ON) &&
			 (HAL_GPIO_ReadPin(DIP1_GPIO_Port, DIP1_Pin) == DIP_IS_ON) &&
			 (HAL_GPIO_ReadPin(DIP2_GPIO_Port, DIP2_Pin) == DIP_IS_OFF))
	{
		// Auto-reconnect DIP-switch
		if (HAL_GPIO_ReadPin(DIP3_GPIO_Port, DIP3_Pin) == DIP_IS_ON)
		{
			// Modus 3 (OVP only. Active level - HIGH)
			LOG_I(TAG, "Mode 3 is selected. Auto-reconnect is ON.");

			AUTO_Mode = &AUTO_OVP_Management;
			// Assigning default functions to function pointers array, depending on OVP state
			LVP_OVP[0] = &OVP_not_present__LVP_ignored;		//  0
			LVP_OVP[1] = &OVP_present__LVP_ignored;			//  1
			LVP_OVP[2] = LVP_OVP[3] = &DoNothing;			// Not used
			//sys_data.s.lvp_state = 2;						// Ignored state
			LVP_OVP_logic = LOGIC_POSITIV;
			sys_data.s.dip_mode = 3 | (1 << 3);
		}
		else
		{
			// Modus 3 (OVP only. Active level - HIGH)
			LOG_I(TAG, "Mode 3 is selected. Auto-reconnect is OFF.");

			AUTO_Mode = &OVP_Management_NoAutoreconnect;
			// Assigning default functions to function pointers array, depending on OVP state
			LVP_OVP[0] = &OVP_not_present__LVP_ignored;				//  0
			LVP_OVP[1] = &OVP_present__LVP_ignored_NoAutoreconnect;	//  1
			LVP_OVP[2] = LVP_OVP[3] = &DoNothing;					// Not used
			//sys_data.s.lvp_state = 2;								// Ignored state
			LVP_OVP_logic = LOGIC_POSITIV;
			sys_data.s.dip_mode = 3;
		}
	}
	else if ((HAL_GPIO_ReadPin(DIP0_GPIO_Port, DIP0_Pin) == DIP_IS_OFF) &&
			 (HAL_GPIO_ReadPin(DIP1_GPIO_Port, DIP1_Pin) == DIP_IS_OFF) &&
			 (HAL_GPIO_ReadPin(DIP2_GPIO_Port, DIP2_Pin) == DIP_IS_ON))
	{
		// Auto-reconnect DIP-switch
		if (HAL_GPIO_ReadPin(DIP3_GPIO_Port, DIP3_Pin) == DIP_IS_ON)
		{
			// Modus 4 (LVP & OVP. Active level - LOW. With autoreconnect)
			LOG_I(TAG, "Mode 4 is selected. Auto-reconnect is ON.");

			AUTO_Mode = &AUTO_LVP_OVP_Management;
			// Assigning default functions to function pointers array, depending on LVP and OVP events combinations
			LVP_OVP[0] = &OVP_not_present__LVP_not_present; // 00 - 0
			LVP_OVP[1] = &OVP_not_present__LVP_present;		// 01 - 1
			LVP_OVP[2] = &OVP_present__LVP_not_present;		// 10 - 2
			LVP_OVP[3] = &OVP_present__LVP_present;			// 11 - 3
			LVP_OVP_logic = LOGIC_NEGATIV;
			sys_data.s.dip_mode = 4 | (1 << 3);
		}
		else
		{
			// Modus 4 (LVP & OVP. Active level - LOW. Without autoreconnect)
			LOG_I(TAG, "Mode 4 is selected. Auto-reconnect is OFF.");

			AUTO_Mode = &LVP_OVP_Management_NoAutoreconnect;
			// Assigning default functions to function pointers array, depending on LVP and OVP events combinations
			LVP_OVP[0] = &OVP_not_present__LVP_not_present;	// 00 - 0
			LVP_OVP[1] = &OVP_not_present__LVP_present;		// 01 - 1
			LVP_OVP[2] = &OVP_present__LVP_not_present;		// 10 - 2
			LVP_OVP[3] = &OVP_present__LVP_present;			// 11 - 3
			LVP_OVP_logic = LOGIC_NEGATIV;
			sys_data.s.dip_mode = 4;
		}
	}
	else if ((HAL_GPIO_ReadPin(DIP0_GPIO_Port, DIP0_Pin) == DIP_IS_ON) &&
			 (HAL_GPIO_ReadPin(DIP1_GPIO_Port, DIP1_Pin) == DIP_IS_OFF) &&
			 (HAL_GPIO_ReadPin(DIP2_GPIO_Port, DIP2_Pin) == DIP_IS_ON))
	{
		// Auto-reconnect DIP-switch
		if (HAL_GPIO_ReadPin(DIP3_GPIO_Port, DIP3_Pin) == DIP_IS_ON)
		{
			// Modus 5 (LVP & OVP. Active level - HIGH. With autoreconnect)
			LOG_I(TAG, "Mode 5 is selected. Auto-reconnect is ON.");

			AUTO_Mode = &AUTO_LVP_OVP_Management;
			// Assigning default functions to function pointers array, depending on LVP and OVP events combinations
			LVP_OVP[0] = &OVP_not_present__LVP_not_present; // 00 - 0
			LVP_OVP[1] = &OVP_not_present__LVP_present;		// 01 - 1
			LVP_OVP[2] = &OVP_present__LVP_not_present;		// 10 - 2
			LVP_OVP[3] = &OVP_present__LVP_present;			// 11 - 3
			LVP_OVP_logic = LOGIC_POSITIV;
			sys_data.s.dip_mode = 5 | (1 << 3);
		}
		else
		{
			// Modus 5 (LVP & OVP. Active level - HIGH. Without autoreconnect)
			LOG_I(TAG, "Mode 5 is selected. Auto-reconnect is OFF.");

			AUTO_Mode = &LVP_OVP_Management_NoAutoreconnect;
			// Assigning default functions to function pointers array, depending on LVP and OVP events combinations
			LVP_OVP[0] = &OVP_not_present__LVP_not_present; // 00 - 0
			LVP_OVP[1] = &OVP_not_present__LVP_present;		// 01 - 1
			LVP_OVP[2] = &OVP_present__LVP_not_present;		// 10 - 2
			LVP_OVP[3] = &OVP_present__LVP_present;			// 11 - 3
			LVP_OVP_logic = LOGIC_POSITIV;
			sys_data.s.dip_mode = 5;
		}
	}
	else  // The rest of the combinations
	{
		// Auto-reconnect DIP-switch
		if (HAL_GPIO_ReadPin(DIP3_GPIO_Port, DIP3_Pin) == DIP_IS_ON)
		{
			// Modus 4 (LVP & OVP. Active level - LOW. With autoreconnect)
			LOG_I(TAG, "Illegal Mode is selected. Default Mode 4 is selected. Auto-reconnect is ON.");

			AUTO_Mode = &AUTO_LVP_OVP_Management;
			// Assigning default functions to function pointers array, depending on LVP and OVP events combinations
			LVP_OVP[0] = &OVP_not_present__LVP_not_present; // 00 - 0
			LVP_OVP[1] = &OVP_not_present__LVP_present;		// 01 - 1
			LVP_OVP[2] = &OVP_present__LVP_not_present;		// 10 - 2
			LVP_OVP[3] = &OVP_present__LVP_present;			// 11 - 3
			LVP_OVP_logic = LOGIC_NEGATIV;
			sys_data.s.dip_mode = 4 | (1 << 3);
		}
		else
		{
			// Modus 4 (LVP & OVP. Active level - LOW. Without autoreconnect)
			LOG_I(TAG, "Illegal Mode is selected. Default Mode 4 is selected. Auto-reconnect is OFF.");

			AUTO_Mode = &LVP_OVP_Management_NoAutoreconnect;
			// Assigning default functions to function pointers array, depending on LVP and OVP events combinations
			LVP_OVP[0] = &OVP_not_present__LVP_not_present;						// 00 - 0
			LVP_OVP[1] = &OVP_not_present__LVP_present;							// 01 - 1
			LVP_OVP[2] = &OVP_present__LVP_not_present;							// 10 - 2
			LVP_OVP[3] = &OVP_present__LVP_present;								// 11 - 3
			LVP_OVP_logic = LOGIC_NEGATIV;
			sys_data.s.dip_mode = 4;
		}
	}

	if (HAL_GPIO_ReadPin(DIP4_GPIO_Port, DIP4_Pin) == DIP_IS_ON)
	{
		manual_overdrive_is_enabled = 1;	// Manual overdrive
		sys_data.s.dip_mode |= (1 << 4);
	}
	else manual_overdrive_is_enabled = 0;

	if (HAL_GPIO_ReadPin(DIP5_GPIO_Port, DIP5_Pin) == DIP_IS_ON)
	{
		auto_recover_from_temp_shutdown_is_enabled = 1;
		sys_data.s.dip_mode |= (1 << 5);
	}
	else auto_recover_from_temp_shutdown_is_enabled = 0;


	// At the end of this function we can DeInit all these ports
	// to minimize power consumption

	HAL_GPIO_DeInit(DIP0_GPIO_Port, DIP0_Pin);
	HAL_GPIO_DeInit(DIP1_GPIO_Port, DIP1_Pin);
	HAL_GPIO_DeInit(DIP2_GPIO_Port, DIP2_Pin);
	HAL_GPIO_DeInit(DIP3_GPIO_Port, DIP3_Pin);
	HAL_GPIO_DeInit(DIP4_GPIO_Port, DIP4_Pin);
	HAL_GPIO_DeInit(DIP5_GPIO_Port, DIP5_Pin);
	HAL_GPIO_DeInit(DIP6_GPIO_Port, DIP6_Pin);
	HAL_GPIO_DeInit(DIP7_GPIO_Port, DIP7_Pin);
}

//------------------------------------------------------------------------------

void OVP_Management_NoAutoreconnect(uint32_t new_time, int reset)
{
	static void (*WhatToDo[LVP_OVP_EVENT_NUM>>1])(void);
	static const uint32_t newEventDelay[LVP_OVP_EVENT_NUM>>1] = {10, 3000};		// Delays in ms for LVP events
	static const uint32_t repeatEventDelay[LVP_OVP_EVENT_NUM>>1] = {1, 100};	// Delays in ms for repeating LVP events
	static uint32_t OVP_SCAN_PERIOD = 1;
	static uint32_t ovp_last_time_checked = 0;
	static int lastIdx = -1;													// Impossible index, to make initial assignment

	// This helps to reinitialize AUTO mode when exiting OFF mode with button or modbus
	if (reset) { lastIdx = -1; return; }

	if (new_time - ovp_last_time_checked > OVP_SCAN_PERIOD)
	{
		// Saving time
		ovp_last_time_checked = new_time;

		// Reading the state of LVP pin
		GPIO_PinState current_OVP_state = HAL_GPIO_ReadPin(OVP_IN_GPIO_Port, OVP_IN_Pin);
		if (LVP_OVP_logic == LOGIC_POSITIV) current_OVP_state = !current_OVP_state;

		// Creating index from LVP values: 0 and 1
		int Idx = current_OVP_state;

		// If new value of LVP sygnal differs from old one, then we must update our function pointer
		if (Idx > lastIdx)
		{
			WhatToDo[Idx] = LVP_OVP[Idx];
			// When state of LVP changes, we introduce a delay, "debouncing"
			OVP_SCAN_PERIOD = newEventDelay[Idx];
		}
		else
		{
			WhatToDo[Idx] = &DoNothing;
			// When previous state is the same like current, then we do nothing with slower rate
			OVP_SCAN_PERIOD = repeatEventDelay[Idx];
		}

		// Depending on the state of the LVP pin, calling specific function, periodically
		WhatToDo[Idx]();
		lastIdx = Idx;
	}
}

//------------------------------------------------------------------------------

void AUTO_OVP_Management(uint32_t new_time, int reset)
{
	static void (*WhatToDo[LVP_OVP_EVENT_NUM>>1])(void);
	static const uint32_t newEventDelay[LVP_OVP_EVENT_NUM>>1] = {10, 3000};		// Delays in ms for LVP events
	static const uint32_t repeatEventDelay[LVP_OVP_EVENT_NUM>>1] = {1, 100};	// Delays in ms for repeating LVP events
	static uint32_t OVP_SCAN_PERIOD = 1;
	static uint32_t ovp_last_time_checked = 0;
	static unsigned int lastIdx = 2;											// Impossible index, to make initial assignment

	// This helps to reinitialize AUTO mode when exiting OFF mode with button or modbus
	if (reset)
	{
		lastIdx = 2;
		return;
	}

	if (new_time - ovp_last_time_checked > OVP_SCAN_PERIOD)
	{
		// Saving time
		ovp_last_time_checked = new_time;

		// Reading the state of LVP pin
		GPIO_PinState current_OVP_state = HAL_GPIO_ReadPin(OVP_IN_GPIO_Port, OVP_IN_Pin);
		if (LVP_OVP_logic == LOGIC_POSITIV) current_OVP_state = !current_OVP_state;

		// Creating index from LVP values: 0 and 1
		unsigned int Idx = current_OVP_state;

		// If new value of LVP sygnal differs from old one, then we must update our function pointer
		if (Idx != lastIdx)
		{
			WhatToDo[Idx] = LVP_OVP[Idx];
			// When state of LVP changes, we introduce a delay, "debouncing"
			OVP_SCAN_PERIOD = newEventDelay[Idx];
		}
		else
		{
			WhatToDo[Idx] = &DoNothing;
			// When previous state is the same like current, then we do nothing with slower rate
			OVP_SCAN_PERIOD = repeatEventDelay[Idx];
		}

		// Depending on the state of the LVP pin, calling specific function, periodically
		WhatToDo[Idx]();
		lastIdx = Idx;
	}
}

//------------------------------------------------------------------------------

void LVP_Management_NoAutoreconnect(uint32_t new_time, int reset)
{
	static void (*WhatToDo[LVP_OVP_EVENT_NUM>>1])(void);
	static const uint32_t newEventDelay[LVP_OVP_EVENT_NUM>>1] = {10, 3000};		// Delays in ms for LVP events
	static const uint32_t repeatEventDelay[LVP_OVP_EVENT_NUM>>1] = {1, 100};	// Delays in ms for repeating LVP events
	static uint32_t LVP_SCAN_PERIOD = 1;
	static uint32_t lvp_last_time_checked = 0;
	static int lastIdx = -1;											// Impossible index, to make initial assignment

	// This helps to reinitialize AUTO mode when exiting OFF mode with button or modbus
	if (reset)
	{
		lastIdx = -1;
		return;
	}

	if (new_time - lvp_last_time_checked > LVP_SCAN_PERIOD)
	{
		// Saving time
		lvp_last_time_checked = new_time;

		// Reading the state of LVP pin
		GPIO_PinState current_LVP_state = HAL_GPIO_ReadPin(LVP_IN_GPIO_Port, LVP_IN_Pin);
		if (LVP_OVP_logic == LOGIC_POSITIV) current_LVP_state = !current_LVP_state;

		// Creating index from LVP values: 0 and 1
		int Idx = current_LVP_state;

		// If new value of LVP sygnal differs from old one, then we must update our function pointer
		if (Idx > lastIdx)
		{
			WhatToDo[Idx] = LVP_OVP[Idx];
			// When state of LVP changes, we introduce a delay, "debouncing"
			LVP_SCAN_PERIOD = newEventDelay[Idx];
		}
		else
		{
			WhatToDo[Idx] = &DoNothing;
			// When previous state is the same like current, then we do nothing with slower rate
			LVP_SCAN_PERIOD = repeatEventDelay[Idx];
		}

		// Depending on the state of the LVP pin, calling specific function, periodically
		WhatToDo[Idx]();
		lastIdx = Idx;
	}
}

//------------------------------------------------------------------------------

void AUTO_LVP_Management(uint32_t new_time, int reset)
{
	static void (*WhatToDo[LVP_OVP_EVENT_NUM>>1])(void);
	static const uint32_t newEventDelay[LVP_OVP_EVENT_NUM>>1] = {10, 3000};		// Delays in ms for LVP events
	static const uint32_t repeatEventDelay[LVP_OVP_EVENT_NUM>>1] = {1, 100};	// Delays in ms for repeating LVP events
	static uint32_t LVP_SCAN_PERIOD = 1;
	static uint32_t lvp_last_time_checked = 0;
	static unsigned int lastIdx = 2;											// Impossible index, to make initial assignment

	// This helps to reinitialize AUTO mode when exiting OFF mode with button or modbus
	if (reset)
	{
		lastIdx = 2;
		return;
	}

	if (new_time - lvp_last_time_checked > LVP_SCAN_PERIOD)
	{
		// Saving time
		lvp_last_time_checked = new_time;

		// Reading the state of LVP pin
		GPIO_PinState current_LVP_state = HAL_GPIO_ReadPin(LVP_IN_GPIO_Port, LVP_IN_Pin);
		if (LVP_OVP_logic == LOGIC_POSITIV) current_LVP_state = !current_LVP_state;

		// Creating index from LVP values: 0 and 1
		unsigned int Idx = current_LVP_state;

		// If new value of LVP sygnal differs from old one, then we must update our function pointer
		if (Idx != lastIdx)
		{
			WhatToDo[Idx] = LVP_OVP[Idx];
			// When state of LVP changes, we introduce a delay, "debouncing"
			LVP_SCAN_PERIOD = newEventDelay[Idx];
		}
		else
		{
			WhatToDo[Idx] = &DoNothing;
			// When previous state is the same like current, then we do nothing with slower rate
			LVP_SCAN_PERIOD = repeatEventDelay[Idx];
		}

		// Depending on the state of the LVP pin, calling specific function, periodically
		WhatToDo[Idx]();
		lastIdx = Idx;
	}
}

//------------------------------------------------------------------------------

void LVP_OVP_Management_NoAutoreconnect(uint32_t new_time, int reset)
{
	static void (*WhatToDo[LVP_OVP_EVENT_NUM])(void);
	static const uint32_t newEventDelay[LVP_OVP_EVENT_NUM] = {10, 3000, 3000, 3000};	// Delays in ms for new LVP&OVP combination events
	static const uint32_t repeatEventDelay[LVP_OVP_EVENT_NUM] = {1, 100, 100, 100};		// Delays in ms for repeating LVP&OVP combination events
	static uint32_t LVP_OVP_SCAN_PERIOD = 1;
	static uint32_t lvp_ovp_last_time_checked = 0;
	static int lastIdx = -1;													// Impossible index, to make initial assignment
	static int ovp_lvp_flag = 0;

	// This helps to reinitialize AUTO mode when exiting OFF mode with button or modbus
	if (reset)
	{
		lastIdx = -1;
		ovp_lvp_flag = 0;
		return;
	}

	if (new_time - lvp_ovp_last_time_checked > LVP_OVP_SCAN_PERIOD)
	{
		// Saving time
		lvp_ovp_last_time_checked = new_time;

		// Reading the states of OVP&LVP pins
		GPIO_PinState current_OVP_state = HAL_GPIO_ReadPin(OVP_IN_GPIO_Port, OVP_IN_Pin);
		GPIO_PinState current_LVP_state = HAL_GPIO_ReadPin(LVP_IN_GPIO_Port, LVP_IN_Pin);
		if (LVP_OVP_logic == LOGIC_POSITIV)
		{
			current_OVP_state = !current_OVP_state;
			current_LVP_state = !current_LVP_state;
		}

		// Creating index from OVP and LVP values combinations: 0, 1, 2 and 3
		int Idx = (current_OVP_state << 1) | current_LVP_state;

		// Checking previous combination of OVP&LVP
		if (Idx != lastIdx)
		{
			if (!ovp_lvp_flag)
			{
				if (Idx > 0) ovp_lvp_flag = 1;

				WhatToDo[Idx] = LVP_OVP[Idx];
				// When states of OVP&LVP changes, we introduce a delay, "debouncing"
				LVP_OVP_SCAN_PERIOD = newEventDelay[Idx];
			}
		}
		else
		{
			WhatToDo[Idx] = &DoNothing;
			// When previous state is the same like current, then we do nothing with slower rate
			LVP_OVP_SCAN_PERIOD = repeatEventDelay[Idx];
		}

		// Depending on the combination of the OVP and LVP states, calling specific function, periodically
		if (WhatToDo[Idx]) WhatToDo[Idx]();
		lastIdx = Idx;
	}
}

//------------------------------------------------------------------------------

void AUTO_LVP_OVP_Management(uint32_t new_time, int reset)
{
	static void (*WhatToDo[LVP_OVP_EVENT_NUM])(void);
	static const uint32_t newEventDelay[LVP_OVP_EVENT_NUM] = {10, 3000, 3000, 3000};	// Delays in ms for new LVP&OVP combination events
	static const uint32_t repeatEventDelay[LVP_OVP_EVENT_NUM] = {1, 100, 100, 100};		// Delays in ms for repeating LVP&OVP combination events
	static uint32_t LVP_OVP_SCAN_PERIOD = 1;
	static uint32_t lvp_ovp_last_time_checked = 0;
	static unsigned int lastIdx = 4;													// Impossible index, to make initial assignment

	// This helps to reinitialize AUTO mode when exiting OFF mode with button or modbus
	if (reset)
	{
		lastIdx = 4;
		return;
	}

	if (new_time - lvp_ovp_last_time_checked > LVP_OVP_SCAN_PERIOD)
	{
		// Saving time
		lvp_ovp_last_time_checked = new_time;

		// Reading the states of OVP&LVP pins
		GPIO_PinState current_OVP_state = HAL_GPIO_ReadPin(OVP_IN_GPIO_Port, OVP_IN_Pin);
		GPIO_PinState current_LVP_state = HAL_GPIO_ReadPin(LVP_IN_GPIO_Port, LVP_IN_Pin);
		if (LVP_OVP_logic == LOGIC_POSITIV)
		{
			current_OVP_state = !current_OVP_state;
			current_LVP_state = !current_LVP_state;
		}

		// Creating index from OVP and LVP values combinations: 0, 1, 2 and 3
		unsigned int Idx = (current_OVP_state << 1) | current_LVP_state;

		// If new combination of OVP&LVP sygnals differ from old one, then we must update our function pointer
		if (Idx != lastIdx)
		{
			WhatToDo[Idx] = LVP_OVP[Idx];
			// When states of OVP&LVP changes, we introduce a delay, "debouncing"
			LVP_OVP_SCAN_PERIOD = newEventDelay[Idx];
		}
		else
		{
			WhatToDo[Idx] = &DoNothing;
			// When previous state is the same like current, then we do nothing with slower rate
			LVP_OVP_SCAN_PERIOD = repeatEventDelay[Idx];
		}

		// Depending on the combination of the OVP and LVP states, calling specific function, periodically
		WhatToDo[Idx]();
		lastIdx = Idx;
	}
}

//------------------------------------------------------------------------------
static uint32_t last_time_started = 0;

void StartAutoMode(void)
{
	uint32_t current_time = HAL_GetTick();

	// Checking whether we are already in this mode
	if ((sys_data.s.user_button_mode != SWITCH_AUTO) && (sys_data.s.user_button_mode != SWITCH_ON))
	{
		// We should not allow to start this mode very often
		if (current_time - last_time_started > 1000)
		{
			if ((current_time - overload_shutdown_time > 10000) && (current_time - overcurrent_shutdown_time > 10000))
			{
    			HAL_TIM_Base_Stop_IT(&ON_MODE_AUTO_OFF_TIMER);
    			last_time_started = current_time;

    			// Turning VBOOST voltage generator on
    			//HAL_GPIO_WritePin(DISABLE_VBOOST_GPIO_Port, DISABLE_VBOOST_Pin, GPIO_PIN_SET);

    			// After 250ms of VBOOST stabilization time, we turn on MOSFETs regulation
    			//HAL_TIM_Base_Stop_IT(&VBOOST_ON_TIMER);
    			__HAL_TIM_CLEAR_FLAG(&VBOOST_ON_TIMER, TIM_FLAG_UPDATE);
    			__HAL_TIM_SET_COUNTER(&VBOOST_ON_TIMER, 0);
    			HAL_TIM_Base_Start_IT(&VBOOST_ON_TIMER);

				//CLEAR_BIT(&hadc1->State, HAL_ADC_STATE_AWD2);
				//CLEAR_BIT(&hadc1->State, HAL_ADC_STATE_AWD3);

#ifdef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF
				//__HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_AWD2);
				//__HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_AWD3);
				//NVIC_ClearPendingIRQ(ADC1_COMP_IRQn);
				HAL_NVIC_EnableIRQ(ADC1_COMP_IRQn);
#endif

    			sys_data.s.switch_cnt++;
    			statDataChanged = 1;
    			//HAL_GPIO_WritePin(FET_PULLDOWN_A_GPIO_Port, FET_PULLDOWN_A_Pin, GPIO_PIN_RESET);
    			//HAL_GPIO_WritePin(FET_PULLDOWN_B_GPIO_Port, FET_PULLDOWN_B_Pin, GPIO_PIN_RESET);
			}
		}
	}
}

//------------------------------------------------------------------------------

void StartOffMode(int reset)
{
	uint32_t current_time = HAL_GetTick();

	// Checking whether we are allowed to enter this mode
	//if ((sys_data.s.user_button_mode == SWITCH_ON) || (sys_data.s.user_button_mode == SWITCH_AUTO) || reset)
	//{
		if ((current_time - last_time_started > 500) || reset)
		{
			HAL_TIM_Base_Stop_IT(&ON_MODE_AUTO_OFF_TIMER);
			if (sys_data.s.user_button_mode != SWITCH_OFF) last_time_started = current_time;

			sys_data.s.user_button_mode = SWITCH_OFF;

#ifdef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF
			DisableShortCutDetection();
#endif
			HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
			MOSFETS_Management = &ADC_Open_Both_MOSFETs;
			sys_data.s.relay_status = RELAY_IS_OPENED;
			HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);

			ExternalGreenLED_Management = &TurnExternalGreenLEDOff;
			ExternalRedLED_Management = &TurnExternalRedLEDOff;
			InternalGreenLED_Management = &TurnGreenLEDOff;

			// After 100ms we turn off VBOOST voltage for power saving
			//HAL_TIM_Base_Stop_IT(&VBOOST_OFF_TIMER);
			__HAL_TIM_CLEAR_FLAG(&VBOOST_OFF_TIMER, TIM_FLAG_UPDATE);
			__HAL_TIM_SET_COUNTER(&VBOOST_OFF_TIMER, 0);
			HAL_TIM_Base_Start_IT(&VBOOST_OFF_TIMER);

#ifndef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF
			__HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_AWD2);
			__HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_AWD3);
			NVIC_ClearPendingIRQ(ADC1_COMP_IRQn);
			HAL_NVIC_EnableIRQ(ADC1_COMP_IRQn);
#endif
			
			overcurrent_shutdown_is_active = 0;
			overload_shutdown_is_active = 0;
			temperature_shutdown_is_active = 0;
			mosfets_voltagedrop_shutdown_is_active = 0;

			sys_data.s.last_shortcut_during_charge = 0;
			sys_data.s.last_shortcut_during_discharge = 0;
		}
	//}
}

//------------------------------------------------------------------------------

void StartOnMode(void)
{
	//static uint32_t last_time_started = 0;

	//uint32_t current_time = HAL_GetTick();

	// Checking whether we are already in this mode
	if ((sys_data.s.user_button_mode != SWITCH_ON) && (sys_data.s.user_button_mode != SWITCH_AUTO))
	{
		if (manual_overdrive_is_enabled)
		{
			// We should not allow to start this mode very often
			//if (current_time - last_time_started > 3000)
			//{
				//last_time_started = current_time;
				last_time_started = HAL_GetTick();

				//HAL_GPIO_WritePin(DISABLE_VBOOST_GPIO_Port, DISABLE_VBOOST_Pin, VBOOST_ENABLE);

				// After 250ms of VBOOST stabilization time, we turn on MOSFETs On mode
				__HAL_TIM_CLEAR_FLAG(&htim6, TIM_FLAG_UPDATE);
				__HAL_TIM_SET_COUNTER(&htim6, 0);
				HAL_TIM_Base_Start_IT(&htim6);

				// To prevent some unintentional damage to cells we turn this mode off after 1 minute
				__HAL_TIM_CLEAR_FLAG(&htim16, TIM_FLAG_UPDATE);
				__HAL_TIM_SET_COUNTER(&htim16, 0);
				HAL_TIM_Base_Start_IT(&htim16);

				sys_data.s.switch_cnt++;
				statDataChanged = 1;
				//HAL_GPIO_WritePin(FET_PULLDOWN_A_GPIO_Port, FET_PULLDOWN_A_Pin, GPIO_PIN_RESET);
				//HAL_GPIO_WritePin(FET_PULLDOWN_B_GPIO_Port, FET_PULLDOWN_B_Pin, GPIO_PIN_RESET);

				//sys_data.s.user_button_mode = SWITCH_ON;

				// We disable shortcut detecting interrupt
				HAL_NVIC_DisableIRQ(ADC1_COMP_IRQn);
			//}
		}
	}
}

//------------------------------------------------------------------------------

void Keys_Management(void)
{
	static uint32_t last_time_checked = 0;

	uint32_t current_time = HAL_GetTick();

	if (current_time - last_time_checked >= 1)
	{
		last_time_checked = current_time;
		checkKeys();

		if (get_key_short(SW_ON_Pin))
		{
			LOG_I(TAG, "UP button is pressed.");
			StartAutoMode();
		}
		else if (get_key_long(SW_ON_Pin))
		{
			LOG_I(TAG, "UP button is long-pressed.");
			StartOnMode();
		}
		else if (get_key_short(SW_OFF_Pin))
		{
			LOG_I(TAG, "DOWN button is pressed.");
			StartOffMode(0);
		}
	}
}

//------------------------------------------------------------------------------

static inline __attribute__((always_inline)) void CalculatingSwitchSideVoltage(void)
{
	static int32_t ubsensea_voltage_accum = 0;

	// Calculatiing and averaging switch side voltage
	int32_t temp = ADC_values[U_SW_CHANNEL];									// Converting ADC value into int32_t type
	temp = (temp*ADC_VREF)>>ADC_RESOLUTION;										// Getting voltage value on ADC input [0:3000]mV
#ifdef VARIANT_24V
	temp = (temp*(R19 + R20 + R27 + R26))/R26;
#else
	temp = (temp*(R25 + R24))/R24;												// Getting voltage value on contact A [0:17100]mV
#endif
	ubsensea_voltage_accum -= sys_data.s.ubsensea_voltage;
	if (ubsensea_voltage_accum < 0) ubsensea_voltage_accum = 0;
	ubsensea_voltage_accum += temp;
	sys_data.s.ubsensea_voltage = ubsensea_voltage_accum / 8;
}

//------------------------------------------------------------------------------

inline __attribute__((always_inline)) void CalculatingMinMaxVoltagesForContactA(void)
{
	// Calculating MIN & MAX voltage values for contact A
	if (sys_data.s.ubsensea_voltage > sys_data.s.max_ubsensea_voltage) sys_data.s.max_ubsensea_voltage = sys_data.s.ubsensea_voltage;
	else if (sys_data.s.ubsensea_voltage < sys_data.s.min_ubsensea_voltage) sys_data.s.min_ubsensea_voltage = sys_data.s.ubsensea_voltage;
}

//------------------------------------------------------------------------------

static inline __attribute__((always_inline)) void CalculatingAndAveragingVoltageOnContactB(void)
{
	static int32_t ubsenseb_voltage_accum = U_BAT_RECOVERY_VOLTAGE * 32;		// << ubsenseb_voltage_accum_avg;

	// Calculating and averaging battery side voltage
	int32_t temp = ADC_values[U_BAT_CHANNEL];									// Converting ADC value into int32_t type
	temp = (temp*ADC_VREF)>>ADC_RESOLUTION;										// Getting voltage value on ADC input [0:3000]mV
#ifdef VARIANT_24V
	temp = (temp*(R23 + R24 + R25 + R33))/R33;									// Getting voltage value on contact B []mV
#else
	temp = (temp*(R23 + R22))/R22;												// Getting voltage value on contact B [0:17100]mV
#endif
	ubsenseb_voltage_accum -= sys_data.s.ubsenseb_voltage;
	if (ubsenseb_voltage_accum < 0) ubsenseb_voltage_accum = 0;
	ubsenseb_voltage_accum += temp;
	sys_data.s.ubsenseb_voltage = ubsenseb_voltage_accum / 32;					//>> ubsenseb_voltage_accum_avg;
}

//------------------------------------------------------------------------------

inline __attribute__((always_inline)) void TestingForLowVoltageShutdown(void)
{
	// If voltage on contact B is lower than critical one, then we must initiate "low voltage shutdown"
	if (sys_data.s.ubsenseb_voltage < U_BAT_CRITICAL_VOLTAGE)
	{
		if (low_bat_shutdown_is_active == 0)
		{
			low_bat_shutdown_is_active = 1;
			sys_data.s.device_status |= (1 << LOWBAT_ERROR);
			sys_data.s.lowbat_error_cnt++;
			statDataChanged = 1;
		}
	}
	else if (sys_data.s.ubsenseb_voltage > U_BAT_RECOVERY_VOLTAGE)
	{
		low_bat_shutdown_is_active = 0;
		sys_data.s.device_status &= ~(1 << LOWBAT_ERROR);
	}
}

//------------------------------------------------------------------------------

inline __attribute__((always_inline)) void CalculatingMinMaxVoltagesForContactB(void)
{
	// Calculating MIN & MAX voltage values for contact B
	if (sys_data.s.ubsenseb_voltage > sys_data.s.max_ubsenseb_voltage) sys_data.s.max_ubsenseb_voltage = sys_data.s.ubsenseb_voltage;
	else if (sys_data.s.ubsenseb_voltage < sys_data.s.min_ubsenseb_voltage) sys_data.s.min_ubsenseb_voltage = sys_data.s.ubsenseb_voltage;
}

//------------------------------------------------------------------------------

static inline __attribute__((always_inline)) void SettingNewValuesForShortcutDetection(int isEnabled)
{
	// Setting limits for analogue watchdog to catch overcurrent events
	static int16_t last_shortcut_current_in_mV = 0;

	if (last_shortcut_current_in_mV != sys_data.s.shortcut_current_in_mV)
	{
		HAL_ADC_Stop_DMA(&hadc1);

		// Compensating possible skin-effect
		// Shortcut current lower then 400A does not make much sense
		if (sys_data.s.shortcut_current_in_mV < sys_data.s.copper_v_drop) sys_data.s.shortcut_current_in_mV = sys_data.s.copper_v_drop;
		if (sys_data.s.shortcut_current_in_mV > ADC_VREF) sys_data.s.shortcut_current_in_mV = ADC_VREF;

		//int32_t maxShortCutCurrent = (CONTROL_CURRENT_A * ADC_MAX_VALUE) / sys_data.s.copper_v_drop_adc;
		//if (compensatedShortcut_current > maxShortCutCurrent) compensatedShortcut_current = maxShortCutCurrent;

		last_shortcut_current_in_mV = sys_data.s.shortcut_current_in_mV;
		// Refreshing shortcut detecting value for Analog Watchdog
		ADC_AnalogWDGConfTypeDef AnalogWDGConfig = {0};

		AnalogWDGConfig.WatchdogNumber = ADC_ANALOGWATCHDOG_2;
		AnalogWDGConfig.WatchdogMode = ADC_ANALOGWATCHDOG_SINGLE_REG;
		AnalogWDGConfig.Channel = ADC_CHANNEL_2;  // I+ current sensor
		AnalogWDGConfig.ITMode = (isEnabled)? ENABLE: DISABLE;
		AnalogWDGConfig.HighThreshold = (sys_data.s.shortcut_current_in_mV * ADC_MAX_VALUE)/ADC_VREF;
		AnalogWDGConfig.LowThreshold = 0x000;
		if (HAL_ADC_AnalogWDGConfig(&hadc1, &AnalogWDGConfig) != HAL_OK) Error_Handler();

		AnalogWDGConfig.WatchdogNumber = ADC_ANALOGWATCHDOG_3;
		AnalogWDGConfig.WatchdogMode = ADC_ANALOGWATCHDOG_SINGLE_REG;
		AnalogWDGConfig.Channel = ADC_CHANNEL_6;  // I- current sensor
		AnalogWDGConfig.ITMode = (isEnabled)? ENABLE: DISABLE;;
		AnalogWDGConfig.HighThreshold = (sys_data.s.shortcut_current_in_mV * ADC_MAX_VALUE)/ADC_VREF;
		//sys_data.s.reserved = AnalogWDGConfig.HighThreshold;
		AnalogWDGConfig.LowThreshold = 0x000;
		if (HAL_ADC_AnalogWDGConfig(&hadc1, &AnalogWDGConfig) != HAL_OK) Error_Handler();

		if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_values, ADC_CHANNELS) != HAL_OK) LOG_E(TAG, "Cannot start ADC in DMA mode!");
		DMA1_Channel1->CCR &= ~DMA_CCR_HTIE;	// Disabling Half-Transfer interrupt, because we don't need it
	}
}

//------------------------------------------------------------------------------

#ifdef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF

inline __attribute__((always_inline)) void DisableShortCutDetection(void)
{
	__HAL_ADC_DISABLE_IT(&hadc1, ADC_IT_AWD2);
	__HAL_ADC_DISABLE_IT(&hadc1, ADC_IT_AWD3);
	__HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_AWD2);
	__HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_AWD3);

	// Switch-off process lasts approximately 1ms
	// We start this timer and in interrupt reactivate shortcut detection
	HAL_TIM_Base_Stop_IT(&htim15);
	__HAL_TIM_SetCounter(&htim15, 0);
	__HAL_TIM_CLEAR_FLAG(&htim15, TIM_IT_UPDATE);
	HAL_TIM_Base_Start_IT(&htim15);

	//HAL_GPIO_WritePin(TP2_GPIO_Port, TP2_Pin, GPIO_PIN_SET);
}

//------------------------------------------------------------------------------

inline __attribute__((always_inline)) void EnableShortCutDetection(void)
{
	__HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_AWD2);
	__HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_AWD3);
	__HAL_ADC_ENABLE_IT(&hadc1, ADC_IT_AWD2);
	__HAL_ADC_ENABLE_IT(&hadc1, ADC_IT_AWD3);

	//HAL_GPIO_WritePin(TP2_GPIO_Port, TP2_Pin, GPIO_PIN_RESET);
}

#endif

//------------------------------------------------------------------------------

void HeavyCalculations(uint32_t current_time)
{
	static uint32_t heavy_calc_last_time = 0;
	static uint32_t HEAVY_CALCULATIONS_PERIOD = 3000;
    static int32_t temperature_accum = 0;
	int current_temperature;
	//static int32_t prev_ursense_voltage = 0;
	//int const ubsenseb_voltage_accum_avg = 5;
	//int const ubsensea_voltage_accum_avg = 4;

	static int64_t ubbsense_voltage_accum = 0;


	// During first start, somtimes we read 0s from ADC, so we need to wait for a while before calculating Min & Max values, especially Min values
	if (current_time - heavy_calc_last_time > HEAVY_CALCULATIONS_PERIOD)
	{
		heavy_calc_last_time = current_time;
		HEAVY_CALCULATIONS_PERIOD = 135;

		CalculatingSwitchSideVoltage();

		CalculatingMinMaxVoltagesForContactA();

		CalculatingAndAveragingVoltageOnContactB();

		TestingForLowVoltageShutdown();

		CalculatingMinMaxVoltagesForContactB();

		// Sliding average calculation for board temperature
		current_temperature = (((MAX_TEMP - MIN_TEMP)*((int32_t)ADC_values[TEMP_CHANNEL] - TEMP_SENSOR_ADC_AT_MINUS30))/(TEMP_SENSOR_ADC_AT_PLUS100 - TEMP_SENSOR_ADC_AT_MINUS30)) + MIN_TEMP;
		temperature_accum -= sys_data.s.temperature;
		temperature_accum += current_temperature;
		sys_data.s.temperature = temperature_accum / 32;//>> 5;
		//sys_data.s.temperature = current_temperature;

		// Calculating MIN & MAX temperature values
		if (sys_data.s.temperature > sys_data.s.max_temperature) sys_data.s.max_temperature = sys_data.s.temperature;
		else if (sys_data.s.temperature < sys_data.s.min_temperature) sys_data.s.min_temperature = sys_data.s.temperature;

		// If temperature is above critical then we initiate temperature shutdown
		if (sys_data.s.temperature >= sys_data.s.temperature_shutdown)
		{
			if (temperature_shutdown_is_active == 0)
			{
				temperature_shutdown_is_active = 1;
				sys_data.s.device_status |= (1 << OVERTEMP_ERROR);
				sys_data.s.overtemp_error_cnt++;
				statDataChanged = 1;
			}
		}
		else if (sys_data.s.temperature < (((100 - TEMP_RECOVER_PERCENT)*sys_data.s.temperature_shutdown)/100))
		{
			if (auto_recover_from_temp_shutdown_is_enabled)
			{
				temperature_shutdown_is_active = 0;
				sys_data.s.device_status &= ~(1 << OVERTEMP_ERROR);
			}
		}

		if (overcurrent_shutdown_is_active == 1) sys_data.s.device_status |= (1 << OVERCURRENT_ERROR);
		else sys_data.s.device_status &= ~(1 << OVERCURRENT_ERROR);

		if (overload_shutdown_is_active == 1) sys_data.s.device_status |= (1 << OVERLOAD_ERROR);
		else sys_data.s.device_status &= ~(1 << OVERLOAD_ERROR);

		if (mosfets_voltagedrop_shutdown_is_active == 1) sys_data.s.device_status |= (1 << ABBA_VOLTAGE_ERROR);
		else sys_data.s.device_status &= ~(1 << ABBA_VOLTAGE_ERROR);

		// Calculating MIN & MAX voltage drop
		if (sys_data.s.ursense_voltage < 0)
		{
			// Discharge
			if (-sys_data.s.ursense_voltage > sys_data.s.max_discharge_ursense_voltage) sys_data.s.max_discharge_ursense_voltage = -sys_data.s.ursense_voltage;
			else if (-sys_data.s.ursense_voltage < sys_data.s.min_discharge_ursense_voltage) sys_data.s.min_discharge_ursense_voltage = -sys_data.s.ursense_voltage;
		}
		else
		{
			// Charge
			if (sys_data.s.ursense_voltage > sys_data.s.max_charge_ursense_voltage) sys_data.s.max_charge_ursense_voltage = sys_data.s.ursense_voltage;
			else if (sys_data.s.ursense_voltage < sys_data.s.min_charge_ursense_voltage) sys_data.s.min_charge_ursense_voltage = sys_data.s.ursense_voltage;
		}

		// Saving raw ADC value for voltage drop in special register
		sys_data.s.adc_plus_current_sensor = rawContactVoltageDropPlus;
		sys_data.s.adc_minus_current_sensor = rawContactVoltageDropMinus;

		// Calculating averaged voltage drop on COPPER-plate on contact B
		static int32_t rawContactVoltageDropPlus_accum = 0;
		static int32_t rawContactVoltageDropMinus_accum = 0;
		static int32_t tmp_i_plus = 0;
		static int32_t tmp_i_minus = 0;
		const int32_t AVG = 16;
		rawContactVoltageDropPlus_accum -= tmp_i_plus;
		rawContactVoltageDropPlus_accum += rawContactVoltageDropPlus;
		tmp_i_plus = rawContactVoltageDropPlus_accum / AVG;
		rawContactVoltageDropMinus_accum -= tmp_i_minus;
		rawContactVoltageDropMinus_accum += rawContactVoltageDropMinus;
		tmp_i_minus = rawContactVoltageDropMinus_accum / AVG;
		uint32_t final_i_plus = (tmp_i_plus >= sys_data.s.i_plus_offset)? tmp_i_plus - sys_data.s.i_plus_offset: 0;
		uint32_t final_i_minus = (tmp_i_minus >= sys_data.s.i_minus_offset)? tmp_i_minus - sys_data.s.i_minus_offset: 0;
		int32_t rawContactVoltageDrop = final_i_plus - final_i_minus;
		sys_data.s.ubbsense_voltage = (rawContactVoltageDrop * ADC_VREF) / ADC_MAX_VALUE;
		sys_data.s.current = (rawContactVoltageDrop * CONTROL_CURRENT_A) / sys_data.s.copper_v_drop_adc;
		//SEGGER_RTT_printf(0, "Iadc+ = %u    Iadc- = %u    Iadc = %d    Ubb = %d mV    I = %d A\n", final_i_plus, final_i_minus, rawContactVoltageDrop, sys_data.s.ubbsense_voltage, sys_data.s.current);

		if ((sys_data.s.relay_status == RELAY_IS_OPENED) && (InternalGreenLED_Management != &TurnGreenLEDOff)) InternalGreenLED_Management = &TurnGreenLEDOff;
		else if ((sys_data.s.relay_status == RELAY_IS_CLOSED) && (InternalGreenLED_Management != &TurnGreenLEDOn)) InternalGreenLED_Management = &TurnGreenLEDOn;
		else if (InternalGreenLED_Management != &GreenLEDShortBlinking) InternalGreenLED_Management = &GreenLEDShortBlinking;

		GPIO_PinState current_OVP_state = HAL_GPIO_ReadPin(OVP_IN_GPIO_Port, OVP_IN_Pin);
		GPIO_PinState current_LVP_state = HAL_GPIO_ReadPin(LVP_IN_GPIO_Port, LVP_IN_Pin);
		if (LVP_OVP_logic == LOGIC_POSITIV)
		{
			//if (sys_data.s.lvp_state != 2)
			//{
				if (current_LVP_state == GPIO_PIN_SET) sys_data.s.lvp_state = 0;
				else sys_data.s.lvp_state = 1;
			//}
			//if (sys_data.s.ovp_state != 2)
			//{
				if (current_OVP_state == GPIO_PIN_SET) sys_data.s.ovp_state = 0;
				else sys_data.s.ovp_state = 1;
			//}
		}
		else
		{
			//if (sys_data.s.lvp_state != 2)
			//{
				if (current_LVP_state == GPIO_PIN_RESET) sys_data.s.lvp_state = 0;
				else sys_data.s.lvp_state = 1;
			//}
			//if (sys_data.s.ovp_state != 2)
			//{
				if (current_OVP_state == GPIO_PIN_RESET) sys_data.s.ovp_state = 0;
				else sys_data.s.ovp_state = 1;
			//}
		}
		//HAL_GPIO_TogglePin(OUT_CTRL_GPIO_Port, OUT_CTRL_Pin);
		// If voltage on contact A is greater than voltage on contact B, then we assume a charger presence
		// We allow a pre-heater to be turned on
		// when OVP sygnal is active - when battery is cold
		static int32_t heater_cnt = 0;
		if ((sys_data.s.relay_status == RELAY_IS_CLOSED) || (sys_data.s.relay_status == ONLY_BA_OPENED) || (sys_data.s.relay_status == ONLY_AB_OPENED))
		{
			if ((sys_data.s.ursense_voltage > BAT_CHARGE_SIGN_V))
			{
				HAL_GPIO_WritePin(OUT_CTRL_GPIO_Port, OUT_CTRL_Pin, OUT_CTRL_ACTIVATE);
				heater_cnt = 500;
				//if (sys_data.s.ovp_state == 1) HAL_GPIO_WritePin(OUT_CTRL_GPIO_Port, OUT_CTRL_Pin, GPIO_PIN_SET);
				//else HAL_GPIO_WritePin(OUT_CTRL_GPIO_Port, OUT_CTRL_Pin, GPIO_PIN_RESET);
			}
			else if (sys_data.s.ursense_voltage <= 0)
			{
				if (heater_cnt > 0) heater_cnt--;
				if (heater_cnt == 0) HAL_GPIO_WritePin(OUT_CTRL_GPIO_Port, OUT_CTRL_Pin, OUT_CTRL_DEACTIVATE);
			}
		}
		else HAL_GPIO_WritePin(OUT_CTRL_GPIO_Port, OUT_CTRL_Pin, OUT_CTRL_DEACTIVATE);

		// Updating Inrush-Current value (A)
		static uint16_t last_inrush_max_current_in_mV = 0;
		if ((last_inrush_max_current_in_mV != sys_data.s.inrush_max_current_in_mV) && (sys_data.s.inrush_max_current_in_mV > sys_data.s.copper_v_drop))
		{
			if (sys_data.s.inrush_max_current_in_mV >= ADC_VREF)  sys_data.s.inrush_max_current_in_mV = ADC_VREF;

			last_inrush_max_current_in_mV = sys_data.s.inrush_max_current_in_mV;
			sys_data.s.inrush_max_current_in_adc = (sys_data.s.inrush_max_current_in_mV * ADC_MAX_VALUE) / ADC_VREF;
			maxIntegral = sys_data.s.inrush_max_current_in_adc * sys_data.s.inrush_curr_integral_steps;
		}

		// Updating Inrush-Current period value (µs)
		static uint16_t last_inrush_curr_period = 0;
		if (last_inrush_curr_period != sys_data.s.inrush_curr_period)
		{
			if (sys_data.s.inrush_curr_period < 31) sys_data.s.inrush_curr_period = 31;

			last_inrush_curr_period = sys_data.s.inrush_curr_period;
			sys_data.s.inrush_curr_integral_steps = (CURRENT_INTEGRAL_FREQ * sys_data.s.inrush_curr_period) / (1000*1000);
			maxIntegral = sys_data.s.inrush_max_current_in_adc * sys_data.s.inrush_curr_integral_steps;
		}

		// Testing if there is a current on battery heater
		if (HAL_GPIO_ReadPin(OUT_CS_GPIO_Port, OUT_CS_Pin) == GPIO_PIN_SET)	sys_data.s.heater_status = 1;
		else sys_data.s.heater_status = 0;

		SettingNewValuesForShortcutDetection(1);
	}
}

//------------------------------------------------------------------------------

void DEBUG_print(uint32_t current_time)
{
	static uint32_t debug_print_old_time = 0;

	if (current_time - debug_print_old_time > 77) // 77
	{
		//SEGGER_RTT_printf(0, "%4d\n", rawVoltageDrop);
		debug_print_old_time = current_time;
		const char* separator_color = RTT_CTRL_TEXT_BLACK;
		const char* values_color = RTT_CTRL_TEXT_BRIGHT_GREEN;

		SEGGER_RTT_printf(0, "%s%s", values_color, TAG);
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		SEGGER_RTT_printf(0, "Vab: %4d mV", sys_data.s.ursense_voltage);
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		SEGGER_RTT_printf(0, "Vbb: %5d mV", sys_data.s.ubbsense_voltage);
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		SEGGER_RTT_printf(0, "I: %5d A", sys_data.s.current);
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		SEGGER_RTT_printf(0, "Va: %6d mV", sys_data.s.ubsensea_voltage);
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		SEGGER_RTT_printf(0, "Vb: %6d mV", sys_data.s.ubsenseb_voltage);
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		SEGGER_RTT_printf(0, "OVP: %1s", (sys_data.s.ovp_state == 0)? "N": RTT_CTRL_TEXT_RED"Y"RTT_CTRL_TEXT_BRIGHT_GREEN);
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		SEGGER_RTT_printf(0, "LVP: %1s", (sys_data.s.lvp_state == 0)? "N": RTT_CTRL_TEXT_RED"Y"RTT_CTRL_TEXT_BRIGHT_GREEN);
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		SEGGER_RTT_printf(0, "DAC_A: %4d", DAC_HANDLE.Instance->DAC_CH_A);
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		SEGGER_RTT_printf(0, "DAC_B: %4d", DAC_HANDLE.Instance->DAC_CH_B);
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		SEGGER_RTT_printf(0, "R: %s", (sys_data.s.relay_status == 0)? "OP": (sys_data.s.relay_status == 1)? "CL": (sys_data.s.relay_status == 2)? "BA": "AB");
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		SEGGER_RTT_printf(0, "T: %2d.%d \260C", sys_data.s.temperature/10, abs(sys_data.s.temperature - (sys_data.s.temperature/10)*10) );
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);
		if (sys_data.s.device_status & (1 << OVERTEMP_ERROR))
		{
			SEGGER_RTT_printf(0, "%s", RTT_CTRL_TEXT_BRIGHT_RED);
			SEGGER_RTT_printf(0, "%s", "OT");
		}
		else SEGGER_RTT_printf(0, "%s", "OT");
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);

		if (sys_data.s.device_status & (1 << OVERCURRENT_ERROR))
		{
			SEGGER_RTT_printf(0, "%s", RTT_CTRL_TEXT_BRIGHT_RED);
			SEGGER_RTT_printf(0, "%s", "OC");
		}
		else SEGGER_RTT_printf(0, "%s", "OC");
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);

		if (sys_data.s.device_status & (1 << OVERLOAD_ERROR))
		{
			SEGGER_RTT_printf(0, "%s", RTT_CTRL_TEXT_BRIGHT_RED);
			SEGGER_RTT_printf(0, "%s", "OL");
		}
		else SEGGER_RTT_printf(0, "%s", "OL");
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);

		if (sys_data.s.device_status & (1 << LOWBAT_ERROR))
		{
			SEGGER_RTT_printf(0, "%s", RTT_CTRL_TEXT_BRIGHT_RED);
			SEGGER_RTT_printf(0, "%s", "LB");
		}
		else SEGGER_RTT_printf(0, "%s", "LB");
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);

		if (sys_data.s.device_status & (1 << ABBA_VOLTAGE_ERROR))
		{
			SEGGER_RTT_printf(0, "%s", RTT_CTRL_TEXT_BRIGHT_RED);
			SEGGER_RTT_printf(0, "%s", "AB");
		}
		else SEGGER_RTT_printf(0, "%s", "AB");
		SEGGER_RTT_printf(0, "%s | %s", separator_color, values_color);

		if (HAL_GPIO_ReadPin(OUT_CTRL_GPIO_Port, OUT_CTRL_Pin) == OUT_CTRL_ACTIVATE)
		{
			SEGGER_RTT_printf(0, "%s", RTT_CTRL_TEXT_BRIGHT_CYAN);
			SEGGER_RTT_printf(0, "%s", "CHG - 1");
		}
		else
		{
			SEGGER_RTT_printf(0, "%s", "CHG - 0");
		}

		SEGGER_RTT_printf(0, "\n");
	}
}

//------------------------------------------------------------------------------

inline __attribute__((always_inline)) void MODBUS_Management(void)
{
	// Modbus Kommunikation
	if (mbGetFrameComplete(&modbusData))
	{
		if (mbSlaveCheckModbusRtuQuery(&modbusData) == RESPOND_TO_QUERY)
		{
			if (RS485ActiveMode)
			{
				mbSlaveProcessRtuQuery(&modbusData);
			}
		}
		else huart1.RxState = HAL_UART_STATE_BUSY_RX;
	}

	// This code prevents unauthorized write-access to registers
	// Prüfe KEY
	if (sys_data.s.lockKey == savedLockKey)
	{
	  sys_data.s.keyAccepted = 1;
	  keyAccepted = 1;
	}
	else
	{
	  sys_data.s.keyAccepted = 0;
	  keyAccepted = 0;
	}

	// Checking whether command parser is active or not
	if (command_parser_is_enabled)
	{
		// Checking command which came via Modbus
		if ((sys_data.s.command != 0) && (modbusData.current_query == MB_QUERY_NOTHING))
		{
			switch (sys_data.s.command)
			{
				case COMMAND_SAVE_CONFIG:
					// Saving new settings without SN in FLASH
					if (FEEPROM_storeConfig(&sys_data, 0)) LOG_E(TAG, "Cannot save data in FLASH memory!");
					// Fetching configuration from FLASH
					if (FEEPROM_readConfig(&sys_data)) LOG_E(TAG, "Cannot read configuration from FLASH memory!");
					//if (FEEPROM_ReadLogData(&sys_data)) LOG_E(TAG, "Cannot read statistcal data from FLASH memory!");
					break;

				case COMMAND_SAVE_LOCK_KEY:
					LOG_I(TAG, "SAVE LOCK-KEY COMMAND.");
					mb_save_lock_key();
					break;

				case COMMAND_RESTART_MODBUS:
					// Modbus Re-initialization
					if (sys_data.s.parity_mode == 'e') mbInit(&modbusData, sys_data.s.baudrate, MODBUS_UART_PARITY_EVEN, &huart1, accessModeTable, &keyAccepted);
					else if (sys_data.s.parity_mode == 'o') mbInit(&modbusData, sys_data.s.baudrate, MODBUS_UART_PARITY_ODD, &huart1, accessModeTable, &keyAccepted);
					else mbInit(&modbusData, sys_data.s.baudrate, MODBUS_UART_PARITY_NONE, &huart1, accessModeTable, &keyAccepted);
					break;

				case COMMAND_RESTORE_DEFAULTS:
					if (FEEPROM_fullRestore(/*&sys_data*/)) LOG_E(TAG, "Cannot restore default settings from FLASH memory!");
					FEEPROM_ResetLogData();
					// Fetching configuration from FLASH
					if (FEEPROM_readConfig(&sys_data)) LOG_E(TAG, "Cannot read configuration from FLASH memory!");
					if (FEEPROM_ReadLogData(&sys_data)) LOG_E(TAG, "Cannot read statistcal data from FLASH memory!");
					break;

				case COMMAND_ON_SWITCH: StartOnMode(); break;

				case COMMAND_OFF_SWITCH: StartOffMode(0); break;

				case COMMAND_AUTO_SWITCH: StartAutoMode(); break;

				case COMMAND_SAVE_CONFIG_WITH_SN:
					// Saving new settings with new SN in FLASH
					if (FEEPROM_storeConfig(&sys_data, 1)) LOG_E(TAG, "Cannot save new SN in FLASH memory!");
					// Fetching configuration from FLASH
					if (FEEPROM_readConfig(&sys_data)) LOG_E(TAG, "Cannot read configuration from FLASH memory!");
					//if (FEEPROM_ReadLogData(&sys_data)) LOG_E(TAG, "Cannot read statistcal data from FLASH memory!");
					break;

				case COMMAND_RESTART:
					OpenBothMOSFETSVeryFast();
					NVIC_SystemReset();
					break;

				case COMMAND_OFFSET_CALIBRATION:
					if (Callibration == DoNothing) Callibration = &CallibrateVoltageDropABMiddlePointOffset;
					break;

				case COMMAND_CURRENT_CALIBRATION:
					if (Callibration == DoNothing) Callibration = &CallibrateControlCurrentVoltageDropOnContactBB;
					break;

				case COMMAND_CURRENT_OFFSET_CALIBRATION:
					if (Callibration == DoNothing) Callibration = &CallibrateCurrentSensorZeroOffsetOnContactBB;
					break;

				case COMMAND_TURN_OVERLOAD_DETECTION_OFF:
					InrushCurrentManagement = &DoNothing;
					break;

				case COMMAND_TURN_OVERLOAD_DETECTION_ON:
					InrushCurrentManagement = &InrushCurrentDetected;
					break;

				default:
					// When we recieve some unknown command we freeze our command parser for 10 sec (to prevent bruteforce).
					__HAL_TIM_CLEAR_FLAG(&htim17, TIM_FLAG_UPDATE);
					__HAL_TIM_SET_COUNTER(&htim17, 0);
					if (HAL_TIM_Base_Start_IT(&htim17) != HAL_OK) LOG_E(TAG, "Cannot start TIM17 in ISR mode!");
					command_parser_is_enabled = 0;
					LOG_W(TAG, "Unknown command!");
			}
			sys_data.s.command = 0;
		}
	}
	else sys_data.s.command = 0;
}

//------------------------------------------------------------------------------

void OVP_not_present__LVP_ignored(void)
{
	HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
#ifdef INVERTER_CAP_PRECHARGE
	SetReturnFunction(&ADC_OVP_not_present__LVP_ignored);
	MOSFETS_Management = &PreChargeStage;
#else
	MOSFETS_Management = &ADC_OVP_not_present__LVP_ignored;
#endif
	sys_data.s.relay_status = RELAY_IS_CLOSED;
	HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);

	ExternalGreenLED_Management = &ExternalGreenLEDShortBlinking;
}

//------------------------------------------------------------------------------

void OVP_present__LVP_ignored(void)
{
#ifdef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF
	DisableShortCutDetection();
#endif
	HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
	MOSFETS_Management = &ADC_OVP_present__LVP_ignored;
	sys_data.s.relay_status = RELAY_IS_OPENED;
	HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);

	ExternalGreenLED_Management = &ExternalGreenLEDShortBlinking;
	sys_data.s.ovp_cnt++;
	statDataChanged = 1;
}

//------------------------------------------------------------------------------

void OVP_present__LVP_ignored_NoAutoreconnect(void)
{
#ifdef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF
	DisableShortCutDetection();
#endif
	HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
	MOSFETS_Management = &ADC_Open_Both_MOSFETs;
	sys_data.s.relay_status = RELAY_IS_OPENED;
	HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);

	sys_data.s.user_button_mode = SWITCH_OFF;

	ExternalGreenLED_Management = &TurnExternalGreenLEDOff;

	// After 100ms we turn off VBOOST voltage for power saving
	__HAL_TIM_CLEAR_FLAG(&VBOOST_OFF_TIMER, TIM_FLAG_UPDATE);
	__HAL_TIM_SET_COUNTER(&VBOOST_OFF_TIMER, 0);
	HAL_TIM_Base_Start_IT(&VBOOST_OFF_TIMER);

	sys_data.s.ovp_cnt++;
	statDataChanged = 1;
}

//------------------------------------------------------------------------------

void OVP_ignored__LVP_not_present(void)
{
	HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
#ifdef INVERTER_CAP_PRECHARGE
	SetReturnFunction(&ADC_OVP_ignored__LVP_not_present);
	MOSFETS_Management = &PreChargeStage;
#else
	MOSFETS_Management = &ADC_OVP_ignored__LVP_not_present;
#endif
	sys_data.s.relay_status = RELAY_IS_CLOSED;
	HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);

	ExternalGreenLED_Management = &ExternalGreenLEDShortBlinking;
	//ExternalRedLED_Management = &TurnExternalRedLEDOff;
}

//------------------------------------------------------------------------------

void OVP_ignored__LVP_present(void)
{
#ifdef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF
	DisableShortCutDetection();
#endif
	HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
	MOSFETS_Management = &ADC_OVP_ignored__LVP_present;
	sys_data.s.relay_status = RELAY_IS_OPENED;
	HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);

	ExternalGreenLED_Management = &ExternalGreenLEDShortBlinking;
	//ExternalRedLED_Management = &ExternalRedLEDShortBlinking;

	sys_data.s.lvp_cnt++;
	statDataChanged = 1;
}

//------------------------------------------------------------------------------

void OVP_ignored__LVP_present_NoAutoreconnect(void)
{
#ifdef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF
	DisableShortCutDetection();
#endif
	HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
	MOSFETS_Management = &ADC_Open_Both_MOSFETs;
	sys_data.s.relay_status = RELAY_IS_OPENED;
	HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);

	sys_data.s.user_button_mode = SWITCH_OFF;

	ExternalGreenLED_Management = &TurnExternalGreenLEDOff;

	// After 100ms we turn off VBOOST voltage for power saving
	__HAL_TIM_CLEAR_FLAG(&VBOOST_OFF_TIMER, TIM_FLAG_UPDATE);
	__HAL_TIM_SET_COUNTER(&VBOOST_OFF_TIMER, 0);
	HAL_TIM_Base_Start_IT(&VBOOST_OFF_TIMER);

	sys_data.s.lvp_cnt++;
	statDataChanged = 1;
}

//------------------------------------------------------------------------------

void OVP_not_present__LVP_not_present(void)
{
	HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
#ifdef INVERTER_CAP_PRECHARGE
	SetReturnFunction(&ADC_OVP_not_present__LVP_not_present);
	MOSFETS_Management = &PreChargeStage;
#else
	MOSFETS_Management = &ADC_OVP_not_present__LVP_not_present;
#endif
	HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);
	sys_data.s.relay_status = RELAY_IS_CLOSED;

	ExternalGreenLED_Management = &ExternalGreenLEDShortBlinking;
	TurnExternalRedLEDOff();
}

//------------------------------------------------------------------------------

void OVP_not_present__LVP_present(void)
{
#ifdef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF
	DisableShortCutDetection();
#endif
	HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
	MOSFETS_Management = &ADC_OVP_not_present__LVP_present;
	sys_data.s.relay_status = ONLY_AB_OPENED;
	HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);

	ExternalGreenLED_Management = &ExternalGreenLEDShortBlinking;
	TurnExternalRedLEDOff();

	sys_data.s.lvp_cnt++;
	statDataChanged = 1;
}

//------------------------------------------------------------------------------

void OVP_present__LVP_not_present(void)
{
	HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
	MOSFETS_Management = &ADC_OVP_present__LVP_not_present;
	sys_data.s.relay_status = ONLY_BA_OPENED;
	HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);

	ExternalGreenLED_Management = &ExternalGreenLEDShortBlinking;
	TurnExternalRedLEDOff();
	//GreenLED_Management =

	sys_data.s.ovp_cnt++;
	statDataChanged = 1;
}

//------------------------------------------------------------------------------

void OVP_present__LVP_present(void)
{
#ifdef DISABLE_SHORTCUT_DETECTION_DURING_SWITCH_OFF
	DisableShortCutDetection();
#endif
	HAL_NVIC_DisableIRQ(ADC_DMA_IRQ);
	MOSFETS_Management = &ADC_OVP_present__LVP_present;
	sys_data.s.relay_status = RELAY_IS_OPENED;
	HAL_NVIC_EnableIRQ(ADC_DMA_IRQ);

	ExternalGreenLED_Management = &TurnExternalGreenLEDOff;
	ExternalRedLED_Management = &ExternalRedLED2ShortOnThen2LongOnThenLongPauseBlinking;

	sys_data.s.lvp_cnt++;
	sys_data.s.ovp_cnt++;
	statDataChanged = 1;
}

//------------------------------------------------------------------------------

#ifdef USE_RAM_FUNC
__RAM_FUNC void ADC_OVP_present__LVP_ignored(void)
#else
void ADC_OVP_present__LVP_ignored(void)
#endif
{
	OpenBothMOSFETS();
}

//------------------------------------------------------------------------------
#ifdef USE_RAM_FUNC
__RAM_FUNC void ADC_OVP_ignored__LVP_present(void)
#else
void ADC_OVP_ignored__LVP_present(void)
#endif
{
	OpenBothMOSFETS();
}

//------------------------------------------------------------------------------

#ifdef USE_RAM_FUNC
__RAM_FUNC void ADC_Open_Both_MOSFETs(void)
#else
void ADC_Open_Both_MOSFETs(void)
#endif
{
	OpenBothMOSFETS();
}

//------------------------------------------------------------------------------

#ifdef USE_RAM_FUNC
__RAM_FUNC void ADC_OVP_present__LVP_present(void)
#else
void ADC_OVP_present__LVP_present(void)
#endif
{
	OpenBothMOSFETS();
}

//------------------------------------------------------------------------------

#ifdef USE_RAM_FUNC
__RAM_FUNC void ADC_OVP_not_present__LVP_present(void)
#else
void ADC_OVP_not_present__LVP_present(void)
#endif
{
    // Reading current DAC value for MOSFET B
    uint32_t dacA_value = DAC_HANDLE.Instance->DAC_CH_A;
	// Checking voltage drop on MOSFETs
    if ((rawMOSFETsVoltageDrop + sys_data.s.ab_middle_point_offset) < POS_ALLOWED_MOSFETS_V_DROP_ADC)
    {
		if (dacA_value >= (DAC_0V + DAC_STEP)) dacA_value -= DAC_STEP;
    }
    else if ((rawMOSFETsVoltageDrop + sys_data.s.ab_middle_point_offset) > POS_ALLOWED_MOSFETS_V_DROP_ADC)
    {
		if (dacA_value <= (DAC_3V - DAC_STEP)) dacA_value += DAC_STEP;
    }
    // Writing DAC value for MOSFET B
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_A_ALIGNMENT(DAC_ALIGN_12B_R)) = dacA_value;

	// Reading DAC value for MOSFET channel A
	uint32_t dacB_value = DAC_HANDLE.Instance->DAC_CH_B;
	// We do not allow DAC value to be greater than max value
	if (dacB_value <= (DAC_3V - DAC_STEP)) dacB_value += DAC_STEP;
    // Sending new DAC value to DAC
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_B_ALIGNMENT(DAC_ALIGN_12B_R)) = dacB_value;
}

//------------------------------------------------------------------------------

#ifdef USE_RAM_FUNC
__RAM_FUNC void ADC_OVP_present__LVP_not_present(void)
#else
void ADC_OVP_present__LVP_not_present(void)
#endif
{
    // Reading current DAC value for MOSFET B channel
    uint32_t dacB_value = DAC_HANDLE.Instance->DAC_CH_B;
	// Making sure that BA voltage drop during discharging is within the limit
    if ((rawMOSFETsVoltageDrop + sys_data.s.ab_middle_point_offset) < NEG_ALLOWED_MOSFETS_V_DROP_ADC)
    {
		if (dacB_value <= (DAC_3V - DAC_STEP)) dacB_value += DAC_STEP;
    }
    else if ((rawMOSFETsVoltageDrop + sys_data.s.ab_middle_point_offset) > NEG_ALLOWED_MOSFETS_V_DROP_ADC)
    {
		if (dacB_value >= (DAC_0V + DAC_STEP)) dacB_value -= DAC_STEP;
    }
    // Writing DAC value for MOSFET A channel
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_B_ALIGNMENT(DAC_ALIGN_12B_R)) = dacB_value;

    // Reading DAC value for MOSFET channel A
	uint32_t dacA_value = DAC_HANDLE.Instance->DAC_CH_A;
	// Channel A must be fully closed, so we increase smoothly DAC value
	// We do not allow DAC value to be greater than max value
	if (dacA_value <= (DAC_3V - DAC_STEP)) dacA_value += DAC_STEP;
    // Sending new DAC value to DAC
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_A_ALIGNMENT(DAC_ALIGN_12B_R)) = dacA_value;
}

//------------------------------------------------------------------------------

#ifdef USE_RAM_FUNC
__RAM_FUNC void ADC_OVP_not_present__LVP_ignored(void)
#else
void ADC_OVP_not_present__LVP_ignored(void)
#endif
{
	CloseBothMOSFETS();
}

//------------------------------------------------------------------------------

#ifdef USE_RAM_FUNC
__RAM_FUNC void ADC_OVP_ignored__LVP_not_present(void)
#else
void ADC_OVP_ignored__LVP_not_present(void)
#endif
{
	CloseBothMOSFETS();
}

//------------------------------------------------------------------------------

#ifdef USE_RAM_FUNC
__RAM_FUNC void ADC_Close_Both_MOSFETs(void)
#else
void ADC_Close_Both_MOSFETs(void)
#endif
{
	CloseBothMOSFETS();
}

//------------------------------------------------------------------------------

#ifdef USE_RAM_FUNC
__RAM_FUNC void ADC_OVP_not_present__LVP_not_present(void)
#else
void ADC_OVP_not_present__LVP_not_present(void)
#endif
{
	CloseBothMOSFETS();
}

//-----------------------------------------------------------------------------

void CallibrateCurrentSensorZeroOffsetOnContactBB(void)
{
	// Assuming that load is not connected

	uint32_t adc_value_accum_i_plus_channel = 0;
	uint32_t adc_value_accum_i_minus_channel = 0;
	const uint32_t SAMPLE_COUNT = 50000;

	// Sampling N values
	for (int i = 0; i < SAMPLE_COUNT; i++)
	{
		// Calculating offsets on both current sensors
		adc_value_accum_i_plus_channel += rawContactVoltageDropPlus;
		adc_value_accum_i_minus_channel += rawContactVoltageDropMinus;
		SEGGER_RTT_printf(0, "\t[%4d] Sampled values: I+ = %6u    I- = %6u\n", i, rawContactVoltageDropPlus, rawContactVoltageDropMinus);
		//SEGGER_RTT_printf(0, "%u,%u\n", ADC_values[I_PLUS_CHANNEL], ADC_values[I_MINUS_CHANNEL]);
	}

	sys_data.s.i_plus_offset = adc_value_accum_i_plus_channel / SAMPLE_COUNT;
	sys_data.s.i_minus_offset = adc_value_accum_i_minus_channel / SAMPLE_COUNT;
	SEGGER_RTT_printf(0, "\t\tOffset values: I+ = %u    I- = %u\n", sys_data.s.i_plus_offset, sys_data.s.i_minus_offset);

	Callibration = &DoNothing;
}

//------------------------------------------------------------------------------

int32_t Rescale(int32_t x, int32_t x1, int32_t x2, int32_t y1, int32_t y2)
{
	int32_t res = ((y2 - y1) * (x - x1)) / (x2 - x1) + y1;
	return res;
}

//------------------------------------------------------------------------------

int32_t TemperatureCompensation(int32_t rawADCValueUnderMaxCurrent, int32_t temp_dGrad)
{
	if      (temp_dGrad < 200)                      rawADCValueUnderMaxCurrent = (rawADCValueUnderMaxCurrent * (100 + 10)) / 100;									// +10%
	else if (temp_dGrad >= 200 && temp_dGrad < 600) rawADCValueUnderMaxCurrent = (rawADCValueUnderMaxCurrent * (100 + Rescale(temp_dGrad, 200, 600, 10, 0))) / 100;	// +[0%...10%]
	else if (temp_dGrad >= 600)                     rawADCValueUnderMaxCurrent =  rawADCValueUnderMaxCurrent;														// +0%

	return rawADCValueUnderMaxCurrent;
}

//-----------------------------------------------------------------------------

void CallibrateControlCurrentVoltageDropOnContactBB(void)
{
	// Assuming that 500A load is connected

	LOG_I(TAG, "Current callibration sequence started.");

	// Turning off inrush current protection
	//InrushCurrentManagement = &DoNothing;

	uint32_t start_time = HAL_GetTick();
	int32_t ubbsense_adc_accum = 0;
	int32_t ubbsense_adc = 0;
	int32_t ubbsense_voltage = 0;

	while (HAL_GetTick() - start_time < 60000)
	{
		ubbsense_adc_accum -= ubbsense_adc;
		ubbsense_adc_accum += rawContactVoltageDropMinus;
		ubbsense_adc = ubbsense_adc_accum / 16;

		SEGGER_RTT_printf(0, "\t\tVoltage-drop ADC value: %5d.\n", ubbsense_adc);
		HAL_Delay(1);
	}

	ubbsense_adc -= sys_data.s.i_minus_offset;
	ubbsense_adc = TemperatureCompensation(ubbsense_adc, sys_data.s.temperature);
	ubbsense_voltage = (ubbsense_adc * ADC_VREF) / ADC_MAX_VALUE;

	// Recording copper-plate voltage-drop under control current
	sys_data.s.copper_v_drop = ubbsense_voltage;
	// Recording ADC value drop under control current
	sys_data.s.copper_v_drop_adc = ubbsense_adc;
	sys_data.s.copper_v_drop_adc_limit = (sys_data.s.copper_v_drop_adc * 110) / 100;

	SEGGER_RTT_printf(0, "\t\t\tFinal voltage-drop ADC value: %4u. Final voltage-drop value: %3u mV\n", sys_data.s.copper_v_drop_adc, sys_data.s.copper_v_drop);

	// Updating inrush current settings
	//sys_data.s.inrush_max_current_in_adc = (sys_data.s.inrush_max_current * sys_data.s.copper_v_drop_adc) / CONTROL_CURRENT_A;
	//maxIntegral = sys_data.s.inrush_max_current_in_adc * sys_data.s.inrush_curr_integral_steps;

	Callibration = &DoNothing;
	//InrushCurrentManagement = &InrushCurrentDetected; // Test program disables this, so we must re-enable it after callibration
}

//-----------------------------------------------------------------------------

void CallibrateVoltageDropABMiddlePointOffset(void)
{
	// Assuming that load is not connected

	uint32_t adc_value_accum = 0;
	const uint32_t SAMPLE_COUNT = 50000;

	// Sampling N values
	for (int i = 0; i < SAMPLE_COUNT; i++)
	{
		// Calculating averaged value for real voltage drop between contacts B and A in ADC values
		adc_value_accum += rawMOSFETsVoltageDrop;
		SEGGER_RTT_printf(0, "\t[%4d] Sampled value: %4d\n", i, rawMOSFETsVoltageDrop);
	}

	sys_data.s.ab_middle_point_offset = (ADC_MAX_VALUE>>1) - (adc_value_accum / SAMPLE_COUNT);
	SEGGER_RTT_printf(0, "\t\tOffset value: %4d\n", sys_data.s.ab_middle_point_offset);

	Callibration = &DoNothing;
}

//------------------------------------------------------------------------------

void InrushCurrentDetected(void)
{
	OpenBothMOSFETSVeryFast();
	MOSFETS_Management = &DoNothing;

	if (overcurrent_shutdown_is_active == 0 )
	{
		if (overload_shutdown_is_active == 0)
		{
			sys_data.s.overload_error_cnt++;
			statDataChanged = 1;
		}

		overload_shutdown_is_active = 1;
		overload_shutdown_time = HAL_GetTick();
	}
}

//------------------------------------------------------------------------------

inline __attribute__((always_inline)) void OpenBothMOSFETS(void)
{	// Reading DAC values for MOSFET channels A and B
    uint32_t dacA_value = DAC_HANDLE.Instance->DAC_CH_A;
    uint32_t dacB_value = DAC_HANDLE.Instance->DAC_CH_B;

	// Opening MOSFETS
	if (dacA_value >= (DAC_0V + DAC_STEP)) dacA_value -= DAC_STEP;
	if (dacB_value >= (DAC_0V + DAC_STEP)) dacB_value -= DAC_STEP;

	// Setting new values
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_A_ALIGNMENT(DAC_ALIGN_12B_R)) = dacA_value;
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_B_ALIGNMENT(DAC_ALIGN_12B_R)) = dacB_value;
}

//------------------------------------------------------------------------------

inline __attribute__((always_inline)) void CloseBothMOSFETS(void)
{
    // Reading DAC values for MOSFET channels A and B
    uint32_t dacA_value = DAC_HANDLE.Instance->DAC_CH_A;
    uint32_t dacB_value = DAC_HANDLE.Instance->DAC_CH_B;

	// Closing MOSFETS
	if (dacA_value <= (DAC_3V - DAC_STEP)) dacA_value += DAC_STEP;
	if (dacB_value <= (DAC_3V - DAC_STEP)) dacB_value += DAC_STEP;

	// Setting new values
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_A_ALIGNMENT(DAC_ALIGN_12B_R)) = dacA_value;
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_B_ALIGNMENT(DAC_ALIGN_12B_R)) = dacB_value;
}

//------------------------------------------------------------------------------

void CloseBothMOSFETSVeryFast(void)
{
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_A_ALIGNMENT(DAC_ALIGN_12B_R)) = DAC_3V;  // Expression from HAL_DAC_SetValue function
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_B_ALIGNMENT(DAC_ALIGN_12B_R)) = DAC_3V;  // Expression from HAL_DAC_SetValue function
	sys_data.s.relay_status = RELAY_IS_CLOSED;
}

//------------------------------------------------------------------------------

inline __attribute__((always_inline)) void OpenBothMOSFETSVeryFast(void)
{
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_A_ALIGNMENT(DAC_ALIGN_12B_R)) = DAC_0V;  // Expression from HAL_DAC_SetValue function
	*(__IO uint32_t *)((uint32_t)DAC_HANDLE.Instance + DAC_CH_B_ALIGNMENT(DAC_ALIGN_12B_R)) = DAC_0V;  // Expression from HAL_DAC_SetValue function
	sys_data.s.relay_status = RELAY_IS_OPENED;
}

//------------------------------------------------------------------------------

void ShowSlaveAddressOnLED(uint16_t address, GPIO_TypeDef *port, uint16_t pin)
{
	for (int i = 0; i < address; i++)
	{
		HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET);
		HAL_Delay(333);
		HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET);
		HAL_Delay(333);
	}
}

//------------------------------------------------------------------------------

void StartUpSequence(void)
{
    HAL_GPIO_WritePin(LED_FUNCTION_GPIO_Port, LED_FUNCTION_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(LED_ERROR_GPIO_Port, LED_ERROR_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(LED_STATE_GPIO_Port, LED_STATE_Pin, GPIO_PIN_SET);

    HAL_Delay(1000);

    HAL_GPIO_WritePin(LED_FUNCTION_GPIO_Port, LED_FUNCTION_Pin, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(LED_ERROR_GPIO_Port, LED_ERROR_Pin, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(LED_STATE_GPIO_Port, LED_STATE_Pin, GPIO_PIN_RESET);

    HAL_Delay(500);
}

//-----------------------------------------------------------------------------
#ifdef USE_RAM_FUNC
__RAM_FUNC void DoNothing(void)
#else
void DoNothing(void)
#endif
{

}

//-----------------------------------------------------------------------------

/* 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 */
  LOG_E(TAG, "HAL error!");
  /* 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,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
     SEGGER_RTT_printf(0, "Wrong parameters value: file %s on line %d\n", file, line);
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
