/**
  ******************************************************************************
  * @file    i2c.c
  * @author  Разваляев Алексей
  * @brief   Драйвер I2C на основе PLIB035.
  *          Этот файл содержит:
  *           + Инициализацию I2C
  *           + Функции для работы в режиме мастера (блокирующие):
  *               - Блокирующая передача/прием
  *               - Функции чтения/записи регистров устройств
  *               - Проверка наличия устройства
  *
  ******************************************************************************
  * @attention
  * 
  * Использование этого драйвера предполагает наличие корректных настроек:
  *  - Определена конфигурационная структура i2c_config в periph_config.h
  *  - Определены пины SDA/SCL для I2C
  *
  ******************************************************************************
  * @verbatim
  ==============================================================================
                        ##### Как использовать этот драйвер #####
  ==============================================================================
  
  1. Настройка в periph_config.h:
      (+) Определите i2c_config для I2C
      (+) Настройте частоту SCL (FSFreq для стандартного режима, HSFreq для высокоскоростного)
      (+) Используйте enum для настройки:
          • I2C_Mode_Master - режим мастера
          • I2C_Mode_Slave - режим ведомого

  2. Инициализация:
      (+) i2c_init_first() - настройка GPIO, тактирования
      (+) i2c_init(&hi2c, &config) - инициализация I2C

  3. Запуск I2C:
      (+) I2C_Start(&hi2c) - включение I2C

  4. Работа в режиме мастера (блокирующие функции):
      - Основные функции:
          (+) I2C_Master_Transmit(&hi2c, addr, buf, size, timeout) - передача данных
          (+) I2C_Master_Receive(&hi2c, addr, buf, size, timeout)  - прием данных
          (+) I2C_Master_WriteReg(&hi2c, addr, reg, val, timeout)  - запись регистра
          (+) I2C_Master_ReadReg(&hi2c, addr, reg, buf, size, timeout) - чтение регистра
          (+) I2C_Master_WriteRegs(&hi2c, addr, reg, buf, size, timeout) - запись нескольких регистров
          (+) I2C_Master_ReadRegs(&hi2c, addr, reg, buf, size, timeout) - чтение нескольких регистров
          (+) I2C_Master_IsDeviceReady(&hi2c, addr, timeout) - проверка наличия устройства

  5. Обработка ошибок:
      (+) I2C_GetLastError(&hi2c) - получение кода последней ошибки
      (+) Callback-функция ошибки вызывается автоматически при ошибках

  6. GPIO для I2C:
      (+) Пины настраиваются автоматически при вызове i2c_init_first()
      (+) При необходимости можно вызвать i2c_gpio_deinit() для восстановления

  ==============================================================================
                          ##### Особенности работы #####
  ==============================================================================

  - Режимы работы:
      - Master: инициирует передачу данных, управляет шиной
      - Slave: реагирует на запросы мастера

  - Скорости работы:
      - Standard Mode (до 100 кГц)
      - Fast Mode (до 400 кГц)
      - Fast Mode Plus (до 1 МГц)
      - High Speed Mode (до 3.4 МГц)

  - Адресация:
      - 7-битная адресация (стандартная)
      - 10-битная адресация (поддерживается)

  - Обработка ошибок:
      - Обрабатываются ошибки шины, арбитража, таймаута
      - При ошибке вызывается ErrCallback и сохраняется код ошибки
      - После ошибки требуется переинициализация I2C

  - Функции работы с регистрами:
      - Упрощают чтение/запись регистров устройств на шине I2C
      - Поддерживают различные размеры регистров (8/16 бит)

  - Проверка устройства:
      - Функция проверяет ответ устройства на свой адрес
      - Полезна для проверки подключения устройств

  @endverbatim
  ******************************************************************************
  */

//-- Includes ------------------------------------------------------------------
#include "periph_config.h"

I2C_HandleTypeDef hi2c; /*!< Хендл I2C */

