SysTick->LOAD 与 SysTick->CALIB

SysTick->LOAD vs SysTick->CALIB

我目前正在将我的 DCF77 library (you may find the source code at GitHub) 从 Arduino(基于 AVR)移植到 Arduino Due(ARM Cortex M3)。

库需要精确的 1ms 计时。一个明显的候选者是系统的使用。方便的是,Arduino Due 已经为 1 kHz 的系统设置好了。

但是我的 (AVR) DCF77 库一旦锁定到 DCF77 就能够调整时序。这是通过像这样

操纵定时器重载值来完成的
void isr_handler() {
    cumulated_phase_deviation += adjust_pp16m;
    // 1 / 250 / 64000 = 1 / 16 000 000
    if (cumulated_phase_deviation >= 64000) {
        cumulated_phase_deviation -= 64000;
        // cumulated drift exceeds 1 timer step (4 microseconds)
        // drop one timer step to realign
        OCR2A = 248;
    } else if (cumulated_phase_deviation <= -64000) {
        // cumulated drift exceeds 1 timer step (4 microseconds)
        // insert one timer step to realign
        cumulated_phase_deviation += 64000;
        OCR2A = 250;
    } else {
        // 249 + 1 == 250 == 250 000 / 1000 =  (16 000 000 / 64) / 1000
        OCR2A = 249;
    }

    DCF77_Clock_Controller::process_1_kHz_tick_data(the_input_provider());
}

我想把它移植到 ARM 处理器上。在ARM信息中心我找到了下面的documentation.

Configuring SysTick

...

To configure the SysTick you need to load the SysTick Reload Value register with the interval required between SysTick events. The timer interrupt or COUNTFLAG bit (in the SysTick Control and Status register) is activated on the transition from 1 to 0, therefore it activates every n+1 clock ticks. If a period of 100 is required 99 should be written to the SysTick Reload Value register. The SysTick Reload Value register supports values between 1 and 0x00FFFFFF.

If you want to use the SysTick to generate an event at a timed interval, for example 1ms, you can use the SysTick Calibration Value Register to scale your value for the Reload register. The SysTick Calibration Value Register is a read-only register that contains the number of pulses for a period of 10ms, in the TENMS field (bits 0 to 23). This register also has a SKEW bit (30) that is used to indicate that the calibration for 10ms in the TENMS section is not exactly 10ms due to small variations in clock frequency. Bit 31 is used to indicate if the reference clock is provided.

...

不幸的是,我没有找到任何有关 SysTick->LOAD 和 SysTick->CALIB 是如何连接的信息。也就是说:如果我想限制或加速系统,我是否需要操纵 LOAD 或 CALIB 值?我需要将哪些值放入这些寄存器?

网上搜索也没有更好的提示。也许我在错误的地方搜索。 这些问题有没有更详细的参考资料?或者甚至是一些很好的例子?

AtMega328 datasheet with the Cortex-M3 TRM 相比,突出的一点是计时器的工作方式相反:在 AVR 上,您正在将一个值加载到 OCR2A 并等待 [=11] 中的计时器=] 向上计数,而在 M3 上,您将延迟值加载到 SYST_RVR,然后系统将从该值 向下计数 到 [=13= 中的 0 ].

校准的最大区别是因为比较值固定为 0 并且您只能调整重新加载值,与直接调整比较值相比,您可能会有更多延迟(假设计数器重新加载发生在同时产生中断)。

SYST_CALIB 中的只读值(如果它确实存在,是实现定义的和可选的),仅用于将 SYSTICK 滴答与实际挂钟时间相关联 - 首次初始化计时器时,您需要知道滴答频率以便为您想要的周期选择一个合适的重新加载值,所以有一个寄存器字段说 "this many reference clock ticks happen in 10ms (possibly)" 提供了一些在运行时以可移植的方式计算它的可能性,而不是必须硬编码可能需要针对不同设备更改的值。

然而,在这种情况下,不仅有一个更准确的外部时钟来同步使得这不那么重要,而且至关重要的是,固件已经为您配置了定时器。因此,您可以假设 SYST_RVR 中的任何值都代表足够接近 1KHz,并从那里开始工作 - 事实上,只需微调 1KHz 周期,您甚至不需要知道实际值是多少是的,如果错误在任何一个方向上都太大了,就做 SysTick->LOAD++SysTick->LOAD--


更深入一点,SAM3X datasheet 表明对于该 SoC 中的特定 M3 实现,SYSTICK 具有 10.5 MHz 参考时钟,因此 SYST_CALIB 寄存器 应该 给出 10ms 的 105000 ticks 值。除非它没有,因为显然 Atmel 认为让明确命名的 TENMS 字段给出 1ms、10500 的滴答计数是非常聪明的。精彩

只是因为其他人不必像必须做的那样四处挖掘 - 这是我另外发现的。

在arduino-1.5.8/hardware/arduino/sam/system/CMSIS/CMSIS/Include/core_cm*.h中有操作SysTick的代码。特别是在 core_cm3.h 中有一个函数

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */

  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

然后在arduino-1.5.8/hardware/arduino/sam/variants/arduino_due_x/variant.cpp函数init中有

  // Set Systick to 1ms interval, common to all SAM3 variants
  if (SysTick_Config(SystemCoreClock / 1000))
  {
    // Capture error
    while (true);
  }

由于 SystemCoreClock 的计算结果为 84000000,因此编译结果与 SysTick_Config(84000) 类似。我针对 DCF77 模块验证了 SysTick_Config(84001) 会减慢 SysTicks 而 SysTick_Config(83999) 会加速它。