嵌入式_常见延时方式的差异与选择(for循环延时、定时器延时、汇编延时…)


这里整理几种常见的延时方式,并做简单测试供大家参考,如果有什么不对的地方,欢迎指正,共同探讨。

文章目录

  • 嵌入式_常见延时方式的差异与选择(for循环延时、定时器延时、汇编延时....)
  • 前言
  • 一、简单的for循环延时
  • 二、常见的定时器延时
  • 三、汇编延时
  • 总结

前言

测试基于GD32F103CBT6硬件平台,标准的72MHz系统时钟, 使用标准库GD32F10x_Firmware_Library_V1.0.0提示:(提示:此库坑多、慎用!)
测试方法为:
1:在Debug模式下延时开始于结束为止使用断点,测试两个断点之间的时间差。
2:通过延对GPIO周期性输出高低电平,使用示波器测试周期的准确性。

准备工作:
需设置Debug模式Keil对于片子的时钟配置参数,不然Debug模式下测试断点时间是不准确的。如图所示,我们使用的是72兆时钟,所以需要设置为实际的系统时钟参数为72.0。


以此保证,测试时间的准确性。


一、简单的for循环延时

for(uint6_t i = 0;i < 10000;i++)
{}


delaytime(s) = 0.00096057 - 0.00012024 = 0.00084033s
大概也就840us左右,使用示波器看一下周期,大概也差不多就这样。

如果把10000改成100,这个时间延时会不会缩小100倍呢?这个实验我也试过了,结果如下:delaytime(s) = 0.00012907 - 0.00011732 = 0.00001165s 大概也就11us左右,还是有一点差异的,不会等比变化。
而且使用Cotex-M3内核与Cotex-M0内核做对比,个人猜想这个结果也是有差异的。(没找到M3内核的板子)


而且我使用Cotex-M4内核与Cotex-M0内核做对比,这个结果差异也很大。

贴一个简单的for循环C语言代码

for(uint6_t i = 0;i < 1000;i++)
{}

该for循环编译形成汇编代码如下,有兴趣的朋友可以去分析一波,我这里就不分析了,

代码如下(示例):

0x0800023C DBFA      BLT           0x08000234
0x08000232 E001      B             0x08000238
0x08000234 1C41      ADDS          r1,r0,#1
0x08000236 B288      UXTH          r0,r1
0x08000238 F5B07F7A  CMP           r0,#0x3E8
0x0800023C DBFA      BLT           0x08000234

二、常见的定时器延时

如果片子上有定时器资源,可以使用定时器延时,但是片子上资源有限要提前做好资源分配,废话不多说这里使用通用定时器进行延时操作,并对结果进行对比。

通用定时器:

