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

SysTick寄存器(嘀嗒定时器实现延时)

delay.h

#ifndef __DELAY_H__
#define __DELAY_H__#include "sys.h"void delay_ms(uint32_t nms);            /* 延时nms */
void delay_us(uint32_t nus);            /* 延时nus */
void delay_s(uint32_t ns);              /* 延时ns */
void HAL_Delay(uint32_t nms);           /* 延时nms */#endif

delay.c:

#include "delay.h"/*** @brief  微秒级延时* @param  nus 延时时长,范围:0~233015* @retval 无*/
void delay_us(uint32_t nus)
{uint32_t temp;SysTick->LOAD = 72 * nus;                           /* 设置定时器重装值 */SysTick->VAL = 0x00;                                /* 清空当前计数值 */SysTick->CTRL |= 1 << 2;                            /* 设置分频系数为1分频 */SysTick->CTRL |= 1 << 0;                            /* 启动定时器 */do{temp = SysTick->CTRL;} while ((temp & 0x01) && !(temp & (1 << 16)));     /* 等待计数到0 */SysTick->CTRL &= ~(1 << 0);                         /* 关闭定时器 */
}/*** @brief  毫秒级延时* @param  nms 延时时长,范围:0~4294967295* @retval 无*/
void delay_ms(uint32_t nms)
{while(nms--)delay_us(1000);
}/*** @brief  秒级延时* @param  ns 延时时长,范围:0~4294967295* @retval 无*/
void delay_s(uint32_t ns)
{while(ns--)delay_ms(1000);
}/*** @brief  重写HAL_Delay函数* @param  nms 延时时长,范围:0~4294967295* @retval 无*/
void HAL_Delay(uint32_t nms)
{delay_ms(nms);
}

实现原理:

void delay_us(uint32_t nus)
{uint32_t temp;/* SysTick 是 Cortex-M3 内核自带的 24 位递减定时器本函数每次都“一次性装载”一个目标计数值,然后忙等直到计数归零 */SysTick->LOAD = 72 * nus;        // 1) 设重装载值//    选择 HCLK=72MHz 做时钟源 → 1us 需要 72 个时钟//    (严格讲应写 72*nus-1,差 1 个时钟≃13.9ns,可忽略)SysTick->VAL  = 0x00;            // 2) 清当前计数值(写任意值清零),确保从 LOAD 开始数SysTick->CTRL |= (1u << 2);      // 3) 选择时钟源:CLKSOURCE=1 → HCLK(72MHz)//    0 则为 HCLK/8(F1 上为 AHB/8)SysTick->CTRL |= (1u << 0);      // 4) ENABLE=1,启动 SysTick(不使能中断,只是计数)do {temp = SysTick->CTRL;        // 5) 读 CTRL 寄存器//    注意:COUNTFLAG(位16)在被读出后自动清零} while ( (temp & 0x01) &&       //       6) 只要 ENABLE 仍为 1 且!(temp & (1u << 16))); //          COUNTFLAG==0(尚未到 0),就一直忙等//    COUNTFLAG=1 表示“从 1 计到 0 发生了一次”,//    也就是本次延时到点了SysTick->CTRL &= ~(1u << 0);     // 7) 关 SysTick,避免影响别人
}