//-- Private function prototypes -----------------------------------------------
static OperationStatus __i2c_master_wait_bus_free(I2C_HandleTypeDef *hi2c, uint32_t timeout);
static OperationStatus __i2c_master_send_start(I2C_HandleTypeDef *hi2c, uint32_t timeout);
static OperationStatus __i2c_master_send_address(I2C_HandleTypeDef *hi2c, uint16_t addr, uint8_t direction, uint32_t timeout);
static OperationStatus __i2c_master_send_stop(I2C_HandleTypeDef *hi2c, uint32_t timeout);
static OperationStatus __i2c_master_transmit_blocking(I2C_HandleTypeDef *hi2c, uint8_t *buf, uint16_t size, uint32_t timeout);
static OperationStatus __i2c_master_receive_blocking(I2C_HandleTypeDef *hi2c, uint8_t *buf, uint16_t size, uint32_t timeout);
static void __i2c_set_error(I2C_HandleTypeDef *hi2c, uint8_t error);

//-- I2C Init functions -------------------------------------------------------

/**
  * @brief  Первичная инициализация I2C
  * @details Настройка I2C и хендла: GPIO, тактирование
  */
void i2c_init_first(void)
{
#if (USE_I2C==1)  
  // Настройка пинов для I2C
  i2c_gpio_init();
    
  // Включение тактирования и сброс I2C
  RCU_APBClkCmd(RCU_APBClk_I2C, ENABLE);
  RCU_APBRstCmd(RCU_APBRst_I2C, ENABLE);
  
  // Инициализируем I2C
  hi2c.Instance = I2C;
  hi2c.LastError = I2C_ERROR_NONE;
  i2c_init(&hi2c, &i2c_config);
#endif
}

/**
  * @brief  Инициализация I2C
  * @param  hi2c указатель на хендл I2C
  * @param  NewConfig указатель на новую конфигурацию I2C, иначе используется та, что в структуре
  * @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка
  */
OperationStatus i2c_init(I2C_HandleTypeDef *hi2c, I2C_ExtInit_TypeDef *NewConfig)
{
  if(!hi2c || !hi2c->Instance)
    return ERROR;
  
  if(NewConfig != NULL)
  {
    hi2c->Config = NewConfig;
  }
  if(hi2c->Config == NULL)
  {
    return ERROR;
  }
  
  // Сбрасываем ошибку
  hi2c->LastError = I2C_ERROR_NONE;
  
  // Настраиваем частоту SCL
  if(hi2c->Config->I2C_Init.HSMode == ENABLE)
  {
    // Высокоскоростной режим
    I2C_HSFreqConfig(hi2c->Config->HSFreq, hi2c->Config->I2CFreq);
  }
  else
  {
    // Стандартный/быстрый режим
    I2C_FSFreqConfig(hi2c->Config->FSFreq, hi2c->Config->I2CFreq);
  }
  
  // Настраиваем таймаут
  if(hi2c->Config->I2C_Init.Timeout != DISABLE)
  {
    I2C_TimeoutClkDivConfig(hi2c->Config->I2C_Init.TimeoutClkDiv);
    I2C_SetTimeoutCounterLoad(hi2c->Config->I2C_Init.TimeoutLoad);
  }
  
  // Настраиваем дополнительные функции
  I2C_AlertResponseMatchCmd(hi2c->Config->I2C_Init.AlertResponse);
  I2C_GlobalCallMatchCmd(hi2c->Config->I2C_Init.GlobalCall);
  
  return OK;
}

/**
  * @brief  Установка коллбека I2C
  * @param  hi2c указатель на хендл I2C
  * @param  CallbackType Тип коллбека
  * @param  Callback Функция коллбека
  * @retval OperationStatus OK - если успешно, ERROR - при ошибке
  */
OperationStatus I2C_Set_Callback(I2C_HandleTypeDef* hi2c, I2C_CallbackTypeDef CallbackType, void (*Callback)())
{
  if (!hi2c || !hi2c->Instance || !hi2c->Config)
    return ERROR;
  
  switch(CallbackType)
  {
    case I2C_Callback_Tx:
      hi2c->Config->TxCallback = Callback;
      break;
    case I2C_Callback_Rx:
      hi2c->Config->RxCallback = Callback;
      break;
    case I2C_Callback_Addr:
      hi2c->Config->AddrCallback = Callback;
      break;
    case I2C_Callback_Error:
      hi2c->Config->ErrCallback = Callback;
      break;
    
    default:
      return ERROR;
  }
  return OK;
}

