/**
******************************************************************************
* @file pwm_thyristors.c
* @brief Модуль для управления тиристорами
******************************************************************************
* @details
******************************************************************************/
#include "pwm_thyristors.h"
#include "tim.h"

static HAL_StatusTypeDef __PWM_SetOutputState(PWM_Channel_t *hCh, uint32_t state);
static HAL_StatusTypeDef __PWM_ReConfigToSoftwarePulses(void);

/** 
  * @brief    Инициализация ШИМ тиристоров.
  * @param    hpwm  Указатель на хендл ШИМ тиристоров
  * @return   HAL Status.
  * @details  Инициализируется 6 структур на каждый канал ШИМ для тиристора. 
  * И одна dummy структура для безопасной инициализации неизвестной фазы.
  * В конце запускаются все каналы ШИМ (с запрещенным выходом).
  */
HAL_StatusTypeDef PWM_Init(PWM_Handle_t *hpwm)
{
  if(hpwm == NULL)
    return HAL_ERROR;
  
  // Фаза A
  hpwm->AllPhases[PHASE_A_POS].State  = PWM_THYR_DISABLED;
  hpwm->AllPhases[PHASE_A_POS].ChMask = PWM_CHANNEL_1;
  hpwm->AllPhases[PHASE_A_POS].htim   = &hpwm1;
  hpwm->AllPhases[PHASE_A_NEG].State  = PWM_THYR_DISABLED;
  hpwm->AllPhases[PHASE_A_NEG].ChMask = PWM_CHANNEL_2;
  hpwm->AllPhases[PHASE_A_NEG].htim   = &hpwm1;
  
  // Фаза B
  hpwm->AllPhases[PHASE_B_POS].State  = PWM_THYR_DISABLED;
  hpwm->AllPhases[PHASE_B_POS].ChMask = PWM_CHANNEL_3;
  hpwm->AllPhases[PHASE_B_POS].htim   = &hpwm1;  
  hpwm->AllPhases[PHASE_B_NEG].State  = PWM_THYR_DISABLED;
  hpwm->AllPhases[PHASE_B_NEG].ChMask = PWM_CHANNEL_4;
  hpwm->AllPhases[PHASE_B_NEG].htim   = &hpwm1;
  
  // Фаза C
  hpwm->AllPhases[PHASE_C_POS].State  = PWM_THYR_DISABLED;
  hpwm->AllPhases[PHASE_C_POS].ChMask = PWM_CHANNEL_5;
  hpwm->AllPhases[PHASE_C_POS].htim   = &hpwm2;  
  hpwm->AllPhases[PHASE_C_NEG].State  = PWM_THYR_DISABLED;
  hpwm->AllPhases[PHASE_C_NEG].ChMask = PWM_CHANNEL_6;
  hpwm->AllPhases[PHASE_C_NEG].htim   = &hpwm2;
  
  // Фаза неизвестная (чтобы не был указатель в никуда, а был на эту структуру)
  hpwm->AllPhases[PHASE_UNKNOWN].State  = PWM_THYR_DISABLED;
  hpwm->AllPhases[PHASE_UNKNOWN].ChMask = 0;
  hpwm->AllPhases[PHASE_UNKNOWN].htim   = NULL;
  hpwm->f.Initialized = 1;
   
  // Инициализируем ничем. Потом когда словим сеть - поставим конкретные тиристоры
  PWM_SetHalfWave(hpwm, UPP_PHASE_A, UPP_WAVE_UNKNOWED);
  PWM_SetHalfWave(hpwm, UPP_PHASE_B, UPP_WAVE_UNKNOWED);
  PWM_SetHalfWave(hpwm, UPP_PHASE_C, UPP_WAVE_UNKNOWED);
  
  HAL_TIM_PWM_Start(&hpwm1, PWM_CHANNEL_1);
  HAL_TIM_PWM_Start(&hpwm1, PWM_CHANNEL_2);
  HAL_TIM_PWM_Start(&hpwm1, PWM_CHANNEL_3);
  HAL_TIM_PWM_Start(&hpwm1, PWM_CHANNEL_4);
  HAL_TIM_PWM_Start(&hpwm2, PWM_CHANNEL_5);
  HAL_TIM_PWM_Start(&hpwm2, PWM_CHANNEL_6);
  PWM_Stop(hpwm, 0, 1);

  __PWM_ReConfigToSoftwarePulses();
  HAL_TIM_Base_Start(&hpwm1);
  
  return HAL_OK;
}

