Skip to main content
Federico Calia
Associate
March 30, 2018
Question

STM32L0 how to calibrate HSI using LSE

  • March 30, 2018
  • 10 replies
  • 5231 views
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
    This topic has been closed for replies.

    10 replies

    waclawek.jan
    Super User
    March 30, 2018
    Posted on March 30, 2018 at 13:34

    I don't use CubeMX so I don't know how to click it in there, but it's only about setting the TIM21_OR register.

    0690X0000060AKyQAM.png

    Looking into Cube's sources,  this should be the incantation to use:

    0690X0000060AKjQAM.png

    JW

    Federico Calia
    Associate
    March 30, 2018
    Posted on March 30, 2018 at 17:22

    Thank you very much

    Bye bye

    Federico Calia
    Associate
    March 30, 2018
    Posted on March 30, 2018 at 22:43

    Well, I was able to associate the LSE clock source to channel 1 of the timer,

    0690X0000060ALSQA2.png

    It was enabled by the HAL function:

    HAL_TIM_IC_Start_IT(&htim21,TIM_CHANNEL_1);

    and I have verified that it works fine.

    Now: I associated to ''Trigger Event Selection'' : Update Event.

    And for each tick of the LSE I get an interrupt, managed in the routine:

    TIM21_IRQHandler(void);

    Here all the possible configurations:

    0690X0000060ALXQA2.png

    And I don't knew what is the best solution to obtainting a correct HSI calibration from the LSE.

    In my mind is sufficient to get a fixed number of ticks from one and from the other oscillator,

    than is necessary to compare expected values with obtained values and is possible to obtain the error.

    For example: I get 32768 ticks from my LSE crystal and 4000000 ticks from my sys clock (4Mhz) in one second.

    If at the 32768th tick I have the HSI counter only with 39999990 ticks, in one second there was an

    error of 4000000-39999990 = 10 ticks on the HSI RC clock and I have to update the HSI16CAL register.

    But I don't knew if this solution is good and fine.

    What do you think about that? are there better strategies to get the result?  I thank you very much

    Bye

    waclawek.jan
    Super User
    March 30, 2018
    Posted on March 30, 2018 at 23:31

    You don't need that second step, setting TRGO  - that would be used if you'd want to link internally some other timer to TIM21.

    The method is roughly correct - in the interrupt, you count up to 32768 and then read out the capture register (TIM21_CCR1) to find out the number of HSI ticks. This all is assuming there is no PLL used and there is no divider on the APB buses.

    As there will be some setup time, it's better to read out the CCR1 in the first and then in the 32769th interrupt and subtract them.

    Note that the dependency of HSI16 frequency on the HSI16CAL is nonlinear, 'jumping' down by typ. 1.5% every 16 values; and even between the 'jumps' the 'tuning step' of HSI16 is rather large, typ. 0.4%, so out of 4000000 it's more 16000 than 10.

    JW

    henry.dick
    Associate II
    March 30, 2018
    Posted on March 31, 2018 at 00:10

    '

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

    forget about Cube for now. here is the gist of the task: you have two clocks, one presumed to be accurate and another not. how do you use the accurate clock to measure the not-so-accurate clock?

    1) set up the accurate clock to increment counter 1, and the inaccurate to increment counter 2; zero both counters

    2) run counter 1 for a known period of time (= known number of cycles), while counter 2 is incrementing.

    3) at the end of the run of counter 1, read the count in counter 2.

    there should be a mathematical relationship between the two frequencies and the two counts. that forms the basis of your calibration.

    now, all you need is to implement it in your favorite CubeMX, or any other mcu you may encounter in the future, ST or not.

    Federico Calia
    Associate
    March 30, 2018
    Posted on March 31, 2018 at 00:30

    Thank you very much!

    Now I knew what to do, and how to do it...

    I hope (:

    I'll try tomorrow, bye bye

    Federico Calia
    Associate
    March 31, 2018
    Posted on March 31, 2018 at 18:48

    I have created a new timer based on internal clock (4Mhz), and I set

     the following configuration parameters:

    htim2.Init.Prescaler = 399;

    htim2.Init.Period = 1;

    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;

    And everytime the timer connected to LSE reaches one second (32768 pulses) I read the other timer and then I set it to '0'.

    According to the settings I have: 1/4'000'000 = 0.00000025 as clock pulse period, and then an interrupt every (0.00000025*400 = 0.0001) 0.0001 seconds... And

     I expect to read in one second: 10000 pulses. Is It correct? 

    Because This is not my real result; the number of interrupts connected to my counter are, for every second, a number from 6784 to 6865... And I don't understand why

    :(

     is this a possible error introduced by debug?

    What do you think is wrong?

    Thank you very much

    henry.dick
    Associate II
    March 31, 2018
    Posted on March 31, 2018 at 21:59

    '

    What do you think is wrong?'

    no code, no tell.

    conceptually, at a 1x prescaler, your timer connected to the 4Mhz oscillator would have accumulated 4M ticks. Assuming that it is a 16-bit timer, it would have overflown 4M/64K = 61x, and has a residual reading of 2304.

    So that's what you should expect. I would increment a MSB counter in the ISR and read it + the counter value in the other timer's ISR and then reset them. the combination of the two should yield a number that's very close to 4M if both clocks are accurate.

    Federico Calia
    Associate
    April 2, 2018
    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
    April 6, 2018
    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
    April 6, 2018
    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

    waclawek.jan
    Super User
    April 16, 2018
    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

    waclawek.jan
    Super User
    April 16, 2018
    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