/*************************************************************************************************************
 * @file    BM25S3221_1_HT32/src/BM25S3221_1.c
 * @version V1.0.1
 * @date    2025-06-21
 * @brief   The function of BM25S3221_1 driver.
 *************************************************************************************************************
 * @attention
 *
 * Firmware Disclaimer Information
 *
 * 1. The customer hereby acknowledges and agrees that the program technical documentation, including the
 *    code, which is supplied by Holtek Semiconductor Inc., (hereinafter referred to as "HOLTEK") is the
 *    proprietary and confidential intellectual property of HOLTEK, and is protected by copyright law and
 *    other intellectual property laws.
 *
 * 2. The customer hereby acknowledges and agrees that the program technical documentation, including the
 *    code, is confidential information belonging to HOLTEK, and must not be disclosed to any third parties
 *    other than HOLTEK and the customer.
 *
 * 3. The program technical documentation, including the code, is provided "as is" and for customer reference
 *    only. After delivery by HOLTEK, the customer shall use the program technical documentation, including
 *    the code, at their own risk. HOLTEK disclaims any expressed, implied or statutory warranties, including
 *    the warranties of merchantability, satisfactory quality and fitness for a particular purpose.
 *
 * <h2><center>Copyright (C) Holtek Semiconductor Inc. All rights reserved</center></h2>
 ************************************************************************************************************/

/* Includes ------------------------------------------------------------------------------------------------*/
#include "BM25S3221_1.h"

/* Settings ------------------------------------------------------------------------------------------------*/
/* Private types -------------------------------------------------------------------------------------------*/
/* Private constants ---------------------------------------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------------------------------------*/
#if (CHECK_SERIALn(BM25S3221_1_SERIAL) == -1)
#error "Error: BM25S3221_1_SERIAL doesn't support the current SERIAL number. Check ht32_board_config.h."
#endif
/* Global variables ----------------------------------------------------------------------------------------*/
uint32_t gBM25S3221_1_SERIAL = BM25S3221_1_SERIAL;
/* Private variables ---------------------------------------------------------------------------------------*/
vu32 _gBFTM0Count; //
uint8_t _gBM25S3221_1_receiveBuffer[32] = {0};

/* Global functions ----------------------------------------------------------------------------------------*/
/************************************************************************************************************
 * @brief  module serial part number select.
 * @param  serial_number: select serial number.
 * @retval BM25S3221_1_SUCCESS/BM25S3221_1_FAILURE
 ************************************************************************************************************/
BM25S3221_1_selStatus BM25S3221_1_selSerial(uint32_t serial_number)
{
  if (CHECK_SERIALn(serial_number) == -1)
  {
    return BM25S3221_1_FAILURE;
  }
  gBM25S3221_1_SERIAL = serial_number;
  return BM25S3221_1_SUCCESS;
}

/***********************************************************************************************************
 * @brief  Module initialization using UART communication.
 * @param  void
 * @retval void
 ************************************************************************************************************/
void BM25S3221_1_Init(void)
{
	{
	USART_InitTypeDef USART_InitStructure = {0};
  USART_InitStructure.USART_BaudRate = 9600;
  USART_InitStructure.USART_WordLength = USART_WORDLENGTH_8B;
  USART_InitStructure.USART_StopBits = USART_STOPBITS_1;
  USART_InitStructure.USART_Parity = USART_PARITY_NO;
  USART_InitStructure.USART_Mode = USART_MODE_NORMAL;
  UARTM_Init(gBM25S3221_1_SERIAL, &USART_InitStructure, BM25S3221_1_UART_TXTIMEOUT);
  }
  // D22(STATUS1):PWM capture
  { /* Enable peripheral clock                                                                              */
    CKCU_PeripClockConfig_TypeDef CKCUClock = {{0}};
    CKCUClock.Bit.BFTM0 = 1;
		CKCUClock.Bit.HTCFG_UART_RX_GPIO_CLK = 1;
    CKCU_PeripClockConfig(CKCUClock, ENABLE);
  }
	  /* Turn on UxART Rx internal pull up resistor to prevent unknow state                                     */
  GPIO_PullResistorConfig(HTCFG_UART_RX_GPIO_PORT, HTCFG_UART_RX_GPIO_PIN, GPIO_PR_UP);
  /* BFTM as Repetitive mode to trigger the match interrupt                                                 */
  BFTM_SetCompare(HT_BFTM0, SystemCoreClock / 100000); // 10us
  BFTM_SetCounter(HT_BFTM0, 0);
  BFTM_IntConfig(HT_BFTM0, ENABLE);
  BFTM_EnaCmd(HT_BFTM0, ENABLE);
  NVIC_DisableIRQ(BFTM0_IRQn);
	
  // INPUT:D22<--->STA Pin(PWM)
  { /* Enable peripheral clock                                                                              */
    CKCU_PeripClockConfig_TypeDef CKCUClock = {{0}};
    CKCUClock.Bit.BM25S3221_1_STAPIN_GPIO = 1;
    CKCUClock.Bit.AFIO = 1;
    CKCU_PeripClockConfig(CKCUClock, ENABLE);
  }
  /* Configure AFIO mode as GPIO                                                                          */
  AFIO_GPxConfig(BM25S3221_1_STAPIN_GPIO_ID, BM25S3221_1_STAPIN_AFIO_PIN, AFIO_FUN_GPIO);
  /* Configure GPIO direction as input                                                                    */
  GPIO_DirectionConfig(BM25S3221_1_STAPIN_GPIO_PORT, BM25S3221_1_STAPIN_GPIO_PIN, GPIO_DIR_IN);
  /* Configure GPIO pull resistor                                                                         */
  GPIO_PullResistorConfig(BM25S3221_1_STAPIN_GPIO_PORT, BM25S3221_1_STAPIN_GPIO_PIN, GPIO_PR_UP);
  /* Enable input function for read                                                                       */
  GPIO_InputConfig(BM25S3221_1_STAPIN_GPIO_PORT, BM25S3221_1_STAPIN_GPIO_PIN, ENABLE);
}
/***********************************************************************************************************
 * @brief  Read the current PM2.5 concentration value through STA pin(PWM signal).
 * @param  void
 * @retval PM2.5 concentration value,unit: μg/m³
 ************************************************************************************************************/
