You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确计数定时器溢出,将32位高精度定时器转为64位

PIC32MZ核心定时器64位计数器实现方案分析

问题背景

我在PIC32MZ微控制器上使用一个以100MHz计数的32位核心定时器,系统运行FreeRTOS多任务,该定时器每42.9秒发生一次溢出。我通过中断服务程序(ISR)维护rollover_count变量来计数溢出次数,希望将其安全转换为64位计数器。现针对以下三种get_counts()函数实现方案,咨询:

  • 哪种方案正确?
  • 应采用何种解决方案?
  • 第一种方案存在哪些问题?

现有溢出ISR代码

static volatile uint32_t rollover_count = 0;

void isr_core_timer_overflow()
{
    rollover_count++;
}

方案一

// 适用于PIC32这类32位微控制器
uint64_t get_counts()
{
    uint32_t count1 = ReadCoreTimer();

    // 无需临界区,因为读取的是32位值,而且关中断也不会阻止核心定时器计数
    uint32_t rollover_count_copy = rollover_count;

    uint32_t count2 = ReadCoreTimer();

    if (count2 < count1)
    {
        // 在读取count1和count2之间刚好发生了溢出,所以手动补计一次;注意:rollover_count会被溢出ISR递增,无需手动更新
        rollover_count_copy++;
    }

    uint64_t total_count = ((uint64_t)rollover_count_copy << 32) | count2;

    return total_count;
}

方案二(含临界区保护)

// 适用于PIC32这类32位微控制器
uint64_t get_counts()
{
    uint32_t count1 = ReadCoreTimer();

    // 保存中断状态
    // 注:`__builtin_*`函数属于XC32编译器,文档可参考DS50002799C(《MPLAB® XC32 C/C++编译器用户指南(PIC32M系列)》)第195、265页附近(函数对应的汇编输出内容在此处)等
    unsigned int isr_state = __builtin_get_isr_state();
    __builtin_disable_interrupts();  // 临界区开始

    uint32_t rollover_count_copy = rollover_count;
    uint32_t count2 = ReadCoreTimer();

    // 恢复中断状态
    __builtin_set_isr_state(isr_state); // 临界区结束

    if (count2 < count1)
    {
        // 在读取count1和count2之间刚好发生了溢出,所以手动补计一次
        rollover_count_copy++;
    }

    uint64_t total_count = ((uint64_t)rollover_count_copy << 32) | count2;

    return total_count;
}

方案三(将count1读取移入临界区)

// 适用于PIC32这类32位微控制器
uint64_t get_counts()
{

    unsigned int isr_state = __builtin_get_isr_state();
    __builtin_disable_interrupts();  // 临界区开始

    uint32_t count1 = ReadCoreTimer();
    uint32_t rollover_count_copy = rollover_count;
    uint32_t count2 = ReadCoreTimer();

    __builtin_set_isr_state(isr_state); // 临界区结束

    if (count2 < count1)
    {
        rollover_count_copy++;
    }

    uint64_t total_count = ((uint64_t)rollover_count_copy << 32) | count2;

    return total_count;
}

方案分析与结论

  1. 方案一的问题

    • 存在计数错误的风险:假设在读取count1之后、读取rollover_count_copy之前发生定时器溢出,ISR会将rollover_count加1。此时count1是溢出前的大值,rollover_count_copy是更新后的新值,后续读取count2得到溢出后的小值,代码会判断count2 < count1为真,进而将rollover_count_copy再加1,最终导致64位计数器多计一次溢出,数据完全错误。
    • 核心原因是rollover_count的读取和定时器计数的读取没有同步,无法保证两者状态的一致性。
  2. 正确方案:方案二

    • 方案二通过临界区保护rollover_count和count2的读取操作,确保这两个操作之间不会触发ISR,避免了rollover_count与当前定时器计数的状态不一致。
    • 临界区外读取的count1用于判断“临界区启动前是否发生溢出”:如果count2 < count1,说明在count1读取后、临界区开始前发生了一次溢出,手动给rollover_count_copy加1即可补上这次计数,逻辑完全正确。
    • 同时临界区仅包含必要的读取操作,耗时极短,对系统实时性影响可以忽略。
  3. 方案三的问题

    • 临界区内读取count1和count2,虽然两次读定时器耗时很短,但完全没必要延长临界区时长。
    • 更关键的是:临界区内关中断后,即使定时器发生溢出,ISR也无法触发更新rollover_count。如果此时count2 < count1(两次读取之间刚好溢出),代码会手动给rollover_count_copy加1,而后续ISR恢复后会再给rollover_count加1,导致重复计数,最终64位计数器值错误。

内容的提问来源于stack exchange,提问作者Gabriel Staples

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 19:40:16