cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L0 how to calibrate HSI using LSE

Federico Calia
Associate II
Posted on March 30, 2018 at 10:24

Hi! I have an STM32L0 nucleo board and I want to calibrate HSI using LSE; I have read the application note: AN4631 that explain a solution: at page 12 is possible to see the Hardware connection with TIM21 CH1 in capture/compare configuration;

But... I don't knew how to translate this info into CUBE-MX configurator for a correct result 

:(

Could you please tell me how to set it? please help me, thank you very very much.

Bye bye

#calibrate-hsi-using-lse
15 REPLIES 15
Posted on April 01, 2018 at 13:57

I would have a free-running clock in the HSI isr, like this:

HSI_ISR(void) {
 clear the flag
 //increment HSI 32-bit counter -> 16-bit timer assumed
 HSI_cnt += 0x10000ul;
}
�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

You can then monitor the LSE overflow - either polling or isr:

 if (LSE overflow) {
 //read HSI counts
 HSI_ticks = HSI_cnt | TIMx->CNT; //code not yet atomic
 //calculate ticks elapsed since last LSE overflow
 HSI_ticks_elapsed = HSI_ticks - HSI_ticks_prev;
 //update previous time ticks
 HSI_ticks_prev = HSI_ticks;
 }
�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

it assumes an up-counter but the same logic applies to a down-counter as well.

HSI_ticks_elapsed contains the number of ticks since the last LSE overflow -> it should be close to 4M, in your case (1:1 prescaler, no repetition, ...)

Federico Calia
Associate II
Posted on April 02, 2018 at 16:27

thank you very much for the great help,

unfortunately I'm still not very good and many things I have not understood yet, sorry...

here my TIMERS settings:

/* TIM3 init function */

void MX_TIM3_Init(void)

{

TIM_ClockConfigTypeDef sClockSourceConfig;

TIM_MasterConfigTypeDef sMasterConfig;

htim3.Instance = TIM3;

htim3.Init.Prescaler = 40-1;

htim3.Init.CounterMode = TIM_COUNTERMODE_UP;

htim3.Init.Period = 10-1;

htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

if (HAL_TIM_Base_Init(&htim3) != HAL_OK)

{

_Error_Handler(__FILE__, __LINE__);

}

sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;

if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)

{

_Error_Handler(__FILE__, __LINE__);

}

sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;

sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)

{

_Error_Handler(__FILE__, __LINE__);

}

}

/* TIM21 init function */

void MX_TIM21_Init(void)