/** 
  * @brief    Запуск ШИМ.
  * @param    hpwm  Указатель на хендл ШИМ тиристоров
  * @param    Phase На какой фазе надо запустить ШИМ
  * @return   HAL Status.
  */
HAL_StatusTypeDef PWM_Start(PWM_Handle_t *hpwm, UPP_Phase_t Phase)
{
  if(assert_upp_phase(hpwm, Phase))
    return HAL_ERROR;
  if (hpwm->Phase[Phase] == NULL || hpwm->Phase[Phase] == &hpwm->AllPhases[PHASE_UNKNOWN])
    return HAL_ERROR;
  
  if(!(hpwm->Config.PhaseMask.all&(1<<Phase)))
    return HAL_ERROR;
  
  switch(hpwm->Phase[Phase]->State)
  {
    // Если мы НЕ в режиме ожидание - ошибка
    case PWM_THYR_DISABLED:
    case PWM_THYR_TIM_START:
    case PWM_THYR_TIM_ACTIVE:    
    case PWM_THYR_TIM_DONE:
      return HAL_BUSY;

    // Запуск только если таймер в режиме ожидания
    case PWM_THYR_TIM_WAIT:
      hpwm->Phase[Phase]->State = PWM_THYR_TIM_START;
      return HAL_OK;
    
    default:
      return HAL_ERROR;
  }
  return HAL_ERROR;
}

/** 
  * @brief    Остановить ШИМ.
  * @param    hpwm  Указатель на хендл ШИМ тиристоров
  * @param    Phase На какой фазе надо остановить ШИМ
  * @param    force_stop_all Принудительно остановить ВЕСЬ ШИМ
  * @return   HAL Status.
  * @details  Включает канал в режим форсированного неактивного выхода.
  * При передаче 1 в force_stop_all, отключаются MOE (Main Output Enable) и все каналы ШИМ
  */
HAL_StatusTypeDef PWM_Stop(PWM_Handle_t *hpwm, UPP_Phase_t Phase, uint8_t force_stop_all)
{
  if(assert_upp_phase(hpwm, Phase))
    return HAL_ERROR;
  
  // Если force_stop_all - сбрасываем ВСЕ КАНАЛЫ
  if(force_stop_all)
  {
    // в первую очередь выключаем канал, потом выставим режим каналов
    __HAL_TIM_MOE_DISABLE_UNCONDITIONALLY(&hpwm1);
    __HAL_TIM_MOE_DISABLE_UNCONDITIONALLY(&hpwm2);

    // выставляем все каналы в FORCE MODE
    for(int ch = 0; ch < 6; ch++)
    {
      hpwm->AllPhases[ch].State = PWM_THYR_DISABLED;
      __PWM_SetOutputState(&hpwm->AllPhases[ch], PWM_DISABLE);
    }
    hpwm->f.Running = 0;
    return HAL_OK;
  }
  // Если НЕ force_stop_all - сбрасываем ТОЛЬКО заданный канал
  else
  {    
    __PWM_SetOutputState(hpwm->Phase[Phase], PWM_DISABLE);
    return HAL_OK;
  }
}
/** 
  * @brief    Установка полуволны для слежения.
  * @param    hpwm      Указатель на хендл ШИМ тиристоров
  * @param    Phase     Для какой фазы надо установить полуволну
  * @param    halfwave  Какую полуволну установить
  * @return   HAL Status.
  * @details  Меняет указатель канала фазы на канал соответствующей полуволны
  */
HAL_StatusTypeDef PWM_SetHalfWave(PWM_Handle_t *hpwm, UPP_Phase_t Phase, UPP_HalfWave_t halfwave)
{
  if(assert_upp_phase(hpwm, Phase))
    return HAL_ERROR;
  
  // Выставляем текущий активную полуволну
  switch(halfwave)
  {
    case UPP_WAVE_POSITIVE:
      hpwm->Phase[Phase] = &hpwm->AllPhases[Phase];
      hpwm->Phase[Phase]->State = PWM_THYR_TIM_WAIT;
      return HAL_OK;
    
    case UPP_WAVE_NEGATIVE:
      hpwm->Phase[Phase] = &hpwm->AllPhases[Phase+3];
      hpwm->Phase[Phase]->State = PWM_THYR_TIM_WAIT;
      return HAL_OK;
    
    default:
      hpwm->Phase[Phase] = &hpwm->AllPhases[PHASE_UNKNOWN];
      return HAL_ERROR;
  }
}


