/**
  ******************************************************************************
  * @file    adc.c
  * @author  Разваляев Алексей
  * @brief   Драйвер ADC на основе PLIB035.
  *          Этот файл содержит:
  *           + Инициализацию ADC и аналогового модуля (AM)
  *           + Управление секвенсорами (SEQ0, SEQ1):
  *               - Инициализацию и конфигурацию секвенсоров
  *               - Запуск и остановку преобразований с буфером
  *               - Обработку прерываний секвенсоров
  *               - Установку callback-функций для событий:
  *                    - завершение секвенсора
  *                    - половина буфера
  *                    - полный буфера
  *                    - ошибка
  *           + Управление цифровыми компараторами (DC0-DC3):
  *               - Инициализацию и конфигурацию компараторов
  *               - Запуск и остановку компараторов
  *               - Обработку прерываний компараторов
  *               - Установку callback-функций для событий:
  *                    - срабатывание компаратора
  *                    - ошибка
  *           + Функции для работы с каналами ADC
  *           + Программный запуск преобразований
  *
  ******************************************************************************
  * @attention
  * 
  * Использование этого драйвера предполагает наличие корректных настроек:
  *  - Определены конфигурационные структуры adc_seqx_config и adc_dcx_config
  *    в periph_config.h
  *  - Настроено тактирование ADC через RCU
  *  - Для использования прерываний - включены соответствующие NVIC IRQ
  *
  ******************************************************************************
  * @verbatim
  ==============================================================================
                        ##### Как использовать этот драйвер #####
  ==============================================================================
  
  -------------------------- Общие функции АЦП --------------------------------
  
  1. Инициализация АЦП:
      (+) adc_init_first() - обязательный вызов перед использованием АЦП
      (+) Настраивает тактирование, GPIO и ожидает готовности аналогового модуля

  2. Калибровка каналов:
      (+) ADC_Channel_Calibr(&hadc, channel, OFFSET, GAIN) - калибровка смещения и усиления
      (+) Формула калибровки: Dc = Dr * (4096+GAIN)/4096 + OFFSET

  3. Чтение значений каналов:
      (+) ADC_Channel_GetValue(&hadc, channel) - получение текущего значения канала
      (+) Данные обновляются автоматически при работе секвенсоров

  4. Инициализация GPIO:
      (+) adc_gpio_init(hadc.ChannelEnable) - настраивает пины выбранных каналов
      (+) Вызывается автоматически в adc_init_first()

  ------------------------ Секвенсоры (SEQ0, SEQ1) ----------------------------
  
  1. Настройка в periph_config.h:
      (+) Определите adc_seq0_config, adc_seq1_config
      (+) Настройте ADC_ClockSource и ADC_ClockMHz для тактирования АЦП
      (+) Определите adc_ch_config для включения нужных каналов

  2. Инициализация секвенсоров:
      (+) adc_seq_init(&hadc, ADC_SEQ_Num_0, &adc_seq0_config) - инициализация SEQ0
      (+) Аналогично для SEQ1 при необходимости

  3. Работа с секвенсорами:
      (+) ADC_SEQ_Start(&hadc, ADC_SEQ_Num_0, buffer, buffer_size) - запуск с буфером
      (+) ADC_SEQ_Stop(&hadc, ADC_SEQ_Num_0) - остановка секвенсора
      (+) ADC_SEQ_Set_Callback(&hadc, ADC_SEQ_Num_0, тип, func) - установка обработчика
      (+) ADC_SEQ_SoftwareStart() - программный запуск преобразования

  4. Обработка прерываний секвенсоров:
      (+) ADC_SEQ0_IRQHandler - вызвать adc_seq_irq_handler(&hadc, ADC_SEQ_Num_0)
      (+) ADC_SEQ1_IRQHandler - вызвать adc_seq_irq_handler(&hadc, ADC_SEQ_Num_1)

  ------------------------ Цифровые компараторы (DC) --------------------------
  

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

  -------------------------- Общие особенности --------------------------------
  
  - Частота ADC:
      - Задается через ADC_ClockMHz в periph_config.h
      - Автоматически настраивается делитель от источника тактирования

  - Аналоговый модуль (AM):
      - Требует времени для старта после включения (до 1 мс)
      - adc_init_first() ожидает готовности AM с таймаутом 1 секунда
      - При неудаче вызывает Error_Handler()

  - Калибровка:
      - OFFSET: смещение в диапазоне [-255, 255] квантов АЦП
      - GAIN: коэффициент усиления в диапазоне [-255, 255] квантов АЦП (4096+GAIN)
      - Калибровка применяется аппаратно для каждого канала отдельно

  ------------------------ Секвенсоры (SEQ0, SEQ1) ----------------------------
  
  - Буферизация данных:
      - Данные в буфере хранятся в формате [канал][время]
      - При buffer_size=100 для 2 каналов буфер будет 200 элементов
      - Буфер может быть кольцевым (BufferCircular=ENABLE)

  - Прерывания секвенсоров:
      - Генерируются после заданного количества рестартов (ITCount)
      - ITCount=0 означает прерывание после каждого прохода секвенсора
      - Данные автоматически читаются из FIFO в прерывании

  - Работа с усреднением:
      - Аппаратное усреднение измерения:
        - 2, 4, 8, 16, 32 или 64 выборок (SEQ_Init.ReqAverage)
        - Включается через SEQ_Init.ReqAverageEn
        - Усредненные значения помещаются в FIFO как единый результат
      - Аппаратное усреднение рестартов
        - 2, 4, 8, 16, 32, 64, 128, 256 выборок (SER_Init.RestartCount + 1)
        - Включается через SEQ_Init.RestartAverageEn 
        - Усредненные значения помещаются в FIFO как единый результат

  - Таймер рестартов:
      - Период в тиках ADC_ClockMHz/2 (драйвер автоматически делит на 2)
      - Используется для периодического запуска секвенсора
      - При RestartTimer=0 рестарт происходит немедленно

  - Чтение FIFO:
      - FIFO буфер на 32 элемента автоматически читается в прерывании
      - Данные распределяются по каналам согласно последовательности в Req[0..3]
      - При переполнении FIFO (>32 элементов до прерывания) возможна потеря данных

  ------------------------ Цифровые компараторы (DC) --------------------------

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


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

ADC_HandleTypeDef hadc; /*!< Хендл ADC */

