/**
  ******************************************************************************
  * @file    Project/Internal_OSC_Calib/src/InternOscCalibration.c
  * @author  MCD Application Team
  * @version V1.1.0
  * @date    24-January-2012
  * @brief   Main program body
  ******************************************************************************
  * @attention
  *
  * THE PRESENT FIRMWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS
  * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE
  * TIME. AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY
  * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING
  * FROM THE CONTENT OF SUCH FIRMWARE AND/OR THE USE MADE BY CUSTOMERS OF THE
  * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS.
  *
  * FOR MORE INFORMATION PLEASE READ CAREFULLY THE LICENSE AGREEMENT FILE
  * LOCATED IN THE ROOT DIRECTORY OF THIS FIRMWARE PACKAGE.
  *
  * <h2><center>&copy; COPYRIGHT 2012 STMicroelectronics</center></h2>
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "stm32l1xx.h"
#include "InternOscCalibration.h"
#include "main.h"

/** @addtogroup Internal_OSC_Calib
  * @{
  */

/* Private typedef -----------------------------------------------------------*/
NVIC_InitTypeDef NVIC_InitStructureForCalib;
RCC_ClocksTypeDef RCC_ClocksForCalib;

/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Calibrates internal oscillators (HSI or MSI) to the minimum computed 
  *         error.
  *         The system clock source is checked:
  *           - If HSI oscillator is used as system clock source, HSI is calibrated
  *             and the new HSI value is returned.
  *           - If MSI oscillator is used as system clock source, MSI is calibrated
  *             and the new MSI value is returned.
  *           - Otherwise function returns 0.
  * @param  None.
  * @retval The optimum computed frequency of HSI/MSI oscillator.
  *         Returning 0 means that the system clock source is neither HSI nor MSI.
  */
uint32_t InternOsc_CalibrateMinError(void)
{
  uint32_t measuredfrequency = 0;
  uint32_t optimumfrequency = 0;
  uint32_t frequencyerror = 0;
  uint32_t optimumfrequencyerror = 99999000; /* Large value */
  uint32_t numbersteps = 0;         /* Number of steps: size of trimming bits */
  uint32_t trimmingvalue = 0;
  uint8_t optimumcalibrationvalue = 0;
  uint8_t loopcounter = 0;

  /* Configure GPIO ports before starting calibration process */
  GPIO_ConfigForCalibration();

  /* Configure clock before starting calibration process */
  CLK_ConfigForCalibration();

  /* Configure TIM10 before starting calibration process */
  TIM10_ConfigForCalibration();

  /* Get system clock frequency: value is in RCC_ClocksForCalib.SYSCLK_Frequency */
  RCC_GetClocksFreq(&RCC_ClocksForCalib);

  /* Check the system clock source */
  if (RCC_GetSYSCLKSource() == 0x04) /* Internoscsource is HSI */
  {
     /* HSITRIM is 5-bit length */
     numbersteps = 32; /* number of steps is 2^5 = 32 */
  }
  else if (RCC_GetSYSCLKSource() == 0x00) /* internoscsource is MSI */
  {
     /* MSITRIM is 8-bit length */
     numbersteps = 256; /* number of steps is 2^8 = 256 */
  }
  else
  {
    /* Invalid clock source: Clock source should be HSI or MSI */
    numbersteps = 0;
  }

  /* Internal Osc frequency measurement for numbersteps */
  for (trimmingvalue = 0; trimmingvalue < numbersteps; trimmingvalue++)
  {
    /* Set the Intern Osc trimming bits to trimmingvalue */
    RCC_AdjustCalibrationValue(RCC_GetSYSCLKSource(), trimmingvalue);

    /* Start frequency measurement for current trimming value */
    measuredfrequency = 0;
    loopcounter = 0;

    /************ Start measuring Internal Oscillator frequency ***************/
    while (loopcounter <= NUMBER_OF_LOOPS)
    {
      CaptureState = CAPTURE_START;
      
      /* Generate update */
      TIM_GenerateEvent(TIM10, TIM_EventSource_Update);

      /* Clear all TM10 flags */
      TIM10->SR = 0;

      /* Enable capture 1 interrupt */
      TIM_ITConfig(TIM10, TIM_IT_CC1, ENABLE);

      /* Enable TIM10 counter */
      TIM_Cmd(TIM10, ENABLE);

      /* Enable the TIM10 IRQ channel */
      NVIC_InitStructureForCalib.NVIC_IRQChannelCmd = ENABLE;
      NVIC_Init(&NVIC_InitStructureForCalib);

      /* Wait for end of capture: two consecutive captures */
      while(CaptureState != CAPTURE_COMPLETED);

      /* Disable IRQ channel */
      NVIC_InitStructureForCalib.NVIC_IRQChannelCmd = DISABLE;
      NVIC_Init(&NVIC_InitStructureForCalib);

      /* Disable TIM10 */
      TIM_Cmd(TIM10, DISABLE);

      if (loopcounter != 0)
      {
        /* Compute the frequency (the Timer prescaler isn't included) */
        measuredfrequency += (uint32_t) (REFERENCE_FREQUENCY * Capture);
      }

      /* Increment loop counter */
      loopcounter++;
    }
    /**************************** END of Measurement **************************/

    /* Compute the average value corresponding the the current trimming value */
    measuredfrequency = (uint32_t)((TIM_GetPrescaler(TIM10) + 1) * (measuredfrequency / NUMBER_OF_LOOPS));

    /* Compute current frequency error corresponding to the current trimming value:
       measured value is subtracted from the typical one */
    frequencyerror = ABS_RETURN((int32_t) (measuredfrequency - RCC_ClocksForCalib.SYSCLK_Frequency));

    /* Get the nearest frequency value to typical one */
    if (optimumfrequencyerror > frequencyerror)
    {
      optimumfrequencyerror = frequencyerror;
      optimumcalibrationvalue = trimmingvalue;
      optimumfrequency = measuredfrequency;
    }
  }

  /* Set trimming bits corresponding to the nearest frequency */
  RCC_AdjustCalibrationValue(RCC_GetSYSCLKSource(), optimumcalibrationvalue);

  /* Return the intern oscillator frequency after calibration */
  return (optimumfrequency);
}