/** 
  * @brief    Установка параметров ШИМ.
  * @param    hpwm      Указатель на хендл ШИМ тиристоров
  * @param    Frequency Частота в ГЦ
  * @return   HAL Status.
  */
HAL_StatusTypeDef PWM_SetConfig(PWM_Handle_t *hpwm, uint8_t PhaseMask, uint16_t Frequency, float Duty, float PulseLength)
{
  if(assert_upp(hpwm))
    return HAL_ERROR;
  if(hpwm->f.Running) // Если есть активные каналы - ниче не меняем
    return HAL_BUSY;
  
  // Остановка таймера
  HAL_TIM_Base_Stop(&hpwm1);
  
  hpwm->Config.PhaseMask.all = PhaseMask;
  hpwm->Config.PulseLength = PulseLength;
  hpwm->Config.Frequency = Frequency;
  hpwm->Config.Duty = Duty;
  // Высставление периодов
  __HAL_TIM_SET_AUTORELOAD(&hpwm1, TIM_FreqToTick(Frequency, PWM_TIM1_FREQ_MHZ-1));
  __HAL_TIM_SET_AUTORELOAD(&hpwm2, TIM_FreqToTick(Frequency, PWM_TIM8_FREQ_MHZ-1));
  // Скважности
  uint32_t pwm1_duty_ccr = (float)__HAL_TIM_GET_AUTORELOAD(&hpwm1)*(1-Duty);
  uint32_t pwm2_duty_ccr = (float)__HAL_TIM_GET_AUTORELOAD(&hpwm2)*(1-Duty);
  __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_1, pwm1_duty_ccr);
  __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_2, pwm1_duty_ccr);
  __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_3, pwm1_duty_ccr);
  __HAL_TIM_SET_COMPARE(&hpwm1, PWM_CHANNEL_4, pwm1_duty_ccr);
  __HAL_TIM_SET_COMPARE(&hpwm2, PWM_CHANNEL_5, pwm2_duty_ccr);
  __HAL_TIM_SET_COMPARE(&hpwm2, PWM_CHANNEL_6, pwm2_duty_ccr);
  
  
  // Сброс счетчиков таймера и запуск заного
  __HAL_TIM_SET_COUNTER(&hpwm1, 0);
  __HAL_TIM_SET_COUNTER(&hpwm2, 0);  
  
  PWM_Stop(hpwm, 0, 1);

  return HAL_TIM_Base_Start(&hpwm1);
}


/** 
  * @brief    Установка полярности шим.
  * @param    hpwm      Указатель на хендл ШИМ тиристоров
  * @param    polarity  Какая полярность: состяоние бита CCxP
  * @return   HAL Status.
  */
HAL_StatusTypeDef PWM_SetPolarity(PWM_Handle_t *hpwm, int polarity)
{
  if(assert_upp(hpwm))
    return HAL_ERROR;
  
  if(polarity)
  {
    hpwm1.Instance->CCER |= TIM_CCER_CC1P | TIM_CCER_CC2P | TIM_CCER_CC3P | TIM_CCER_CC4P;
    hpwm2.Instance->CCER |= TIM_CCER_CC1P | TIM_CCER_CC2P | TIM_CCER_CC3P | TIM_CCER_CC4P;
  }
  else
  {
    hpwm1.Instance->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC2P | TIM_CCER_CC3P | TIM_CCER_CC4P);
    hpwm2.Instance->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC2P | TIM_CCER_CC3P | TIM_CCER_CC4P);
  }
  return HAL_OK;
}

/** 
  * @brief    Хендл ШИМ тиристоров.
  * @param    hpwm  Указатель на хендл ШИМ тиристоров
  * @return   HAL Status.
  * @details  Автомат состояний, который определяет поведение каналов ШИМ
  */
