4、高精度定时器:辅助时钟(auxiliary clock)配置、使用sysAuxClkConnect实现微秒级定时、高精度定时与CPU占用权衡

4.1 为什么需要辅助时钟?

讲系统时钟的时候,我提过系统时钟的粒度通常是毫秒级的。你想想看,对于大多数任务调度、超时管理来说,1ms 的 tick 完全够用了。但有些场景——比如高速数据采集、脉冲宽度测量、或者某些外设的精确时序控制——1ms 的精度就显得太粗糙了。

这时候,辅助时钟(Auxiliary Clock)就派上用场了。说白了,它就是 VxWorks 提供的第二个独立硬件定时器,专门用来干「系统时钟干不了」的精细活。我个人习惯把它叫做「副定时器」,它和系统时钟共用同一个硬件定时器模块的不同通道,互不干扰。

核心区别一句话:系统时钟负责「调度与超时」,辅助时钟负责「精确计时与事件触发」。

4.2 辅助时钟的硬件基础

辅助时钟的精度取决于板级支持包(BSP)中配置的硬件定时器。大多数 PowerPC、ARM 或 x86 架构的板卡,都会提供一个 32 位或 64 位的自由运行计数器,配合一个比较寄存器来实现定时中断。

嗯,这里要注意:辅助时钟的时钟源通常是 CPU 的主频或者某个外设时钟。比如我曾在某款 Freescale 的 P2020 板卡上,辅助时钟的输入频率是 CPU 主频的一半——800MHz。这意味着理论上它的 tick 周期可以达到 1.25 纳秒。当然,实际应用中我们不会用到这么极端的精度,但微秒级是绰绰有余的。

参数 系统时钟 辅助时钟
典型精度 1ms ~ 10ms 1μs ~ 100μs
中断频率 100Hz ~ 1000Hz 10kHz ~ 1MHz
主要用途 任务调度、超时 精确计时、波形生成
CPU 占用 较高(取决于频率)

4.3 配置辅助时钟:sysAuxClkConnect

配置辅助时钟的核心 API 就是 sysAuxClkConnect。它的原型长这样:

STATUS sysAuxClkConnect
    (
    FUNCPTR routine,   /* 中断服务函数指针 */
    int arg            /* 传递给 ISR 的参数 */
    )

这个函数的作用,就是把你的中断服务函数「挂」到辅助时钟的中断向量上。调用成功后,每次辅助时钟产生中断,你的 ISR 就会被执行。

我在项目中遇到过一个问题:有些 BSP 要求必须先调用 sysAuxClkConnect,再调用 sysAuxClkRateSet 设置频率。顺序反了,ISR 可能不会被触发。所以我的建议是——严格按照 BSP 文档的顺序来,别想当然。

小技巧:在调用 sysAuxClkConnect 之前,最好先确认辅助时钟是否已经被其他驱动占用了。可以用 sysAuxClkShow() 查看当前状态。

4.4 设置定时频率:sysAuxClkRateSet

连接好 ISR 之后,下一步就是设置定时频率:

STATUS sysAuxClkRateSet
    (
    int rate  /* 每秒中断次数,单位 Hz */
    )

举个例子,如果你想要 100μs 的定时周期,那么频率就是 10kHz:

#define TIMER_RATE 10000  /* 10kHz,即 100μs 周期 */

void myAuxIsr(int arg)
{
    /* 你的高精度处理逻辑 */
    /* 注意:ISR 里不要做耗时操作! */
}

void initAuxClock(void)
{
    /* 第一步:连接 ISR */
    if (sysAuxClkConnect((FUNCPTR)myAuxIsr, 0) != OK)
    {
        printf("辅助时钟连接失败!\n");
        return;
    }

    /* 第二步:设置频率 */
    if (sysAuxClkRateSet(TIMER_RATE) != OK)
    {
        printf("辅助时钟频率设置失败!\n");
        return;
    }

    /* 第三步:启动辅助时钟 */
    sysAuxClkEnable();
}

警告:辅助时钟的 ISR 运行在中断上下文中。你想想看,如果 ISR 里调用了 printf 或者 malloc,系统很可能直接挂掉。我曾经见过一个同事在 100kHz 的辅助时钟 ISR 里做浮点运算,结果 CPU 直接跑飞了。嗯,血的教训。

4.5 启动与停止:sysAuxClkEnable / sysAuxClkDisable

这两个函数没什么复杂的,就是开关辅助时钟:

sysAuxClkEnable();   /* 启动 */
sysAuxClkDisable();  /* 停止 */

但有一点要注意:辅助时钟一旦启动,就会持续产生中断,直到你显式调用 sysAuxClkDisable。所以如果你只是临时需要一次定时,记得在 ISR 里关掉它,或者用计数方式控制。

我个人习惯的做法是:在 ISR 里维护一个计数器,达到目标次数后调用 sysAuxClkDisable。这样既实现了单次定时,又避免了不必要的 CPU 开销。

4.6 高精度定时与 CPU 占用的权衡

这是本节最核心的话题。你想想看,辅助时钟的频率越高,精度就越高,但 CPU 的负担也越重。为什么?因为每次中断都要保存上下文、执行 ISR、恢复上下文。如果 ISR 本身还有一定的处理逻辑,那 CPU 的有效利用率就会下降。

我给大家一个经验数据:

  • 1kHz(1ms 周期):CPU 占用几乎可以忽略不计,约 0.1% ~ 0.5%。
  • 10kHz(100μs 周期):CPU 占用约 1% ~ 3%,取决于 ISR 复杂度。
  • 100kHz(10μs 周期):CPU 占用可能达到 10% ~ 30%,ISR 必须极度精简。
  • 1MHz(1μs 周期):除非你的 ISR 只有几条汇编指令,否则 CPU 基本被吃满。

核心原则:精度够用就好,不要盲目追求微秒级。如果你的应用只需要 500μs 的精度,那就用 2kHz 的频率,别上 10kHz。

我曾经在一个数据采集项目中,把辅助时钟设成了 50kHz(20μs 周期)。结果发现系统响应变慢了,任务调度出现明显抖动。后来我把频率降到 10kHz,同时改用 DMA 方式采集数据,问题就解决了。说白了,高精度定时器不是万能的,它需要和系统其他部分协调工作。

4.7 实战建议:如何选择辅助时钟频率

我总结了一个简单的决策流程,供你参考:

  1. 明确需求精度:你的应用到底需要多高的定时精度?是 100μs 还是 10μs?
  2. 评估 ISR 开销:你的 ISR 里要做什么?是简单的置位标志,还是复杂的数据处理?
  3. 估算 CPU 占用:用公式 CPU占用 ≈ 中断频率 × ISR执行时间 粗略估算。
  4. 留有余量:不要让辅助时钟吃掉超过 20% 的 CPU 资源,否则系统其他任务会受影响。
  5. 实测验证:用 spy 工具或者 taskShow 观察 CPU 使用率,确认没有异常。

最后一个小提示:如果你需要的是「一次性」的高精度延时,而不是周期性中断,可以考虑用辅助时钟的硬件比较功能,配合 sysAuxClkConnect 实现单次触发。这样比开一个周期性中断要省资源得多。

好了,关于辅助时钟的内容就讲到这里。下一节我们会聊一个更高级的话题——如何用看门狗定时器(Watchdog Timer)来做任务级别的超时管理。到时候见。