/**
  * @brief  Calibrates the internal oscillator (HSI only) with the maximum allowed 
  *         error value set by user.
  *         If this value was not found, this function sets the oscillator
  *         to default value.
  * @param  MaxAllowedError: maximum absolute value allowed of the HSI frequency 
  *                          error given in Hz.
  * @param  Freq: pointer to an uint32_t variable that will contain the value  
  *               of the internal oscillator frequency after calibration.
  * @retval ErrorStatus:
  *            - SUCCESS: a frequency error =< MaxAllowedError was found.
  *            - ERROR: a frequency error =< MaxAllowedError was not found.
  */
ErrorStatus HSI_CalibrateFixedError(uint32_t MaxAllowedError, uint32_t* Freq)
{
  uint32_t measuredfrequency;
  uint32_t frequencyerror = 0;
  uint8_t loopcounter = 0;
  uint8_t trimmingindex = 0;
  uint8_t trimmingvalue = 16;
  int8_t Sign = 1;
  ErrorStatus calibrationstatus = ERROR;

  /* Configure the GPIO ports before starting calibration process */
  GPIO_ConfigForCalibration();
  
  /* Configure clock before starting calibration process */
  CLK_ConfigForCalibration();

  /* Configure TIM10 before starting calibration process */
  TIM10_ConfigForCalibration();

  /* Get system clock frequency: value is in RCC_ClocksForCalib.SYSCLK_Frequency */
  RCC_GetClocksFreq(&RCC_ClocksForCalib);

  /* RC Frequency measurement for different values */
  for(trimmingindex = 0; trimmingindex < 32; trimmingindex++)
  {
    /* Compute trimming value */
    trimmingvalue = trimmingvalue + (trimmingindex * Sign);
    Sign *= (-1);

    /* Set the HSITRIMR register to trimmingvalue to be ready for measurement */
    RCC_AdjustHSICalibrationValue(trimmingvalue);

    /* Start frequency measurement for current trimming value */
    measuredfrequency = 0;
    loopcounter = 0;

    /************ Start measuring Internal Oscillator frequency ***************/
    while (loopcounter <= NUMBER_OF_LOOPS)
    {
      CaptureState = CAPTURE_START;

      /* Generate update */
      TIM_GenerateEvent(TIM10, TIM_EventSource_Update);

      /* Clear all TM10 flags */
      TIM10->SR = 0;

      /* Enable capture 1 interrupt */
      TIM_ITConfig(TIM10, TIM_IT_CC1, ENABLE);

      /* Enable TIM */
      TIM_Cmd(TIM10, ENABLE);
      NVIC_InitStructureForCalib.NVIC_IRQChannelCmd = ENABLE;
      NVIC_Init(&NVIC_InitStructureForCalib);

      /* Wait for end of capture: two consecutive captures */
      while(CaptureState != CAPTURE_COMPLETED);

      /* Disable IRQ channel */
      NVIC_InitStructureForCalib.NVIC_IRQChannelCmd = DISABLE;
      NVIC_Init(&NVIC_InitStructureForCalib);

      /* Disable TIM */
      TIM_Cmd(TIM10, DISABLE);

      if (loopcounter != 0)
      {
        /* Compute the frequency (the Timer prescaler isn't included) */
        /* Then add the current frequency to previous cumulation */
        measuredfrequency += (uint32_t) (REFERENCE_FREQUENCY * Capture);
      }

      /* Increment loop counter */
      loopcounter++;
    }
    /**************************** END of Measurement **************************/
    /* Compute current frequency Error corresponding to the current HSITRIM value */
    /* Compute the average value corresponding the the current trimming value */
    measuredfrequency = (uint32_t)((TIM_GetPrescaler(TIM10) + 1) * (measuredfrequency / NUMBER_OF_LOOPS));

    /* Compute current frequency error corresponding to the current trimming value:
       measured value is subtracted from the typical one */
    frequencyerror = ABS_RETURN((int32_t) (measuredfrequency - RCC_ClocksForCalib.SYSCLK_Frequency));

    /* Check if frequency error is less or equal to value set by the user */
    if(frequencyerror <= MaxAllowedError)
    {
      calibrationstatus = SUCCESS; /* The calibration has succeed */
      break; /* stop searching and measurements for frequencies */
    }
  }

  /* Save the HSI measured value */
  *Freq = measuredfrequency;

  /* If the frequency error set by the user was not found */
  if(calibrationstatus == ERROR)
  {
    /* Set the HSITRIMR register to default value */
     RCC_AdjustHSICalibrationValue(16);
  }

  /* Return the calibration status: ERROR or SUCCESS */
  return (calibrationstatus);
}