//-- I2C API functions --------------------------------------------------------

/**
  * @brief  Запуск I2C
  * @param  hi2c указатель на хендл I2C
  * @retval OperationStatus OK - если успешно, ERROR - при ошибке
  */
OperationStatus I2C_Start(I2C_HandleTypeDef *hi2c)
{
  if (!hi2c)
    return ERROR;
    
  // Сбрасываем ошибку
  hi2c->LastError = I2C_ERROR_NONE;
  
  // Включаем I2C
  I2C_Cmd(ENABLE);
  
  // Если режим ведомого, настраиваем адрес и включаем распознавание
  if(hi2c->Config->I2C_Init.Mode == I2C_Mode_Slave)
  {
    if(hi2c->Config->I2C_Init.Addr10Bit == ENABLE)
    {
      I2C_Slave10AddrCmd(ENABLE);
      I2C_SetSlave10Addr(hi2c->Config->I2C_Init.SlaveAddr >> 8);
      I2C_SetSlaveAddr(hi2c->Config->I2C_Init.SlaveAddr & 0xFF);
    }
    else
    {
      I2C_Slave10AddrCmd(DISABLE);
      I2C_SetSlaveAddr(hi2c->Config->I2C_Init.SlaveAddr);
    }
    I2C_SlaveCmd(ENABLE);
  }
  
  return OK;
}

/**
  * @brief  Остановка I2C
  * @param  hi2c указатель на хендл I2C
  * @retval OperationStatus OK - если успешно, ERROR - при ошибке
  */
OperationStatus I2C_Stop(I2C_HandleTypeDef *hi2c)
{
  if (!hi2c)
    return ERROR;
  
  // Выключаем I2C
  I2C_Cmd(DISABLE);
  
  // Если режим ведомого, выключаем распознавание адреса
  if(hi2c->Config->I2C_Init.Mode == I2C_Mode_Slave)
  {
    I2C_SlaveCmd(DISABLE);
  }
  
  return OK;
}

/**
  * @brief  Получение кода последней ошибки
  * @param  hi2c указатель на хендл I2C
  * @retval Код последней ошибки I2C
  */
uint8_t I2C_GetLastError(I2C_HandleTypeDef *hi2c)
{
  if (!hi2c)
    return I2C_ERROR_NONE;
  
  return hi2c->LastError;
}

//-- I2C Master functions -----------------------------------------------------

/**
  * @brief  Передача данных в режиме мастера (блокирующий режим)
  * @param  hi2c указатель на хендл I2C
  * @param  addr адрес устройства на шине I2C
  * @param  buf указатель на буфер данных
  * @param  size размер данных в байтах
  * @param  timeout таймаут ожидания (мс)
  * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте
  */
OperationStatus I2C_Master_Transmit(I2C_HandleTypeDef *hi2c,
                                   uint16_t addr,
                                   uint8_t *buf,
                                   uint16_t size,
                                   uint32_t timeout)
{
  OperationStatus status;
  
  if (!hi2c || !buf || size == 0)
  {
    __i2c_set_error(hi2c, I2C_ERROR_NONE);
    return ERROR;
  }
  
  // Проверяем, свободна ли шина
  status = __i2c_master_wait_bus_free(hi2c, timeout);
  if (status != OK)
    return status;
  
  // Отправляем START
  status = __i2c_master_send_start(hi2c, timeout);
  if (status != OK)
    return status;
  
  // Отправляем адрес с направлением "запись"
  status = __i2c_master_send_address(hi2c, addr, I2C_Direction_Transmitter, timeout);
  if (status != OK)
  {
    // При NACK отправляем STOP и выходим
    if (hi2c->LastError == I2C_ERROR_NACK)
    {
      __i2c_master_send_stop(hi2c, timeout);
    }
    return status;
  }
  
  // Передаем данные
  status = __i2c_master_transmit_blocking(hi2c, buf, size, timeout);
  
  // Отправляем STOP (даже при ошибке в данных)
  __i2c_master_send_stop(hi2c, timeout);
  
  // Если была ошибка передачи данных, возвращаем ошибку
  if (status != OK)
    return status;
  
  // Вызываем callback успешной передачи
  if (hi2c->Config->TxCallback)
    hi2c->Config->TxCallback();
  
  return OK;
}

