cancel
Showing results for 
Search instead for 
Did you mean: 

Bad jitter on LSE over ~0.1s time scales

In our product, we would like to use the LSE crystal to accurately time a measurement of roughly 0.1 seconds. In this case, we are using an STM32G474 MCU. Using a Nucleo-G474 board for proof-of-concept work, we have found that there is very bad jitter on the LSE clock over time scales of 0.1 seconds. Our procedure to test the LSE stability, using the Nucleo board, is as follows:

  1. Configure TIM5.1 to input LSE pulses on TI1.
  2. Configure TIM5.2 to input PA1 pulses on TI2.
  3. Enable the LSE.
  4. Feed a 32.768 kHz signal from a very stable frequency generator into PA1.
  5. Enable the HSE.
  6. Configure the PLL for 170 MHz.
  7. Switch SYSCLK to run off the 170 MHz PLL.
  8. Repeatedly measure the number of 170 MHz ticks in 3272 pulses of TIM5.1 and TIM5.2 (both channels measured simultaneously).

By comparing the number of 170 MHz HSE-derived ticks that elapse in 3272 counts of the LSE vs. the number of ticks that elapse in 3272 of our reference generator, we can see that the tick counts on the reference generator channel are very stable, while the tick counts on the LSE channel vary significantly.

The fact that the reference generator measurement is very stable implies that the 170 MHz timebase must be very stable which implies that the HSE must be very stable. Everything is so stable that the tick counts on this channel fall into 2-3 bins. This is very good and the kind of accuracy we are looking for and is what we typically see when measuring other types of stable oscillators (our product is based on precisely measuring a number of different type of crystal oscillators).

The LSE measurement, on the other hand, is very bad and over short time frames falls into a range of 25 or more bins.

To try and understand the problem, we modified one of our own (non-Nucleo) boards from a different design so that we could feed a second signal from the frequency generator into the LSE OSC_IN pin and configure it for bypass mode (LSEBYP=1). In this mode, using the same technique to measure the LSE we achieve an accuracy of 2-3 bins which matches the accuracy measured on channel TIM5.2. So, it doesn't appear to be some sort of resynchronization problem between the backup power domain and the main power domain but actually the STM LSE crystal drive circuit being very jittery.

To illustrate the problem, here are 50 samples taken over an approximately 5-second period on the Nucleo board:

    LSE        TIM5.2
    ---------- ----------
 1: 0x01030389 0x01030413
 2: 0x01030382 0x01030412
 3: 0x0103038F 0x01030412
 4: 0x0103038D 0x01030412
 5: 0x0103038E 0x01030413
 6: 0x0103038C 0x01030413
 7: 0x0103038B 0x01030412
 8: 0x01030391 0x01030412
 9: 0x0103038C 0x01030412
10: 0x01030392 0x01030411
11: 0x01030394 0x01030412
12: 0x01030387 0x01030411
13: 0x01030388 0x01030412
14: 0x01030397 0x01030412
15: 0x0103038B 0x01030413
16: 0x0103038E 0x01030412
17: 0x01030388 0x01030412
18: 0x0103038D 0x01030412
19: 0x0103038A 0x01030412
20: 0x0103038D 0x01030411
21: 0x01030390 0x01030412
22: 0x0103038B 0x01030412
23: 0x01030388 0x01030413
24: 0x0103038B 0x01030413
25: 0x01030387 0x01030412
26: 0x0103038A 0x01030412
27: 0x0103038A 0x01030411
28: 0x01030390 0x01030412
29: 0x01030389 0x01030412
30: 0x0103038C 0x01030411
31: 0x01030389 0x01030412
32: 0x01030384 0x01030413
33: 0x0103038A 0x01030412
34: 0x01030389 0x01030412
35: 0x01030386 0x01030412
36: 0x0103038F 0x01030411
37: 0x01030383 0x01030412
38: 0x0103038F 0x01030412
39: 0x0103038A 0x01030412
40: 0x01030396 0x01030411
41: 0x0103038F 0x01030412
42: 0x0103038F 0x01030413
43: 0x01030395 0x01030412
44: 0x0103038F 0x01030412
45: 0x0103038C 0x01030411
46: 0x0103038F 0x01030411
47: 0x0103038E 0x01030412
48: 0x01030393 0x01030412
49: 0x0103038B 0x01030412
50: 0x01030390 0x01030412

