delay.c:

#include "delay.h"/*** @brief  微秒级延时* @param  nus 延时时长,范围:0~233015* @retval 无*/
void delay_us(uint32_t nus)
{uint32_t ticks;uint32_t tcnt = 0, told, tnow;uint32_t reload = SysTick->LOAD;                //重装载值ticks = nus * 72;                               //需要计的节拍数told = SysTick->VAL;                            //刚进入while循环时计数器的值while(1){tnow = SysTick->VAL;if(tnow != told){if(tnow < told)tcnt += told - tnow;elsetcnt += reload - (tnow -told);told = tnow;                            //下次进入while循环时,当前VAL的值作为toldif(tcnt >= ticks)                       //已计的数超过/等于需要计的数时,退出循环break;}}
}/*** @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);
}

delay_us() 的思路是:不去改动 SysTick 的配置,而是把 SysTick 当作一个已在运行的“全局倒计时器”,通过读取当前计数值 VAL 并累计消逝的时钟拍数,直到达到目标“微秒对应的时钟拍数”为止。

关键硬件背景(STM32F103C8T6 的 SysTick)

  • SysTick 是一个 24 位 向下计数定时器:

    • LOAD:重装载值(计数到 0 后会重新装入)。

    • VAL:当前计数值(从 LOAD 往 0 递减)。

    • CTRL:控制/状态,包含是否用 AHB(HCLK)还是 AHB/8 作时钟源。

  • 常见配置(比如 HAL 里)会把 SysTick 配成 1ms 节拍,例如在 72 MHz 下:

    • 若时钟源为 AHBLOAD = 72,000 - 1

    • 若时钟源为 AHB/8LOAD = 9,000 - 1

本函数不设置 SysTick,只读取它的 LOADVAL


代码逐步做了什么

  1. 读出 reload = SysTick->LOAD
    该值用于在检测到 VAL 回卷(underflow)时,计算跨回卷的增量。

  2. 把“需要的微秒”换算成“需要的时钟拍数”

ticks = nus * 72;

这里假设 SysTick 的时钟源是 AHB=72 MHz(即每微秒 72 拍)。

如果你的工程把 SysTick 时钟设为 AHB/8,那应改为 ticks = nus * 9;

3.拍下进入循环时的 VAL

told = SysTick->VAL;

之后在循环里一直比较“这次的 VAL”与“上次的 VAL”,推算出经过了多少拍,累计到 tcnt

4.循环内:根据 VAL 的变化,计算经过的拍数

    • 先读本次的 VALtnow = SysTick->VAL

    • 如果发生变化(tnow != told),分两种情况:

      • 未回卷tnow < told):
        tcnt += told - tnow;

      • 发生回卷tnow > told,因为计数器是向下数,回卷后会从 LOAD 重新开始):
        tcnt += reload - (tnow - told);
        等价于 tcnt += (told + (reload - tnow)) —— 即把“从 told 数到 0 的拍数”和“从 reload 数到 tnow 的拍数”相加。

    • 更新基准:told = tnow

    • 当累计的 tcnt >= ticks 时,退出循环。

注意:该算法假设两次读取 VAL 之间至多回卷一次。因为循环很紧,这在 72 MHz 下是可靠的(一次回卷对应最多 LOAD+1 个拍,1 ms 基础节拍时 LOAD≈72000,循环体远小于这个量级)。

为什么上限写成 0 ~ 233015 us

  • 计算:233015 × 72 = 16,777,080 ≈ 2^24 - 136
    24 位计数器的上限是 2^24 = 16,777,216 拍。

  • 把延时限制在此范围内,意味着需要的总拍数不超过 24 位计数器最大值,这在很多示例里是一个保守的安全上限。

  • 实际上,由于本函数是按增量累计 tcntuint32_t),并不严格只能延时到 24 位上限;之所以给出这个上限,是为了:

    • 规避 ticks = nus * 72 的乘法溢出风险;

    • 保证在不同优化级/不同 SysTick 配置下有稳定行为的“推荐范围”。

  • 如果你需要更长的阻塞延时,建议外层用毫秒/秒延时分段循环,或使用定时器/RTOS 延时。


实现原理总结

用 SysTick 当前值 VAL 的变化来“量时间”,每次根据是否回卷来计算经过的拍数并累加,直到达到想要的“微秒 × 每微秒拍数”的总拍数。


优点

  • 无需重配 SysTick:与 HAL/RTOS 的 1 ms 系统节拍共存,不破坏全局时基。

  • 分辨率高:在 72 MHz 下理想分辨率可达 1/72 µs(实际受循环开销影响,一般微秒级精度是可达的)。

  • 处理回卷:显式考虑了 VAL 回卷,不会在回卷点产生大误差或卡住。

  • 实现简单、移植性好:只依赖 CMSIS 寄存器。


使用与精度注意事项

  1. 确保时钟源匹配

    • 如果 CTRL.CLKSOURCE 选择的是 AHB:用 ticks = nus * (HCLK_MHz)

    • 如果是 AHB/8:用 ticks = nus * (HCLK_MHz/8)
      上面示例用 72,等价于 72 MHz AHB 且 SysTick 用 AHB 作时钟。

  2. SysTick 要在运行
    如果 SysTick 没开或被停用,VAL 不会变化,函数会阻塞不返回

  3. 忙等占用 CPU
    这是阻塞式延时。在 RTOS 或需省电场景下,不宜用于长延时;长延时应使用 vTaskDelay/定时器/外设定时器中断等方案。

  4. 误差来源

    • 循环体执行开销(与编译优化等级、总线等待状态有关);

    • HCLK 频率与注释假设不一致;

    • 中断打断(若中断较多将拉大实际延时)。
      若要更稳定的微秒延时,可考虑 DWT->CYCCNT(数据观测单元周期计数器)方案。


总结

这段 delay_us() 的精髓是:利用 SysTick 的当前值做“时间刻度”,按差分累加经过的拍数并处理回卷,直到凑够目标拍数。它的优势是不破坏系统节拍、实现简单,并且能在 STM32F103 上提供可靠的微秒级延时;只要确保时钟源和乘数(72 或 9)匹配、避免用它做长时间阻塞即可。

为什么这种实现方式可以带操作系统?

其实关键点就在于它不去重新配置 SysTick,而是只利用 SysTick 的当前值 VAL 来做时间基准。

传统写法 vs 本写法的差别

传统写法(常见 HAL 的 delay_us

  • 会重新配置 SysTick->LOAD,然后开定时器,等计满退出。

  • 这种方式会破坏 RTOS 的时基,因为大多数 RTOS(比如 FreeRTOS)就是用 SysTick 作为心跳节拍(1ms Tick)。

  • 如果你在任务里调用这样的延时函数,就会导致系统的任务调度失效(心跳丢失),进而“带不动 RTOS”。

本写法(上面的代码)

  • 它假定 SysTick 已经在跑(系统节拍已经配置好,比如 1ms Tick),不去改 LOADCTRL

  • 仅仅是每次读取 VAL 值,看它减少了多少,自己在软件里累加消逝的“拍数”。

  • 这样 RTOS 的 SysTick 中断依然可以正常产生,任务调度不受影响。


实现原理回顾

  1. VAL 是一个递减的计数器,RTOS 每 1ms 会重装载。

  2. 代码通过 told - tnow(或处理回卷)得到经过的拍数

  3. 累加到 tcnt,直到达到目标“微秒 × 每微秒的时钟拍数”为止。

  4. 整个过程里,不改 LOAD、不关中断,RTOS 的 Tick 中断仍然照常运行。


优点总结(为什么能“带操作系统”)

  1. 不会破坏 RTOS 的时基
    SysTick 继续为 FreeRTOS 提供 1ms Tick,调度器能正常工作。

  2. 延时和任务调度兼容
    延时过程只是忙等,占用当前任务 CPU 时间片,但不会影响中断。
    → 高优先级的中断(比如 SysTick 中断)仍能进来,调度照常。

  3. 粒度更细
    FreeRTOS 默认只能延时到毫秒级(vTaskDelay()),而这种方法能做微秒级延时,适合某些需要精准时序的外设操作。

  4. 简单通用
    不依赖额外定时器,直接用系统 SysTick 的“当前值”寄存器做时间戳。


 结论:
这种延时方法 不会重配置 SysTick,所以不会破坏操作系统的节拍机制;它仅仅是读 VAL 值计算经过的时钟周期。这样就能在 FreeRTOS 等 RTOS 中使用,同时实现微秒级延时。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93653.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93653.shtml
英文地址,请注明出处:http://en.pswp.cn/web/93653.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES Module 和 CommonJS的区别

ES Module&#xff08;ESM&#xff0c;ES6 模块系统&#xff09;和 CommonJS 是 JavaScript 中两种主流的模块规范&#xff0c;分别用于现代前端和 Node.js 环境&#xff08;早期&#xff09;&#xff0c;它们在语法、加载机制、特性等方面有显著区别。以下是详细对比&#xff…

猫头虎AI分享|一款智能量化交易系统:QuantCell,从数据收集到策略执行全流程自动化

猫头虎AI分享&#xff5c;一款智能量化交易系统&#xff1a;QuantCell&#xff0c;从数据收集到策略执行全流程自动化 在当今金融市场中&#xff0c;量化交易系统已经成为越来越多投资者和机构的重要选择。无论是股票、期货还是加密货币&#xff0c;自动化交易与人工智能的结合…

直播美颜SDK架构揭秘:动态贴纸功能的实现原理与性能优化

如今&#xff0c;美颜SDK 已经不再只是“磨皮、美白”的基础工具&#xff0c;而是逐渐进化为一个涵盖 人脸识别、实时特效、动态贴纸交互 的复杂技术体系。尤其是 动态贴纸功能 的加入&#xff0c;让主播与观众之间的互动更加生动有趣&#xff0c;也成为提升用户粘性与平台差异…

Docker安装CDC

Docker安装CDC拉取镜像离线形式安装上传文件并创建docker-compose.yml把镜像加载到docker中启动容器连接数据库创建账号&#xff0c;并给账号授权设置wal_level确认wal_level的值创建链接查询连接状态使用kafdrop消息中看不到修改之前的信息怎么办补充拉取镜像 docker pull co…

如何在win服务器中部署若依项目

一、安装jdk的环境&#xff1a; 这一步很简单&#xff0c;直接拿到安装包双击安装即可。 二、配置jdk的环境变量默认安装的路径为&#xff1a;C:\Program Files (x86)\Java\jdk1.7.0_51安装完成之后进行环境变量配置右击计算机&#xff08;此电脑&#xff09;点击属性点击高级系…

CSS从入门到精通完整指南

第一部分&#xff1a;CSS基础入门1.1 什么是CSSCSS&#xff08;层叠样式表&#xff0c;Cascading Style Sheets&#xff09;是用于描述HTML文档外观和格式的样式语言。CSS将内容与表现分离&#xff0c;让HTML专注于内容结构&#xff0c;CSS专注于视觉效果。1.2 CSS语法结构选择…

重温k8s基础概念知识系列二(Pod)

文章目录1、Pod概念2、K8s 中的 Pod 的两种用法3、定义Pod4、Pod的创建资源5、Pod 模板6、容器探针7、总结干货8、 K8s Pod 经典面试题速查表Pod是Kubernetes中最小的单元&#xff1a; 1、Pod概念 Pod 是可以在 Kubernetes中创建和管理的、最小的可部署的计算单元。它由一组、一…

设计模式之静态代理

一些个人理解 顾名思义&#xff0c;就是代理一个对象。 那么&#xff0c;既然要代理一个东西&#xff0c;就要传入它吧? 【1】所以将代理对象当作属性【【2】往往通过构造方法传入被代理的目标对象】。 既然要代理&#xff0c;那必然要和代理对象拥有相同的功能吧? 所以实现了…

牛津大学xDeepMind 自然语言处理(1)

牛津大学xDeepMind 自然语言处理 Natural Language Processing 词向量与词汇语义学 Word Vectors and Lexical Semantics 词语表示的基本问题与分布语义思想 传统词语表示&#xff08;如独热向量&#xff09;存在稀疏、正交、语义弱的问题&#xff0c;无法表达语义相似性。分布…

StarRocks数据库集群的完整部署流程

目录 依赖环境 下载安装包 部署FE 部署BE 搭建集群 停止集群 依赖环境 详见&#xff1a;StarRocks 部署&#xff1a;依赖环境-CSDN博客 下载安装包 在官方网站下载安装包&#xff1a;StarRocks 部署FE 创建元数据目录。 mkdir -p <meta_dir> 修改 FE 配置文件 f…

简单的 VSCode 设置

以下是我使用的vscode设置。虽然有些主观&#xff0c;但很实用。1 主题。我放弃了那些炫酷的主题。我选择了Tokyo Night (Storm)。理由是&#xff1a;它平静、赏心悦目&#xff0c;并且与代码形成了美丽的对比&#xff0c;却又不显得刺眼。2. 字体。我切换到了 JetBrains Mono …

Rust 条件语句

Rust 条件语句 在编程语言中&#xff0c;条件语句是程序流程控制的重要组成部分。Rust 作为一种系统编程语言&#xff0c;其条件语句的设计简洁而强大。本文将详细介绍 Rust 中的条件语句&#xff0c;包括其语法、用法以及一些高级特性。 1. 基本条件语句 Rust 中的基本条件语句…

【Java EE进阶 --- SpringBoot】初识Spring(创建SpringBoot项目)

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 &#xff0c;Java, Java EE初阶&#xff0c; Java数据结构 欢迎大家访问~ 创作不易&#xff0c;大佬们点赞鼓励…

脑潜在进展:基于潜扩散模型的三维脑磁共振成像个体时空疾病进展研究|文献速递-深度学习人工智能医疗图像

Title题目Brain Latent Progression: Individual-based spatiotemporal diseaseprogression on 3D Brain MRIs via latent diffusion脑潜在进展&#xff1a;基于潜扩散模型的三维脑磁共振成像个体时空疾病进展研究01文献速递介绍神经退行性疾病是现代医疗保健领域最紧迫的挑战之…

专题:2025AI技术应用与发展报告|附600+份报告PDF、数据仪表盘汇总下载

原文链接&#xff1a;https://tecdat.cn/?p43632 当企业管理者看着后台65%的任务被AI自动分配&#xff0c;却仍在为下周的营销方案熬夜改稿时&#xff0c;一个现实的矛盾浮出水面&#xff1a;AI到底能帮企业做什么&#xff1f; 2025年&#xff0c;算法研发投入占企业AI预算的…

【笔记】扩散模型(一一):Stable Diffusion XL 理论与实现

论文链接&#xff1a;SDXL: Improving Latent Diffusion Models for High-Resolution Image Synthesis 官方实现&#xff1a;Stability-AI/generative-models 非官方实现&#xff1a;huggingface/diffusers Stable Diffusion XL (SDXL) 是 Stablility AI 对 Stable Diffusion 进…

学习安卓APP开发,10年磨一剑,b4a/Android Studio

学习安卓APP开发 记得上次学APP都是在2016年前了&#xff0c;一晃就过去了10年。 当时用ANDROID studio打开一个空项目和编绎分别用了300秒&#xff0c;一下就用了10分钟。 后来买了一台一万多的电脑&#xff0c;CPU换成了I5 8600K 4.2GHZ*6核&#xff0c;再加上M2固态硬盘。 编…

调试技巧(vs2022 C语言)

调试之前首先要保证我们的脑袋是清晰的&#xff0c;我们调试的过程主要是看代码有没有按照我们的想法去运行调试最常使用的几个快捷键F5启动调试&#xff0c;经常用来直接跳到下一个断点处&#xff08;F5通常和F9配合使用&#xff0c;打了断点按F5程序可以直接运行到断点处&…

MySQL深度理解-Innodb底层原理

1.MySQL的内部组件结构大体来说&#xff0c;MySQL可以分为Server层和存储引擎层两部分。2.Server层Server层主要包括连接器、查询缓存、分析器、优化器和执行器等&#xff0c;涵盖MySQL的大多数核心服务功能&#xff0c;以及所有的内置函数&#xff08;如日期、时间、数据和加密…

QFtp在切换目录、上传文件、下载文件、删除文件等一系列操作时,如何按照预期操作指令顺序执行

FTP服务初始化时&#xff0c;考虑到重连、以及commandFinished信号信号执行完成置m_bCmdFinished 为true; void ICore::connectFtpServer() {if(g_pFile nullptr){g_pFile new QFile;}if(g_pFtp){g_pFtp->state();g_pFtp->abort();g_pFtp->deleteLater();g_pFtp n…