关键寄存器位(SysTick)

  • CTRL

    • bit0 ENABLE:1=启动;0=停止

    • bit1 TICKINT:1=到 0 触发异常(中断);本函数没开

    • bit2 CLKSOURCE:1=HCLK;0=HCLK/8

    • bit16 COUNTFLAG:从 1 到 0 时置 1,读出后自动清零

  • LOAD:24 位重装载值(最大 0xFFFFFF

  • VAL:当前计数值(写任意值清零)


执行流程(一步步发生了什么)

  1. LOAD:把需要的“倒计时时钟数”写入(这里按 72*nus 计算)。

  2. VAL:让计数器从 LOAD 开始递减。

  3. 选时钟源:置 CLKSOURCE=1,用 72 MHz HCLK

  4. 启动:置 ENABLE=1

  5. 忙等:循环读取 CTRL,直到 COUNTFLAG=1(说明从 1 数到 0 发生过一次)。

  6. 停止:清 ENABLE,退出。

由于 SysTick 是递减计数器,从 LOAD 数到 0 的时间是 (LOAD+1)/时钟频率
所以严格写法应是 LOAD = 72*nus - 1,你的写法多等了 1 个时钟(≈14 ns),通常可以忽略。


时间上限 & 精度

  • SysTick 只有 24 位LOAD 最大 2^24-1=16,777,215

  • CLKSOURCE=HCLK=72 MHz

    • 最大延时16,777,215 / 72e6233,018 µs ≈ 233 ms

    • 超过这个值会溢出,需要分段多次调用或使用更长周期的延时函数(如 delay_ms 循环)。

  • 误差来源:

    • LOAD 没减 1 带来的 +1 个时钟(≈14 ns);

    • 中断抢占:本函数是忙等,若中途有高优先级中断抢占,实际延时会被拉长。


与 HAL/RTOS 的关系与注意点(很重要)

  • 可能破坏 HAL 的系统节拍:HAL 默认使用 SysTick 做 1ms 节拍(HAL_IncTick())。
    在此函数里重配/停止了 SysTick,会影响 HAL_Delay()、时间戳、RTOS 等。

    • 解决:

      • 不要改 CLKSOURCE/ENABLE 等全局配置,或者

      • 单独使用定时器 TIMx/DWT 实现微秒延时,或

      • 进入临界区后恢复原状态(保存/还原 CTRL/LOAD/VAL)。

  • RTOS 场景:SysTick 常被 RTOS 占用为系统时钟,更不建议直接改。

    • 推荐改用 DWT->CYCCNTTIM 硬件定时器实现微秒延时。

可选的更稳妥写法

  1. 与系统时钟无关的写法(自动适配 8/72/其它频点):

/*** @brief 微秒级延时(忙等法,基于 SysTick 24 位递减计时器)* @param nus  需要延时的微秒数* @note  会“临时接管”SysTick:重新配置 LOAD/VAL/CTRL,并在结束时关闭。*        如果工程里 HAL/RTOS 正在使用 SysTick,请谨慎使用或改用 DWT/TIM。*/
void delay_us(uint32_t nus)
{/* 1) 计算需要的“时钟周期数”SystemCoreClock 是当前 CPU 时钟(HCLK)频率,单位 Hz。例如 F103 72MHz:SystemCoreClock = 72,000,0001us 需要 SystemCoreClock/1,000,000 个时钟周期。*/uint32_t ticks = (SystemCoreClock / 1000000u) * nus;/* 2) 配置 SysTick 的重装载值:SysTick 是 24 位递减计数器,实际延时 = (LOAD + 1) / 时钟频率。因此写入 (ticks - 1)。若 ticks 为 0 会溢出,请保证 nus>0。 */SysTick->LOAD = ticks - 1U;/* 3) 清当前计数器值(写任意值会把 VAL 清零),让计数器从 LOAD 开始递减。 */SysTick->VAL  = 0U;/* 4) 选择时钟源并启动:- CLKSOURCE_Msk:选择 HCLK 作为计数源(不分频)- ENABLE_Msk   :启动 SysTick(不使能中断 TICKINT) */SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;/* 5) 忙等直到计数归零:- COUNTFLAG 位在从 1 计到 0 时置 1,且“读后自动清零”- 条件含义:当 ENABLE 仍为 1 且 COUNTFLAG 还没置位时,继续等待 */while ( (SysTick->CTRL & SysTick_CTRL_ENABLE_Msk) &&!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) ){/* 空循环等待 */}/* 6) 关闭 SysTick,避免影响到其他代码(比如 HAL 的 1ms 节拍) */SysTick->CTRL = 0U;
}

