单片机编程过程中经常用到延时函数,最常用的莫过于微秒级延时delay_us()和毫秒级delay_ms()。本文基于STM32F207介绍4种不同方式实现的延时函数。

1、普通延时

这种延时方式应该是大家在51单片机时候,接触最早的延时函数。这个比较简单,让单片机做一些无关紧要的工作来打发时间,经常用循环来实现,在某些编译器下,代码会被优化,导致精度较低,用于一般的延时,对精度不敏感的应用场景中。

  1. 1 //微秒级的延时
  2. 2 void delay_us(uint32_t delay_us)
  3. 3 {
  4. 4 volatile unsigned int num;
  5. 5 volatile unsigned int t;
  6. 6
  7. 7
  8. 8 for (num = 0; num < delay_us; num++)
  9. 9 {
  10. 10 t = 11;
  11. 11 while (t != 0)
  12. 12 {
  13. 13 t--;
  14. 14 }
  15. 15 }
  16. 16 }
  17. 17 //毫秒级的延时
  18. 18 void delay_ms(uint16_t delay_ms)
  19. 19 {
  20. 20 volatile unsigned int num;
  21. 21 for (num = 0; num < delay_ms; num++)
  22. 22 {
  23. 23 delay_us(1000);
  24. 24 }
  25. 25 }

上述工程源码仓库:https://github.com/strongercjd/STM32F207VCT6/tree/master/02-Template

2、定时器中断

定时器具有很高的精度,我们可以配置定时器中断,比如配置1ms中断一次,然后间接判断进入中断的次数达到精确延时的目的。这种方式精度可以得到保证,但是系统一直在中断,不利于在其他中断中调用此延时函数,有些高精度的应用场景不适合,比如其他外设正在输出,不允许任何中断打断的情况。

STM32任何定时器都可以实现,下面我们以SysTick 定时器为例介绍:

初始化SysTick 定时器:

  1. 1 /* 配置SysTick为1ms */
  2. 2 RCC_GetClocksFreq(&RCC_Clocks);
  3. 3 SysTick_Config(RCC_Clocks.HCLK_Frequency / 1000);

中断服务函数:

  1. 1 void SysTick_Handler(void)
  2. 2 {
  3. 3 TimingDelay_Decrement();
  4. 4 }
  5. 5 void TimingDelay_Decrement(void)
  6. 6 {
  7. 7 if (TimingDelay != 0x00)
  8. 8 {
  9. 9 TimingDelay--;
  10. 10 }
  11. 11 }

延时函数:

  1. 1 void Delay(__IO uint32_t nTime)
  2. 2 {
  3. 3 TimingDelay = nTime;
  4. 4 while(TimingDelay != 0);
  5. 5 }

上述工程源码仓库:https://github.com/strongercjd/STM32F207VCT6/tree/master/02-Template

3、查询定时器

为了解决定时器频繁中断的问题,我们可以使用定时器,但是不使能中断,使用查询的方式去延时,这样既能解决频繁中断问题,又能保证精度。

STM32任何定时器都可以实现,下面我们以SysTick 定时器为例介绍。

STM32的CM3内核的处理器,内部包含了一个SysTick定时器,SysTick是一个24位的倒计数定时器,当计到0时,将从RELOAD寄存器中自动重装载定时初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息。

SYSTICK的时钟固定为HCLK时钟的1/8,在这里我们选用内部时钟源120M,所以SYSTICK的时钟为(120/8)M,即SYSTICK定时器以(120/8)M的频率递减。SysTick 主要包含CTRL、LOAD、VAL、CALIB 等4 个寄存器。

▼CTRL:控制和状态寄存器

▼LOAD:自动重装载除值寄存器

▼VAL:当前值寄存器

▼CALIB:校准值寄存器

使用不到,不再介绍

示例代码

  1. 1 void delay_us(uint32_t nus)
  2. 2 {
  3. 3 uint32_t temp;
  4. 4 SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000000/8*nus;
  5. 5 SysTick->VAL=0X00;//清空计数器
  6. 6 SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
  7. 7 do
  8. 8 {
  9. 9 temp=SysTick->CTRL;//读取当前倒计数值
  10. 10 }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
  11. 11 SysTick->CTRL=0x00; //关闭计数器
  12. 12 SysTick->VAL =0X00; //清空计数器
  13. 13 }
  14. 14 void delay_ms(uint16_t nms)
  15. 15 {
  16. 16 uint32_t temp;
  17. 17 SysTick->LOAD = RCC_Clocks.HCLK_Frequency/1000/8*nms;
  18. 18 SysTick->VAL=0X00;//清空计数器
  19. 19 SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
  20. 20 do
  21. 21 {
  22. 22 temp=SysTick->CTRL;//读取当前倒计数值
  23. 23 }while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
  24. 24 SysTick->CTRL=0x00; //关闭计数器
  25. 25 SysTick->VAL =0X00; //清空计数器
  26. 26 }