/**
  * @brief  Прием данных в режиме мастера (блокирующий режим)
  * @param  hi2c указатель на хендл I2C
  * @param  addr адрес устройства на шине I2C
  * @param  buf указатель на буфер приема
  * @param  size количество принимаемых байт
  * @param  timeout таймаут ожидания (мс)
  * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте
  */
OperationStatus I2C_Master_Receive(I2C_HandleTypeDef *hi2c,
                                  uint16_t addr,
                                  uint8_t *buf,
                                  uint16_t size,
                                  uint32_t timeout)
{
  OperationStatus status;
  
  if (!hi2c || !buf || size == 0)
  {
    __i2c_set_error(hi2c, I2C_ERROR_NONE);
    return ERROR;
  }
  
  // Проверяем, свободна ли шина
  status = __i2c_master_wait_bus_free(hi2c, timeout);
  if (status != OK)
    return status;
  
  // Отправляем START
  status = __i2c_master_send_start(hi2c, timeout);
  if (status != OK)
    return status;
  
  // Отправляем адрес с направлением "чтение"
  status = __i2c_master_send_address(hi2c, addr, I2C_Direction_Receiver, timeout);
  if (status != OK)
  {
    // При NACK отправляем STOP и выходим
    if (hi2c->LastError == I2C_ERROR_NACK)
    {
      __i2c_master_send_stop(hi2c, timeout);
    }
    return status;
  }
  
  // Принимаем данные
  status = __i2c_master_receive_blocking(hi2c, buf, size, timeout);
  
  // Отправляем STOP (даже при ошибке приема)
  __i2c_master_send_stop(hi2c, timeout);
  
  // Если была ошибка приема данных, возвращаем ошибку
  if (status != OK)
    return status;
  
  // Вызываем callback успешного приема
  if (hi2c->Config->RxCallback)
    hi2c->Config->RxCallback();
  
  return OK;
}

/**
  * @brief  Запись регистра устройства (блокирующий режим)
  * @param  hi2c указатель на хендл I2C
  * @param  addr адрес устройства на шине I2C
  * @param  reg адрес регистра
  * @param  value значение для записи
  * @param  timeout таймаут ожидания (мс)
  * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте
  */
OperationStatus I2C_Master_WriteReg(I2C_HandleTypeDef *hi2c,
                                   uint16_t addr,
                                   uint16_t reg,
                                   uint8_t value,
                                   uint32_t timeout)
{
  uint8_t data[3];
  uint16_t reg_size = (hi2c->Config->I2C_Init.RegSize == I2C_RegSize_8bit) ? 1 : 2;
  
  if (reg_size == 1)
  {
    data[0] = (uint8_t)reg;
    data[1] = value;
    return I2C_Master_Transmit(hi2c, addr, data, 2, timeout);
  }
  else
  {
    data[0] = (uint8_t)(reg >> 8);
    data[1] = (uint8_t)reg;
    data[2] = value;
    return I2C_Master_Transmit(hi2c, addr, data, 3, timeout);
  }
}

/**
  * @brief  Чтение регистра устройства (блокирующий режим)
  * @param  hi2c указатель на хендл I2C
  * @param  addr адрес устройства на шине I2C
  * @param  reg адрес регистра
  * @param  buf указатель на буфер для данных
  * @param  size размер данных для чтения
  * @param  timeout таймаут ожидания (мс)
  * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте
  */
