当前位置: 首页 > news >正文

滴答时钟延时

SysTick 工作原理

SysTick 是 Cortex-M 内核的一个递减计数器,时钟源可配置为处理器时钟或外部时钟。初始化时需设置 LOAD 寄存器作为重装载值,VAL 寄存器存储当前计数值,从 LOAD 开始递减至 0 后自动重载。

周期计算方式为 SysTick->LOAD - SysTick->VAL,表示当前已走过的时钟周期数。时钟配置通常使用处理器时钟(SysTick_CTRL_CLKSOURCE_Msk),并启用中断(SysTick_CTRL_TICKINT_Msk)和计数器(SysTick_CTRL_ENABLE_Msk)。

解读一些不清晰的地方

我们注意到SysTick是一个递减计数器

初始化时我们设置了LOAD

它是一个重装载的值

VAL是当前计数器的值,

它会从LOAD开始向下递减

到达0之后,会被重新赋值为LOAD的值

 

SysTick->LOAD - SysTick->VAL是当前走过的周期数

关键宏定义

#define TICKS_PER_MS (SystemCoreClock / 1000)  // 1ms 对应的时钟周期数
#define TICKS_PER_US (SystemCoreClock / 1000000)  // 1us 对应的时钟周期数

初始化函数

void cpu_tick_init(void) {SysTick->LOAD = TICKS_PER_MS;  // 设置重装载值为 1ms 的周期数SysTick->VAL = 0;  // 清空当前计数值SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |  // 使用处理器时钟SysTick_CTRL_TICKINT_Msk |    // 允许中断SysTick_CTRL_ENABLE_Msk;      // 启动计数器
}

时间获取函数

uint64_t cpu_now(void) {uint64_t now, last_count;do {last_count = cpu_tick_count;  // 记录中断累积的计数值now = cpu_tick_count + (SysTick->LOAD - SysTick->VAL);  // 计算当前总周期数} while (last_count != cpu_tick_count);  // 避免读取时中断触发导致数据不一致return now;
}uint64_t cpu_get_us(void) {return cpu_now() / TICKS_PER_US;  // 转换为微秒
}uint64_t cpu_get_ms(void) {return cpu_now() / TICKS_PER_MS;  // 转换为毫秒
}

延时函数

void cpu_delay_us(uint32_t us) {uint64_t now = cpu_now();while (cpu_now() - now < (uint64_t)us * TICKS_PER_US);  // 等待指定微秒数
}void cpu_delay_ms(uint32_t ms) {uint64_t now = cpu_now();while (cpu_now() - now < (uint64_t)ms * TICKS_PER_MS);  // 等待指定毫秒数
}

中断处理函数

void SysTick_Handler(void) {cpu_tick_count += TICKS_PER_MS;  // 每次中断增加 1ms 的周期数
}

注意事项

  1. 原子操作cpu_now() 使用循环检查确保数据一致性,避免中断导致读取错误。
  2. 时钟频率SystemCoreClock 需正确配置为当前处理器时钟频率。
  3. 中断优先级:SysTick 中断优先级需合理设置,避免影响其他关键任务。
  4. 溢出处理cpu_tick_count 为 64 位变量,可支持长时间运行,但需注意延时函数的参数范围。

原子操作解读

初始状态设定

text

系统时钟: 168MHz
TICKS_PER_MS = 168,000
LOAD = 168,000

当前时刻:
cpu_tick_count = 1,000,000  (表示已运行约5.95秒)
SysTick->VAL = 500          (当前ms还剩500个周期)

场景:在读取过程中发生中断

⚠️ 没有 do-while 的情况

c

uint64_t cpu_now_bad(void)
{
// 直接计算,没有保护
return cpu_tick_count + SysTick->LOAD - SysTick->VAL;
}

执行过程:

text

时间点1: 读取 cpu_tick_count = 1,000,000
时间点2: 读取 VAL = 500
时间点3: **发生中断!** cpu_tick_count 变成 1,168,000
时间点4: 计算 now = 1,000,000 + 168,000 - 500 = 1,167,500
返回: 1,167,500 ❌

结果分析:

  • 正确时间应该是:1,168,000 + (168,000 - 500) = 1,168,000 + 167,500 = 1,335,500
  • 实际返回:1,167,500
  • 误差:1,335,500 - 1,167,500 = 168,000 个周期 = 1ms的误差!

✅ 有 do-while 的情况

c

uint64_t cpu_now_good(void)
{
uint64_t now, last_count;
do {
        last_count = cpu_tick_count;     // 第1次: 1,000,000
        now = cpu_tick_count + SysTick->LOAD - SysTick->VAL;  // 计算: 1,167,500
} while (last_count != cpu_tick_count);  // 检查: 1,000,000 ≠ 1,168,000 → 重新循环
   
// 第2轮循环:
do {
        last_count = cpu_tick_count;     // 第2次: 1,168,000
        now = cpu_tick_count + SysTick->LOAD - SysTick->VAL;  // 计算: 1,335,500
} while (last_count != cpu_tick_count);  // 检查: 1,168,000 = 1,168,000 → 退出
   
return now;  // 返回: 1,335,500 ✓
}

加锁的意义在于,防止在计算时,突然发生了中断,而cpu_tick_count却来不及更新而导致的误差

源代码:

uint64_t cpu_now(void)

{

    uint64_t now, last_count;

    do {

        last_count = cpu_tick_count;

        now = cpu_tick_count + SysTick->LOAD - SysTick->VAL;

    } while (last_count != cpu_tick_count);

    return now;

}

http://www.dtcms.com/a/478449.html

相关文章:

  • 【C++篇】:ServiceBus RPC 分布式服务总线框架项目
  • 后训练——Post-training技术介绍
  • 获取KeyStore的sha256
  • Linux (5)| 入门进阶:Linux 权限管理的基础规则与实践
  • 常见压缩包格式详解:区别及在不同系统中的解压方式
  • 【数学 进制 数位DP】P9362 [ICPC 2022 Xi‘an R] Find Maximum|普及+
  • .net过滤器和缓存
  • 张家港网站建设培训班电力建设专家答疑在哪个网站
  • 零基础学AI大模型之大模型的“幻觉”
  • 网站快速优化排名排名c语言入门自学零基础
  • MySQL排序规则utf8mb4_0900_ai_ci解析
  • 做网站别名解析的目的是什么同城广告发布平台
  • GPT4Free每日更新的免登录工作AI提供商和模型列表
  • 网站群建设座谈会云浮新增病例详情
  • Proxmox 9 一键更新虚拟机mac
  • C# WPF DataGrid使用Observable<Observable<object>类型作为数据源
  • sem网站建设网站是由多个网页组成的吗
  • redis中的数据类型和适用场景
  • 从字节到网页:HTTP 与 TCP 的底层密码全解析
  • 建设局招标办网站百度seo搜索引擎优化厂家
  • 隧道高清晰广播+紧急电话系统的应用
  • Ubuntu使用图片
  • C# 求圆柱体的周长(Find the perimeter of a cylinder)
  • php 网站部署点击网站出现微信二维码的链接怎么做
  • MCU和GPIO (1)
  • STM32H743-ARM例程18-SPI
  • 力扣Hot100--94.二叉树的中序遍历
  • NXP - 用MDK建立基于arm-none-eabi工具链的工程框架
  • 中卫网站推广网络营销电器网站建设流程
  • MavenException【测试】