上述工程源码仓库:https://github.com/strongercjd/STM32F207VCT6/tree/master/04-Delay

4、汇编指令

如果系统硬件资源紧张,或者没有额外的定时器提供,又不想方法1的普通延时,可以使用汇编指令的方式进行延时,不会被编译优化且延时准确。

STM32F207在IAR环境下

  1. 1 /*!
  2. 2 * @brief 软件延时
  3. 3 * @param ulCount:延时时钟数
  4. 4 * @return none
  5. 5 * @note ulCount每增加1,该函数增加3个时钟
  6. 6 */
  7. 7 void SysCtlDelay(unsigned long ulCount)
  8. 8 {
  9. 9 __asm(" subs r0, #1\n"
  10. 10 " bne.n SysCtlDelay\n"
  11. 11 " bx lr");
  12. 12 }

这3个时钟指的是CPU时钟,也就是系统时钟。120MHZ,也就是说1s有120M的时钟,一个时钟也就是1/120 us,也就是周期是1/120 us。3个时钟,因为执行了3条指令。

使用这种方式整理ms和us接口,在Keil和IAR环境下都测试通过。

  1. 1 /*120Mhz时钟时,当ulCount为1时,函数耗时3个时钟,延时=3*1/120us=1/40us*/
  2. 2 /*
  3. 3 SystemCoreClock=120000000
  4. 4 us级延时,延时n微秒
  5. 5 SysCtlDelay(n*(SystemCoreClock/3000000));
  6. 6 ms级延时,延时n毫秒
  7. 7 SysCtlDelay(n*(SystemCoreClock/3000));
  8. 8 m级延时,延时n秒
  9. 9 SysCtlDelay(n*(SystemCoreClock/3));
  10. 10 */
  11. 11
  12. 12 #if defined (__CC_ARM) /*!< ARM Compiler */
  13. 13 __asm void
  14. 14 SysCtlDelay(unsigned long ulCount)
  15. 15 {
  16. 16 subs r0, #1;
  17. 17 bne SysCtlDelay;
  18. 18 bx lr;
  19. 19 }
  20. 20 #elif defined ( __ICCARM__ ) /*!< IAR Compiler */
  21. 21 void
  22. 22 SysCtlDelay(unsigned long ulCount)
  23. 23 {
  24. 24 __asm(" subs r0, #1\n"
  25. 25 " bne.n SysCtlDelay\n"
  26. 26 " bx lr");
  27. 27 }
  28. 28
  29. 29 #elif defined (__GNUC__) /*!< GNU Compiler */
  30. 30 void __attribute__((naked))
  31. 31 SysCtlDelay(unsigned long ulCount)
  32. 32 {
  33. 33 __asm(" subs r0, #1\n"
  34. 34 " bne SysCtlDelay\n"
  35. 35 " bx lr");
  36. 36 }
  37. 37
  38. 38 #elif defined (__TASKING__) /*!< TASKING Compiler */
  39. 39 /*无*/
  40. 40 #endif /* __CC_ARM */

上述工程源码仓库:https://github.com/strongercjd/STM32F207VCT6/tree/master/03-ASM

备注:

理论上:汇编方式的延时也是不准确的,有可能被其他中断打断,最好使用us和ms级别的延时,采用for循环延时的函数也是如此。采用定时器延时理论上也可能不准确的,定时器延时是准确的,但是可能在判断语句的时候,比如if语句,判断延时是否到了的时候,就在判断的时候,被中断打断执行其他代码,返回时已经过了一小段时间。不过汇编方式和定时器方式,只是理论上不准确,在实际项目中,这两种方式的精度已经足够高了。

点击查看本文所在的专辑,STM32F207教程