//-- Private function prototypes -----------------------------------------------
static void __adc_seq_fifo_read(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num);
static int __adc_seq_calc_fifo_load(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num);
//-- Defines -------------------------------------------------------------------

//-- ADC Init functions --------------------------------------------------------
/**
  * @brief  Первичная инициализация ADC
  * @details Настройка ADC и хендла: тактирования, прерывания 
  *          и секвенсоры с компараторами
  */
void adc_init_first(void)
{
#if (USE_ADC_SEQ0==1) || (USE_ADC_SEQ1==1) || (USE_ADC_DC0==1) || (USE_ADC_DC1==1) || (USE_ADC_DC2==1) || (USE_ADC_DC3==1)
  // Настройка тактирования
  if(rcu_set_clock_adc(ADC_ClockSource, ADC_ClockMHz, ENABLE) != OK)
  {
    Error_Handler();
  }
  RCU_ADCRstCmd(ENABLE);
  // Включаем аналоговый модуль
  ADC_AM_Cmd(ENABLE); 
  
  // Настройка пинов для ADC
  hadc.ChannelEnable = &adc_ch_config;
  adc_gpio_init(hadc.ChannelEnable);
  
  hadc.Instance = ADC;
#endif
  
#if (USE_ADC_SEQ0==1)
  adc_seq_init(&hadc, ADC_SEQ_Num_0, &adc_seq0_config); 
  if(hadc.SEQ[ADC_SEQ_Num_0].Config->IT == ENABLE)
  {
    NVIC_EnableIRQ(ADC_SEQ0_IRQn);
  }
#endif
#if (USE_ADC_SEQ1==1)
  adc_seq_init(&hadc, ADC_SEQ_Num_1, &adc_seq1_config); 
  if(hadc.SEQ[ADC_SEQ_Num_1].Config->IT == ENABLE)
  {
    NVIC_EnableIRQ(ADC_SEQ1_IRQn);
  }
#endif
  
#if (USE_ADC_SEQ0==1) || (USE_ADC_SEQ1==1) || (USE_ADC_DC0==1) || (USE_ADC_DC1==1) || (USE_ADC_DC2==1) || (USE_ADC_DC3==1)
  uint32_t starttick = millis();
  while (!ADC_AM_ReadyStatus()) {
    if((millis() - starttick) > 1000)
      Error_Handler();
  }
#endif
}

/**
  * @brief  Записать калибровочные значения канала
  * @param  hadc указатель на хендл АЦП
  * @param  channel номер канала
  * @param  OFFSET  Коэффициент корректировки смещения нуля АЦП в квантах [-255:255]
  * @param  GAIN     Коэффициент корректировки усиления зн АЦП в квантах [-255:255]
  * @retval значение ADC (0 если данные невалидны)
  * @details Результат преобразования передается на схему коррекции, которая нивелирует
  *          ошибку усиления и смещения нуля и работа которой описывается формулой
  *           Dc = Dr * (4096+GAIN)/4096 + OFFSET
  */