/**
  * @brief Calibrates the internal oscillator (MSI only) with the maximum allowed error 
  *        value set by user.
  *        If this value was not found, this function sets the oscillator
  *        to default value.
  * @param MaxAllowedError: maximum absolute value allowed of the internal 
  *                         oscillator frequency error given in Hz.
  * @param Freq: pointer to an uint32_t variable that will contain the value  
  *              of MSI oscillator frequency after calibration.
  * @retval ErrorStatus:
  *      - SUCCESS: a frequency error =< MaxAllowedError was found.
  *      - ERROR: a frequency error =< MaxAllowedError was not found.
  */
ErrorStatus MSI_CalibrateFixedError(uint32_t MaxAllowedError, uint32_t* Freq)
{
  uint32_t measuredfrequency;
  int32_t frequencyerror = 0;
  uint32_t absfrequencyerror = 0;
  uint8_t loopcounter = 0;
  uint16_t trimmingindex = 0;
  uint8_t calibrationvalue;
  uint8_t max = 0xFF;
  uint8_t min = 0x00;
  uint8_t mid = 0x00;
  int8_t trimmingvalue = 0;
  ErrorStatus calibrationstatus = ERROR;

  /* Configure the GPIO ports before starting calibration process */
  GPIO_ConfigForCalibration();
  /* Configure clock before starting calibration process */
  CLK_ConfigForCalibration();
  /* Configure TIM10 before starting calibration process */
  TIM10_ConfigForCalibration();

  /* Get system clock frequency: value is in RCC_ClocksForCalib.SYSCLK_Frequency */
  RCC_GetClocksFreq(&RCC_ClocksForCalib);

  /* Get calibration value MSICAL[7:0] */
  calibrationvalue = ((RCC->ICSCR & 0x00FF0000) >> 16);

  while((trimmingindex < 256) && (calibrationstatus == ERROR))
  {
    /* Compute the middle */
    mid = ((max + min) >> 1);
    trimmingvalue = (mid - calibrationvalue);

    /* Set the MSITRIM bits to trimmingvalue to be ready for measurement */
    RCC_AdjustMSICalibrationValue(trimmingvalue);

    /* Start frequency measurement for current trimming value */
    measuredfrequency = 0;
    loopcounter = 0;

    /************ Start measuring Internal Oscillator frequency ***************/
    while (loopcounter <= NUMBER_OF_LOOPS)
    {
      CaptureState = CAPTURE_START;

      /* Generate update */
      TIM_GenerateEvent(TIM10, TIM_EventSource_Update);

      /* Clear all TM10 flags */
      TIM10->SR = 0;

      /* Enable capture 1 interrupt */
      TIM_ITConfig(TIM10, TIM_IT_CC1, ENABLE);

      /* Enable TIM */
      TIM_Cmd(TIM10, ENABLE);
      NVIC_InitStructureForCalib.NVIC_IRQChannelCmd = ENABLE;
      NVIC_Init(&NVIC_InitStructureForCalib);

      /* Wait for end of capture: two consecutive captures */
      while(CaptureState != CAPTURE_COMPLETED);

      /* Disable IRQ channel */
      NVIC_InitStructureForCalib.NVIC_IRQChannelCmd = DISABLE;
      NVIC_Init(&NVIC_InitStructureForCalib);

      /* Disable TIM */
      TIM_Cmd(TIM10, DISABLE);

      if (loopcounter != 0)
      {
        /* Compute the frequency (the Timer prescaler isn't included) */
        /* Then add the current frequency to previous cumulation */
        measuredfrequency += (uint32_t) (REFERENCE_FREQUENCY * Capture);
      }

      /* Increment loop counter */
      loopcounter++;
    }
    /**************************** END of Measurement **************************/
    /* Compute current frequency Error corresponding to the current MSITRIM value */
    /* Compute the average value corresponding the the current trimming value */
    measuredfrequency = (uint32_t)((TIM_GetPrescaler(TIM10) + 1) * (measuredfrequency / NUMBER_OF_LOOPS));

    /* Compute current frequency error corresponding to the current trimming value */
    frequencyerror = (measuredfrequency - RCC_ClocksForCalib.SYSCLK_Frequency);
    absfrequencyerror = ABS_RETURN(frequencyerror);

    if (absfrequencyerror < MaxAllowedError)
    {
      /* Calibration succeeded */
      calibrationstatus = SUCCESS;

      /* Save the MSI measured value */
      *Freq = measuredfrequency;
    }
    else if (frequencyerror < 0)
    {
      /* Update the minimum */
      min = mid;
    }
    else
    {
      /* Update the maximum */
      max = mid;
    }

    /* Increment trimming index */
    trimmingindex++;
  }

  /* If calibration doesn't succeed to find a frequency with the allowed error
     set trimming bits to default value */
  if (calibrationstatus == ERROR)
  {
    /* Set MSITRIM bits to default value */
    RCC_AdjustMSICalibrationValue(0);
  }

  /* Return the calibration status: ERROR or SUCCESS */
  return (calibrationstatus);
}