STM32延时函数的四种方法的更多相关文章

  1. iOS延时执行的四种方法

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  2. C语言中返回字符串函数的四种实现方法 2015-05-17 15:00 23人阅读 评论(0) 收藏

    C语言中返回字符串函数的四种实现方法 分类: UNIX/LINUX C/C++ 2010-12-29 02:54 11954人阅读 评论(1) 收藏 举报 语言func存储 有四种方式: 1.使用堆空 ...

  3. C语言中返回字符串函数的四种实现方法

    转自C语言中返回字符串函数的四种实现方法 其实就是要返回一个有效的指针,尾部变量退出后就无效了. 有四种方式: 1.使用堆空间,返回申请的堆地址,注意释放 2.函数参数传递指针,返回该指针 3.返回函 ...

  4. C语言中函数返回字符串的四种方法

    在讨论着四种方法之前,首先要对函数有一个简单的认识,无论是在形实结合时,还是在return语句返回时,都有一个拷贝的过程.你传进来的参数是个值,自然函数在工作之前要把这个值拷贝一份供自己使用,你传进来 ...

  5. JS高级. 06 缓存、分析解决递归斐波那契数列、jQuery缓存、沙箱、函数的四种调用方式、call和apply修改函数调用方法

    缓存 cache 作用就是将一些常用的数据存储起来 提升性能 cdn //-----------------分析解决递归斐波那契数列<script> //定义一个缓存数组,存储已经计算出来 ...

  6. 运行jar应用程序引用其他jar包的四种方法

    转载地址:http://www.iteye.com/topic/332580 大家都知道一个java应用项目可以打包成一个jar,当然你必须指定一个拥有main函数的main class作为你这个ja ...

  7. java中定时器的四种方法

    package com.lid; import java.util.Calendar; import java.util.Date; import java.util.Timer; import ja ...

  8. Angular--页面间切换及传值的四种方法

    1. 基于ui-router的页面跳转传参(1) 在AngularJS的app.js中用ui-router定义路由,比如现在有两个页面,一个页面(producers.html)放置了多个produce ...

  9. MYSQL获取自增ID的四种方法

    MYSQL获取自增ID的四种方法 1. select max(id) from tablename 2.SELECT LAST_INSERT_ID() 函数 LAST_INSERT_ID 是与tabl ...

随机推荐

  1. ssh 免密码登陆设置不成功

    记一次centos6设置免密码登陆设置不成功的解决.自己挖的坑自己填. ssh 免密码登陆设置( 正常情况下是这样的,设置成功后登陆主机是不需要密码的) [root@master .ssh]# ssh ...

  2. Web服务器-正则表达式-小例子(3.1.2)

    @ 目录 1.邮箱 2.手机号码 关于作者 1.邮箱 import re def main(): email = input("请输入一个邮件地址:") ret = re.matc ...

  3. Restful API 接口设计标准及规范

    Restful API 接口设计标准以及规范 RESTful概念 理解和评估以网络为基础的应用软件的架构设计,得到一个功能强.性能好.适宜通信的架构.REST指的是一组架构约束条件和原则." ...

  4. Flutter ListTile - Flutter每周一组件

    该文章属于[Flutter每周一组件]系列,其它组件可以查看该系列下的文章,该系列会不间断更新:所有组件的demo已经上传值Github: https://github.com/xj124456/fl ...

  5. 将XML转换为实体

    需求 将XML文件中的数据经过转换后插入到数据库中. 参考 C#实体类和XML的相互转换 https://blog.csdn.net/pan_junbiao/article/details/82938 ...

  6. 2.mysql explain命令详解

    EXPLAIN详解 SQL编写和解析 编写过程 select-distinct-from-join-on-where-group by-having-order by-limit- 解析过程 from ...

  7. 容器编排系统K8s之ConfigMap、Secret资源

    前文我们了解了k8s上的pv/pvc/sc资源的使用和相关说明,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14188621.html:今天我们主要来聊一下 ...

  8. JPDA 远程调试方法记录

    一.JPDA概念 JPDA(Java platform debugger architecture)是java平台调试架构的简称,由java虚拟机后端和调试平台前端组成. 1.java虚拟机提供了ja ...

  9. MM-采购模块相关业务

    采购模块主要业务流程: 1.收集采购需求(采购申请单),系统采购申请单单据可以由需求部门手工产生,也可以由系统的MRP(物料需求计划)来产生. 2,货源确定,用来确定所申请的物料,通过何种方式向供应商 ...

  10. select机制

    select机制 函数作用: 在一段时间指定的时间内,监听用户感兴趣的文件描述符上可读.可写和异常事件. 函数原型: #include <sys/time.h> #include < ...