/**
  ******************************************************************************
  * @file    tmr.c
  * @author  Разваляев Алексей
  * @brief   Драйвер таймеров на основе PLIB035.
  *          Этот файл содержит:
  *           + Инициализацию таймеров TMR0-TMR3
  *           + Запуск и остановку таймера
  *           + Блокирующие и неблокирующие задержки
  *           + Настройку callback-функций
  *           + Общий обработчик прерываний таймера
  *           + Псевдо-PLIB функции для инициализации таймеров
  *
  ******************************************************************************
  * @attention
  * 
  * Использование этого драйвера предполагает наличие корректных настроек:
  *  - Определены конфигурационные структуры tmrx_config в periph_config.h
  *
  ******************************************************************************
  * @verbatim
  ==============================================================================
                        ##### Как использовать этот драйвер #####
  ==============================================================================
  
  1. Настройка в periph_config.h:
      (+) Определите структуры tmr0_config, tmr1_config и т.д.
      (+) Используйте макросы для задания периода:
          • PERIOD_US(us)    - период в микросекундах
          • FREQ_HZ(hz)      - частота в герцах  
          • LOAD(ticks, presc) - период в тиках с псевдопрескалером

  2. Инициализация:
      (+) tmr_init_first() - включает тактирование и сброс таймеров
      (+) Для каждого таймера автоматически создается хендл (htmr0, htmr1...)

  3. Работа с таймером:
      (+) TMR_Start(&htmr, IT) / TMR_Stop(&htmr, IT) - запуск и остановка
      (+) TMR_Set_Callback(&htmr, TMR_Callback_Update, func) - установка обработчика
      (+) TMR_Get_Cnt(&htmr) - текущее значение (уже поделенное на прескалер)
      (+) TMR_Get_Period(&htmr) - период (уже поделенный на прескалер)

  4. Задержки:
      (+) TMR_Delay(&htmr, ticks) - блокирующая задержка в тиках таймера с псевдопрескалером
      (+) TMR_Delay_Start(&htmr, &var) + TMR_Delay_Done(&htmr, ticks, &var) - неблокирующая
          задержка в тиках таймера с псевдопрескалером

  5. Обработка прерываний:
      (+) Callback-функция будет вызвана автоматически
      
  ==============================================================================
                          ##### Особенности работы #####
  ==============================================================================

  - Псевдопрескалер:
      - Это программная абстракция, реальный счетчик всегда считает полные тики
      - TMR_Get_Cnt() возвращает значение уже поделенное на (Prescaler+1)
      - Позволяет увеличить максимальный период в (Prescaler+1) раз
      - Пример: LOAD(1000, 99) даст реальный период в 100 раз больше

  - Особенности счетчиков:
      - Максимальный период: 42.9 сек при 100 МГц
      - Таймеры используют убывающий счет (от LOAD до 0). Функция TMR_Get_Cnt 
        возвращает инвертированный счетчик (LOAD-VALUE)

  - Задержки:
      - TMR_Delay проверяет, что задержка < LOAD, иначе вернет ERROR
      - Неблокирующие задержки используют "сырые" тики (без учета прескалера)

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


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

TMR_HandleTypeDef htmr0; /*!< Хендл TMR0 */
TMR_HandleTypeDef htmr1; /*!< Хендл TMR1 */
TMR_HandleTypeDef htmr2; /*!< Хендл TMR2 */
TMR_HandleTypeDef htmr3; /*!< Хендл TMR3 */
//-- Defines -------------------------------------------------------------------

//-- TMR Init functions --------------------------------------------------------
/**
  * @brief  Первичная инициализация таймеров TMR0-TMR3
  * @details Настройка таймеров и хендлов: тактирование, прерывания
  */
void tmr_init_first(void)
{
#if (USE_TMR0==1)
  RCU_APBClkCmd(RCU_APBClk_TMR0, ENABLE);
  RCU_APBRstCmd(RCU_APBRst_TMR0, ENABLE);
  
  htmr0.Instance = TMR0;
  tmr_init(&htmr0, &tmr0_config);
  if(htmr0.Config->IT == ENABLE)
  {
    NVIC_EnableIRQ(TMR0_IRQn);
  }
#endif
  
#if (USE_TMR1==1)
  RCU_APBClkCmd(RCU_APBClk_TMR1, ENABLE);
  RCU_APBRstCmd(RCU_APBRst_TMR1, ENABLE);
  
  htmr1.Instance = TMR1;
  tmr_init(&htmr1, &tmr1_config);
  if(htmr1.Config->IT == ENABLE)
  {
    NVIC_EnableIRQ(TMR1_IRQn);
  }
#endif
  
#if (USE_TMR2==1)
  RCU_APBClkCmd(RCU_APBClk_TMR2, ENABLE);
  RCU_APBRstCmd(RCU_APBRst_TMR2, ENABLE);
  
  htmr2.Instance = TMR2;
  tmr_init(&htmr2, &tmr2_config);
  if(htmr2.Config->IT == ENABLE)
  {
    NVIC_EnableIRQ(TMR2_IRQn);
  }
#endif
  
#if (USE_TMR3==1)
  RCU_APBClkCmd(RCU_APBClk_TMR3, ENABLE);
  RCU_APBRstCmd(RCU_APBRst_TMR3, ENABLE);
  
  htmr3.Instance = TMR3;
  tmr_init(&htmr3, &tmr3_config);
  if(htmr3.Config->IT == ENABLE)
  {
    NVIC_EnableIRQ(TMR3_IRQn);
  }
#endif
}

