完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
接触过单片机的,肯定都用过延时函数,从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下非阻塞延时(暂无更新) 结语:由于水平有限,文中难免有错误之处,欢迎指正,也欢迎探讨交流。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1609 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1540 浏览 1 评论
970 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
681 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1587 浏览 2 评论
1861浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
644浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
515浏览 3评论
528浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
503浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-20 22:34 , Processed in 0.779298 second(s), Total 76, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号