OperationStatus I2C_Master_ReadReg(I2C_HandleTypeDef *hi2c,
                                  uint16_t addr,
                                  uint16_t reg,
                                  uint8_t *buf,
                                  uint16_t size,
                                  uint32_t timeout)
{
  OperationStatus status;
  uint8_t reg_data[2];
  uint16_t reg_size = (hi2c->Config->I2C_Init.RegSize == I2C_RegSize_8bit) ? 1 : 2;
  
  // Сначала отправляем адрес регистра
  if (reg_size == 1)
  {
    reg_data[0] = (uint8_t)reg;
    status = I2C_Master_Transmit(hi2c, addr, reg_data, 1, timeout);
  }
  else
  {
    reg_data[0] = (uint8_t)(reg >> 8);
    reg_data[1] = (uint8_t)reg;
    status = I2C_Master_Transmit(hi2c, addr, reg_data, 2, timeout);
  }
  
  if (status != OK)
    return status;
  
  // Затем читаем данные
  return I2C_Master_Receive(hi2c, addr, buf, size, timeout);
}

/**
  * @brief  Запись нескольких регистров (блокирующий режим)
  * @param  hi2c указатель на хендл I2C
  * @param  addr адрес устройства на шине I2C
  * @param  reg начальный адрес регистра
  * @param  buf указатель на буфер данных
  * @param  size количество регистров для записи
  * @param  timeout таймаут ожидания (мс)
  * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте
  */
OperationStatus I2C_Master_WriteRegs(I2C_HandleTypeDef *hi2c,
                                    uint16_t addr,
                                    uint16_t reg,
                                    uint8_t *buf,
                                    uint16_t size,
                                    uint32_t timeout)
{
  OperationStatus status;
  uint16_t reg_size = (hi2c->Config->I2C_Init.RegSize == I2C_RegSize_8bit) ? 1 : 2;
  
  // Проверяем, свободна ли шина
  status = __i2c_master_wait_bus_free(hi2c, timeout);
  if (status != OK)
    return status;
  
  // Отправляем START
  status = __i2c_master_send_start(hi2c, timeout);
  if (status != OK)
    return status;
  
  // Отправляем адрес с направлением "запись"
  status = __i2c_master_send_address(hi2c, addr, I2C_Direction_Transmitter, timeout);
  if (status != OK)
  {
    // При NACK отправляем STOP и выходим
    if (hi2c->LastError == I2C_ERROR_NACK)
    {
      __i2c_master_send_stop(hi2c, timeout);
    }
    return status;
  }
  
  // Отправляем адрес регистра (первый байт)
  if (reg_size == 1)
  {
    status = __i2c_master_transmit_blocking(hi2c, (uint8_t*)&reg, 1, timeout);
  }
  else
  {
    // Для 16-битного адреса отправляем старший, затем младший байт
    uint8_t reg_bytes[2] = {(uint8_t)(reg >> 8), (uint8_t)reg};
    status = __i2c_master_transmit_blocking(hi2c, reg_bytes, 2, timeout);
  }
  
  if (status != OK)
  {
    __i2c_master_send_stop(hi2c, timeout);
    return status;
  }
  
  // Отправляем данные
  status = __i2c_master_transmit_blocking(hi2c, buf, size, timeout);
  
  // Отправляем STOP
  __i2c_master_send_stop(hi2c, timeout);
  
  // Если была ошибка передачи данных, возвращаем ошибку
  if (status != OK)
    return status;
  
  // Вызываем callback успешной передачи
  if (hi2c->Config->TxCallback)
    hi2c->Config->TxCallback();
  
  return OK;
}

/**
  * @brief  Чтение нескольких регистров (блокирующий режим)
  * @param  hi2c указатель на хендл I2C
  * @param  addr адрес устройства на шине I2C
  * @param  reg начальный адрес регистра
  * @param  buf указатель на буфер для данных
  * @param  size количество регистров для чтения
  * @param  timeout таймаут ожидания (мс)
  * @retval OperationStatus OK - если успешно, ERROR - при ошибке или таймауте
  */
OperationStatus I2C_Master_ReadRegs(I2C_HandleTypeDef *hi2c,
                                   uint16_t addr,
                                   uint16_t reg,
                                   uint8_t *buf,
                                   uint16_t size,
                                   uint32_t timeout)
{
  // Для чтения нескольких регистров используется та же функция, что и для одного
  // т.к. после отправки адреса регистра можно читать любое количество байт
  return I2C_Master_ReadReg(hi2c, addr, reg, buf, size, timeout);
}

