/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 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"

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

/* USER CODE END Includes */

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

/* USER CODE END PTD */

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

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

/* USER CODE END PM */

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

/* USER CODE BEGIN PV */
/*
 * BASE CLOCK 12MHz
 * Desired interrupt period 60s
 */
const uint16_t tim21_prescaler = 60000-1; // 100Hz
//const uint16_t tim21_period = 12000-1; // 60s
const uint16_t tim21_period = 1200-1; // 6s
//const uint16_t tim21_period = 200-1; // 1s

/* Input register memory map
 * (All registers are 16-bit)
 * -------------------------
 *
 * 30010 : CO2  [ppm]        Unsigned value in range [0,40000]
 * 30011 : T    [deg_C * 10] From SHT4x; unsigned value in range [0; 1250]; e.g. 21.5 C => 215
 * 30012 : RH   [%]          From SHT4x; unsigned value in range [0; 100]
 *
 * Backup T and RH sensor:
 * 30013 : T    [deg_C * 10] From SCD4x; unsigned value in range [0; 600]; e.g. 21.5 C => 215
 * 30014 : RH   [%]          From SCD4x; unsigned value in range [0; 100]
 *
 * Extended temperature range (signed values):
 * 30015 : T    [deg_C * 10] From SHT4x; signed value (two's complement) in range [-400;1250]
 * 30016 : T    [deg_C * 10] From SCD4x; signed value (two's complement) in range [-100;600]; e.g. -12.3 C => -123
 *
 */
/* Input registers memory map implementation */
enum
{
	REGISTER_NUM_CO2 = 30010,
	REGISTER_NUM_T_SHT4x = 30011,
	REGISTER_NUM_RH_SHT4x = 30012,
	REGISTER_NUM_T_SCD4x = 30013,
	REGISTER_NUM_RH_SCD4x = 30014,
	REGISTER_NUM_T_SHT4x_SIGNED = 30015,
	REGISTER_NUM_T_SCD4x_SIGNED = 30016
} data_registers_numbers;

enum
{
	REGISTER_NUM_LED_ON = 40001,
	REGISTER_NUM_LED_BRIGHTNESS = 40002,
	REGISTER_NUM_LED_SMOOTH = 40003,
	REGISTER_NUM_CO2_ALERT_LIMIT1 = 40004,
	REGISTER_NUM_CO2_ALERT_LIMIT2 = 40005,
	REGISTER_NUM_SCD4x_T_OFFSET = 40006,
	REGISTER_NUM_MODBUS_ADDR = 40007,
	REGISTER_NUM_BAUDRATE = 40008
} config_registers_numbers;

enum
{
	REGISTER_NUM_VENDOR_NAME = 30010,
	REGISTER_NUM_PRODUCT_CODE = 30011,
	REGISTER_NUM_REVISION = 30012,
	REGISTER_NUM_PRODUCT_NAME = 30013,
	REGISTER_NUM_SERIAL_NUMBER = 30014
} identification_registers_numbers;

/* Variables to store the measured data */
int CO2, T_SCD4x, RH_SCD4x;
int T_SHT4x, RH_SHT4x;
uint16_t sps30_measured_data[10];