HAL_StatusTypeDef PWM_Handle(PWM_Handle_t *hpwm, float PeriodMs)
{
  if(assert_upp(hpwm))
    return HAL_ERROR;

  uint8_t phase = 0;
  PWM_Channel_t *hPhase = NULL;

  for(int ch = 0; ch < 6; ch++)
  {
    hPhase = &hpwm->AllPhases[ch];
    if (hPhase->htim == NULL)
      continue;

    switch (hPhase->State)
    {
      case PWM_THYR_DISABLED: // канал отключен
        __PWM_SetOutputState(hPhase, PWM_DISABLE);
        break;

      case PWM_THYR_TIM_WAIT: // канал ожидает команды
        __PWM_SetOutputState(hPhase, PWM_DISABLE);
        break;

      case PWM_THYR_TIM_START: // начать ШИМ (пачка импульсов)
        __PWM_SetOutputState(hPhase, PWM_ENABLE);
        hPhase->PulseCnt = MS_TO_FAST_TICKS(PeriodMs*hpwm->Config.PulseLength) - 1; // 1 импульс уже прошел
        hPhase->State = PWM_THYR_TIM_ACTIVE;
        hpwm->f.Running++;
        break;

      case PWM_THYR_TIM_ACTIVE: // управление пачкой импульсов ШИМ
        hPhase->PulseCnt--;
        if (hPhase->PulseCnt <= 1) // если остался один импльс в след раз идем в PWM_THYR_TIM_DONE
        {
          hPhase->PulseCnt = 0;
          hPhase->State = PWM_THYR_TIM_DONE;
        }
        break;

      case PWM_THYR_TIM_DONE: // пачка импульсов отправлена - отключение
        hPhase->State = PWM_THYR_DISABLED;
        if(hpwm->f.Running)
          hpwm->f.Running--;
        break;

      default: // чзх
        __PWM_SetOutputState(hPhase, PWM_DISABLE);
        break;
    }
  }
  return HAL_OK;
}


//================ ВНУТРЕННИЕ ФУНКЦИИ ===================
/**
  * @brief    Установка режима для канала ШИМ.
  * @param    hpwm  Указатель на хендл ШИМ тиристоров
  * @param    Phase Для какой фазы надо установить состояние
  * @param    state Какое состояние установить
  * @return   HAL Status.
  */
static HAL_StatusTypeDef __PWM_SetOutputState(PWM_Channel_t *hCh, uint32_t state)
{
  if(hCh == NULL)
    return HAL_ERROR;
  if (hCh->htim == NULL)
    return HAL_ERROR;

  // Если режим уже выставлен
  if(hCh->CurrentMode == state)
  {
    return HAL_OK;
  }
    
  hCh->CurrentMode = state;
  
  // выставляем режим каналов
  switch(hCh->ChMask)
  {
    case TIM_CHANNEL_1:
      hCh->htim->Instance->CCMR1 &= ~TIM_OCMODE_PWM2;
      hCh->htim->Instance->CCMR1 |= state;
      break;
    case TIM_CHANNEL_2:
      hCh->htim->Instance->CCMR1 &= ~(TIM_OCMODE_PWM2 << 8);
      hCh->htim->Instance->CCMR1 |= (state << 8);
      break;
    case TIM_CHANNEL_3:
      hCh->htim->Instance->CCMR2 &= ~TIM_OCMODE_PWM2;
      hCh->htim->Instance->CCMR2 |= state;
      break;
    case TIM_CHANNEL_4:
      hCh->htim->Instance->CCMR2 &= ~(TIM_OCMODE_PWM2 << 8);
      hCh->htim->Instance->CCMR2 |= (state << 8);
      break;
    default:
      break;
  }
  
  return HAL_OK;
}

/**
  * @brief    Переконфигурация таймером для софтварного формирования пачки импульсов.
  * @return   HAL Status.
  */
static HAL_StatusTypeDef __PWM_ReConfigToSoftwarePulses(void)
{
  /* One Pulse и Repetitive не используем */
  hpwm1.Instance->RCR = 0;
  hpwm1.Instance->CR1 &= ~TIM_CR1_OPM;
  hpwm2.Instance->RCR = 0;
  hpwm2.Instance->CR1 &= ~TIM_CR1_OPM;
  /* Настраиваем Slave на втором таймере*/
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER;
  sSlaveConfig.InputTrigger = TIM_TS_ITR0;
  return HAL_TIM_SlaveConfigSynchro(&htim8, &sSlaveConfig);
}