We can see that the reference generator is very consistent with counts between 0x01030411 - 0x01030413, while the LSE is all over the place. Our understanding is that the LSE is supposed to be extremely accurate, so these results are very disappointing to us.

As a further test, we configured the board to echo the LSE clock on RCC_MCO (PA8) and just eyeballing the second rising edge there seems to be a lot of jitter visible on the oscilloscope, although we didn't try to characterize it other than by eye.

I am attaching the code we used to perform this measurement in case there may be any issues here. Note that this code uses our internal libraries, but it should be pretty obvious what it is doing. Because the measurement on TIM5_CH2 is very stable and because the measurement on the LSE channel in bypass mode is also very stable, we think that we are performing the measurement correctly and that the problem lies with the behaviour of the LSE crystal or drive circuit. We have also tried varying the LSEDRV values but this has not helped the jitter in any way and it actually changes the LSE frequency when we do this.

Has anyone else attempted to characterize the LSE performance over short time scales? What is the expectation here - is it only accurate over scales on the order of 1 second used by the RTC so that the jitter averages out? Is there anything we can do to improve this performance? We are currently considering using an external crystal oscillator in bypass mode if there's no way to make the STM drive circuit perform adequately.

#define LSE_MODE_XTAL   1
#define LSE_MODE_BYPASS 2
#define LSE_MODE        LSE_MODE_XTAL
 
#if LSE_MODE == LSE_MODE_XTAL
#define LSE_DRV_VAL     1
#endif
 
// The number of prescaled edges to count on each TIM5 channel.  We set the
// prescaler to 8, and want to perform measurements of roughly 0.1 seconds on
// the LSE and TIM5.2 timer inputs which are both running at 32.768 kHz, so an
// edge count of 409 is appropriate.
#define TIM_EDGE_MEASURE_COUNT  409
 
// We discard the first N edges counted on each TIM5 channel to ensure we are
// aligned to the rising edge of a pulse.
#define TIM_EDGE_DISCARD_COUNT  2
 
// The total number of edges we should count.
#define TIM_EDGE_TOTAL_COUNT \
    (TIM_EDGE_MEASURE_COUNT + TIM_EDGE_DISCARD_COUNT)
 