uint16_t BM25S3221_1_readPM25Value(void)
{
  uint16_t highPulse, lowPulse, PM25Value = 0;
  uint32_t tmp0 = 0, tmp1 = 0;
  float PM25ValueTmp = 0;
  while (GPIO_ReadInBit(BM25S3221_1_STAPIN_GPIO_PORT, BM25S3221_1_STAPIN_GPIO_PIN) == 0)
    ;
  if (GPIO_ReadInBit(BM25S3221_1_STAPIN_GPIO_PORT, BM25S3221_1_STAPIN_GPIO_PIN) == 1)
  {
    _gBFTM0Count = 0;
    NVIC_EnableIRQ(BFTM0_IRQn);
  }
  while (GPIO_ReadInBit(BM25S3221_1_STAPIN_GPIO_PORT, BM25S3221_1_STAPIN_GPIO_PIN) == 1)
    ;
  if (GPIO_ReadInBit(BM25S3221_1_STAPIN_GPIO_PORT, BM25S3221_1_STAPIN_GPIO_PIN) == 0)
  {
    tmp0 = _gBFTM0Count;
  }
  while (GPIO_ReadInBit(BM25S3221_1_STAPIN_GPIO_PORT, BM25S3221_1_STAPIN_GPIO_PIN) == 0)
    ;
  if (GPIO_ReadInBit(BM25S3221_1_STAPIN_GPIO_PORT, BM25S3221_1_STAPIN_GPIO_PIN) == 1)
  {
    NVIC_DisableIRQ(BFTM0_IRQn);
    tmp1 = _gBFTM0Count;
  }
  if (tmp0 < 100)
  {
    highPulse = 0;
  }
  else
  {
    highPulse = tmp0 / 100;
  }
  lowPulse = (tmp1 - tmp0) / 100;
  PM25ValueTmp = ((float)highPulse / (highPulse + lowPulse)) * 1000;
  PM25Value = PM25ValueTmp + 0.5;
  return PM25Value;
}

/***********************************************************************************************************
 * @brief  Use the command to read the dust concentration
 * @param  array[]:storing dust concentration values
 * @retval result 
 *    @arg 0: read successed
 *    @arg 1: Verification failed
 *    @arg 2: Timeout error
 * @others  array[0]:PM1.0, array[1]:PM2.5, array[2]:PM10
 ************************************************************************************************************/