/* Struct to store the sensor config */
config_t sensor_config;
uint8_t sensor_config_pending_write = 0;
uint8_t baudrate_changed = 0;
uint8_t modbus_address_changed = 0;
uint8_t co2_valid = 0;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
static void MX_LPUART1_UART_Init(void);
static void MX_TIM21_Init(void);
/* USER CODE BEGIN PFP */
void LPUART1_TX_Buffer(uint8_t* buffer_tx, uint16_t buffer_tx_len);
/* 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 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);

  /* System interrupt init*/

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */
  /* Read config from EEPROM - if unsuccessful, set the default values*/
  int8_t config_read_status = config_read(&sensor_config);
  if (config_read_status != CONFIG_OK)
  {
	  sensor_config.modbus_addr = MODBUS_DEFAULT_SLAVE_ADDRESS;
	  sensor_config.led_co2_alert_limit1 = CONFIG_DEFAULT_LED_ALERT1_LIMIT;
	  sensor_config.led_co2_alert_limit2 = CONFIG_DEFAULT_LED_ALERT2_LIMIT;
	  sensor_config.led_on = CONFIG_DEFAULT_LED_ON;
	  sensor_config.led_brightness = CONFIG_DEFAULT_LED_BRIGHTNESS;
	  sensor_config.led_smooth = CONFIG_DEFAULT_LED_SMOOTH;
	  sensor_config.scd4x_t_offset = CONFIG_DEFAULT_SCD4x_T_OFFSET;
	  sensor_config.baudrate_index = CONFIG_DEFAULT_BAUDRATE_INDEX;
  }
  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();
  MX_LPUART1_UART_Init();
  MX_TIM21_Init();
  /* USER CODE BEGIN 2 */

  /* Turn on MAGENTA LED to signal startup state */
  LL_GPIO_ResetOutputPin(LED_R_GPIO_Port, LED_R_Pin);
  LL_GPIO_ResetOutputPin(LED_G_GPIO_Port, LED_G_Pin);
  LL_GPIO_ResetOutputPin(LED_B_GPIO_Port, LED_B_Pin);

  /* Enable I2C for sensors */
  LL_I2C_Enable(I2C1);


  /* Set the modbus address */
  modbus_slave_set_address(sensor_config.modbus_addr);

  /* Enable UART for RS485 */
  LL_LPUART_Enable(LPUART1);

  /* Start the timer for measurement triggering */
  LL_TIM_EnableCounter(TIM21);
  LL_TIM_EnableIT_UPDATE(TIM21);

  /* I2C context init (for SHT4x and SCD4x) */
  i2c_context_t i2c_context;
  i2c_context.i2c = I2C1;
  i2c_init(&i2c_context);

  scd4x_start_periodic_measurement();
  uint8_t scd4x_is_connected = 1;
  uint8_t sps30_is_connected = 0;
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  /*uint8_t scd4x_is_connected = 0;
  if (scd4x_start_periodic_measurement() == SCD4X_OK)
  {
	  scd4x_is_connected = 1;
  }*/

  /* Attempt to start SPS30 measurement and check if it's connected */
  /*sps30_reset();
  uint8_t sps30_is_connected = 0;
  if (sps30_start_measurement() == SPS30_OK)
  {
	  sps30_is_connected = 1;
  }*/

  /* Wait 1000ms for sensors initialization */
  /* SHT4x Init Time: max 1 	ms 	(datasheet pg. 8) */
  /* SCD4x Init Time: max 1000 	ms 	(datasheet pg. 6) */
  /* SPS30 Init Time: max 30000 ms 	(datasheet pg. 2) */
  LL_mDelay(1000);

  static uint32_t new_baud;

  /* Enter the main loop */
  while (1)
  {
	  if (lpuart1_rx_done == 1)
	  {
	  /* Process the message */
		  if (lpuart1_rx_message_too_long)
		  {
			  /* Do nothing, just delete the buffer and set the flag back to zero*/
			  lpuart1_rx_message_too_long = 0;
		  } else
		  {
			  /* Process the message:
			   * message is stored in modbus_buffer[], no copying necessary;
			   * but we need to make sure that modbus_buffer[] will not be used while
			   * processing the message: this can be done by disabling RX interrupt */
			  LL_LPUART_DisableIT_RXNE(LPUART1);
			  modbus_slave_process_msg(modbus_buffer, lpuart1_rx_message_len);
			  /* Reset the RX DONE flag */
			  lpuart1_rx_done = 0;
			  LL_LPUART_EnableIT_RXNE(LPUART1);
		  }
		  /* Reset the RX DONE flag */
		  lpuart1_rx_done = 0;
	  }
	  /* if config changed (MODBUS write), reflect changes to EEPROM */
	  if (sensor_config_pending_write) {
		  config_write(&sensor_config);
		  sensor_config_pending_write = 0;
	  }
	  if (modbus_address_changed)
	  {
		  modbus_slave_set_address(sensor_config.modbus_addr);
		  modbus_address_changed = 0;
	  }
	  if (baudrate_changed)
	  {
		  while (!LL_LPUART_IsActiveFlag_TXE(LPUART1));
		  uart_disable_interrupts();
//		  LL_LPUART_Disable(LPUART1);
//		  LL_LPUART_DisableIT_IDLE(LPUART1);
//		  LL_LPUART_EnableIT_RXNE(LPUART1);

		  LL_LPUART_SetBaudRate(LPUART1, SYSTICK_FREQ_HZ, config_baudrates[sensor_config.baudrate_index]);

//		  LL_LPUART_Enable(LPUART1);
//		  LL_LPUART_EnableIT_IDLE(LPUART1);
//		  LL_LPUART_EnableIT_RXNE(LPUART1);
		  uart_enable_interrupts();
		  LL_LPUART_EnableDirectionRx(LPUART1);
		  LL_LPUART_EnableDirectionTx(LPUART1);
		  baudrate_changed = 0;

		  new_baud = LL_LPUART_GetBaudRate(LPUART1, SYSTICK_FREQ_HZ);
	  }

	  /* It is time for measurement */
	  if (tim21_elapsed_period == 1)
	  {
		  /* TODO: Check the data */
		  /* Read SHT4x data (always connected) */
		  sht4x_measure(&T_SHT4x, &RH_SHT4x);

		  /* Read SCD4x data (if connected) */
		  if (scd4x_is_connected == 1)
		  {
			  scd4x_read_measurement(&CO2,
					  	  	  	  	 &T_SCD4x,
									 &RH_SCD4x);
			  if (CO2 > 0) {
				  co2_valid = 1;
			  } else {
				  co2_valid = 0;
			  }
		  }

		  /* Read SPS30 data (if connected) */
		  if (sps30_is_connected == 1)
		  {
			  sps30_read_measured_values(sps30_measured_data, 10);
		  }
		  /* TODO: Process data and light a desired color of LED */
		  /* TODO: Add hystheresis */

		  /* Reset the TIM21 Elapsed Period Flag */
		  tim21_elapsed_period = 0;
	  }
	  /* TEST END */
	  if (sensor_config.led_on) {
		  if (co2_valid == 1) {
			  if (CO2 <= sensor_config.led_co2_alert_limit1) {
				  /* CO2 is OK -> GREEN */
				  LL_GPIO_SetOutputPin(LED_R_GPIO_Port, LED_R_Pin);
				  LL_GPIO_ResetOutputPin(LED_G_GPIO_Port, LED_G_Pin);
				  LL_GPIO_SetOutputPin(LED_B_GPIO_Port, LED_B_Pin);

			  } else if ((sensor_config.led_co2_alert_limit1 < CO2) && (CO2 <= sensor_config.led_co2_alert_limit2)) {
				  /* CO2 is NOT OK -> YELLOW */
				  LL_GPIO_ResetOutputPin(LED_R_GPIO_Port, LED_R_Pin);
				  LL_GPIO_ResetOutputPin(LED_G_GPIO_Port, LED_G_Pin);
				  LL_GPIO_SetOutputPin(LED_B_GPIO_Port, LED_B_Pin);

			  } else if (sensor_config.led_co2_alert_limit2 < CO2) {
				  /* CO2 is CRITICAL -> RED */
				  LL_GPIO_ResetOutputPin(LED_R_GPIO_Port, LED_R_Pin);
				  LL_GPIO_SetOutputPin(LED_G_GPIO_Port, LED_G_Pin);
				  LL_GPIO_SetOutputPin(LED_B_GPIO_Port, LED_B_Pin);
			  }
		  } else {

			  LL_GPIO_ResetOutputPin(LED_R_GPIO_Port, LED_R_Pin);
			  LL_GPIO_SetOutputPin(LED_G_GPIO_Port, LED_G_Pin);
			  LL_GPIO_ResetOutputPin(LED_B_GPIO_Port, LED_B_Pin);
		  }

	  } else {
		  LL_GPIO_SetOutputPin(LED_R_GPIO_Port, LED_R_Pin);
		  LL_GPIO_SetOutputPin(LED_G_GPIO_Port, LED_G_Pin);
		  LL_GPIO_SetOutputPin(LED_B_GPIO_Port, LED_B_Pin);
	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  LL_FLASH_SetLatency(LL_FLASH_LATENCY_0);
  while(LL_FLASH_GetLatency()!= LL_FLASH_LATENCY_0)
  {
  }
  LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1);
  LL_RCC_HSI_Enable();

   /* Wait till HSI is ready */
  while(LL_RCC_HSI_IsReady() != 1)
  {

  }
  LL_RCC_HSI_SetCalibTrimming(16);
  LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_HSI, LL_RCC_PLL_MUL_3, LL_RCC_PLL_DIV_4);
  LL_RCC_PLL_Enable();

   /* Wait till PLL is ready */
  while(LL_RCC_PLL_IsReady() != 1)
  {

  }
  LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
  LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
  LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1);
  LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);

   /* Wait till System clock is ready */
  while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL)
  {

  }

  LL_Init1msTick(12000000);

  LL_SetSystemCoreClock(12000000);
  LL_RCC_SetLPUARTClockSource(LL_RCC_LPUART1_CLKSOURCE_PCLK1);
  LL_RCC_SetI2CClockSource(LL_RCC_I2C1_CLKSOURCE_PCLK1);
}