int
main()
{
    // Initialize trace logging.
    trace_init();
 
    // Reset the backup domain.
    RCC.enable_rcc_device(PWR);
    PWR.unlock_backup_domain();
    RCC.reset_backup_domain();
 
    // Start the LSE.
#if LSE_MODE == LSE_MODE_XTAL
    RCC->bdcr = (LSE_DRV_VAL << 3);
    RCC->bdcr = (LSE_DRV_VAL << 3) | 1;
#elif LSE_MODE == LSE_MODE_BYPASS
    RCC->bdcr = 0x00000004;
    RCC->bdcr = 0x00000005;
#endif
 
    // Configure PLL for 170 MHz.
    PWR.set_vcore_voltage(stm32g4::VCORE_1V28);
    RCC.enable_hse();
    RCC.select_hsclk(stm32g4::HSCLK_SOURCE_HSE);
    RCC.configure_pllr(6,85,2);
    FLASH.set_latency(4);
    RCC.set_hpre(2);
    RCC.select_sysclk(stm32g4::SYSCLK_SOURCE_PLLRCLK);
    __spin_delay_m4(85);
    RCC.set_hpre(1);
 
    // Configure pin PA1 to act as TIM5_CH2.
    RCC.enable_rcc_device(GPIOA);
    GPIOA.configure(1,2,stm32::GPIO_AF);
 
    // Configure TIM5 to measure LSE pulses on channel 1 and TIM5_CH2 pulses
    // on channel 2, both with a prescaler value of 8.  Channels 3 and 4 are
    // put into input mode and disabed.
    RCC.enable_rcc_device(TIM5);
    TIM5->ccer    = 0;
    TIM5->cr1     = 0x00000000;
    TIM5->tisel   = 0x00000002;
    TIM5->ccmr[0] = 0x00003D3D;
    TIM5->ccmr[1] = 0x00000101;
    TIM5->arr     = 0xFFFFFFFF;
    TIM5->psc     = 0;
 
    // Repeatedly count edges on TIM5.1 and TIM5.2, discarding the first 2
    // edges from each channel to ensure we are aligned to the start of a pulse.
    for (;;)
    {
        // Reset TIM5 for the new measurement.
        TIM5->cnt  = 0;
        TIM5->sr   = 0;
        TIM5->cr1  = 0x00000001;
        TIM5->ccer = 0x00000011;
 
        // Wait for the desired number of edges.
        uint32_t lse_edges     = 0;
        uint32_t ch2_edges     = 0;
        uint32_t lse_start_cnt = 0;
        uint32_t ch2_start_cnt = 0;
        uint32_t lse_end_cnt   = 0;
        uint32_t ch2_end_cnt   = 0;
        while (lse_edges < TIM_EDGE_TOTAL_COUNT ||
               ch2_edges < TIM_EDGE_TOTAL_COUNT)
        {
            uint32_t sr = TIM5->sr;
            if (sr & (1 << 1))
            {
                uint32_t ccr1 = TIM5->ccr[0];
                if (lse_edges < TIM_EDGE_TOTAL_COUNT)
                {
                    ++lse_edges;
                    if (lse_edges == TIM_EDGE_DISCARD_COUNT)
                        lse_start_cnt = ccr1;
                    else if (lse_edges == TIM_EDGE_TOTAL_COUNT)
                        lse_end_cnt = ccr1;
                }
            }
            if (sr & (1 << 2))
            {
                uint32_t ccr2 = TIM5->ccr[1];
                if (ch2_edges < TIM_EDGE_TOTAL_COUNT)
                {
                    ++ch2_edges;
                    if (ch2_edges == TIM_EDGE_DISCARD_COUNT)
                        ch2_start_cnt = ccr2;
                    else if (ch2_edges == TIM_EDGE_TOTAL_COUNT)
                        ch2_end_cnt = ccr2;
                }
            }
        }
 
        // Stop TIM5.
        TIM5->ccer = 0;
        TIM5->cr1  = 0x00000000;
 
        // Transmit/record the number of elapsed TIM ticks on each channel.
        uint32_t lse_ticks = (lse_end_cnt - lse_start_cnt);
        uint32_t ch2_ticks = (ch2_end_cnt - ch2_start_cnt);
        TRACE("(nedges) (lse_ticks) (ch2_ticks)",
              TIM_EDGE_MEASURE_COUNT,lse_ticks,ch2_ticks);
    }
}

Thanks for any insights others may have, and for taking any time to look at this!

1 ACCEPTED SOLUTION

Accepted Solutions

> Are you referring to the main PLL that is being driven by the HSE,

Yes.

> The measurement of the reference signal on TIM5.2 seems to imply that the combination of HSE + 170 MHz PLL is actually extremely stable.

IMO extremely stable would not allow deviation other than 1 tick, yet we see 2 ticks.

> I bet the LSE waveform is more sinusoidal than square

Of course it's a *pure* sinus - that's the whole point of using a crystal, it's the best possible filter for the money.

> Why are the LSE comparators so bad compared to the HSE comparators which seem quite good?

