STM32
直播中

李伟

7年用户 1524经验值
私信 关注
[问答]

怎样去实现STM32单片机的延时功能呢

循环延时是什么意思?
怎样去实现STM32单片机的延时功能呢?

回帖(1)

张玲

2021-11-15 15:25:14
  接触过单片机的,肯定都用过延时函数,从while循环,到定时器延时,到systick延时,再到DWT延时等等。延时含义即从某一刻开始,到下一个某刻结束。
  从用途上来说,延时有功能性和非功能性之分。功能性延时即某些功能实现必须需要延时,如模拟IIC等;非功能性延时,是为了方便观察或记录现象而进行的延时,一般来说正式发布的时候可以去掉,如加入延时为了printf打印慢一点,非功能性延时通常不用很精确。
  根据单片机延时时CPU的动作,分为阻塞延时和非阻塞延时。通俗理解就是阻塞延时就是死等,CPU进入延时程序后,除了响应中断外啥也不干,就等延时时间到达。非阻塞延时是指,进入延时后,CPU可以执行除中断意外的其他功能。
  根据延时时是否是靠中断计时,分为中断延时(定时器溢出中断)和非中断延时。
  主控:STM32F407ZGT6(HSE:8MHz)
  库:STD标准库V1.8.0
  工具:RIGOL DS1104Z PLUS数字示波器
  各种延时函数测试标准:无论那种方式,mian函数中功能有RTC自动唤醒事件(1s唤醒),通过这些来模拟日常开发,每种延时方式开启后主要测试微秒us延时,毫秒ms延时因为有中断的存在会不准确,当然也有毫秒延时本身误差在。微妙延时会测试50微秒us级(《100us)3组,500微秒us级(《1000us)3组。毫秒延时会测试50毫秒ms级(《100ms)5组,500ms级(《1000us)3组。延时后翻转PB4脚电平,示波器测量电平翻转时间。
  提示:如果不想看数据测试客户以直接阅读结论(黄色字体)。
  一、阻塞延时函数
  延时方式从阻塞方式到非阻塞方式,中断从无中断到有中断,准确性从粗略延时到精确延时。(本文持续更新,因此有些函数暂无)。
  延时函数版本号说明
  主版本号:1阻塞延时,2非阻塞延时
  次版本号:0非中断,1中断,
  修订号:
  开源协议、版权和免责声明:
  本文为原创,代码开源使用开源协议Apache-2.0,可以随意使用。请署名版权信息,格式:
  Copyright (c) 20021-2021, Logan(wangzhaoyangly@Foxmail.com)。本文数据只做参考,
  本人不对数据正确性和准确性做保证,由此产生问题本人概不负责。
  1. 循环延时(V1.0.0)
  循环延时即在一个循环中让CPU做一些没有意义的工作来完成延时的目的。由于NOP空指令在不同架构的MCU上执行时间不一定,因此不使用NOP进行延时。循环延时和NOP指令延时本质是一样的。不过这个含税延时时间需要摸索,下面这样设置,实际扩大5倍。
  代码(while形式)
  void delay_while_us(uint16_t time)
  {
  uint16_t i = 0;
  while(time--)
  {
  i=168;//168MHz下
  while(i--);
  }
  }
  测试数据
  5us延时,理论周期为10us,实际测试周期为50.8us,数据偏差+408%。
  50us延时,理论周期为100us,实际测试周期为500.8us,数据偏差+408%。
  这个数据已经说明问题了,微秒级延时很不准确。
  上面已经说明了,这个延时函数只适合粗略延时,其他量级数据也没有测试必要。
  特点总结
  优点:实现简单
  缺点:延时不准确,针对不同单片机需要调整
  准确性:粗略定时
  OS:理论可用,实际上由于OS多线程的原因,偏差更大。
  2. SYSTICK非中断延时(V1.0.1)
  正点原子的systick非中断延时,使用硬件定时器SYSTICK嘀嗒定时器,采用往LOAD寄存器里写值倒计时结束即延时结束,不占用额外定时器资源,不靠中断计时,但是还是阻塞方式延时,微秒级延时可以用于中断内,但是不建议在中断中使用毫秒级延时。可用于OS,但是需要改动,大多数OS的时钟节拍是靠SYSTICK的,采用时间摘取法,延时前后进行开中断和关中断,避免中断干扰。ticks 是延时 nus 需要等待的 SysTick 计数次数(也就是延时时间), told 用于记录最近一次的 SysTick-》VAL 值,然后 tnow 则是当前的SysTick-》VAL 值,通过他们的对比累加,实现 SysTick 计数次数的统计,统计值存放在 tcnt 里面,然后通过对比 tcnt 和 ticks,来判断延时是否到达,从而达到不修改 SysTick 实现 nus 的延时,从而可以和 OS 共用一个 SysTick(用于OS请参考正点原子代码)。
  代码
  /*注意systick的时钟来自AHB时钟(HCLK)8分频。一般配置系统时钟SYSCLK=AHB时钟HCLK。假设外部晶振为8MHz,然后倍频到168MHz,那么systick的时钟为21MHz,也就是systick的计数器VALMeizu减一,就代表时间过了1/21us=46.62ns(如果systick时钟源选择AHB时钟,则systick周期为1/168us=5.95ns)。所以fac_us=SystemCoreClock/8000000,意识是极端在系统时钟频率下1us需要多少个systick的周期,fac_ms同理,fac_ms=fac_us*1000。
  nus为要延时的us数,nus的值《2^24/fac_us@fac_us=21 = 798915us
  mus为延时的ms数,范围为0-798ms*/
  #define fac_us SystemCoreClock/8000000 //us延时基数
  #define fac_ms fac_us*1000
  void delay_us(u32 nus)
  {
  u32 temp;
  SysTick-》LOAD = fac_us*nus;
  SysTick-》VAL=0X00;//清空计数器
  SysTick-》CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
  do
  {
  temp=SysTick-》CTRL;//读取当前倒计数值
  }while((temp&0x01)&&(!(temp&(1《《16))));//等待时间到达
  SysTick-》CTRL=0x00; //关闭计数器
  SysTick-》VAL =0X00; //清空计数器
  }
  void delay_ms(u16 nms) //168MHz下nms《=798ms
  {
  u32 temp;
  SysTick-》LOAD = fac_ms*nms;
  SysTick-》VAL=0X00;//清空计数器
  SysTick-》CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
  do
  {
  temp=SysTick-》CTRL;//读取当前倒计数值
  }while((temp&0x01)&&(!(temp&(1《《16))));//等待时间到达
  SysTick-》CTRL=0x00; //关闭计数器
  SysTick-》VAL =0X00; //清空计数器
  }
  //延时nms
  //nms:0~65535
  void delay_ms(u16 nms)
  {
  u8 repeat=nms/540; //这里用540,是考虑到某些客户可能超频使用,
  //比如超频到248M的时候,delay_xms最大只能延时541ms左右了
  u16 remain=nms%540;
  while(repeat)
  {
  delay_xms(540);
  repeat--;
  }
  if(remain)delay_xms(remain);
  }
  测试数据
  50us级延时
  延时5us,理论周期10us,实际周期10.8us,数据偏差8%。
  延时50us,理论周期100us,实际周期100.0us,数据偏差0%。
  延时80us,理论周期160us,实际周期162.0us,数据偏差1.25%。
  结论:50us级延时偏差在0%-8%。
  500us级延时
  延时200us,理论周期400us,实际周期400.0us,数据偏差0%。
  延时500us,理论周期1000us,实际周期1000.0us,数据偏差0%。
  延时800us,理论周期1600us,实际周期1600.0us,数据偏差0%。
  结论:500us级延时偏差在0%是十分准确的,因为毫秒级延时以微秒延时为基础,因此毫秒级延时也是十分准确。
  特点总结
  优点:非中断,硬件计时,非常准确。
  缺点:占用硬件资源,不可嵌套(主函数使用毫秒延时,中断中使用微秒延时,如果在微秒延时时中断产生,进行微秒延时再次修改SYSTICK寄存器的值,因此会导致主函数毫秒延时不准确)。
  准确性:精确延时。
  OS:理可用于OS,OS时需要开启SYSTICK中断。
  3. DWT延时(V1.0.2)
  DWT外设用于系统调试及跟踪,DWT 中有剩余的计数器,它们典型地用于程序代码的“性能速写”(profiling)。通过编程它们,就可以让它们在计数器溢出时发出事件(以跟踪数据包的形式)。最典型地,就是使用 CYCCNT寄存器来测量执行某个任务所花的周期数,这也可以用作时间基准相关的目的(操作系统中统计 CPU使用率可以用到它)。
  
  
  适用范围:m3、m4、m7实测可用(m0不可用)。
  精度:1/内核频率(s),1/168MHz=5.95ns。
  代码
  //.h
  #include “stdio.h”
  #include “stm32f4xx_conf.h”
  #include “sys.h”
  #include “core_cm4.h”
  #define DWT_CR *(__IO uint32_t *)0xE0001000 //DWT控制寄存器
  #define DWT_CYCCNT *(__IO uint32_t *)0xE0001004 //时钟周期寄存器
  #define DEM_CR *(__IO uint32_t *)0xE000EDFC //内核调试控制寄存器,使能DWT外设,DEMCR的位24控制,写1使能
  #define DWT_CYCCNT_VAL (*(__IO uint32_t *)0xE0001004)
  #define DWT_CTRL (*(__IO uint32_t *)0xE0001000)
  #define DEM_CR_TRCENA (uint32_t)(1 《《 24) //使能DWT外设
  #define DWT_CR_CYCCNTENA (uint32_t)(1 《《 0) //启动CYCCNT计数器
  #define SYSCLK 168000000 //系统时钟频率
  void DWT_Init(uint32_t sys_clk_c);
  void delay_dwt_us(uint32_t nus);
  #define delay_dwt_ms(nms) delay_dwt_us(nms*1000)
  //.c
  static uint32_t sysclk = 0;
  void DWT_Init(uint32_t sys_clk_c)
  {
  //使能SWT外设
  DEM_CR |= DEM_CR_TRCENA;
  //CYCCNT寄存器清0
  DWT_CYCCNT = (uint32_t)0;
  //使能CYCCNT
  DWT_CR |= DWT_CR_CYCCNTENA;
  sysclk = sys_clk_c;
  }
  void delay_dwt_us(uint32_t nus)
  {
  uint32_t ticks_start = 0, ticks_end = 0, tcnt = 0;
  DWT_CYCCNT = (uint32_t)0;
  ticks_start = DWT_CYCCNT_VAL;
  if (!sysclk)
  {
  DWT_Init(SYSCLK);
  }
  tcnt = (nus * (sysclk / 1000000));
  ticks_end = ticks_start + tcnt;
  if (ticks_end 》 ticks_start)
  {
  while (DWT_CYCCNT_VAL 《 ticks_end)
  {
  };
  }
  else
  {
  while (DWT_CYCCNT_VAL 》= ticks_end)
  {
  };
  while (DWT_CYCCNT_VAL 《 ticks_end)
  {
  };
  }
  }
  测试数据
  50us级延时
  延时5us,理论周期10us,实际周期10.6us,数据偏差6%。
  延时50us,理论周期100us,实际周期101.0us,数据偏差1%。
  延时80us,理论周期160us,实际周期160.0us,数据偏差0%。
  结论:50us级延时偏差在0%-6%。
  500us级延时
  延时200us,理论周期400us,实际周期400.0us,数据偏差0%。
  延时500us,理论周期1000us,实际周期1000.0us,数据偏差0%。
  延时800us,理论周期1600us,实际周期1600.0us,数据偏差0%。
  结论:500us级延时偏差在0%是十分准确的,因为毫秒级延时以微秒延时为基础,因此毫秒级延时也是十分准确。
  注:50s级误差是基本一致的,无论是SYSTICK定时器还是DWT计时,误差都是一定的,在》100us时是没有误差的,推测《100us计时可能是示波器采样的因素。不过这个100us下不到10%的误差可以忽略不计,并且100us下,计时时间越接近于100us,误差越小,80us时误差就为0了,第一组测试数据5us延时有较大误差,我认为情有可原,是可以接收的。总的来说,硬件计时是十分准确的计时方式。
  特点总结
  优点:非中断,硬件计时,非常准确,延时时间长(168MHZ下可达25s),可嵌套(最终延时时间大于等于需要延时时间),可用于任何中断。
  缺点:占用硬件资源(但是这个资源是不用白不用的资源)
  准确性:精确延时。
  OS:可用于OS,不占用OS心跳时钟。
  二、非阻塞延时
  本文主要讨论无OS下非阻塞延时函数实现,OS下有系统调度,线程可以挂起执行效率较高,如果有需要定时轮训的话还是需要用到非阻塞延时功能,思想是一样的。虽然这章是研究无OS下的非阻塞延时函数的,但在研究过程发现,在无OS下实现非阻塞延时,跟做个OS差不多,无OS下实现非阻塞延时照样会用到任务的概念,任务切换,任务状态等,跟OS下是一样的,但是OS下也会用到非阻塞延时。无OS下实现非阻塞延时实际上跟OS下的硬实时概念挺像的,不过应该称为硬延时,比如从系统运行初始化完成后开始,每50ms点亮一次LED灯,每100ms上传一次数据灯。本章就无OS下非阻塞延时就行研究讨论,后续可能会更新OS下的非阻塞延时。
  非阻塞延时思想:使用定时器周期性中断产生定时时基,如10ms产生一次中断,然后在定时器中断函数中记录时间,while循环中根据时间执行相关任务,在任务A中判断是否到达执行任务时间,如果到达则执行,没有则调出判断任务B。实际上是将时间作为状态机状态来工作,何为有限状态机还请百度学习,不在本文讨论范围。
  1. 无OS下非阻塞延时(待更新)
  2. OS下非阻塞延时(暂无更新)
  结语:由于水平有限,文中难免有错误之处,欢迎指正,也欢迎探讨交流。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分