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

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


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

文章目录

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


前言

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

准备工作:
需设置Debug模式Keil对于片子的时钟配置参数,不然Debug模式下测试断点时间是不准确的。如图所示,我们使用的是72兆时钟,所以需要设置为实际的系统时钟参数为72.0。
嵌入式_常见延时方式的差异与选择(for循环延时、定时器延时、汇编延时....)_第1张图片
嵌入式_常见延时方式的差异与选择(for循环延时、定时器延时、汇编延时....)_第2张图片
以此保证,测试时间的准确性。


一、简单的for循环延时

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

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

delaytime(s) = 0.00096057 - 0.00012024 = 0.00084033s
大概也就840us左右,使用示波器看一下周期,大概也差不多就这样。
嵌入式_常见延时方式的差异与选择(for循环延时、定时器延时、汇编延时....)_第5张图片

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

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

而且我使用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循环延时、定时器延时、汇编延时....)_第8张图片

嵌入式_常见延时方式的差异与选择(for循环延时、定时器延时、汇编延时....)_第9张图片
delaytime(s) = 0.00032718 - 0.00012229 = 0.00020587s 大概也就205us左右,使用示波器看一下周期,还是比较准确的。

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

结果:

三、汇编延时

汇编延时,利用内嵌汇编进行循环进行延时,这里简单解读一下:
内嵌汇编就是在它让你可以在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循环延时、定时器延时、汇编延时....)_第11张图片
嵌入式_常见延时方式的差异与选择(for循环延时、定时器延时、汇编延时....)_第12张图片

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

嵌入式_常见延时方式的差异与选择(for循环延时、定时器延时、汇编延时....)_第13张图片
但是这里有个疑问?SysCtlDelay()函数中当ulCount大于1时候,跳回主函数指令 bx lr;该指令只执行了一次,另两条指令分别执行两次,如果按照3次计算 是不是不准确。而且内核如果是三级流水线操作,执行时间是不是不能这么算?


总结

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

你可能感兴趣的:(单片机嵌入式,汇编,单片机,linux)