/**
  * @brief  Adjust calibration value (writing to trimming bits) of selected oscillator.
  * @param  InternOsc: Internal Oscillator source: HSI or MSI
  * @param  TrimmingValue: calibration value to be written in trimming bits.
  * @retval None.
  */
void RCC_AdjustCalibrationValue(uint8_t InternOsc, uint32_t TrimmingValue)
{
  if (InternOsc == 0x04) /* InternOsc is HSI */
  {
    RCC_AdjustHSICalibrationValue(TrimmingValue);
  }
  else if (InternOsc == 0x00) /* InternOsc is MSI */
  {
    RCC_AdjustMSICalibrationValue(TrimmingValue);
  }
}

/**
  * @brief Configures the TIM10 in input capture to measure HSI frequency.
  * @param  None.
  * @retval None.
  */
void TIM10_ConfigForCalibration(void)
{
  TIM_ICInitTypeDef  TIM_ICInitStructure;

  /* Enable TIM10 clock */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM10, ENABLE);

  /* Reset TIM10 registers */
  TIM_DeInit(TIM10);

  /* Configure TIM10 prescaler */
  TIM_PrescalerConfig(TIM10, TIM10_COUNTER_PRESCALER, TIM_PSCReloadMode_Immediate);

#ifdef USE_REFERENCE_LSE
  /* Connect LSE clock to TIM10 Input Capture 1 */
  TIM_RemapConfig(TIM10, TIM10_LSE);