OperationStatus ADC_Channel_Calibr(ADC_HandleTypeDef *hadc, ADC_CH_Num_TypeDef channel, int OFFSET, int GAIN)
{
  if (!hadc || (int)channel >= ADC_CH_Total)
    return ERROR;
  
  ADC_CH_SetOffsetTrim(channel, OFFSET);
  ADC_CH_SetGainTrim(channel, GAIN);
  return OK;
}

/**
  * @brief  Получение текущего значения канала
  * @param  hadc указатель на хендл АЦП
  * @param  channel номер канала
  * @retval значение ADC (0 если данные невалидны)
  */
uint16_t ADC_Channel_GetValue(ADC_HandleTypeDef *hadc, ADC_CH_Num_TypeDef channel)
{
  if (!hadc || (int)channel >= ADC_CH_Total)
    return 0;
  
  return hadc->ChannelData[channel];
}

//-- ADC Sequencers API functions ----------------------------------------------

/**
  * @brief  Инициализация секвенсора АЦП
  * @param  hadc указатель на хендл АЦП
  * @param  SEQ_Num номер секвенсора
  * @param  NewConfig указатель на новую конфигурацию ADC, иначе используется та, что в структуре
  * @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка
  */
OperationStatus adc_seq_init(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num, ADC_SEQ_ExtInit_TypeDef *NewConfig)
{
  if(!hadc || !hadc->Instance)
    return ERROR;
  
  ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
  
  if(NewConfig != NULL)
  {
    hseq->Config = NewConfig;
  }
  if(hseq->Config == NULL)
  {
    return ERROR;
  }
  
  ADC_SEQ_ExtInit_TypeDef *conf = hseq->Config;
  if(conf->SEQ_Init.RestartTimer) // не значю почему но этот таймер работает в 2 раза медленее чем AdcClk
  {
    conf->SEQ_Init.RestartTimer = (conf->SEQ_Init.RestartTimer+1)/2-1; // поэтому дополнительно делим всё на два чтобы работало
  }
  
  if(__adc_seq_calc_fifo_load(hadc, SEQ_Num) < 0)
    return ERROR;
  
  
  ADC_SEQ_Init(SEQ_Num, &hseq->Config->SEQ_Init);
  
  return OK;
}

/**
  * @brief  Установка коллбека секвенсора АЦП
  * @param  hadc указатель на хендл АЦП
  * @param  CallbackType Тип коллбека
  * @param  Callback Функция коллбека
  * @retval void
  */
OperationStatus ADC_SEQ_Set_Callback(ADC_HandleTypeDef* hadc, ADC_SEQ_Num_TypeDef SEQ_Num, ADC_CallbackTypeDef CallbackType, void (*Callback)(void))
{
  if (!hadc || !hadc->Instance || !hadc->SEQ[SEQ_Num].Config)
    return ERROR;
  
  ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
  
  switch(CallbackType)
  {
    case ADC_Callback_SeqCplt:
      hseq->Config->SEQCpltCallback = Callback;
      break;
    case ADC_Callback_Error:
      hseq->Config->ErrorCallback = Callback;
      break;
    case ADC_Callback_BuffFull:
      hseq->Config->BuffFullCallback = Callback;
      break;
    case ADC_Callback_BuffHalf:
      hseq->Config->BuffFullCallback = Callback;
      break;
    default:
      return ERROR;
  }
  return OK;
}


/**
  * @brief  Запуск секвенсора АЦП с буфером
  * @param  hadc указатель на хендл АЦП
  * @param  SEQ_Num номер секвенсора
  * @param  data_buffer указатель на буфер данных [ch][buffer_size] или NULL
  * @param  buffer_size размер буфера для каждого канала (0 если буфер не используется)
  * @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка
  */