/**
  * @brief I2C1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C1_Init(void)
{

  /* USER CODE BEGIN I2C1_Init 0 */

  /* USER CODE END I2C1_Init 0 */

  LL_I2C_InitTypeDef I2C_InitStruct = {0};

  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
  /**I2C1 GPIO Configuration
  PA9   ------> I2C1_SCL
  PA10   ------> I2C1_SDA
  */
  GPIO_InitStruct.Pin = LL_GPIO_PIN_9;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_1;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = LL_GPIO_PIN_10;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_1;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* Peripheral clock enable */
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1);

  /* USER CODE BEGIN I2C1_Init 1 */

  /* USER CODE END I2C1_Init 1 */
  /** I2C Initialization
  */
  LL_I2C_EnableAutoEndMode(I2C1);
  LL_I2C_DisableOwnAddress2(I2C1);
  LL_I2C_DisableGeneralCall(I2C1);
  LL_I2C_EnableClockStretching(I2C1);
  I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C;
  I2C_InitStruct.Timing = 0x40000A0B;
  I2C_InitStruct.AnalogFilter = LL_I2C_ANALOGFILTER_ENABLE;
  I2C_InitStruct.DigitalFilter = 0;
  I2C_InitStruct.OwnAddress1 = 0;
  I2C_InitStruct.TypeAcknowledge = LL_I2C_ACK;
  I2C_InitStruct.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT;
  LL_I2C_Init(I2C1, &I2C_InitStruct);
  LL_I2C_SetOwnAddress2(I2C1, 0, LL_I2C_OWNADDRESS2_NOMASK);
  /* USER CODE BEGIN I2C1_Init 2 */
  /* USER CODE END I2C1_Init 2 */

}