/***********************************************************************1-函数名:Timer2_Init*2-函数功能:初始化定时器**********************************************************************/void Timer2_Init(void){/*由于GD32与ST32寄存器差异,此处配置的Timer1定时器*//*步长1us计时器*/TIMER_BaseInitPara  sTIM_TimeBaseStructure;NVIC_InitPara   NVIC_InitStructure;//NVIC_PRIGroup_Enable(NVIC_PRIGROUP_0);RCC_APB1PeriphClock_Enable(RCC_APB1PERIPH_TIMER2, ENABLE); //实际是复位Timer2(代码名字错位)TIMER_DeInit(TIMER2);sTIM_TimeBaseStructure.TIMER_Period = 0x0000FFFF;                 //计数器自动重装值sTIM_TimeBaseStructure.TIMER_Prescaler = 71;                     //计数器时钟预分频值,计数器时钟等于 PSC 时钟除以 (PSC+1)sTIM_TimeBaseStructure.TIMER_ClockDivision = TIMER_CDIV_DIV2;  //设置时钟分割:fDTS=fTIMER_CKsTIM_TimeBaseStructure.TIMER_CounterMode = TIMER_COUNTER_UP;   //TIM向上计数模式TIMER_BaseInit(TIMER2, &sTIM_TimeBaseStructure);                 //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位TIMER_INTConfig(TIMER2,TIMER_INT_UPDATE,ENABLE);      //中断使能NVIC_InitStructure.NVIC_IRQ = TIMER2_IRQn;           //TIM2中断NVIC_InitStructure.NVIC_IRQPreemptPriority = 0;        //Q抢占优先级优先级0级NVIC_InitStructure.NVIC_IRQSubPriority = 8;           //副优先级2级NVIC_InitStructure.NVIC_IRQEnable = ENABLE;            //IRQ通道被使能NVIC_Init(&NVIC_InitStructure);                       //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器TIMER_Enable(TIMER2, ENABLE);  //使能定时器外设}/***********************************************************************1-函数名:Task_Init*2-函数功能:获取定时器计数个数**********************************************************************/uint16_t Timer1_GetTimerCounter(void){uint16_t counter = 0;counter = (uint16_t)TIMER_GetCounter(TIMER2);return counter;}
/***********************************************************************1-函数名:Delay_us*2-函数功能:Delay_us**********************************************************************/
void Delay_us(uint16_t usCounter)
{uint16_t TempCounter = 0;uint16_t TotalCounter = 0;uint16_t InitCounter = 0;InitCounter = Timer1_GetTimerCounter();TotalCounter = InitCounter + usCounter;if(TotalCounter >= InitCounter){do{TempCounter = Timer1_GetTimerCounter();} while (TempCounter < TotalCounter);}else{do{TempCounter = Timer1_GetTimerCounter();} while ((TempCounter > TotalCounter) && (TempCounter < InitCounter));}
}

通用定时器:


delaytime(s) = 0.00032718 - 0.00012229 = 0.00020587s 大概也就205us左右,使用示波器看一下周期,还是比较准确的。

结果:

三、汇编延时

汇编延时,利用内嵌汇编进行循环进行延时,这里简单解读一下:
内嵌汇编就是在它让你可以在C程序中插入使用汇编语言编写的函数, 详细见代码注释

#if defined   (__CC_ARM) /*!< ARM Compiler */
/*
delayus = (1/SystemCoreClock * 3 * ulCount)us
*/
__ASM volatile void SysCtlDelay(unsigned long ulCount)
{subs    r0, #1;                //函数参数保存在R0寄存器,这里将参数进行自减操作并把结果依旧保存在R0中。bne     SysCtlDelay;      //R0 != 0,则跳到SysCtlDelay,这里如果R0自减不等于0,则继续执行该函数        bx      lr;                 //返回主程序(不可省略)
} void SysCtlDelayus(unsigned long ulCount)
{//CPU_INI_DISABLE();           //关闭中断SysCtlDelay(ulCount * (SystemCoreClock/3000000)); //CPU_INI_ENABLE();             //打开中断
}
#endif /* __CC_ARM */

__ASM volatile void SysCtlDelay(unsigned long ulCount)函数:参数ulCount表示循环执行该函数的次数
该函数由三条汇编指令构成,当参数ulcount = 1时,只执行一次三条汇编指令,
执行一条汇编指令时间为:1/系统时钟(MHz),所以ulcount = 1时,延时为delayus = (1/SystemCoreClock(MHz)* 3)us
当参数ulcount = n时,执行n次三条汇编指令,
执行一条汇编指令时间为:1/系统时钟(MHz),所以ulcount = n时,延时为delayus = (1/SystemCoreClock (MHz)* 3 * n)us

void SysCtlDelayus(unsigned long ulCount)函数:参数表示延时多少us
计算方法:系统中SystemCoreClock为CPU时钟频率,单位为Hz,所以SystemCoreClock(MHz)= SystemCoreClock(Hz)/1000000
根据上述,
因为延时 (1/SystemCoreClock(MHz)* 3)us需要ucount = 1,
所以延时1us,需要ucount = 1/(1/SystemCoreClock(MHz)* 3),

根据计算方法所述,带入公式,延时1us,需要ucount = 1/(1/(SystemCoreClock(Hz)/1000000)* 3)
化简得:
延时1 us ucount = SystemCoreClock(Hz)/3000000)
延时x us ucount = x * SystemCoreClock(Hz)/3000000),得到函数void SysCtlDelayus(unsigned long ulCount),


delaytime(s) = 0.00112594 - 0.00012038 = 0.00100556s 大概也就1000us左右,用示波器测试,延时比较精准,在使用前需要明确板子时钟是以兆为时钟单位,可打开或关闭系统中断会更加精准


但是这里有个疑问?SysCtlDelay()函数中当ulCount大于1时候,跳回主函数指令 bx lr;该指令只执行了一次,另两条指令分别执行两次,如果按照3次计算 是不是不准确。而且内核如果是三级流水线操作,执行时间是不是不能这么算?


总结

三种延时处理方式各有优缺点:
for循环:简单明了,几乎适用于所有C平台;但是延时精度不高,所以适用于延时精度要求不高,不会多次重复调用的情况(重复调用会使时间误差叠加放大),例如ADC初始化完成之后有一个短延时后再使能,
Timer定时器:延时精度相当准确,但是会多占用一个定时器资源,所以实际开发时候要先对资源做好规划。
汇编延时:延时也非常准确,不会占用资源,但是移植性差。原理可以通用,但代码不通用,计算过程比较复杂,需要对时钟和周期有较深的理解才行。