OperationStatus ADC_SEQ_Start(ADC_HandleTypeDef *hadc, 
                             ADC_SEQ_Num_TypeDef SEQ_Num,
                             uint16_t (*data_buffer)[],
                             uint32_t buffer_size)
{
  if (!hadc || !hadc->Instance || !hadc->SEQ[SEQ_Num].Config)
    return ERROR;
  
  ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
  ADC_SEQ_ExtInit_TypeDef *conf = hseq->Config;
  
  // Сохраняем буферные параметры
  hseq->data_buffer = (uint16_t *)data_buffer;
  hseq->buffer_size = buffer_size;
  hseq->buffer_count = 0; // Сбрасываем счетчик
  
  
  // DMA > IT
  if(conf->SEQ_Init.DMAEn == ENABLE)
  {
    conf->IT = DISABLE;
  }
  
  // Настраиваем прерывания если нужно
  if (conf->IT == ENABLE)
  {
    uint32_t it_real_cnt = (uint32_t)(conf->ITCount+1)*(conf->SEQ_Init.ReqMax+1);
    ADC_SEQ_ITConfig(SEQ_Num, it_real_cnt-1, DISABLE);
    ADC_SEQ_ITCmd(SEQ_Num, ENABLE);
  }
  
  // Включаем секвенсор
  ADC_SEQ_Cmd(SEQ_Num, ENABLE);
  
  
  return OK;
}


/**
  * @brief  Остановка секвенсора АЦП
  * @param  hadc указатель на хендл АЦП
  * @param  SEQ_Num номер секвенсора
  * @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка
  */
OperationStatus ADC_SEQ_Stop(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num)
{
  if (!hadc || !hadc->Instance || !hadc->SEQ[SEQ_Num].Config)
    return ERROR;
  
//  ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
  
  // Выключаем секвенсор
  ADC_SEQ_Cmd(SEQ_Num, DISABLE);
  
  // Выключаем прерывания
  ADC_SEQ_ITCmd(SEQ_Num, DISABLE);
    
  return OK;
}

/**
  * @brief  Программный запуск преобразования
  */
void ADC_SEQ_SoftwareStart(void)
{
    ADC_SEQ_SwStartCmd();
}

/**
  * @brief  Обработчик прерываний секвенсора
  * @param  hadc указатель на хендл АЦП
  * @param  SEQ_Num номер секвенсора
  * @retval void
  */
void adc_seq_irq_handler(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num)
{
  if (!hadc || !hadc->Instance || !hadc->SEQ[SEQ_Num].Config)
    return;
//  GPIO_SetBits(GPIOA, GPIO_Pin_7);
  
  ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
  ADC_SEQ_ExtInit_TypeDef *conf = hseq->Config;
  
  // Проверяем флаг прерывания
  if (ADC_SEQ_ITMaskedStatus(SEQ_Num) == SET)
  {
    // Очищаем флаг
    ADC_SEQ_ITStatusClear(SEQ_Num);
    
    __adc_seq_fifo_read(hadc, SEQ_Num);
  }
  
  // Обработка ошибок
  if (ADC_SEQ_DMAErrorStatus(SEQ_Num) == SET)
  {
    ADC_SEQ_DMAErrorStatusClear(SEQ_Num);
    
    if (conf->ErrorCallback)
    {
      conf->ErrorCallback();
    }
  }
  
//  GPIO_ClearBits(GPIOA, GPIO_Pin_7);
}

/**
  * @brief  Внутренняя функция для расчета загрузки FIFO буфера
  * @param  hadc указатель на хендл АЦП
  * @param  SEQ_Num номер секвенсора
  * @retval Кол-во данных FIFO буфера, или -1 если переполнение FIFO
  * @details  Рассчитывает сколько данных накопится в fifo буфере за одно прерывание
  */
static int __adc_seq_calc_fifo_load(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num)
{
  ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
  ADC_SEQ_ExtInit_TypeDef *conf = hseq->Config;
  
  int numb_of_data = 0;
  uint32_t numb_of_req = ((uint32_t)conf->SEQ_Init.ReqMax+1);
  uint32_t it_cnt = ((uint32_t)conf->ITCount+1);
  
  
  if(conf->SEQ_Init.RestartAverageEn)
  {
    numb_of_data = numb_of_req;
  }
  else
  {
    numb_of_data = it_cnt*numb_of_req;
    if(numb_of_data > 32) // Максимальный размер FIFO - 32 элемента, поэтому если слишком многшо данных за время работы перед прерыванием - то ошибка
      return -1;
  }
  
  return numb_of_data;
}
/**
  * @brief  Внутренняя функция для чтения данных из FIFO
  * @param  hadc указатель на хендл АЦП
  * @param  SEQ_Num номер секвенсора
  * @retval void
  * @details  Автоматически достает данные из FIFO и складывает их в заданный буфер
              и в структуру hadc
  */
