解决在FreeRTOS运行时无法进行精确微秒与毫秒延时问题

解决在 FreeRTOS 运行时无法进行精确微秒与毫秒延时问题

目录

1. 问题背景

2. 常见延时方法及其局限性

3. 解决方案:使用 DWT 计数器

4. 在 FreeRTOS 中正确使用 DWT 延时

5. 总结


1. 问题背景

在 FreeRTOS 运行环境下,通常使用 vTaskDelay()vTaskDelayUntil() 进行任务延时。但这些函数的延时精度是 Tick 级,默认最小单位为 1 Tick(比如 1ms),无法满足 精确到微秒级 的延时需求。

对于某些应用,例如 传感器通信(DHT11)、软件定时、信号采集,我们需要更高精度的微秒级或毫秒级延时。


2. 常见延时方法及其局限性

方法 1:vTaskDelay()(毫秒级)
vTaskDelay(pdMS_TO_TICKS(10));  // 延时 10ms

优点: 使用 FreeRTOS 任务调度,不阻塞 CPU。
缺点:Tick 时间粒度限制,无法实现微秒级精确延时。

方法 2:for 循环软件延时(不可取)
void delay_us(uint32_t us) {
    for (volatile uint32_t i = 0; i < us * 10; i++);  // 假设 SystemCoreClock 72MHz
}

缺点:

  • 编译优化 影响,延时时间不准确。
  • 占用 CPU,无法执行其他任务。
  • 不同频率 MCU 需要调整延时系数,不通用。
方法 3:SysTick 计数(受限于 FreeRTOS)
  • FreeRTOS 会占用 SysTick,无法直接使用 SysTick->VAL 进行延时。
  • 需使用 DWT(数据观察点和跟踪)计数器 实现高精度延时。

3. 解决方案:使用 DWT 计数器

DWT(Data Watchpoint and Trace)是 Cortex-M 内核中的调试组件,其中的 DWT->CYCCNT 计数器每个 CPU 时钟周期递增 1。我们可以利用它实现 高精度的微秒级延时

SystemCoreClock = 72MHz时:

DWT->CYCCNT 每 1 个周期增加 1。

1 微秒(us) = 72 个时钟周期

通过 DWT->CYCCNT 计算 us 级别的时间。

默认情况下,DWT 计数器是关闭的,我们需要手动启用它。

3.1 启用 DWT

DWT 计数器默认是关闭的,我们需要手动启用:

void DWT_Init(void) {
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // 使能 DWT 计数器
    DWT->CYCCNT = 0;  // 计数器清零
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // 使能 CYCCNT 计数功能
}
3.2 实现微秒延时
void delay_us(uint32_t us) {
    uint32_t start = DWT->CYCCNT;  // 记录起始计数值
    uint32_t ticks = us * (SystemCoreClock / 1000000); // 计算需要的周期数
    while ((DWT->CYCCNT - start) < ticks); // 等待计时完成
}

说明:

  • SystemCoreClock / 1000000 计算 1 微秒所需的 CPU 周期数。
  • DWT->CYCCNT 不会受到 FreeRTOS 调度影响,确保高精度计时。
3.3 实现毫秒延时
void delay_ms(uint32_t ms) {
    while (ms--) delay_us(1000);
}

将这个代码进行与RTOS结合进阶一下:

static u8 fac_us = 0;  // us延时倍乘数
static u16 fac_ms = 0; // ms延时倍乘数,在FreeRTOS下,代表每个节拍的ms数
// 延时nms
void delay_ms(u32 nms)
{
    if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) // 系统已经运行
    {
        if (nms >= fac_ms) // 延时的时间大于OS的最少时间周期
        {
            vTaskDelay(nms / fac_ms); // FreeRTOS延时
        }
        nms %= fac_ms; // OS已经无法提供这么小的延时了,采用普通方式延时
    }
    delay_us((u32)(nms * 1000)); // 普通方式延时
}

3.4 实现不会引起任务调度的毫秒延时

static u8 fac_us = 0;  // us延时倍乘数
static u16 fac_ms = 0; // ms延时倍乘数,在FreeRTOS下,代表每个节拍的ms数
// 延时nms,不会引起任务调度
void delay_xms(u32 nms)
{
    u32 ticks;
    u32 told, tnow, tcnt = 0;
    u32 reload = SysTick->LOAD; // LOAD的值
    ticks = nms * fac_us * 1000; // 需要的节拍数 (nms * 1000 * fac_us)
    told = SysTick->VAL; // 刚进入时的计数器值
    while (1) {
        tnow = SysTick->VAL;
        if (tnow != told) {
            if (tnow < told) {
                tcnt += told - tnow; // SYSTICK是递减计数器
            } else {
                tcnt += reload - tnow + told;
            }
            told = tnow;
            if (tcnt >= ticks) {
                break; // 时间超过/等于要延迟的时间,则退出
            }
        }
    }
}


4. 在 FreeRTOS 中正确使用 DWT 延时

由于 FreeRTOS 任务调度不允许长时间阻塞,我们可以在 非实时任务(如数据采集、I/O 操作)中使用 delay_us(),但对于任务调度仍然推荐 vTaskDelay() 进行 非阻塞式等待

示例:在任务中读取 DHT11 传感器
void DHT11_Task(void *pvParameters) {
    uint8_t temp, humi;
    while (1) {
        if (DHT11_Read_Data(&temp, &humi) == 0) {
            printf("Temp: %d, Humi: %d\n", temp, humi);
        }
        vTaskDelay(pdMS_TO_TICKS(1000));  // 每 1 秒读取一次
    }
}
  • DHT11 传感器 需要 delay_us() 来正确读取数据。
  • 任务本身仍使用 vTaskDelay() 进行定时,避免 CPU 资源浪费。

5. 总结

问题: FreeRTOS 中 vTaskDelay() 仅支持 Tick 级延时,无法实现微秒级精确延时。
解决方案: 启用 DWT->CYCCNT 计数器,实现 高精度微秒级延时
最佳实践:

  • 短时间延时(<1ms) → 使用 delay_us()
  • 长时间延时(≥1ms) → 使用 vTaskDelay(),减少 CPU 占用。
  • 任务中使用高精度延时结合 FreeRTOS 任务调度,避免长时间阻塞 CPU

通过 DWT 计数器,我们可以在 FreeRTOS 下实现 精确微秒级延时,满足高精度计时需求!🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值