/**
  * @brief  Проверка наличия устройства на шине
  * @param  hi2c указатель на хендл I2C
  * @param  addr адрес устройства на шине I2C
  * @param  timeout таймаут ожидания (мс)
  * @retval OperationStatus OK - если устройство отвечает, ERROR - если нет ответа
  */
OperationStatus I2C_Master_IsDeviceReady(I2C_HandleTypeDef *hi2c,
                                        uint16_t addr,
                                        uint32_t timeout)
{
  OperationStatus status;
  
  if (!hi2c)
    return ERROR;
  
  // Проверяем, свободна ли шина
  status = __i2c_master_wait_bus_free(hi2c, timeout);
  if (status != OK)
    return status;
  
  // Отправляем START
  status = __i2c_master_send_start(hi2c, timeout);
  if (status != OK)
    return status;
  
  // Отправляем адрес с направлением "запись" (любое направление подойдет для проверки)
  status = __i2c_master_send_address(hi2c, addr, I2C_Direction_Transmitter, timeout);
  
  // Отправляем STOP независимо от результата
  __i2c_master_send_stop(hi2c, timeout);
  
  // Если устройство ответило ACK, значит оно готово
  if (status == OK && hi2c->LastError == I2C_ERROR_NONE)
    return OK;
  
  return ERROR;
}

//-- I2C GPIO functions -------------------------------------------------------

/**
  * @brief  Инициализация GPIO для I2C
  */
void i2c_gpio_init(void)
{
#if USE_I2C==1
  // Получаем структуры
  GPIO_Init_TypeDef *sda_config = gpio_get_init(I2C_GPIO_Port, I2C_SDA_Pin);
  GPIO_Init_TypeDef *scl_config = gpio_get_init(I2C_GPIO_Port, I2C_SCL_Pin);
  
  // SDA пин
  if (sda_config != NULL)
  {
    GPIO_StructInit(sda_config);
    sda_config->Pin = I2C_SDA_Pin;
    sda_config->AltFunc = ENABLE;
    sda_config->Digital = ENABLE;
    sda_config->OutMode = GPIO_OutMode_OD; // Открытый сток для I2C
    sda_config->PullMode = GPIO_PullMode_PU;       // Подтяжка к питанию для I2C
    GPIO_Init(I2C_GPIO_Port, sda_config);
  }
  
  // SCL пин
  if (scl_config != NULL)
  {
    GPIO_StructInit(scl_config);
    scl_config->Pin = I2C_SCL_Pin;
    scl_config->AltFunc = ENABLE;
    scl_config->Digital = ENABLE;
    scl_config->OutMode = GPIO_OutMode_OD; // Открытый сток для I2C
    scl_config->PullMode = GPIO_PullMode_PU;       // Подтяжка к питанию для I2C
    GPIO_Init(I2C_GPIO_Port, scl_config);
  }
#endif
}

/**
  * @brief  Деинициализация GPIO для I2C
  */
void i2c_gpio_deinit(void)
{
#if USE_I2C==1
  // Получаем структуры
  GPIO_Init_TypeDef *sda_config = gpio_get_init(I2C_GPIO_Port, I2C_SDA_Pin);
  GPIO_Init_TypeDef *scl_config = gpio_get_init(I2C_GPIO_Port, I2C_SCL_Pin);
  
  // Восстанавливаем оригинальные настройки из таблицы
  if (sda_config != NULL)
  {
    GPIO_StructInit(sda_config);
    sda_config->Pin = I2C_SDA_Pin;
    GPIO_Init(I2C_GPIO_Port, sda_config);
  }
  
  if (scl_config != NULL)
  {
    GPIO_StructInit(scl_config);
    scl_config->Pin = I2C_SCL_Pin;
    GPIO_Init(I2C_GPIO_Port, scl_config);
  }
#endif
}