/**
  * @brief LPUART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_LPUART1_UART_Init(void)
{

	 /* USER CODE BEGIN LPUART1_Init 0 */

	  /* USER CODE END LPUART1_Init 0 */

	  LL_LPUART_InitTypeDef LPUART_InitStruct = {0};

	  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

	  /* Peripheral clock enable */
	  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_LPUART1);

	  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
	  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB);
	  /**LPUART1 GPIO Configuration
	  PA0-CK_IN   ------> LPUART1_RX
	  PA1   ------> LPUART1_TX
	  PB1   ------> LPUART1_DE
	  */
	  GPIO_InitStruct.Pin = LL_GPIO_PIN_0;
	  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
	  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
	  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
	  GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
	  GPIO_InitStruct.Alternate = LL_GPIO_AF_6;
	  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	  GPIO_InitStruct.Pin = LL_GPIO_PIN_1;
	  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
	  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
	  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
	  GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
	  GPIO_InitStruct.Alternate = LL_GPIO_AF_6;
	  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	  GPIO_InitStruct.Pin = LL_GPIO_PIN_1;
	  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
	  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
	  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
	  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
	  GPIO_InitStruct.Alternate = LL_GPIO_AF_4;
	  LL_GPIO_Init(GPIOB, &GPIO_InitStruct);

	  /* LPUART1 interrupt Init */
	  NVIC_SetPriority(LPUART1_IRQn, 0);
	  NVIC_EnableIRQ(LPUART1_IRQn);

	  /* USER CODE BEGIN LPUART1_Init 1 */

	  /* USER CODE END LPUART1_Init 1 */
	  LPUART_InitStruct.BaudRate = config_baudrates[sensor_config.baudrate_index];
	  LPUART_InitStruct.DataWidth = LL_LPUART_DATAWIDTH_9B;
	  LPUART_InitStruct.StopBits = LL_LPUART_STOPBITS_1;
	  LPUART_InitStruct.Parity = LL_LPUART_PARITY_EVEN;
	  LPUART_InitStruct.TransferDirection = LL_LPUART_DIRECTION_TX_RX;
	  LPUART_InitStruct.HardwareFlowControl = LL_LPUART_HWCONTROL_NONE;
	  LL_LPUART_Init(LPUART1, &LPUART_InitStruct);
	  LL_LPUART_EnableDEMode(LPUART1);
	  LL_LPUART_SetDESignalPolarity(LPUART1, LL_LPUART_DE_POLARITY_HIGH);
	  LL_LPUART_SetDEAssertionTime(LPUART1, 0);
	  LL_LPUART_SetDEDeassertionTime(LPUART1, 0);
	  /* USER CODE BEGIN LPUART1_Init 2 */

	  /* Enable IDLE Interrupt */
	  LL_LPUART_EnableIT_IDLE(LPUART1);

	  /* Enable RX Not Empty Interrupt */
	  LL_LPUART_EnableIT_RXNE(LPUART1);

	  LL_LPUART_EnableDirectionRx(LPUART1);
	  LL_LPUART_EnableDirectionTx(LPUART1);
	  LL_LPUART_Enable(LPUART1);
	  /* USER CODE END LPUART1_Init 2 */

}