{

TIM_MasterConfigTypeDef sMasterConfig;

TIM_IC_InitTypeDef sConfigIC;

htim21.Instance = TIM21;

htim21.Init.Prescaler = 0;

htim21.Init.CounterMode = TIM_COUNTERMODE_UP;

htim21.Init.Period = 0;

htim21.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

if (HAL_TIM_IC_Init(&htim21) != HAL_OK)

{

_Error_Handler(__FILE__, __LINE__);

}

sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;

sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

if (HAL_TIMEx_MasterConfigSynchronization(&htim21, &sMasterConfig) != HAL_OK)

{

_Error_Handler(__FILE__, __LINE__);

}

sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;

sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;

sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;

sConfigIC.ICFilter = 0;

if (HAL_TIM_IC_ConfigChannel(&htim21, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)

{

_Error_Handler(__FILE__, __LINE__);

}

if (HAL_TIMEx_RemapConfig(&htim21, TIM21_TI1_LSE) != HAL_OK)

{

_Error_Handler(__FILE__, __LINE__);

}

}

And... My IC routines...

/**

* @brief This function handles TIM3 global interrupt.

*/

void TIM3_IRQHandler(void)

{

/* USER CODE BEGIN TIM3_IRQn 0 */

/* USER CODE END TIM3_IRQn 0 */

HAL_TIM_IRQHandler(&htim3);

/* USER CODE BEGIN TIM3_IRQn 1 */

HSI_cnt++;

/* USER CODE END TIM3_IRQn 1 */

}

/**

* @brief This function handles TIM21 global interrupt.

*/

void TIM21_IRQHandler(void)

{

/* USER CODE BEGIN TIM21_IRQn 0 */

/* USER CODE END TIM21_IRQn 0 */

HAL_TIM_IRQHandler(&htim21);

/* USER CODE BEGIN TIM21_IRQn 1 */

if(LSE_cnt++>=32768)

{

HSI_ticks = HSI_cnt;

LSE_cnt = 0;

HSI_cnt = 0;

}

/* USER CODE END TIM21_IRQn 1 */

}

the two counters are uint32_t. what I observe is that if there is a second timer, (HSI, in the example tim3)... The LSE tick counter no longer works properly... in fact trying to make the toggle of the LED known that the frequency visibly slows down. as in the tim3 configuration: 

htim3.Init.Prescaler = 40-1;

htim3.Init.Period = 10-1;

with an sysclk of 4Mhz I'm waiting to observe 10000 ticks of the tim3 in one second.

I do not understand why with such a simple code the result obtained is so far from what is expected... I have been using stm32 for a few days and I have a long way to deliver... I would not ask too much but where could I find a simple example? I think I understand your examples, but I can not implement them yet, sorry Thenk you very very much
Federico Calia
Associate II
Posted on April 06, 2018 at 11:00

Hi to All!!

I moved to the use of low-level libraries, this is what I managed to do:

timers init:

-   TIM2 has a prescaler of '1' and as autoreload: '65535' because it is 16-bit, I have an interrupt everytime the counter      reaches '65535'.

-   TIM21 uses LSE as clock source, no prescaler or autoreload needed...

/* TIM2 init function */

void MX_TIM2_Init(void)

{

   LL_TIM_InitTypeDef TIM_InitStruct;

   /* Peripheral clock enable */

   LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2);

   /* TIM2 interrupt Init */

   NVIC_SetPriority(TIM2_IRQn, 0);

   NVIC_EnableIRQ(TIM2_IRQn);

   TIM_InitStruct.Prescaler = 1;

   TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;

   TIM_InitStruct.Autoreload = 65535;

   TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;

   LL_TIM_Init(TIM2, &TIM_InitStruct);

   LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL);

   LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_UPDATE);

   LL_TIM_DisableMasterSlaveMode(TIM2);

//-----

   LL_TIM_EnableIT_UPDATE(TIM2);

   LL_TIM_EnableCounter(TIM2);

//-----

}

/* TIM21 init function */

void MX_TIM21_Init(void)

{

   LL_TIM_InitTypeDef TIM_InitStruct;

   /* Peripheral clock enable */

   LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM21);

   TIM_InitStruct.Prescaler = 0;

   TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;

   TIM_InitStruct.Autoreload = 0;

   TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;

   LL_TIM_Init(TIM21, &TIM_InitStruct);

   LL_TIM_SetTriggerOutput(TIM21, LL_TIM_TRGO_UPDATE);

   LL_TIM_DisableMasterSlaveMode(TIM21);

   LL_TIM_IC_SetActiveInput(TIM21, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_DIRECTTI);

   LL_TIM_IC_SetPrescaler(TIM21, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1);

   LL_TIM_IC_SetFilter(TIM21, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1);

   LL_TIM_IC_SetPolarity(TIM21, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_RISING);

   LL_TIM_SetRemap(TIM21, LL_TIM_TIM21_TI1_RMP_LSE);

//-----

   LL_TIM_EnableIT_CC1(TIM21);

   LL_TIM_CC_EnableChannel(TIM21, LL_TIM_CHANNEL_CH1);

   LL_TIM_EnableCounter(TIM21);

//-----

}

Now... the interrupt routines...

volatile uint32_t    LSE_cnt = 0;

volatile uint32_t    HSI_cnt = 0;

volatile uint16_t    HSI_overflow_cnt = 0;

void TIM2_IRQHandler(void)