/**
  * @brief  Инициализация таймера
  * @param  htmr указатель на хендл таймера
  * @param  NewConfig указатель на новую конфигурацию UART, иначе используется та, что в структуре
  * @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка
  */
OperationStatus tmr_init(TMR_HandleTypeDef *htmr, TMR_ExtInit_TypeDef *NewConfig)
{
  if(!htmr || !htmr->Instance)
    return ERROR;
  
  if(NewConfig != NULL)
  {
    htmr->Config = NewConfig;
  }
  if(htmr->Config == NULL)
  {
    return ERROR;
  }
  
  TMR_Init(htmr->Instance, htmr->Config);
  
  return OK;
}


/**
  * @brief  Установка коллбека таймера
  * @param  htmr указатель на хендл таймера
  * @param  CallbackType Тип коллбека
  * @param  UpdCallback Функция коллбека
  * @retval void
  */
OperationStatus TMR_Set_Callback(TMR_HandleTypeDef* htmr, TMR_CallbackTypeDef CallbackType, void (*Callback)(void))
{
  if(!htmr || !htmr->Instance || !htmr->Config)
  {
    return ERROR;
  }
  
  
  switch(CallbackType)
  {
    case TMR_Callback_Update:
      htmr->Config->UpdCallback = Callback;
      break;
    default:
      return ERROR;
  }
  return OK;
}

//-- TMR API functions --------------------------------------------------------
/**
  * @brief  Запуск таймера
  * @param  htmr указатель на хендл таймера
  * @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка
  */
OperationStatus TMR_Start(TMR_HandleTypeDef *htmr, FunctionalState IT)
{
  if(!htmr || !htmr->Instance)
  {
    return ERROR;
  }
  
  TMR_ITCmd(htmr->Instance, IT);
  TMR_Cmd(htmr->Instance, ENABLE);
  
  return OK;
}

/**
  * @brief  Остановка таймера
  * @param  htmr указатель на хендл таймера
  * @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка
  */
OperationStatus TMR_Stop(TMR_HandleTypeDef *htmr, FunctionalState IT)
{
  if(!htmr || !htmr->Instance)
  {
    return ERROR;
  }
  
  TMR_ITCmd(htmr->Instance, IT);
  TMR_Cmd(htmr->Instance, DISABLE);
  
  return OK;
}

/**
  * @brief  Получить счетчик таймера (с псевдопрескалером)
  * @param  htmr указатель на хендл таймера
  * @retval Текущие значение счетчика с псевдопрескалером
  * @details  Если частота таймера 100 МГц, и псведопрескалер 100-1,
  *           то при реальном счетчике 100,000, вернется значение 1,000
  *           Также переводит счетчик в возрастающий (LOAD-VALUE)
  */
uint32_t TMR_Get_Cnt(TMR_HandleTypeDef *htmr)
{
  if(!htmr || !htmr->Instance)
  {
    return ERROR;
  }
  
  uint32_t presc = htmr->Config->Prescaler+1;  
  uint32_t currtick = (htmr->Instance->LOAD - htmr->Instance->VALUE);
  
  return currtick/presc;
}

/**
  * @brief  Получить период таймера (с псевдопрескалером)
  * @param  htmr указатель на хендл таймера
  * @retval Период с псевдопрескалером
  * @details  Если частота таймера 100 МГц, и псведопрескалер 100-1, и период равен 1 мс,
  *           то при реальном периоде 100,000, вернется значение 1,000
  */
uint32_t TMR_Get_Period(TMR_HandleTypeDef *htmr)
{
  if(!htmr || !htmr->Instance)
  {
    return ERROR;
  }
  
  uint32_t presc = htmr->Config->Prescaler+1;  
  uint32_t currload = htmr->Instance->LOAD;
  
  return currload/presc;
}
//-- TMR Delays API functions -------------------------------------------------
/**  
  * @brief  Задержка в тиках таймера (блокирующая).
  * @param  htmr    Указатель на хендл таймера.
  * @param  delay   Задержка в тиках таймера.
  * @return OperationStatus.
  * @details  Формирует задержку с блокировкой программы.
  */
