新闻中心

EEPW首页 > 嵌入式系统 > 牛人业话 > 嵌入式里的“延迟”

嵌入式里的“延迟”

作者:jobs时间:2015-05-27来源:电子产品世界收藏

  前些天在版主群里有人问“有没有好用的延迟函数啊?”我的第一反应就是“延迟函数要视自己的应用而编写,不可能千篇一律的应用。”可是回首一看,单片机的发展历程,在不同时期里有着不一样的延迟函数。

本文引用地址:http://www.eepw.com.cn/article/274824.htm

  在版主上学的年代里,单片机课程老师对汇编语言有着非常深入的了解,如XX指令是单指令周期,XX指令是双指令周期。如果使用了编程,也要仔细看生成的汇编代码然后再调节。例如下面的代码示例:

  功能 延时(12M 24M)

  误差 Ms S 5% 10Us 8%-80%

  //24M晶振 延时 n毫秒

  void DelayMs_24M(unsigned int n)

  {

  unsigned int i=0,j=0;

  for(i=0;i

  for(j=0;j<357;j++);

  }

  延迟函数是通过的两个循环计算而形成的停机等待而达到延迟的目的。代码是通过查看由生成的汇编代码指令——那个357便是由此计算出来的。当然,延迟函数是否精准也完全取决于那个357数字的选择了。

  单周期指令,双周期指令,数一数便可以了?其实查看汇编代码没有这么简单的,毕竟For循环也需要系统开销的,还有其它比较,判断指令什么的。但这一切在IAR for AVR编译环境里似乎就简单多了。

  在IAR for AVR编译环境里,用户只需要 #include "intrinsics.h"便可以调用void __delay_cycles(unsigned long);函数,这个函数是系统函数,其代表着一个机器周期。用户不再需要计算汇编语言的指令周期,不必再细读单片机的操作手册,强大的IAR编译环境自己就算好了——单片机发展到IAR for AVR时代,也基本代表着汇编退居二线。由于篇幅的原因,版主就不再这里为大家帖出代码示例了。

  在Atmel的8位单片机AVR系列一统天下的时候,ARM内核为代表的单片机在悄然崛起。不知不觉,以ST公司stm32f103为代表的32位Cortex-M3内核的单片机占据了市场大部分分额,各大论坛争先推出STM32版块。

  其中,某位牛人推出的使用systick函数来完成延迟函数颇具人气。我们来看一下源代码:

  //初始化延迟函数

  void delay_init(u8 SYSCLK)

  {

  SysTick->CTRL&=0xfffffffb;//选择内部时钟 HCLK/8

  fac_us=SYSCLK/8;

  fac_ms=(u16)fac_us*1000;

  }

  void delay_ms(u16 nms)

  {

  SysTick->LOAD=(u32)nms*fac_ms; //时间加载

  SysTick->CTRL|=0x01; //开始倒数

  while(!(SysTick->CTRL&(1<<16))); //等待时间到达

  SysTick->CTRL&=0XFFFFFFFE; //关闭计数器

  SysTick->VAL=0X00000000; //清空计数器

  }

  牛人的代码还是非常简洁的,使用起来也方便,首先调用delay_init函数,然后,再调用delay_ms()函数。这个延迟函数也是非常准确的,因为其使用了单片机的硬件定时器模块。在STM32F103高达72MHz的主频,优化的指令集系统下,系统的开销完成可以忽略。笔者也将其成功应用于单总线通讯方式的数字温度采集传感器18B20芯片上,测试良好。

  写到这里,笔者已经介绍了三种延迟函数,它们三个都有一个共同的特点:阻塞延迟函数——在“等待”延迟函数到来的时候里,单片机并没有处理其它有用,有意义的事情,而是停机在等待着时间的到来。对于我们要处理大量数据的单片机系统来说,这个劣势有时就很难接受的。那么我们要怎么解决呢?

  我们仍然以STM32F103为例,仍然要使用强大的定时器,这里我们再次选用systick定时器。我们首先要初始化ST单片机systick,其每1ms进入中断一次,代码如下:

  if (SysTick_Config(72000)) //参数为系统时钟的向上溢出值,此配置为72000,即1ms中断一次

  {

  /* Capture error */

  while (1);

  }

  之后,我们在systick的中断函数里计数,示例代码如下:

  void SysTick_Handler(void)

  {

  if(gCntLed[0] > 0)

  {

  gCntLed[0]--;

  }

  else

  {

  gCntLed[0] = 0;

  }

  }

  从上面代码可以清楚看到,每1ms,gCntLed[0]将计数值减1,直到为0时止。而main函数里,就要不断的查询这个gCntLed[0]的值,当未达到0值时,就去做别的事情,而查询到0值时,再去处理自己的事情,示例代码如下:

  while(1)

  {

  if(gCntLed[0] == 0)

  {

  LedToggle(0);

  gCntLed[0] = 200;

  }

  KeyScan();

  }

  通过未阻塞的延迟函数,我们实现了LED灯每隔200ms闪烁一次的效果,与其同时,我们也没有停止不断扫描按键。——这就是非阻塞延迟函数的强大优势。非阻塞式延迟函数还主要应用于操作系统函数里,喜欢的网友可以自己查看相关函数。

  随着时代的进步,能源的问题逐渐突出出来。刚刚笔者介绍的几种函数都是在不停的“运行”,看似什么事情也没有做,但是单片机确实在全力的“奔跑”,这与当前节碳减排,低功耗格格不入。MSP430算得上是低功耗的代表了,其延迟函数可以拿来借鉴一下。

  在MSP430的低功耗设计中,阻塞式延迟函数是基本不用的——因为功耗太大,未阻塞式延迟函数是必备条件。设计主要思想是,定时让MSP430从睡眠模式里“醒”过来,查看一下当前的时间与状态,然后再做决定如何处理。换句话说,上面的示例就变成了,MSP430每1ms准时醒来一次,处理了一下gCntLed[0]的值,然后又查看了一下,如果非0值,则继续“睡”去了;如果恰好是0值,则再干一会儿事情……这里,MSP430大部分时间里就处于了低功耗的睡眠模式,自然也就节能了。

  又到总结的时候了,几种延迟函数各有特点与应用场景,各位亲爱的网友们根据自己的需求来自行选择吧!当然,也可以来论坛的ARM版块发帖求助,版主也会倾力奉献的。

linux操作系统文章专题:linux操作系统详解(linux不再难懂)
尘埃粒子计数器相关文章:尘埃粒子计数器原理
晶振相关文章:晶振原理


关键词: 嵌入式 C语言

评论


相关推荐

技术专区

关闭