嵌入式_常见延时方式的差异与选择(for循环延时、定时器延时、汇编延时....)相关推荐

  1. python可逆加密算法_常见加密方式及Python实现

    由于计算机软件的非法复制,通信的泄密.数据安全受到威胁,所以加密在开发过程中是经常使用到的技术,在一些重要场景中都有所应用,如:登录.支付.oauth等,场景不同需要搭配不一样的签名加密算法来达到业务 ...

  2. 常见蛋白质种类_常见蛋白粉种类大全,你选择对了吗?

    该文为原创作品,抄袭视侵权举报! 相信接触到健身,或是正在健身路上的朋友或多或少都有需要补充更多蛋白质的时候,那么这时候可能蛋白粉就要派上用场了.我记得我选择第一桶蛋白粉的时候,完全没有方向,最后只是 ...

  3. python对excel表统计视频_列表常见统计方式2_【曾贤志】用Python处理Excel数据 - 第1季 基础篇_Excel视频-51CTO学院...

    ---------------------------------------------------------------- 学完本课程可继续巩固篇:https://edu.51cto.com/c ...

  4. Spring事务管理详解_基本原理_事务管理方式

    Spring事务管理详解_基本原理_事务管理方式 1. 事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,使用JDBC的事务管理机制,就是利用java.sql.Connection对象 ...

  5. 【Excel】绘图案例_常见复合图:簇状图+堆积图+折线图

    [Excel]绘图案例_常见复合图:簇状图+堆积图+折线图 前言 最近有朋友让我帮忙用excel画图,老实说我很讨厌用excel画图,点来点去,复杂一些还不能复用,非常繁琐.当然,入门也很简单.需求时 ...

  6. 【宋红康 MySQL数据库】【03】SQL概述_常见的数据库对象

    持续学习&持续更新中- 学习态度:守破离 [宋红康 MySQL数据库][03]SQL概述_常见的数据库对象 SQL概述 什么是SQL SQL背景知识 SQL分类 DDL(Data Defini ...

  7. 嵌入式的固件烧录方式

    1.固件 固件,firmware 所谓固件,就是文件,固化在存储介质上的文件,而文件,其实就是数据. 嵌入式开发中,尤其是Linux开发,常见的方式是,从板子(个人用的是arm的板子)上启动,会允许U ...

  8. chrdev字符设备几种注册方式的差异

    数据结构 #define CHRDEV_MAJOR_HASH_SIZE 255static struct char_device_struct {struct char_device_struct * ...

  9. python使用numpy的np.fmod函数计算numpy数组除以某一特定数值剩余的余数(remainder)、np.mod函数和np.fmod函数对负值的处理方式有差异

    python使用numpy的np.fmod函数计算numpy数组除以某一特定数值剩余的余数(remainder).np.mod函数和np.fmod函数对负值的处理方式有差异 目录

最新文章

  1. 分布式锁用 Redis 还是 Zookeeper?
  2. python array与 list区别
  3. 跨域解决方案(史上最易懂)
  4. Serv-u 10.3 的图文安装教程及使用方法
  5. 新手的深度学习综述 | 入门
  6. 尚硅谷_JS DOM编程_学习笔记
  7. Java虚拟机10:类加载器
  8. 关于windows窗体应用程序 1117
  9. c语言中的内存分配malloc、alloca、calloc、malloc、free、realloc、sbr
  10. MATLAB 学习资料整理
  11. tkinter 界面设计工具
  12. 基于Java平台实现发送短信功能
  13. 明翰英语教学系列之雅思写作篇V0.2(持续更新)
  14. Spring DI和AOP简介(一)
  15. 开源OCR文字识别软件Calamari
  16. 数量遗传学 第三章 Hardy -Weinberg Weinberg 法则及应用
  17. IMU输出的角度、角速度、加速度信息的坐标系
  18. 最大值最小值算法(象棋博弈)
  19. 轮滑基础(一)(前摔,葫芦步,推步,A字转弯,弓步转弯)
  20. Install cuDNN v8.0.5 in ubuntu 20.04

热门文章

  1. 反思加班文化:胡新宇,不该消失的生命_网易商业报道_中国经理人门户网站
  2. 为你的CSDN博客添加CNZZ流量统计功能
  3. 如何在Excel中查找和替换文本和数字
  4. 【机器学习】P25 随机森林算法(2) 实现 “波士顿房价” 预测
  5. 《十周成为数据分析师》笔记——业务线 第三节 不做只懂技术不懂业务的“工具人”
  6. 微博全量开放IP属地功能;“月薪三千能买什么样的手机”上热搜,苹果第一;​华为轮值董事长胡厚崑:没有自建芯片厂计划 | EA周报...
  7. vs2013发布网页_网页设计:2013年值得关注的20个最热门趋势
  8. 有关路径规划入门的学习记录
  9. 《Centos系统——Nginx优化》
  10. 解决IOS不能自动获取焦点问题