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

这篇具有很好参考价值的文章主要介绍了嵌入式_常见延时方式的差异与选择(for循环延时、定时器延时、汇编延时....)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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


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


前言

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

准备工作:
需设置Debug模式Keil对于片子的时钟配置参数,不然Debug模式下测试断点时间是不准确的。如图所示,我们使用的是72兆时钟,所以需要设置为实际的系统时钟参数为72.0。
for循环内的延时用定时器,单片机之路,汇编,单片机,linux
for循环内的延时用定时器,单片机之路,汇编,单片机,linux
以此保证,测试时间的准确性。


一、简单的for循环延时

foruint6_t i = 0;i < 10000;i++{
}

for循环内的延时用定时器,单片机之路,汇编,单片机,linux
for循环内的延时用定时器,单片机之路,汇编,单片机,linux

delaytime(s) = 0.00096057 - 0.00012024 = 0.00084033s
大概也就840us左右,使用示波器看一下周期,大概也差不多就这样。
for循环内的延时用定时器,单片机之路,汇编,单片机,linux

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

for循环内的延时用定时器,单片机之路,汇编,单片机,linux
for循环内的延时用定时器,单片机之路,汇编,单片机,linux

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

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

foruint6_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_CK
    sTIM_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));
  }
}

通用定时器:
for循环内的延时用定时器,单片机之路,汇编,单片机,linux

for循环内的延时用定时器,单片机之路,汇编,单片机,linux
delaytime(s) = 0.00032718 - 0.00012229 = 0.00020587s 大概也就205us左右,使用示波器看一下周期,还是比较准确的。

for循环内的延时用定时器,单片机之路,汇编,单片机,linux

结果:

三、汇编延时

汇编延时,利用内嵌汇编进行循环进行延时,这里简单解读一下:
内嵌汇编就是在它让你可以在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),

for循环内的延时用定时器,单片机之路,汇编,单片机,linux
for循环内的延时用定时器,单片机之路,汇编,单片机,linux

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

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


总结

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

到了这里,关于嵌入式_常见延时方式的差异与选择(for循环延时、定时器延时、汇编延时....)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MCU嵌入式开发-硬件和开发语言选择

    主要考虑以下方面来决定是否需要RTOS支持: 需要实现高响应时的多任务处理能力 需要实现实时性能要求高的任务 需要完成多个复杂的并发任务 具备满足工控系统实时性要求的各项功能特性。通过它提供的硬件库、线程支持、中断支持等,可以完全控制微控制器的各个外设,实

    2024年02月12日
    浏览(46)
  • 嵌入式Linux驱动开发——常见框架梳理

    本文主要介绍了Linux驱动开发中一些常用的驱动框架,platform、input、iic、spi等,硬件平台使用的是正点原子的imx6ull开发板。 不管什么框架最后都是要追溯到配置IO的电气属性和复用功能 如果要使用外部中断,设备树节点中还需添加相关信息,什么边沿触发 1:module_init和mod

    2024年02月15日
    浏览(52)
  • 嵌入式硬件中常见的面试问题与实现

    01 请列举您知道的电阻、电容、电感品牌(最好包括国内、国外品牌) ▶电阻 美国:AVX、VISHAY威世 日本:KOA兴亚、Kyocera京瓷、muRata村田、Panasonic松下、ROHM罗姆、susumu、TDK 台湾:LIZ丽智、PHYCOM飞元、RALEC旺诠、ROYALOHM厚生、SUPEROHM美隆、TA-I大毅、TMTEC泰铭、TOKEN德键、TYOHM幸亚

    2024年04月10日
    浏览(30)
  • (嵌入式c语言)c语言编译常见错误

    预处理(gcc -E -o)-编译(gcc -S -o)-汇编(gcc -c -o)-链接(gcc -o) 将代码中的define 和 include替换成实体码  define和include不是,是在编译过程中处理的。 包含 #include 包含头文件 宏 #define 宏  替换  不会进行语法检查 #define 宏  宏体   宏体要加括号 #define ABC 

    2024年02月11日
    浏览(31)
  • 嵌入式软件中常见的 8 种数据结构详解

      目录   第一:数组 1、数组的应用 第二:链表 1、链表操作 2、链表的应用 第三:堆栈 1、堆栈操作 2、堆栈的应用 第四:队列 1、队列操作 2、队列的应用 第五:哈希表 1、哈希函数 2、哈希表的应用 第六:树 1、二叉搜索树 2、树的应用 第七:堆 1、堆的应用 第八:图

    2023年04月26日
    浏览(25)
  • 嵌入式中详解 ARM 几个常见的寄存器方法

    大家好,今天来聊聊对于ARM几个特殊寄存器的理解,FP、SP和LR。 1、介绍 FP:栈顶指针,指向一个栈帧的顶部,当函数发生跳转时,会记录当时的栈的起始位置。 SP:栈指针(也称为栈底指针),指向栈当前的位置, LR:链接寄存器,保存函数返回的地址。 关于gcc就有一个关

    2024年02月20日
    浏览(33)
  • 面试嵌入式工程师过程中的常见问题和回答

    1、请介绍一下你的嵌入式系统开发经验。 an:首先,回答此类问题时应该尽可能地详细和具体。可以从以下方面介绍自己的嵌入式系统开发经验: 1、开发环境和工具:介绍自己使用过哪些开发环境和工具,例如Keil、IAR、Eclipse等。可以说明自己对这些工具的熟练程度,以及

    2024年02月03日
    浏览(39)
  • 嵌入式学习笔记(3)ARM的异常处理方式介绍

    什么是异常   正常工作之外的流程都叫异常   异常会打断正在执行的工作,并且一般我们希望异常处理完后继续回来执行原工作   中断是异常的一种 异常向量表   所有的CPU都有异常向量表,这是CPU设计时就设定好的,是硬件决定的。   当异常发生时,CPU会自动动作(

    2024年02月11日
    浏览(39)
  • 嵌入式硬件库的基本操作方式与分析

    本次要介绍的开源软件是 c-periphery: 一个用 C 语言编写的硬件外设访问库。 我们可以用它来读写 Serial、SPI、I2C 等,非常适合在嵌入式产品上使用。 我们可以基于它优秀的代码框架,不断地扩展出更多的功能模块,最终形成自己产品适用的 Linux 硬件抽象层。 源文件: 约

    2024年02月06日
    浏览(31)
  • CSS的三种链接方式(内联式、嵌入式、外部式)

    其实就是用html中style属性 嵌入式css样式,就是可以把css样式代码写在style type=“text/css”/style标签之间。 (样式一多,内联式就很繁杂,不易于阅读和维护,这没法发挥CSS的优势了) 外部式css样式(也可称为外联式)就是把css代码写一个单独的外部文件中,这个css样式文件以“

    2024年02月06日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包