The temporal uncertainty of LSE is a fraction of 1/32kHz, assuming uncertainty of 1% of period it would be 300ns. The 170MHz against which you measure it is almost perfect, i.e. you measure with resolution of cca 6ns; so you should see around 50 counts of jitter - you actually see around 20 so the jitter either is somewhat better, or the measurement process somewhat hides it.

OTOH, uncertainty of HSE is fraction of 1/12MHz=83.3ns, so assuming an absolutely perfect PLL and absolutely perfect external sampling source, you won't be able to see any difference unless the deviation is cca >6ns which is >7%, 10 times what you see with LSE. Actually, the 1-cycle difference you see might be it - it's hard to tell what's PLL's contribution, the characterization in DS (cycle-to-cycle jitter) is not very informative in telling what PLL's loop's contribution may actually be.

> The HSE and LSE crystals shift their frequencies primarily due to temperature and the ratio of HSE:LSE can be measured during a calibration procedure

I'm not sure what would be the outcome of this, even if it would be measured over a reasonably long time. The frequency shift is not monotonic, contrary; so you probably can't unambiguously calibrate one from the other anyway. It would be probably more reasonable to measure the temperature of the HSE crystal e.g. using a cheap thermistor placed very close to it.

JW

PS. What's that language, C++?

View solution in original post

8 REPLIES 8
LCE
Principal

You could start by checking the RDY bit in RCC->bdcr.

Next, try changing the drive level with LSE_DRV_VAL.

I can only guess what some functions are doing... so are you sure that all needed peripheral clocks are enabled, though I'm not sure if that's necessary for settings to the LSE.

I wouldn't be concerned.

What you see is the noise of the input comparator which "digitizes" the oscillator's output (plus the small jitter from PLL). You see it as a rather high cycle-to-cycle jitter. What it tells you is, that LSE is not suitable for short-time timing (0.1s) which was never the purpose of LSE, but it tells you absolutely nothing about the intended purpose of LSE, i.e what's its stability in long term.

If the reference is precise, the long term deviation of LSE appears to be cca 138/ 0x01030413 which is cca 8pps, which is pretty good for a typical watch crystal.

JW

Ah, the LSERDY check was indeed omitted, although it doesn't change the result. LSERDY becomes set to 1 after the MCU counts 4096 pulses from the crystal, and since it doesn't apply to LSEBYP mode I did omit it accidentally. The LSE clock is not released to other peripherals such as TIM5.1 until LSERDY=1, though, and the algorithm is explicitly discarding the first two pulses measured on TIM5.1 so it happens to work by implicitly waiting for it to be set. I agree it is the wrong way to do it though!

As I mentioned in the post, varying LSEDRV does not help with the jitter. All peripheral clocks are properly enabled.

Are you referring to the main PLL that is being driven by the HSE, or by a different PLL associated with the LSE circuit? The measurement of the reference signal on TIM5.2 seems to imply that the combination of HSE + 170 MHz PLL is actually extremely stable.

The deviation of 138 counts isn't bad at all and part of our commissioning process would be to calibrate each board to find the LSE center frequency at various temperature setpoints.

Why are the LSE comparators so bad compared to the HSE comparators which seem quite good? Are the same comparators used when the LSE is in bypass mode where we have seen very stable results? If so, does this imply that the LSE in non-bypass mode has a very slow rise time so tiny bits of noise at the comparator input cause the jitter? I have certainly seen in other applications the symptom of measurements "smearing" over many bins when measuring a sine wave vs. a square wave and attribute it to the input comparators or triggers acting as you describe.

Danish1
Lead II

I would expect the LSE to be stable. Both over short-term counts and long-term.

But what I don't expect to be good short-term is any pll derived from LSE, even though that will be good long-term.

