/** 
**************************************************************************
* @file general_gpio.c
* @brief Модуль для инициализации портов и работы с ними.
**************************************************************************
* @details 
Реализация функций для работы с GPIO:
 - Включение тактирования портов
 - Инициализация светодиодов и кнопок
 - Управление светодиодами: включение, выключение, моргание, плавное затухание
 - Чтение состояния кнопок с фильтром от дребезга
*************************************************************************/
#include "general_gpio.h"

//-------------------------------------------------------------------
//------------------------GPIO INIT FUNCTIONS------------------------


/**
  * @brief Включить тактирование порта GPIO
  */
HAL_StatusTypeDef GPIO_Clock_Enable(GPIO_TypeDef *GPIOx)
{ 
  if(check_null_ptr_1(GPIOx))
    return HAL_ERROR;

  HAL_StatusTypeDef status = HAL_OK;
  // choose port for enable clock
  if (GPIOx==GPIOA)
    __HAL_RCC_GPIOA_CLK_ENABLE();
  else if (GPIOx==GPIOB)
    __HAL_RCC_GPIOB_CLK_ENABLE();
#ifdef GPIOC
  else if (GPIOx==GPIOC)
    __HAL_RCC_GPIOC_CLK_ENABLE();
#endif
#ifdef GPIOD
  else if (GPIOx==GPIOD)
    __HAL_RCC_GPIOD_CLK_ENABLE();
#endif
#ifdef GPIOE
  else if (GPIOx==GPIOE)
    __HAL_RCC_GPIOE_CLK_ENABLE();
#endif
#ifdef GPIOF
  else if (GPIOx==GPIOF)
    __HAL_RCC_GPIOF_CLK_ENABLE();
#endif
#ifdef GPIOH
  else if (GPIOx==GPIOF)
    __HAL_RCC_GPIOH_CLK_ENABLE();
#endif
  else
    status = HAL_ERROR;
  
  return status;
}

//------------------------GPIO INIT FUNCTIONS------------------------
//-------------------------------------------------------------------


//-------------------------------------------------------------------
//------------------------GPIO LED FUNCTIONS-------------------------

/**
  * @brief  Инициализировать светодиод (структуру светодиода)
  * @param led              Указатель на структуру светодиода
  * @param GPIOx            Указатель на структуру порта для светодиода
  * @param GPIO_PIN_X       Пин для светодиода
  * @param LED_ActiveLevel  Состояния пина, при котором светодиод будет включен
  */
HAL_StatusTypeDef GPIO_LED_Init(GPIO_LEDTypeDef *led, GPIO_TypeDef *GPIOx, uint32_t GPIO_PIN_X, uint8_t LED_ActiveLevel)
{
  if(check_null_ptr_3(led, GPIOx, GPIO_PIN_X))
    return HAL_ERROR;
  
  led->LED_Port       = GPIOx;
  led->LED_Pin        = GPIO_PIN_X;
  led->LED_ActiveLvl  = LED_ActiveLevel;
  
  GPIO_LED_Off(led);
  return HAL_OK;
}

/**
  * @brief  Включить светодиод 
  * @param  led Указатель на структуру светодиода
  * @return HAL Status
  */
HAL_StatusTypeDef GPIO_LED_On(GPIO_LEDTypeDef *led)
{
  if(check_null_ptr_3(led, led->LED_Port, led->LED_Pin))
    return HAL_ERROR;  
  
  led->state = LED_IS_ON;
  HAL_GPIO_WritePin(led->LED_Port, led->LED_Pin, led->LED_ActiveLvl);

  
  return HAL_OK;
}
/**
  * @brief  Выключить светодиод 
  * @param  led Указатель на структуру светодиода
  * @return HAL Status
  */
HAL_StatusTypeDef GPIO_LED_Off(GPIO_LEDTypeDef *led)
{
  if(check_null_ptr_3(led, led->LED_Port, led->LED_Pin))
    return HAL_ERROR;  
  
  led->state = LED_IS_OFF;
  HAL_GPIO_WritePin(led->LED_Port, led->LED_Pin, !led->LED_ActiveLvl);

  
  
  return HAL_OK;
}
/**
  * @brief  Выставить светодиод по переменной
  * @param  led        Указатель на структуру светодиода
  * @param  led_state  Состояние светодиода
  * @return HAL Status
  */
HAL_StatusTypeDef GPIO_LED_Set(GPIO_LEDTypeDef *led, uint8_t led_state)
{
  if(check_null_ptr_3(led, led->LED_Port, led->LED_Pin))
    return HAL_ERROR;  
  
  if(led_state)
  {
    return GPIO_LED_On(led);
  }
  else
  {
    return GPIO_LED_Off(led);
  }
}
/**
  * @brief  Активировать моргание светодиодом
  * @param  led Указатель на структуру светодиода
  * @param  period Период плавного моргания светодиода
  * @return HAL Status
  * @details  Функция ставит режим моргания, который после управляется в @ref GPIO_LED_Dynamic_Handle
  */
HAL_StatusTypeDef GPIO_LED_Blink_Start(GPIO_LEDTypeDef *led, uint32_t period)
{
  if(check_null_ptr_3(led, led->LED_Port, led->LED_Pin))
    return HAL_ERROR;  
  
  led->state = LED_IS_BLINKING;
  led->LED_Period = period;
    
  return HAL_OK;
}
  
/**
  * @brief  Активировать моргание светодиодом
  * @param  led  Указатель на структуру светодиода
  * @param  period Период плавного моргания светодиода
  * @return HAL Status
  * @details  Функция ставит режим моргания, который после управляется в @ref GPIO_LED_Dynamic_Handle
  */