//-- I2C private functions ----------------------------------------------------

/**
  * @brief  Установка ошибки I2C
  */
static void __i2c_set_error(I2C_HandleTypeDef *hi2c, uint8_t error)
{
  if (!hi2c)
    return;
  
  hi2c->LastError = error;
  
  // Вызываем callback ошибки, если он установлен
  if (hi2c->Config->ErrCallback)
    hi2c->Config->ErrCallback(error);
}

/**
  * @brief  Ожидание освобождения шины I2C
  */
static OperationStatus __i2c_master_wait_bus_free(I2C_HandleTypeDef *hi2c, uint32_t timeout)
{
  uint32_t starttick = millis();
  
  while(I2C_BusBusyStatus() == SET)
  {
    if(millis() - starttick > timeout)
    {
      __i2c_set_error(hi2c, I2C_ERROR_BUS_BUSY);
      return ERROR;
    }
  }
  
  return OK;
}

/**
  * @brief  Отправка условия START
  */
static OperationStatus __i2c_master_send_start(I2C_HandleTypeDef *hi2c, uint32_t timeout)
{
  uint32_t starttick = millis();
  I2C_State_TypeDef state;
  
  // Формируем START
  I2C_StartCmd();
  
  // Ожидаем подтверждения формирования START
  while(1)
  {
    state = I2C_GetState();
    
    if (state == I2C_State_STDONE || state == I2C_State_HMTMCOK)
    {
      // START успешно сформирован
      return OK;
    }
    else if (state == I2C_State_BERROR)
    {
      __i2c_set_error(hi2c, I2C_ERROR_BUS_ERROR);
      return ERROR;
    }
    else if (state == I2C_State_IDLARL || state == I2C_State_HIDLARL)
    {
      __i2c_set_error(hi2c, I2C_ERROR_ARBITRATION_LOST);
      return ERROR;
    }
    
    if(millis() - starttick > timeout)
    {
      __i2c_set_error(hi2c, I2C_ERROR_TIMEOUT);
      return ERROR;
    }
  }
}

/**
  * @brief  Отправка адреса устройства
  */
static OperationStatus __i2c_master_send_address(I2C_HandleTypeDef *hi2c, uint16_t addr, uint8_t direction, uint32_t timeout)
{
  uint8_t address;
  uint32_t starttick = millis();
  I2C_State_TypeDef state;
  
  // Формируем адрес
  if (addr > 0x7F)
  {
    // 10-битный адрес - первая часть всегда на запись
    address = 0xF0 | ((addr >> 7) & 0x06);
    I2C_SetData(address | I2C_Direction_Transmitter);
  }
  else
  {
    // 7-битный адрес
    address = (addr << 1);
    I2C_SetData(address | direction);
  }
  
  // Сбрасываем флаг прерывания
  I2C_ITStatusClear();
  
  // Ожидаем подтверждения адреса
  while(1)
  {
    state = I2C_GetState();
    
    // ACK получен для 7-битного адреса
    if ((direction == I2C_Direction_Transmitter && 
         (state == I2C_State_MTADPA || state == I2C_State_HMTADPA)) ||
        (direction == I2C_Direction_Receiver && 
         (state == I2C_State_MRADPA || state == I2C_State_HMRADPA)))
    {
      return OK;
    }
    // NACK получен
    else if ((direction == I2C_Direction_Transmitter && 
              (state == I2C_State_MTADNA || state == I2C_State_HMTADNA)) ||
             (direction == I2C_Direction_Receiver && 
              (state == I2C_State_MRADNA || state == I2C_State_HMRADNA)))
    {
      __i2c_set_error(hi2c, I2C_ERROR_NACK);
      return ERROR;
    }
    // Ошибка шины
    else if (state == I2C_State_BERROR)
    {
      __i2c_set_error(hi2c, I2C_ERROR_BUS_ERROR);
      return ERROR;
    }
    // Потеря арбитража
    else if (state == I2C_State_IDLARL || state == I2C_State_HIDLARL)
    {
      __i2c_set_error(hi2c, I2C_ERROR_ARBITRATION_LOST);
      return ERROR;
    }
    
    if(millis() - starttick > timeout)
    {
      __i2c_set_error(hi2c, I2C_ERROR_TIMEOUT);
      return ERROR;
    }
  }
}