The comparison-rate is too low (it can't be any faster than half a cycle of LSE). The phase-locking can't clean up any phase noise lower than that comparison-rate. So close-in you're down to the phase-noise of the VCO that generates the 170 MHz.

And when you start looking at individual counts of the pll output, you have to include the multiplication-factor of the pll, in this case about 5000, whereas for HSE it is more like 170 (I think it's normal in stm32 to divide HSE down to 1 or 2 MHz for PLL).

Hope this helps,

Danish

I think Jan's answer is probably the key; I bet the LSE waveform is more sinusoidal than square so noise in the comparators is causing the jitter over short periods.

We don't intend to feed the LSE into a PLL (although, on the STM32U5 the MSI clocks can be compensated with the LSE interestingly enough). What we are actually trying to do is measure the ratio of the HSE:LSE over an 0.1-second interval where we are also performing measurements on other inputs. The HSE and LSE crystals shift their frequencies primarily due to temperature and the ratio of HSE:LSE can be measured during a calibration procedure in the lab environment over many temperature setpoints so that the true HSE frequency (or, at least, a better estimate of the HSE frequency) can be extracted from the ratio in the production environment. The end goal being to know the actual HSE value more accurately for the measurements we are doing on the other inputs. If the jitter on the LSE is high then measuring the HSE:LSE ratio may not be of much use to us for this purpose.

> Are you referring to the main PLL that is being driven by the HSE,

Yes.

> The measurement of the reference signal on TIM5.2 seems to imply that the combination of HSE + 170 MHz PLL is actually extremely stable.

IMO extremely stable would not allow deviation other than 1 tick, yet we see 2 ticks.

> I bet the LSE waveform is more sinusoidal than square

Of course it's a *pure* sinus - that's the whole point of using a crystal, it's the best possible filter for the money.

> Why are the LSE comparators so bad compared to the HSE comparators which seem quite good?

The temporal uncertainty of LSE is a fraction of 1/32kHz, assuming uncertainty of 1% of period it would be 300ns. The 170MHz against which you measure it is almost perfect, i.e. you measure with resolution of cca 6ns; so you should see around 50 counts of jitter - you actually see around 20 so the jitter either is somewhat better, or the measurement process somewhat hides it.

OTOH, uncertainty of HSE is fraction of 1/12MHz=83.3ns, so assuming an absolutely perfect PLL and absolutely perfect external sampling source, you won't be able to see any difference unless the deviation is cca >6ns which is >7%, 10 times what you see with LSE. Actually, the 1-cycle difference you see might be it - it's hard to tell what's PLL's contribution, the characterization in DS (cycle-to-cycle jitter) is not very informative in telling what PLL's loop's contribution may actually be.

> The HSE and LSE crystals shift their frequencies primarily due to temperature and the ratio of HSE:LSE can be measured during a calibration procedure

I'm not sure what would be the outcome of this, even if it would be measured over a reasonably long time. The frequency shift is not monotonic, contrary; so you probably can't unambiguously calibrate one from the other anyway. It would be probably more reasonable to measure the temperature of the HSE crystal e.g. using a cheap thermistor placed very close to it.

JW

PS. What's that language, C++?

> IMO extremely stable would not allow deviation other than 1 tick, yet we see 2 ticks.

I would characterize that as "perfectly stable"! Depending on how the LSE and 170 MHz clocks align for a specific measurement, I would expect perfectly stable clocks to sometimes clip one 170 MHz tick when the LSE clock is center-aligned in the measurement vs. counting that tick when the LSE clock is more left- or right-aligned in the measurement. I attribute the extra bin to noise in the system and maybe even actual fine temperature fluctuations affecting the HSE or MCU as a whole.

> It would be probably more reasonable to measure the temperature of the HSE crystal e.g. using a cheap thermistor placed very close to it.

Which of course comes with its own set of issues. Like trying to get a nice stable ADC reading out of an ST part... We need the LSE to be running to wake us up periodically to do our measurements (this is a battery-powered device that samples over a period of weeks or months) so it seemed natural to try and use it for "free".

> PS. What's that language, C++?

Yes, our libraries are all in C++ which is why you see strange things like "RCC.foo()" and "RCC->reg".

Thanks for your reply, this has been very insightful.