/**
  * @brief TIM21 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM21_Init(void)
{

  /* USER CODE BEGIN TIM21_Init 0 */

  /* USER CODE END TIM21_Init 0 */

  LL_TIM_InitTypeDef TIM_InitStruct = {0};

  /* Peripheral clock enable */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM21);

  /* TIM21 interrupt Init */
  NVIC_SetPriority(TIM21_IRQn, 0);
  NVIC_EnableIRQ(TIM21_IRQn);

  /* USER CODE BEGIN TIM21_Init 1 */

  /* USER CODE END TIM21_Init 1 */
  TIM_InitStruct.Prescaler = tim21_prescaler;
  TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
  TIM_InitStruct.Autoreload = tim21_period;
  TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
  LL_TIM_Init(TIM21, &TIM_InitStruct);
  LL_TIM_EnableARRPreload(TIM21);
  LL_TIM_SetClockSource(TIM21, LL_TIM_CLOCKSOURCE_INTERNAL);
  LL_TIM_SetTriggerOutput(TIM21, LL_TIM_TRGO_RESET);
  LL_TIM_DisableMasterSlaveMode(TIM21);
  /* USER CODE BEGIN TIM21_Init 2 */

  /* USER CODE END TIM21_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);

  /**/
  LL_GPIO_SetOutputPin(LED_B_GPIO_Port, LED_B_Pin);

  /**/
  LL_GPIO_SetOutputPin(LED_G_GPIO_Port, LED_G_Pin);

  /**/
  LL_GPIO_SetOutputPin(LED_R_GPIO_Port, LED_R_Pin);

  /**/
  GPIO_InitStruct.Pin = LED_B_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(LED_B_GPIO_Port, &GPIO_InitStruct);

  /**/
  GPIO_InitStruct.Pin = LED_G_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(LED_G_GPIO_Port, &GPIO_InitStruct);

  /**/
  GPIO_InitStruct.Pin = LED_R_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(LED_R_GPIO_Port, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */
void LPUART1_TX_Buffer(uint8_t* buffer_tx, uint16_t buffer_tx_len)
{
	__disable_irq();
	for (uint16_t i = 0; i < buffer_tx_len; i++)
	{
		  LL_LPUART_TransmitData9(LPUART1, buffer_tx[i]);
		  while (!LL_LPUART_IsActiveFlag_TXE(LPUART1));
	}
	__enable_irq();
}

int8_t uart_disable_interrupts(void)
{
	LL_LPUART_Disable(LPUART1);
	LL_LPUART_DisableIT_IDLE(LPUART1);
	LL_LPUART_EnableIT_RXNE(LPUART1);

	return 0;
}

int8_t uart_enable_interrupts(void)
{
	LL_LPUART_Enable(LPUART1);
	LL_LPUART_ClearFlag_IDLE(LPUART1);
	LL_LPUART_ClearFlag_NE(LPUART1);
	LL_LPUART_EnableIT_IDLE(LPUART1);
	LL_LPUART_EnableIT_RXNE(LPUART1);

	return 0;
}

int8_t modbus_slave_callback(modbus_transaction_t *transaction)
{
	uint16_t register_number = transaction->register_number;
	switch (transaction->function_code)
	{
		case MODBUS_READ_INPUT_REGISTERS:
			for (int i = 0; i < transaction->register_count; i++, register_number++)
			{
				switch (register_number)
				{
					case REGISTER_NUM_CO2:
						transaction->input_registers[i] = (uint16_t)CO2;
						break;
					case REGISTER_NUM_T_SHT4x:
						transaction->input_registers[i] = (uint16_t)T_SHT4x;
						break;
					case REGISTER_NUM_RH_SHT4x:
						transaction->input_registers[i] = (uint16_t)RH_SHT4x;
						break;
					case REGISTER_NUM_T_SCD4x:
						transaction->input_registers[i] = (uint16_t)T_SCD4x;
						break;
					case REGISTER_NUM_RH_SCD4x:
						transaction->input_registers[i] = (uint16_t)RH_SCD4x;
						break;
					case REGISTER_NUM_T_SHT4x_SIGNED:
						transaction->input_registers_signed[i] = (int16_t)T_SHT4x;
						break;
					case REGISTER_NUM_T_SCD4x_SIGNED:
						transaction->input_registers_signed[i] = (int16_t)T_SCD4x;
						break;
					default:
						return MODBUS_ERROR_FUNCTION_NOT_IMPLEMENTED;
				}
			}
			return MODBUS_OK;
		case MODBUS_READ_HOLDING_REGISTERS:
			for (int i = 0; i < transaction->register_count; i++, register_number++)
			{
				switch (register_number)
				{
					case REGISTER_NUM_LED_ON:
						transaction->holding_registers[i] = (uint16_t)(sensor_config.led_on);
						break;
					case REGISTER_NUM_LED_BRIGHTNESS:
						transaction->holding_registers[i] = (uint16_t)(sensor_config.led_brightness);
						break;
					case REGISTER_NUM_LED_SMOOTH:
						transaction->holding_registers[i] = (uint16_t)(sensor_config.led_smooth);
						break;
					case REGISTER_NUM_CO2_ALERT_LIMIT1:
						transaction->holding_registers[i] = (uint16_t)(sensor_config.led_co2_alert_limit1);
						break;
					case REGISTER_NUM_CO2_ALERT_LIMIT2:
						transaction->holding_registers[i] = (uint16_t)(sensor_config.led_co2_alert_limit2);
						break;
					case REGISTER_NUM_SCD4x_T_OFFSET:
						transaction->holding_registers[i] = (uint16_t)(sensor_config.scd4x_t_offset);
						break;
					case REGISTER_NUM_MODBUS_ADDR:
						transaction->holding_registers[i] = (uint16_t)(sensor_config.modbus_addr);
						break;
					case REGISTER_NUM_BAUDRATE:
						transaction->holding_registers[i] = (uint16_t)(sensor_config.baudrate_index);
						break;
					default:
						return MODBUS_ERROR_FUNCTION_NOT_IMPLEMENTED;
				}
			}
			return MODBUS_OK;
		case MODBUS_WRITE_SINGLE_REGISTER:
			transaction->register_count = 1;
		case MODBUS_WRITE_MULTIPLE_REGISTERS:
			for (int i = 0; i < transaction->register_count; i++, register_number++)
			{
				switch (register_number)
				{
					case REGISTER_NUM_LED_ON:
						sensor_config.led_on = (uint8_t) transaction->holding_registers[i];
						break;
					case REGISTER_NUM_LED_BRIGHTNESS:
						sensor_config.led_brightness = (uint16_t) transaction->holding_registers[i];
						break;
					case REGISTER_NUM_LED_SMOOTH:
						sensor_config.led_smooth = (uint16_t) transaction->holding_registers[i];
						break;
					case REGISTER_NUM_CO2_ALERT_LIMIT1:
						sensor_config.led_co2_alert_limit1 = (uint16_t) transaction->holding_registers[i];
						break;
					case REGISTER_NUM_CO2_ALERT_LIMIT2:
						sensor_config.led_co2_alert_limit2 = (uint16_t) transaction->holding_registers[i];
						break;
					case REGISTER_NUM_SCD4x_T_OFFSET:
						sensor_config.scd4x_t_offset = (int16_t) transaction->holding_registers[i];
						break;
					case REGISTER_NUM_MODBUS_ADDR:
						sensor_config.modbus_addr = (uint16_t) transaction->holding_registers[i];
						modbus_address_changed = 1;
						break;
					case REGISTER_NUM_BAUDRATE:
						if (transaction->holding_registers[0] < config_baudrates_length)
						{
							sensor_config.baudrate_index = (uint16_t) (transaction->holding_registers[i]);
							baudrate_changed = 1;
						}
						break;
					default:
						return MODBUS_ERROR_FUNCTION_NOT_IMPLEMENTED;
				}
			}
			sensor_config_pending_write = 1;
			return MODBUS_OK;
		default:
			return MODBUS_ERROR_FUNCTION_NOT_IMPLEMENTED;
	}
	/* Catch-all error */
	return MODBUS_ERROR_FUNCTION_NOT_IMPLEMENTED; /* nothing implemented yet! TODO */
}

int8_t modbus_transmit_function(uint8_t *buffer, uint16_t data_len)
{
	 /* TODO */
	LPUART1_TX_Buffer(buffer, data_len);
	return MODBUS_OK;
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