OperationStatus TMR_Delay(TMR_HandleTypeDef *htmr, uint32_t delay)
{
  if(!htmr || !htmr->Instance)
    return ERROR;

  uint32_t presc = htmr->Config->Prescaler+1;
  uint64_t delay_load = delay*presc;
  if(delay_load > 0xFFFFFFFF)
  {
    delay_load = 0xFFFFFFFF;
  }
  
  if(delay_load >= htmr->Instance->LOAD)
  {
    return ERROR;
  }
  uint32_t starttick = htmr->Instance->VALUE;
  while(1)
  {
    if((starttick - htmr->Instance->VALUE) >= delay_load)
    {
      return OK;
    }
  }
}

/**  
  * @brief  Начать отсчет неблокирующей задержки.
  * @param  htmr    Указатель на хендл таймера.
  * @param  var     Указатель на переменную куда положить значение тиков.
  * @return OperationStatus.
  * @details  Запоминает счетчик для начала отсчета неблокирующей задержки.
  *           @ref TMR_Delay_Done для проверки статуса задержки.
  * @note Переменная содержит сырые тики, без преобразования под псевдопрескалер,
  *       поэтому в var могут быть неадекватно большие значения тиков
  */
OperationStatus TMR_Delay_Start(TMR_HandleTypeDef *htmr, uint32_t *var)
{  
  if(!htmr || !htmr->Instance)
    return ERROR;

  *var = htmr->Instance->VALUE;
  
  return OK;
}

/**  
  * @brief  Задержка в тиках таймера (неблокирующая).
  * @param  htmr    Указатель на хендл таймера.
  * @param  delay   Задержка в тиках таймера.
  * @param  var     Указатель на переменную где хранится тики в момент начала задержки.
  * @return 1 - задержка прошла. 0 - задержка в процессе.
  * @details  Формирует задержку с блокировкой программы.
  *           Перед ожиданием задержки надо инициализировать задержку @ref TMR_Delay_Start
  * @note Функция считает задержку через сырые тики, поэтому в var могут быть 
  *       неадекватно большие значения тиков
  */
int TMR_Delay_Done(TMR_HandleTypeDef *htmr, uint32_t delay, uint32_t *var)
{  
  if(!htmr || !htmr->Instance)
    return 0;

  uint32_t presc = htmr->Config->Prescaler+1;
  uint64_t delay_load = delay*presc;
  if(delay_load > 0xFFFFFFFF)
  {
    delay_load = 0xFFFFFFFF;
  }
  
  if(delay_load >= htmr->Instance->LOAD)
  {
    return 0;
  }
  
  if((*var - htmr->Instance->VALUE) >= delay_load)
  {
    return 1;  // Задержка прошла
  }
  else
  {
    return 0;
  }
}


//-- TMR Handler functions ----------------------------------------------------

/**
  * @brief  Обработчик прерывания таймера
  * @param  htmr указатель на хендл таймера
  * @retval void
  */
void tmr_irq_handler(TMR_HandleTypeDef* htmr)
{
  if((htmr->Instance == NULL) || (htmr->Config == NULL))
  {
    return;
  }
  
  /* Проверка флага прерывания таймера */
  if (TMR_ITStatus(htmr->Instance) == SET)
  {
    /* Если есть коллбек вызываем его */
    if(htmr->Config->UpdCallback)
      htmr->Config->UpdCallback();
    
    /* Очистка флага прерывания */
    TMR_ITStatusClear(htmr->Instance);
  }
}


//-- TMR pseudoPLIB functions -------------------------------------------------
/**
  * @brief   Инициализирует модуль TMRx согласно параметрам структуры InitStruct.
  * @param   TMRx  Выбор таймера, где x=A|B
  * @param   InitStruct  Указатель на структуру типа @ref TMR_ExtInit_TypeDef,
  *                      которая содержит конфигурационную информацию
  * @retval  void
  */
void TMR_Init(TMR_TypeDef* TMRx, TMR_ExtInit_TypeDef* InitStruct)
{
  uint32_t tim_clk = InitStruct->ClkFreq*__MHZ;
  uint32_t presc = InitStruct->Prescaler+1;
  if(InitStruct->PeriodUs)
  {
    TMR_PeriodConfig(TMRx, tim_clk, InitStruct->PeriodUs);
  }
  else if(InitStruct->FreqHz)
  {
    TMR_FreqConfig(TMRx, tim_clk, InitStruct->FreqHz);
  }
  else
  {
    uint64_t load = (uint64_t)InitStruct->Load*presc;
    if(load > 0xFFFFFFFF)
      load = 0xFFFFFFFF;
    TMR_SetLoad(TMRx, load);
  }
  
  TMR_ITCmd(TMRx, InitStruct->IT);
  
  TMR_DMAReqCmd(TMRx, InitStruct->DMAReq);
  TMR_ADCSOCCmd(TMRx, InitStruct->ADCSOC);
  TMR_ExtInputConfig(TMRx, InitStruct->ExtInput);
}

