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

单片机:实现延时函数(附带源码)

延时函数的实现:单片机编程的核心需求

在嵌入式开发中,延时函数是一个非常常见的需求。在很多情况下,程序需要暂停执行一定时间,通常是为了控制外设的工作周期,或者是为了实现定时功能。比如,控制LED闪烁、按键消抖、定时读取传感器数据等,往往需要利用延时函数来控制时间间隔。

实现延时函数通常有两种方式:

  1. 基于软件的空循环延时(Software Delay)
  2. 基于硬件定时器的延时(Hardware Timer Delay)

在本篇解读中,我们将聚焦于 软件延时 函数的实现,尤其是基于 空循环 来实现延时的原理。对于要求较高的精度和稳定性的应用,硬件定时器方式将是另一种更精确的方案,但在一些不复杂的任务中,空循环延时方式仍然是广泛应用的解决方案。

1. 延时函数实现的基本思路

延时函数的核心思想是通过执行一定数量的空操作指令(例如 NOP,即 No Operation 指令)来消耗时钟周期,达到延迟的效果。具体来说,通过控制 CPU 执行一个空循环,反复执行一些指令,从而消耗出一定数量的时钟周期,进而实现时间延迟。

延时函数的实现思路:

  1. 时钟频率计算: 单片机的时钟频率(如 16 MHz,72 MHz 等)决定了每个时钟周期的时间。通过知道每个时钟周期的时长,我们可以计算出一个指定延时时间(如 1 毫秒,1 微秒)所需要的循环次数。

  2. 空循环延时: 空循环延时是最简单的一种延时方式,CPU 执行的每一条指令会消耗一定的时钟周期,因此通过执行多次空指令(NOP 或其他无效指令),就可以累积消耗一定的时钟周期,进而控制延时时间。

  3. 控制延时时间: 通过计算系统时钟频率和所需的延时,我们可以通过编写一个循环,控制 NOP 指令的次数,来实现指定时间的延时。

2. 延时函数的工作原理与实现细节

2.1 延时函数的设计:

延时函数通常需要两个参数:

  • Delay_us:以微秒(us)为单位的延时函数。
  • Delay_ms:以毫秒(ms)为单位的延时函数。

在 C 语言中,这些函数的实现原理类似,但精度不同。微秒级的延时通常更精确,而毫秒级延时是通过调用多个微秒级延时函数来实现的。

Delay_us 函数设计:

为了设计 Delay_us(微秒延时),首先需要根据单片机的系统时钟频率来计算每次空循环的时间。假设单片机的时钟频率为 72 MHz,每个时钟周期的时长是:

时钟周期时间=172,000,000≈13.89 纳秒\text{时钟周期时间} = \frac{1}{72,000,000} \approx 13.89 \, \text{纳秒}时钟周期时间=72,000,0001​≈13.89纳秒

如果我们想实现 1 微秒的延时,那么我们需要让 CPU 执行大约 72 次空循环指令(72 * 1)。这样可以消耗大约 1 微秒的时间。

2.2 Delay_ms 函数设计:

对于 Delay_ms(毫秒延时),我们通过多次调用 Delay_us 来实现。每调用一次 Delay_us(1000),就相当于延时了 1 毫秒。因此,通过将 ms 作为参数传递,并递减 ms 值,可以实现所需的毫秒级延时。

3. 延时函数的代码实现

接下来,我们提供一个基于上述原理的具体代码实现:

#include "stm32f10x.h"// 延时函数(单位:微秒)
void Delay_us(uint32_t us) {// 假设系统时钟频率为 72 MHz(72,000,000 Hz)// 每个时钟周期的时间 = 1 / 72,000,000 = 13.89 纳秒// 为了实现微秒级延时,我们计算出需要多少个循环来实现目标延时。uint32_t count = 72 * us / 10;  // 系统时钟为 72 MHz,/10 来弥补编译器优化等因素while (count--) {__NOP();  // 执行空操作指令,消耗一个时钟周期}
}// 延时函数(单位:毫秒)
void Delay_ms(uint32_t ms) {// 通过调用 Delay_us 实现毫秒级延时while (ms--) {Delay_us(1000);  // 每次延时 1 毫秒(即 1000 微秒)}
}