uint8_t BM25S3221_1_readDustValue(uint16_t array[])
{
  uint16_t tmp = 0;
  uint8_t sendBuf[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
  uint8_t recBuf[9] = {0}, errFlag = 1;
  BM25S3221_1_writeBytes(sendBuf, 9);
  _BM25S3221_1_delayMs(1);
  errFlag = BM25S3221_1_readBytes(recBuf, 9);
  if (errFlag == 0)
  {
    if (recBuf[1] == 0x86)
    {
      tmp = recBuf[6];
      tmp = tmp << 8;
      array[0] = tmp + recBuf[7]; // PM1.0

      tmp = recBuf[2];
      tmp = tmp << 8;
      array[1] = tmp + recBuf[3]; // PM2.5

      tmp = recBuf[4];
      tmp = tmp << 8;
      array[2] = tmp + recBuf[5]; // PM10
      return 0;
    }
    else
    {
      array[0] = 0;
      array[1] = 0;
      array[2] = 0;
    }
  }
  return errFlag;
}

/***********************************************************************************************************
 * @brief  Query whether the 32-byte data automatically uploaded by the module is received
 * @param  void
 * @retval result
 *    @arg true(1): Received
 *    @arg false(0): Not received
 * @others Only used in module automatic upload mode
 ************************************************************************************************************/
bool BM25S3221_1_isInfoAvailable(void)
{
  uint8_t header[4] = {0x42, 0x4D, 0x00, 0x1C}; // Fixed code for first 4 bytes of 32-byte data
  uint8_t recBuf[32] = {0}, recLen = 32;
  uint8_t i, num = 0, readCnt = 0, failCnt = 0;
  uint8_t checkSumH = 0, checkSumL = 0;
  uint16_t checkSum = 0;
  bool isHeader = false, result = false;

  for (i = 0; i < recLen; i++)
  {
    _gBM25S3221_1_receiveBuffer[i] = 0;
  }

  /* Select hardSerial or softSerial according to the setting */
  num = UARTM_GetReadBufferLength(gBM25S3221_1_SERIAL);

  /* Serial buffer contains at least one 32-byte data */
  if (num >= recLen)
  {
    while (failCnt < 5) // Didn't read the required data twice, exiting the loop
    {
      /* Find 4-byte data header */
      for (i = 0; i < 4;)
      {
        UARTM_ReadByte(gBM25S3221_1_SERIAL, recBuf + i);
        if (recBuf[i] == header[i])
        {
          isHeader = true; // Fixed code is correct
          i++;             // Next byte
        }
        else if ((recBuf[i] != header[i]) && (i > 0))
        {
          isHeader = false; // Next fixed code error
          failCnt++;
          break;
        }
        else if ((recBuf[i] != header[i]) && (i == 0))
        {
          readCnt++; // "0x42" not found, continue
        }
        if (readCnt > (num - recLen))
        {
          return false;
        }
      }

      /* Find the correct fixed code */
      if (isHeader)
      {
        for (checkSum = 0, i = 0; i < 4; i++)
        {
          checkSum += recBuf[i]; // Sum checkSum
        }
        for (i = 4; i < recLen; i++) // Read subsequent 28-byte data
        {
          UARTM_ReadByte(gBM25S3221_1_SERIAL, recBuf + i);
          checkSum += recBuf[i]; // Sum checkSum
        }
        /* Calculate checkSum */
        checkSumH = 0;
        checkSumL = 0;
        checkSum = checkSum - recBuf[recLen - 2];
        checkSum = checkSum - recBuf[recLen - 1];
        checkSumH = checkSum >> 8;
        checkSumL = checkSum & 0x00ff;

        /* Compare whether the check code is correct */
        if ((checkSumH == recBuf[recLen - 2]) && (checkSumL == recBuf[recLen - 1]))
        {
          for (i = 0; i < recLen; i++)
          {
            _gBM25S3221_1_receiveBuffer[i] = recBuf[i]; // True, assign data to _recBuf[]
          }
          result = true;
          break; // Exit "while (failCnt < 5)" loop
        }
        else
        {
          failCnt++; // Error, failCnt++, return "while (failCnt < 5)" loop
        }
      }
    }
  }
  return result;
}

/***********************************************************************************************************
 * @brief  Read the 32-byte data of sent by the module
 * @param  array[]: The array for storing the 32-byte module information
           (refer to datasheet for meaning of each bit)
 * @retval void
 * @others Use after "isInfoAvailable() == true"
 ************************************************************************************************************/
void BM25S3221_1_readInfoPacket(uint8_t array[])
{
  uint8_t i;
  for (i = 0; i < 32; i++)
  {
    array[i] = _gBM25S3221_1_receiveBuffer[i];
    _gBM25S3221_1_receiveBuffer[i] = 0;
  }
}

/***********************************************************************************************************
 * @brief  Set the mode of module uploading data(dust concentration)
 * @param  modeCode
 *    @arg 0x40: Automatically upload data
 *    @arg 0x41: Not automatically upload data
 * @retval void
 ************************************************************************************************************/
void BM25S3221_1_setUploadMode(uint8_t modeCode)
{
  uint8_t i, sendBuf[9] = {0xFF, 0x01, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
  sendBuf[3] = modeCode;
  for (i = 1; i < 8; i++)
  {
    sendBuf[8] += sendBuf[i];
  }
  sendBuf[8] = ~sendBuf[8] + 1;
  BM25S3221_1_writeBytes(sendBuf, 9);
}

/***********************************************************************************************************
 * @brief  Enter sleep mode
 * @param  void
 * @retval result
 *    @arg 0: Setup successed
 *    @arg 1: Verification failed
 *    @arg 2: Timeout error
 *    @arg 3: Setup failed
 ************************************************************************************************************/
uint8_t BM25S3221_1_sleep(void)
{
  uint8_t errFlag;
  uint8_t sendBuf[9] = {0xFF, 0x01, 0xA7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x57};
  uint8_t recBuf[9] = {0};
  BM25S3221_1_writeBytes(sendBuf, 9);
  _BM25S3221_1_delayMs(20); // Waiting for module to receive data and reply
  errFlag = BM25S3221_1_readBytes(recBuf, 9);
  if (errFlag == BM25S3221_1_CHECK_OK)
  {
    if (recBuf[2] == 0x01)
    {
      return 0;
    }
    else
    {
      return 3;
    }
  }
  else
  {
    return errFlag;
  }
}

/***********************************************************************************************************
 * @brief  Exit sleep mode
 * @param  void
 * @retval result
 *    @arg 0: Setup successed
 *    @arg 1: Verification failed
 *    @arg 2: Timeout error
 *    @arg 3: Setup failed
 ************************************************************************************************************/
uint8_t BM25S3221_1_wakeUp(void)
{
  uint8_t errFlag;
  uint8_t sendBuf[9] = {0xFF, 0x01, 0xA7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58};
  uint8_t recBuf[9] = {0};
  BM25S3221_1_writeBytes(sendBuf, 9);
  _BM25S3221_1_delayMs(45); // Waiting for module to receive data and reply
  errFlag = BM25S3221_1_readBytes(recBuf, 9);
  if (errFlag == BM25S3221_1_CHECK_OK)
  {
    if (recBuf[2] == 0x01)
    {
      return 0;
    }
    else
    {
      return 3;
    }
  }
  else
  {
    return errFlag;
  }
}
/***********************************************************************************************************
 * @brief  write data through UART.
 * @param  wbuf:Variables for storing Data to be sent
 * @param  wlen:Length of data sent
 * @retval void
 ************************************************************************************************************/
void BM25S3221_1_writeBytes(uint8_t wbuf[], uint8_t wlen)
{
  UARTM_Write(gBM25S3221_1_SERIAL, wbuf, wlen);
}

/***********************************************************************************************************
 * @brief  read data through UART.
 * @param  rbuf:Variables for storing Data to be obtained
 * @param  rlen:Length of data to be obtained
 * @retval BM25S3221_1_READ_OK / BM25S3221_1_CHECK_ERROR / BM25S3221_1_TIMEOUT_ERROR
 ************************************************************************************************************/
uint8_t BM25S3221_1_readBytes(uint8_t rbuf[], uint8_t rlen)
{
  uint8_t i = 0, _delayCount = 0;
  uint8_t checkSum = 0, sumTmp = 0;
  for (i = 0; i < rlen; i++)
  {
    _delayCount = 0;
    while (UARTM_GetReadBufferLength(gBM25S3221_1_SERIAL) == 0)
    {
      if (_delayCount > BM25S3221_1_UART_RXTIMEOUT_BYTE)
      {
        return BM25S3221_1_TIMEOUT_ERROR;
      }
      _BM25S3221_1_delayMs(1);
      _delayCount++;
    }
    UARTM_ReadByte(gBM25S3221_1_SERIAL, rbuf + i);
  }
  //	printf("rbuf:%x  %x  %x  %x  %x  %x  %x  %x  %x\n",rbuf[0],rbuf[1],rbuf[2],rbuf[3],rbuf[4],rbuf[5],rbuf[6],rbuf[7],rbuf[8]);
  // Check
  if (rbuf[0] == 0xff)
  {
    checkSum = rbuf[rlen - 1];
    for (i = 1; i < (rlen - 1); i++)
    {
      sumTmp += rbuf[i];
    }
    sumTmp = ~sumTmp + 1;
    if (sumTmp == checkSum)
    {
      return BM25S3221_1_CHECK_OK; // Check sum correct!
    }
    else
    {
      return BM25S3221_1_CHECK_ERROR; // Check sum incorrect
    }
  }
  else
  {
    return BM25S3221_1_CHECK_ERROR;
  }
}

/* Private functions ---------------------------------------------------------------------------------------*/
/***********************************************************************************************************
 * @brief  delay ms.
 * @param  count:unit ms
 * @retval void
 ************************************************************************************************************/
void _BM25S3221_1_delayMs(uint32_t count)
{
  count = SystemCoreClock / 8000 * count;
  while (count--)
    ;
}