实现原理(怎么工作的)

  • 硬件基础:SysTick 是 Cortex-M3 内核自带的 24 位递减计数器,有三个关键寄存器:

    • LOAD:重装载值(最大 2^24−1)。

    • VAL :当前计数值(写任意值清零)。

    • CTRL:控制/状态位:

      • CLKSOURCE 选择时钟源(这里选 HCLK,即 SystemCoreClock)。

      • ENABLE 启停计数。

      • COUNTFLAG 从 1 减到 0 时置 1(读出后自动清零)。

  • 时序

    1. 计算需要的时钟周期数 ticks = HCLK(Hz) × 延时(s),并写入 LOAD = ticks - 1

    2. VAL,使计数器从 LOAD 开始递减。

    3. CLKSOURCE=HCLKENABLE=1 启动计数。

    4. 忙等直到 COUNTFLAG=1(说明从 1 数到 0 发生过一次)→ 延时达成。

    5. 关闭计数器。

  • 延时公式
    delay = (LOAD + 1) / HCLK
    这里 LOAD = ticks - 1,因此理论延时≈ ticks / HCLK = nus(µs)


重要注意

  1. 24 位上限LOAD ≤ 0xFFFFFF。当 CLKSOURCE=HCLK=72MHz 时,单次最大延时约
    0xFFFFFF / 72e6 ≈ 0.233 s。更长延时需分段或用 delay_ms 循环。

  2. 与 HAL/RTOS 冲突:HAL 默认用 SysTick 产生 1ms 节拍(HAL_IncTick())。这段代码会覆盖并关闭 SysTick 设置,可能影响 HAL_Delay() 或 RTOS。

    • 更稳妥:改用 DWT->CYCCNTTIMx 定时器做微秒延时。

  3. 中断影响:这是忙等方式,若期间有更高优先级中断抢占,实际延时会被拉长。

  4. 时钟自适应:用 SystemCoreClock 计算 ticks,能在变频后仍保持正确延时,前提是在变频后调用过 SystemCoreClockUpdate() 或 HAL 已正确更新该变量。

2.不影响 SysTick(HAL/RTOS 友好):用 DWT 周期计数器(Cortex-M3 支持)

/* ===================== DWT 初始化 ===================== */
/*** @brief  使能 DWT 的周期计数器 CYCCNT(只需调用一次)* @note   DWT 属于 Cortex-M 的调试/跟踪模块。要读写 DWT 寄存器,*         需先在 CoreDebug->DEMCR 中打开 TRCENA(Trace Enable)。*         CYCCNT 每个 CPU 时钟周期自增 1(32 位计数器)。*/
static inline void dwt_init(void)
{CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;  // 开 DWT/ITM/ETM 访问开关DWT->CYCCNT = 0;                                 // 复位周期计数器DWT->CTRL  |= DWT_CTRL_CYCCNTENA_Msk;            // 使能 CYCCNT 自增
}/* ===================== 微秒延时(忙等) ===================== */
/*** @brief  基于 DWT->CYCCNT 的微秒级延时(非侵入 SysTick)* @param  us: 需要延时的微秒数* @note   读起始时刻的 CYCCNT,计算目标“时钟周期数”ticks,*         然后忙等直到 (当前CYCCNT - 起始CYCCNT) >= ticks。*         这种写法天然支持 CYCCNT 的 32 位回绕。*/
static inline void delay_us_dwt(uint32_t us)
{uint32_t start = DWT->CYCCNT;                         // 起始时间戳(CPU 周期计数)uint32_t ticks = (SystemCoreClock / 1000000u) * us;   // 需要等待的周期数 = HCLK * us/* 使用无符号减法处理回绕:CYCCNT 是 32 位递增,溢出后从 0 重新开始(模 2^32)。只要等待时长小于一个回绕周期(72MHz 时约 59.6 s),(当前 - 起始) 的结果就是从起始到当前的“正确经过周期数”。 */while ((DWT->CYCCNT - start) < ticks) { /* busy wait */ }
}

这样既不改 SysTick,也更精确(纳秒级分辨率),常用于裸机/RTOS。