HAL_StatusTypeDef GPIO_LED_Fading_Start(GPIO_LEDTypeDef *led, uint32_t period)
{
  if(check_null_ptr_3(led, led->LED_Port, led->LED_Pin))
    return HAL_ERROR;  
  
  led->state = LED_IS_FADING;
  led->LED_Period = period;
  
    
  return HAL_OK;
}

//uint8_t LED_PWM_FADING_DUTYS[LED_PWM_TICKS] = {0 1 2 3 4 5 6 7 8 9 10 11 12 }
/**
  * @brief  Управление динамическими режимами свечения светодиода
  * @param  Указатель на структуру светодиода
  * @details  Функция моргает/плавно моргает светодиодом в неблокирующем режиме
  * Т.е. функцию надо вызывать постоянно, чтобы она мониторила тики 
  * и в нужный момент переключала светодиод
  */
void GPIO_LED_Dynamic_Handle(GPIO_LEDTypeDef *led)
{
  if(check_null_ptr_3(led, led->LED_Port, led->LED_Pin))
    return;
  
  /* Режим моргания светодиода */
  if(led->state == LED_IS_BLINKING)
  {
    uint32_t tickcurrent = local_time();
    /* Ожидание истечения периода моргания */
    if((tickcurrent - led->tickprev) > led->LED_Period)
    {
      /* Моргание */
      HAL_GPIO_TogglePin(led->LED_Port, led->LED_Pin);
      
      led->tickprev = tickcurrent;
    }
  }
  /* Режим плавного моргания светодиода */
  else if(led->state == LED_IS_FADING)
  {
    static unsigned direction = 0;
    static int duty = 0;
    uint32_t tickcurrent = local_time();
    /* Ожидание момента изменения яркости */
    /* Период ШИМ 20 мс, поэтому менять яроксть надо 40 раз за период (туда обратно) */
    if((tickcurrent - led->tickprev) > led->LED_Period/(LED_PWM_TICKS*2))
    {
      /* Формирование разтухания */
      if(direction == 0)
      {
        if(++duty >= LED_PWM_TICKS)
        {
          direction = 1;
          duty = LED_PWM_TICKS;
        }
      }
      /* Формирование затухания */
      else
      {
        if(--duty  <= 0)
        {
          direction = 0;
          duty = 0;
        }
      }
      led->tickprev = tickcurrent;
    }
    /* Формирование ШИМ для изменения яркости */
    int duty_crt = (duty*duty/LED_PWM_TICKS);
    if(tickcurrent%LED_PWM_TICKS < duty_crt)
    {
      HAL_GPIO_WritePin(led->LED_Port, led->LED_Pin, led->LED_ActiveLvl);
    }
    else
    {
      HAL_GPIO_WritePin(led->LED_Port, led->LED_Pin, !led->LED_ActiveLvl);
    }
  }
}
//------------------------GPIO LED FUNCTIONS-------------------------
//-------------------------------------------------------------------

//-------------------------------------------------------------------
//------------------------GPIO SW FUNCTIONS-------------------------

/**
* @brief  Инициализировать кнопку (структуру кнопки)
  * @param  sw             Указатель на структуру кнопки
  * @param  GPIOx          Указатель на структуру порта для кнопки
  * @param  GPIO_PIN_X     Пин для кнопки
  * @param  SW_ActiveLevel Состояния пина, когда кнопка нажата
  * @return HAL Status
  */
HAL_StatusTypeDef GPIO_Switch_Init(GPIO_SwitchTypeDef *sw, GPIO_TypeDef *GPIOx, uint32_t GPIO_PIN_X, uint8_t SW_ActiveLevel)
{
  if(check_null_ptr_3(sw, GPIOx, GPIO_PIN_X))
    return HAL_ERROR;
  
  sw->Sw_Port       = GPIOx;
  sw->Sw_Pin        = GPIO_PIN_X;
  sw->Sw_ActiveLvl  = SW_ActiveLevel;
  return HAL_OK;
}

/**
  * @brief  Считать состоянии кнопки 
  * @param  sw  Указатель на структуру кнопки
  * @return 1 - если кнопка нажата, 
  *         0 - если отжата, 
  *         -1 - если ошибка
  * @details  Функция включает в себя неблокирующую проверку на дребезг
  * Т.е. функцию надо вызывать постоянно, чтобы она мониторила состояние кнопки
  */
int GPIO_Read_Switch(GPIO_SwitchTypeDef *sw)
{
  if(check_null_ptr_3(sw, sw->Sw_Port, sw->Sw_Pin))
    return -1;
  
  int current_level = (HAL_GPIO_ReadPin(sw->Sw_Port, sw->Sw_Pin) == sw->Sw_ActiveLvl);
    
  if(sw->Sw_FilterDelay) // если включена защита от дребезга
  {
    // Если таймер не запущен и состояние изменилось - запускаем таймер
    if(sw->tickprev == 0 && current_level != sw->Sw_CurrentState)
    {
      sw->tickprev = local_time();
    }
    
    // Если таймер запущен
    if(sw->tickprev != 0)
    {
      // Проверяем, прошел ли достаточный интервал для фильтрации
      if((local_time() - sw->tickprev) >= sw->Sw_FilterDelay)
      {
        // Обновляем состояние только если оно все еще отличается
        if(current_level != sw->Sw_CurrentState)
        {
          sw->Sw_CurrentState = current_level;
        }
        // Останавливаем таймер в любом случае
        sw->tickprev = 0;
      }
    }
  }
  else // если нет защиты от дребезга
  {
    sw->Sw_CurrentState = current_level;
  }
  return sw->Sw_CurrentState;
}
//------------------------GPIO SW FUNCTIONS-------------------------
//-------------------------------------------------------------------