{

      if(LL_TIM_IsActiveFlag_UPDATE(TIM2) == 1)

      {

            HSI_overflow_cnt++;                           /*everytime there is an interrupt i'm increment the overflow counter*/

            LL_TIM_ClearFlag_UPDATE(TIM2);   /* Clear the update interrupt flag*/

      }

}

void TIM21_IRQHandler(void)

{

      if(LL_TIM_IsActiveFlag_CC1(TIM21) == 1)

      {

            if(LSE_cnt++>=32768)

            {

                  HSI_cnt = (65536*HSI_overflow_cnt+LL_TIM_GetCounter(TIM2));      /*HSI_cnt = (overflow_cnt * max_counter_value_setted) + actual counter value*/

                  HSI_overflow_cnt = 0;                                                                            /*reset overflow_cnt*/

                  LSE_cnt = 0;                                                                                           /*reset LSE_cnt*/

                  LL_TIM_SetCounter(TIM2, 0);                                                                /*reset TIM2 counter*/

           }

           LL_TIM_ClearFlag_CC1(TIM21);    /* Clear the update interrupt flag*/

    }

}

With this configuration... If I put a breakpoint after the 'HSI_cnt' update I read a value in the the range... '2066370 - 2067220'...

and it seems a value not appropriate ... since I have a sysclock of 4Mhz and an APB1 timer clock of 4Mhz... the same.

What do you think about the code? 

I can not understand what is escaping me...

:(

Thank you very much

Federico Calia
Associate II
Posted on April 06, 2018 at 13:16

Wow... maybe I solved...

I got about 2Mhz instead of 4Mhz, setting the prescaler to '0' I actually get values on average: 4'065'775...

Stopping the counter in this way and making it start again immediately after zero...

I get values closer to 4000000...

void TIM21_IRQHandler(void)

{

      if(LL_TIM_IsActiveFlag_CC1(TIM21) == 1)

      {

            if(LSE_cnt++>=32768)

            {

                  LL_TIM_DisableCounter(TIM2);

                  HSI_cnt = (65536*HSI_overflow_cnt+LL_TIM_GetCounter(TIM2));

                  HSI_overflow_cnt = 0;

                  LSE_cnt = 0;

                  LL_TIM_SetCounter(TIM2, 0);

                  LL_TIM_EnableCounter(TIM2);

            }

            LL_TIM_ClearFlag_CC1(TIM21);

      }

}

(ex: 4000657, 4001225,  4000627, 4000543, 3998503)

what do you think?

do the results obtained and the way in which they were obtained seem to you to be effective?

Is it possible to improve this system? or it is quite good?

Thank you very much for your attention and patience,

have a good day

Posted on April 16, 2018 at 00:23

http://www.efton.sk/STM32/timchain.zip

is a demo (based on the L053 DISCO, with installed 768kHz crystal) of what should result in as precise measurement of the MSI/HSI-vs.-LSE as it gets, purely in hardware, with no software influencing the precision:

0690X0000060AbVQAU.png

The 16-bit limitation of TIM2 is mitigated by counting overflows in the update interrupt (and accounting for the case when overflow happens after capture but before the status register is read).

The findings for both HSI16 and MSI are - unsurprisingly - that they permanently fluctuate up and down hundreds of ppm, around a center offset from the nominal by tenths of % (except the MSI at its lowest setting of 64kHz, which was wildly off).

The example is barebones, written for gcc, using only the CMSIS-mandated headers (to be extracted from CubeL0). Results are simply written to memory, to be observed by a debugger.

Enjoy!

JW

PS A similar thread

https://community.st.com/thread/49041-rtc-calibration-stm32l011g4

Posted on April 16, 2018 at 02:34

(except the MSI at its lowest setting of 64kHz, which was wildly off)

It was wildly off because the STM32 timers are strictly synchronous (except LPTIM, but that isn't linked), channels have an edge detector at their front-end thus edges of the 32kHz LSE signal at the input of timer clocked at around 64kHz sometimes simply got lost...

I tweaked the code so that TIM21 CH1 OR is set to MCO, and MCO is set to LSE div 2, and it works as expected, but is not in the published version yet.

JW