#else
  /* Connect GPIO to TIM10 Input Capture 1 */
  TIM_RemapConfig(TIM10, TIM10_GPIO);
#endif /* USE_REFERENCE_LSE */

  /* TIM10 configuration: Input Capture mode ---------------------
     The reference clock(LSE or external) is connected to TIM10 CH1
     The Rising edge is used as active edge,
     The TIM10 CCR1 is used to compute the frequency value 
  ------------------------------------------------------------ */
  TIM_ICInitStructure.TIM_Channel     = TIM_Channel_1;
  TIM_ICInitStructure.TIM_ICPolarity  = TIM_ICPolarity_Rising;
  TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
  TIM_ICInitStructure.TIM_ICPrescaler = TIM10_IC_DIVIDER;
  TIM_ICInitStructure.TIM_ICFilter = 0x0;
  TIM_ICInit(TIM10, &TIM_ICInitStructure);

  /* Enable the TIM10 global Interrupt */
  NVIC_InitStructureForCalib.NVIC_IRQChannel = TIM10_IRQn;
  NVIC_InitStructureForCalib.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructureForCalib.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructureForCalib.NVIC_IRQChannelCmd = DISABLE;
  NVIC_Init(&NVIC_InitStructureForCalib);
}

/**
  * @brief Configures the clock
  * @param  None.
  * @retval None.
  */
void CLK_ConfigForCalibration(void)
{
  /* Set AHB and APB2 prescalers to 1 so TIM10 is clocked by SYSCLK (no divider) */
  RCC_HCLKConfig(RCC_SYSCLK_Div1);
  RCC_PCLK2Config(RCC_HCLK_Div1);

#ifdef USE_REFERENCE_LSE
  /* Enable PWR clock */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);

  /* Allow access to RTC */
  PWR_RTCAccessCmd(ENABLE);
  /* Reset Backup Domain */
  RCC_RTCResetCmd(ENABLE);
  RCC_RTCResetCmd(DISABLE);

  /* LSE Enable */
  RCC_LSEConfig(RCC_LSE_ON);

  /* Wait till LSE is ready */
  while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
  {}
#endif /* USE_REFERENCE_LSE */
}

/**
  * @brief Configures the GPIO: configure each used I/O in the suitable setting
  * @param None.
  * @retval None.
  */
void GPIO_ConfigForCalibration(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;

#ifdef OUTPUT_INTERNOSC_ON_MCO

  /* Enable the GPIOA peripheral */ 
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

  /* Output the system clock on MCO pin (PA.08) */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  /* Select PA8 as MCO pin */
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_MCO);

  /* Check the system clock source */
  if (RCC_GetSYSCLKSource() == 0x04)  /* system clock source is HSI */
  {
    /* Output HSI on MCO pin */
    RCC_MCOConfig(RCC_MCOSource_HSI, RCC_MCODiv_1);
  }
  else if (RCC_GetSYSCLKSource() == 0x00) /* system clock source is MSI */
  {
    /* Output MSI on MCO pin */
    RCC_MCOConfig(RCC_MCOSource_MSI, RCC_MCODiv_1);
  }
#endif /* OUTPUT_INTERNOSC_ON_MCO */

#ifndef USE_REFERENCE_LSE
  /* Enable the GPIOB peripheral */ 
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

  /* TIM10 channel 10 pin (PB.08) configuration */
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_40MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_8;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_TIM10);
#endif /* USE_REFERENCE_LSE */
}

/**
  * @}
  */ 

/******************* (C) COPYRIGHT 2012 STMicroelectronics *****END OF FILE****/