4. 代码解读:

  1. Delay_us 函数解读:

    • Delay_us 函数接收一个参数 us,即希望延迟的微秒数。
    • 根据系统时钟频率,计算每个微秒延时需要执行多少次循环。这里我们假设系统时钟为 72 MHz,所以每个时钟周期为 13.89 纳秒。为了实现精准的微秒延时,我们通过 count = 72 * us / 10 来计算所需的循环次数。
    • __NOP() 是一个空操作指令,它不会进行任何计算,但会消耗一个时钟周期,达到延时效果。
  2. Delay_ms 函数解读:

    • Delay_ms 函数通过调用 Delay_us(1000) 来实现每次延时 1 毫秒。
    • 循环递减 ms,每次调用 Delay_us(1000),这样可以实现总的毫秒级延时。

5. 延时函数的优化与问题

  1. 编译器优化问题:

    • 在编写空循环延时函数时,编译器可能会进行优化,导致延时不准确。为了避免编译器优化掉空循环,我们使用了 volatile 关键字来声明 count 变量。这可以告诉编译器不要优化该变量,从而确保延时函数的正确性。
  2. 时钟频率问题:

    • 系统时钟频率对延时精度有直接影响。如果系统时钟频率较低,延时函数的精度就会降低。反之,时钟频率越高,延时越精确。如果时钟频率过高,空循环的延时会变得非常短,可能会超出计算的精度范围。
  3. 延时精度问题:

    • 空循环延时的精度较低,尤其在高频时钟下,延时误差可能会增加。因此,在一些对延时精度要求较高的应用中,建议使用硬件定时器来替代软件延时。
  4. 优化空循环:

    • 在计算循环次数时,需要考虑编译器对代码的优化,因此通常会稍微增加循环次数(如 /10 的修正)来弥补编译器优化的误差。

6. 延时函数的应用场景

延时函数在单片机开发中广泛应用,以下是一些典型应用场景:

  1. LED 闪烁:

    • 在嵌入式系统中,控制 LED 灯的闪烁是一项常见需求。通过调用延时函数,我们可以定时控制 LED 的开关,实现在固定时间间隔内闪烁的效果。
  2. 按键消抖:

    • 按键输入的扫描通常会受到 抖动 的影响,延时函数常用于 按键消抖。通过延时函数,可以延时一段时间后再重新扫描按键,从而避免重复触发。
  3. 串口通信:

    • 在串口通信中,延时函数用于控制数据的发送和接收。通过控制时间间隔,避免数据丢失或串口冲突。
  4. 传感器轮询:

    • 延时函数常用于 传感器数据轮询。例如每隔一定时间读取一次温湿度传感器的值,或者在给定时间内等待传感器的响应。
  5. 蜂鸣器控制:

    • 蜂鸣器的控制也常依赖延时函数,通过控制蜂鸣器的响铃时长和频率,可以实现报警、提示等功能。

7. 总结与优化建议

延时函数在嵌入式系统中的作用非常重要,虽然通过空循环延时的方式简单易实现,但它也有一些局限性,特别是在高精度要求的场合。为了提高延时函数的精度,建议使用硬件定时器。此外,了解系统时钟频率、编译器优化等因素对于编写准确的延时函数非常重要。


http://www.mrgr.cn/news/80874.html

相关文章:

  • Vue3 重置ref或者reactive属性值
  • 电子应用设计方案-62:智能鞋柜系统方案设计
  • 如何测量分辨率
  • vue框架的搭建
  • dify.ai和fastgpt,各有什么优缺点,有什么区别
  • JAVA期末速成(下)
  • 《剑网三》遇到找不到d3dx9_42.dll的问题要怎么解决?缺失d3dx9_42.dll是什么原因?
  • 字节跳动C++面试题及参考答案(下)
  • git使用和gitlab部署
  • [LeetCode-Python版] 定长滑动窗口3——1461. 检查一个字符串是否包含所有长度为 K 的二进制子串
  • 二十一、Ingress 进阶实践
  • 十大排序算法汇总(基于C++)
  • Unity开发哪里下载安卓Android-NDK-r21d,外加Android Studio打包实验
  • Fast-Planner 改进与优化:支持ROS Noetic构建与几何A*路径规划
  • ENSP实验
  • 红队规范:减少工具上传,善用系统自带程序
  • Linux基础及命令复习
  • Makefile文件编写的学习记录(以IMX6ULL开发板的Makefile文件和Makefile.build文件来进行学习)
  • Express (nodejs) 相关
  • [LeetCode-Python版] 定长滑动窗口1(1456 / 643 / 1343 / 2090 / 2379)
  • 【NLP 16、实践 ③ 找出特定字符在字符串中的位置】
  • jmeter中的prev对象
  • Qt学习笔记第71到80讲
  • 字符串类算法
  • Linux-Profile工具
  • QT实战经验总结 连载中