实现原理(怎么工作的)

  • DWT(Data Watchpoint and Trace)CYCCNT
    Cortex-M 内核自带的调试/跟踪单元。CYCCNT32 位CPU 时钟周期计数器”,在使能后,每个 HCLK 周期 +1。
    对于 STM32F103C8T6(72 MHz),分辨率是 1/72 MHz ≈ 13.9 ns

  • 延时思路

    1. 启用 DWT 并开启 CYCCNT 自增(TRCENA=1CYCCNTENA=1)。

    2. 读取起始值 start = CYCCNT

    3. 计算目标等待的周期数ticks = SystemCoreClock * us / 1e6

    4. 忙等:while (CYCCNT - start < ticks)。当差值达到 ticks,说明流逝了指定的微秒数。

      • 回绕安全CYCCNT 以 2^32 为模,使用无符号减法可自动处理回绕,只要等待时间小于一个回绕周期即可。

      • 在 72 MHz 下,回绕周期 2^32 / 72e6 ≈ 59.6 s

  • 优点

    • 不改动 SysTick,对 HAL/RTOS 友好;

    • 精度高(周期级),性能开销极小。

  • 使用要点 / 注意事项

    • 请在系统时钟配置完成后调用 dwt_init() 一次;若后续切换系统时钟,确保 SystemCoreClock 已更新(SystemCoreClockUpdate() 或 HAL 自动更新)。

    • 该函数是忙等,期间若有高优先级中断,会拉长实际延时;若需要可预先临界区保护。

    • 单次延时不应超过一个回绕周期(F103/72 MHz 下 < ~59.6 s,远大于常见微秒/毫秒延时需求)。

    • 个别芯片/安全环境可能默认锁定 DWT(TRCENA 受限),普通 F1/F4/F7 工程一般可直接使用。

总结

  •  delay_us() 是用 SysTick 递减计数器做的忙等延时
    装载计数 → 选 HCLK → 启动 → 等待 COUNTFLAG → 停止

  • 假定 HCLK=72 MHzLOAD=72*us,最大一次能延时约 233 ms

  • 在使用 HAL/RTOS 的工程里,不建议频繁重配 SysTick,更推荐 DWTTIMx 来实现微秒延时。

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

相关文章:

  • cPanel Python 应用部署流程
  • 记录一下第一次patch kernel的经历
  • CSV 生成 Gantt 甘特图
  • 2^{-53} 单位舍入误差、机器精度、舍入的最大相对误差界限
  • 【QGIS数据篇】QGIS 3.40 栅格计算器经典实用公式全集
  • 高并发场景下如何避免重复支付
  • 17.3 全选购物车
  • 双椒派E2000D开发板LED驱动开发实战指南
  • 线程回收与线程间通信
  • [Python 基础课程]抽象类
  • 强化学习入门教程(附学习文档)
  • (第十七期)HTML图像标签详解:从入门到精通
  • 创新词汇表设计:UniVoc - 中英文混合处理的新方案
  • 安卓11 12系统修改定制化_____列举与安卓 9、10 系统在定制化方面的差异与权限不同
  • 数学建模Topsis法笔记
  • 非功能性需求设计:可解释性、鲁棒性、隐私合规
  • 【数据结构初阶】--排序(五):计数排序,排序算法复杂度对比和稳定性分析
  • 启发式合并 + 莫队 恋恋的心跳大冒险
  • 汽车大灯ABD算法介绍
  • 【算法】——力扣hot100常用算法技巧
  • leetcode_ 739 每日温度
  • 分享一个大数据的源码实现 基于Hadoop的二手车市场数据分析与可视化 基于Spark的懂车帝二手车交易数据可视化分析系统
  • Windows MCP.Net:革命性的 .NET Windows 桌面自动化 MCP 服务器
  • 嵌入式硬件篇---电容电感
  • 【C++】动态内存管理
  • 嵌入式硬件篇---电平转换电路
  • Python-深度学习(一)
  • Flutter开发 网络请求
  • Obot MCP 网关:用于安全管理 MCP 服务器采用的开源平台
  • DINOv3 论文精读(逐段解析)