/**
  * @brief  Отправка условия STOP
  */
static OperationStatus __i2c_master_send_stop(I2C_HandleTypeDef *hi2c, uint32_t timeout)
{
  uint32_t starttick = millis();
  
  // Формируем STOP
  I2C_StopCmd();
  
  // Сбрасываем флаг прерывания
  I2C_ITStatusClear();
  
  // Ожидаем освобождения шины (опционально, не обязательно)
  // while(I2C_BusBusyStatus() == SET)
  // {
  //   if(millis() - starttick > timeout)
  //   {
  //     __i2c_set_error(hi2c, I2C_ERROR_TIMEOUT);
  //     return ERROR;
  //   }
  // }
  
  return OK;
}

/**
  * @brief  Блокирующая передача данных
  */
static OperationStatus __i2c_master_transmit_blocking(I2C_HandleTypeDef *hi2c, uint8_t *buf, uint16_t size, uint32_t timeout)
{
  uint32_t starttick;
  I2C_State_TypeDef state;
  
  for(uint16_t i = 0; i < size; i++)
  {
    // Отправляем байт данных
    I2C_SetData(buf[i]);
    
    // Сбрасываем флаг прерывания
    I2C_ITStatusClear();
    
    starttick = millis();
    
    // Ожидаем подтверждения байта
    while(1)
    {
      state = I2C_GetState();
      
      // ACK получен
      if (state == I2C_State_MTDAPA || state == I2C_State_HMTDAPA)
      {
        break;
      }
      // NACK получен
      else if (state == I2C_State_MTDANA || state == I2C_State_HMTDANA)
      {
        __i2c_set_error(hi2c, I2C_ERROR_NACK);
        return ERROR;
      }
      // Ошибка шины
      else if (state == I2C_State_BERROR)
      {
        __i2c_set_error(hi2c, I2C_ERROR_BUS_ERROR);
        return ERROR;
      }
      // Потеря арбитража
      else if (state == I2C_State_IDLARL || state == I2C_State_HIDLARL)
      {
        __i2c_set_error(hi2c, I2C_ERROR_ARBITRATION_LOST);
        return ERROR;
      }
      
      if(millis() - starttick > timeout)
      {
        __i2c_set_error(hi2c, I2C_ERROR_TIMEOUT);
        return ERROR;
      }
    }
  }
  
  return OK;
}

/**
  * @brief  Блокирующий прием данных
  */
static OperationStatus __i2c_master_receive_blocking(I2C_HandleTypeDef *hi2c, uint8_t *buf, uint16_t size, uint32_t timeout)
{
  uint32_t starttick;
  I2C_State_TypeDef state;
  
  for(uint16_t i = 0; i < size; i++)
  {
    starttick = millis();
    
    // Ожидаем приема байта
    while(1)
    {
      state = I2C_GetState();
      
      // Байт получен с ACK или NACK
      if (state == I2C_State_MRDAPA || state == I2C_State_MRDANA ||
          state == I2C_State_HMRDAPA || state == I2C_State_HMRDANA)
      {
        // Читаем данные
        buf[i] = I2C_GetData();
        
        // Для последнего байта отправляем NACK
        if (i == size - 1)
        {
          I2C_NACKCmd();
        }
        
        break;
      }
      // Ошибка шины
      else if (state == I2C_State_BERROR)
      {
        __i2c_set_error(hi2c, I2C_ERROR_BUS_ERROR);
        return ERROR;
      }
      // Потеря арбитража
      else if (state == I2C_State_IDLARL || state == I2C_State_HIDLARL)
      {
        __i2c_set_error(hi2c, I2C_ERROR_ARBITRATION_LOST);
        return ERROR;
      }
      
      if(millis() - starttick > timeout)
      {
        __i2c_set_error(hi2c, I2C_ERROR_TIMEOUT);
        return ERROR;
      }
    }
  }
  
  return OK;
}