static void __adc_seq_fifo_read(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num)
{  
  ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
  _ADC_SEQ_TypeDef *SEQx = &hadc->Instance->SEQ[SEQ_Num];
  ADC_SEQ_ExtInit_TypeDef *conf = hseq->Config;
  
  // Последний запрос в секвенсоре
  uint32_t last_request = SEQx->SRQCTL_bit.RQMAX;
  uint32_t numb_of_request = last_request+1;
  
  // Читаем все доступные данные из FIFO
  uint32_t fifo_size = ADC_SEQ_GetFIFOLoad(SEQ_Num);
  uint32_t req_num = ((int)ADC_SEQ_GetReqCurrent(SEQ_Num)-fifo_size)%(numb_of_request); // первая дата в fifo будет (следующий запрос - количество данных в буфере)
  while (ADC_SEQ_GetFIFOLoad(SEQ_Num))
  { 
    uint32_t data = ADC_SEQ_GetFIFOData(SEQ_Num);
    
    if(ADC_SEQ_GetFIFOLoad(SEQ_Num) == 0)
    {
      __NOP();
    }
    if ((int)req_num < ADC_SEQ_Req_Total)
    {
      ADC_CH_Num_TypeDef channel = conf->SEQ_Init.Req[req_num];
      
      if ((int)channel < ADC_CH_Total)
      {
        // 1. Обновляем текущее значение
        hadc->ChannelData[channel] = (uint16_t)data;
        
        // 2. Записываем в буфер если он есть
        if (hseq->data_buffer && 
            hseq->buffer_size > 0)
        {
          // Вычисляем offset для [ch][buffer_size]
          uint32_t offset = req_num * hseq->buffer_size + 
                            hseq->buffer_count;
          hseq->data_buffer[offset] = (uint16_t)data;
        }
      }
      
      // Если это последний канал в секвенсоре - увеличиваем счетчик буфера
      if (req_num == last_request)
      {
        hseq->buffer_count++;
      
        if(hseq->buffer_count == hseq->buffer_size/2)
        {
          if (conf->BuffHalfCallback)
          {
            conf->BuffHalfCallback();
          }
        }
        if(hseq->buffer_count >= hseq->buffer_size)
        {
          // Кольцевой буфер
          if(conf->BufferCircular)
            hseq->buffer_count = 0;
          else
            ADC_SEQ_Stop(hadc, SEQ_Num);
            
          if (conf->BuffFullCallback)
          {
            conf->BuffFullCallback();
          }
        }
      }
    }
    
    // Переход на Следующий канал
    req_num = (req_num+1)%numb_of_request;
  }
  
  // Вызываем коллбек пользователя
  if (conf->SEQCpltCallback)
  {
    conf->SEQCpltCallback();
  }
}


//-- ADC Digital Comparators API functions -------------------------------------



//-- ADC GPIO functions --------------------------------------------------------
/**
  * @brief  Инициализация GPIO для ADC
  */
void adc_gpio_init(ADC_ChannelEnableTypeDef *ChEn)
{
#if (USE_ADC_SEQ0==1) || (USE_ADC_SEQ1==1) || (USE_ADC_DC0==1) || (USE_ADC_DC1==1) || (USE_ADC_DC2==1) || (USE_ADC_DC3==1)
  // Получаем структуру
  GPIO_Init_TypeDef *ch0_config = gpio_get_init(ADC_Ch0_GPIO_Port, ADC_Ch0_Pin);
  GPIO_Init_TypeDef *ch1_config = gpio_get_init(ADC_Ch1_GPIO_Port, ADC_Ch1_Pin);
  GPIO_Init_TypeDef *ch2_config = gpio_get_init(ADC_Ch2_GPIO_Port, ADC_Ch2_Pin);
  GPIO_Init_TypeDef *ch3_config = gpio_get_init(ADC_Ch3_GPIO_Port, ADC_Ch3_Pin);
  
  if(ChEn->Ch0)
  {
    GPIO_StructInit(ch0_config);
    ch0_config->Pin = ADC_Ch0_Pin;
    GPIO_Init(ADC_Ch0_GPIO_Port, ch0_config);
  }
  if(ChEn->Ch1)
  {
    GPIO_StructInit(ch1_config);
    ch1_config->Pin = ADC_Ch1_Pin;
    GPIO_Init(ADC_Ch1_GPIO_Port, ch1_config);
  }
  if(ChEn->Ch2)
  {
    GPIO_StructInit(ch2_config);
    ch2_config->Pin = ADC_Ch2_Pin;
    GPIO_Init(ADC_Ch2_GPIO_Port, ch2_config);
  }
  if(ChEn->Ch3)
  {
    GPIO_StructInit(ch3_config);
    ch3_config->Pin = ADC_Ch3_Pin;
    GPIO_Init(ADC_Ch3_GPIO_Port, ch3_config);
  }
#endif
}
