裸机延时效率低的问题

单片机软件定时器的使用方法
特别声明:文章是原创但是本文讲述的思想是在国外的开源代码中借鉴的

初学者在编写单片机程序时经常会用到延时函数,但是当系统逐步复杂以后(没有复杂到使用操作系统)延时会因为延时降低MCU的利用率,更严重的会影响系统中的“并行”操作例如一个既有按键又有蜂鸣器的系统中,如果要求按下按键发出不同的声音,每次发声时间在1秒-2秒之间, 如果用延时来做代码很简单:

//蜂鸣器发出“哔-哔-哔”声音时间约1s
void BeepFuction(void)
{
  unsigned char i;
  for(i=0;i<3;i=++)
  {
    BeepEn(); //开启蜂鸣器
    Delayms(220);//延时220ms
    BeepDis();//关闭蜂鸣器  
    Delayms(110);//延时110ms  
  }
}

当这段代码执行时MCU不可能同时处理按键检查程序因为它大部分时间在执行Delayms()函数中的nop指令,这样就不可能去执行检查按键了(不使用中断时),如果把程序改成流程形式的写法则结果会大为不同,下面先介绍一下基本原理。

我们都知道一般的定时器为16位或8位循环计数,例如对于16位的计数器当计数器数值从0增加到65535时再加一就会回到0那么我们来比较下面两种情况(不考虑计数器在记录当前时刻T后再次回到或超过T这种情况我暂且称它为“压圈”): 
情况1: 
T1时刻计数器数值为300 
T2时刻计数器数值为400 
则T1时刻到T2为100个计数单位。 
这段时间差也为100个计数单位。 
情况2: 
T1时刻计数器数值为65535 
T2时刻计数器数值为99 
则T1到T2 可以算出为65535到0的1个计数单位再加上 0到99的99个计数单位总共为100个计数单位。 
所以时间差还是100个计数单位。 
在C语言中如果使用两个无符号数作减法会得到如下结果:99-65535=100,这个很好理解就和10进制的借位一样只不过借位后不用管高位了也就相当于99+65536-65535结果是100了,当然这些前提条件都是计数器不会出现“压圈”。 
有了上面对定时器的了解就可以从新写这个Beep函数了

//蜂鸣器发出“哔-哔-哔”声音时间约1s
bit BeepFlag = 0;//蜂鸣流程忙标志位
bit BeepCtrl = 0;//蜂鸣器流程控制标志位
void BeepProc(void)
{
  static unsigned int BeepTimer;
  static unsigned char BeepStatus = 0;
  static unsigned char i;
  switch(BeepStatus)
  {
    case 0://
       if(BeepCtrl)
       {
        i = 3;//蜂鸣次数
        BeepFlag = 1;//置位忙标志位
        BeepCtrl = 0;//清除控制标志位
        BeepTimer = TIMER;//这里TIMER为系统定时器计数时钟为1ms
        BeepEn(); //开启蜂鸣器
        BeepStatus = 1;//进入下一个状态
       }
    break;
    case 1://蜂鸣状态
       if(TIMER-BeepTimer>220)//220ms
       {
         BeepDis(); //关闭蜂鸣器
         BeepTimer = TIMER;//记录时刻
         BeepStatus = 2;//进入下一个状态
       }
    break;
    case 2://停止蜂鸣状态
       if(TIMER-BeepTimer>110)//110ms
       {
         if(i!=0)
         {
           i--;
           BeepTimer = TIMER;//记录时刻
           BeepEn(); //开启蜂鸣器
           BeepStatus = 2;//回到蜂鸣状态
         }
         else
         {
           BeepStatus = 0;//回到初始状态
           BeepFlag = 0;//清除忙标志位
         }
       }    
    break;
    default:
      BeepFlag = 0;//清除忙标志位
      BeepStatus = 0;//回到初始状态
    break;
  }
}

用这样的方法实现的蜂鸣程序在使用时也有不同的地方,因为使用的switch状态所有在主循环中要一直调用:

void main()
{
  SystemInitial();//系统初始化
  ...............

  //主循环
  while(1)
  {
     Fun1Proc();//功能1流程
     Fun2Proc();//功能2流程
     ....     
     BeepProc();//蜂鸣流程
     ....
  }

}

在别的函数中需要使蜂鸣器工作时只需要下面代码即可:

if(!BeepFlag)//检查是否忙
 BeepCtrl = 1;//启动蜂鸣器

用这种方法能充分利用MCU,在蜂鸣器发声或发声间隔的等待时间MCU可以处理别的函数,但是还要有几点需要注意

第一,主循环while(1)的循环周期最好小于定时器计数时钟周期 
第二,主循环中尽量不要使用硬延时Delayms 
第三,代码中如果存在多个地方需要控制一个流程时一定要先读取标志位再控制
--------------------- 
作者:-忍者神龟- 
来源:CSDN 
原文:https://blog.csdn.net/zhangren_ham/article/details/51541841 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值