在我们的产品中,我们希望使用 LSE 晶体来准确计时大约 0.1 秒的测量。在本例中,我们使用的是
STM32G474 MCU。使用 Nucleo-G474 板进行概念验证工作,我们发现 LSE 时钟在 0.1 秒的时间尺度上存在非常严重的抖动。我们使用 Nucleo 板测试 LSE 稳定性的程序如下:
- 配置 tiM5.1 以在 TI1 上输入 LSE 脉冲。
- 配置 TIM5.2 在 TI2 上输入 PA1 脉冲。
- 启用 LSE。
- 将来自非常稳定的频率发生器的 32.768 kHz 信号馈入 PA1。
- 启用 HSE。
- 将 PLL 配置为 170 MHz。
- 切换 SYSCLK 以运行 170 MHz PLL。
- 重复测量 TIM5.1 和 TIM5.2(两个通道同时测量)的 3272 个脉冲中 170 MHz 滴答的数量。
通过比较在 LSE 的 3272 次计数中经过的 170 MHz HSE 派生滴答数与在我们的参考发生器的 3272 次中经过的滴答数,我们可以看到参考发生器通道上的滴答计数非常稳定,而 LSE 通道上的滴答计数差异很大。
参考发生器测量非常稳定这一事实意味着 170 MHz 时基必须非常稳定,这意味着 HSE 必须非常稳定。一切都非常稳定,以至于该通道上的滴答计数落入 2-3 个 bin。这非常好,也是我们正在寻找的精度,也是我们在测量其他类型的稳定振荡器时通常看到的精度(我们的产品基于精确测量许多不同类型的晶体振荡器)。
另一方面,LSE 测量非常糟糕,在短时间范围内落入 25 个或更多 bin 的范围内。
为了尝试理解这个问题,我们从不同的设计中修改了我们自己的一块(非 Nucleo)板,以便我们可以将来自频率发生器的第二个信号馈送到 LSE OSC_IN 引脚并将其配置为旁路模式(LSEBYP = 1 ). 在这种模式下,使用相同的技术来测量 LSE,我们实现了 2-3 个 bin 的精度,这与通道 TIM5.2 上测量的精度相匹配。因此,这似乎不是备用
电源域和主电源域之间的某种重新同步问题,而是实际上 STM LSE 晶体驱动
电路非常不稳定。
为了说明这个问题,这里有 50 个样本在大约 5 秒的时间内在 Nucleo 板上采集:
- 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
我们可以看到参考生成器与 0x01030411 - 0x01030413 之间的计数非常一致,而 LSE 到处都是。我们的理解是 LSE 应该非常准确,所以这些结果让我们非常失望。
作为进一步的测试,我们将电路板配置为在 RCC_MCO (PA8) 上回显 LSE 时钟,并且只观察第二个上升沿,示波器上似乎有很多可见的抖动,尽管我们没有尝试表征它,除了用眼睛。
我附上了我们用来执行此测量的代码,以防此处可能出现任何问题。请注意,这段代码使用了我们的内部库,但它在做什么应该很明显。由于 TIM5_CH2 上的测量非常稳定,并且旁路模式下 LSE 通道上的测量也非常稳定,我们认为我们正在正确执行测量,问题出在 LSE 晶体或驱动电路的行为上。我们也尝试过改变 LSEDRV 值,但这对抖动没有任何帮助,而且当我们这样做时它实际上改变了 LSE 频率。
有没有其他人试图描述 LSE 在短时间范围内的表现?这里的期望是什么 - 它是否仅在 RTC 使用的 1 秒数量级上准确,以便抖动平均?我们可以做些什么来改善这种表现吗?如果没有办法使STM驱动电路充分发挥作用,我们目前正在考虑在旁路模式下使用外部晶振。
- #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);
- }
- }
感谢其他人可能有的任何见解,并感谢您花时间看这个!
0