解决延时函数耗费单片机内部资源的问题。可以将延时函数放在中断中……方法解释一下

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解决延时函数耗费单片机内部资源的问题。可以将延时函数放在中断中……方法解释一下相关的知识,希望对你有一定的参考价值。

if(P1_0 == 0)

Delay(10);//问题就在这里,你让CPU在这里空转?
if(P1_0 == 0)

//...add your code here.


进入第1个if判断语句后,就进入了Delay(10);再看Delay函数,完全让CPU执行(;空语句),所以在做大的产品或者代码时,这个是非常耗费单片机内部资源的。有什么办法吗?呵呵,那是肯定的。
解决方法大致有如下2种:
1.将延时函数放在中断中,在中断里查询延时的标志位。/*不仅仅用于键盘识别,亦可以用于其他的延时代码.
2.直接在中断中查询按键的标志位.

这两种解决方法我没看懂,
举个例解释一下,用定时器中断就能不浪费单片机内部资源吗?噢噢~~用中断的时候单片机可以做其他事情?百度是可以得到例子,但是要能解释清楚。谢谢。
还有~
什么叫查询按键的标志位?
什么叫查询延时的标志位?

2级,希望你能认真看了并采纳,毕竟这问题挺复杂,说起来麻烦。

原文并没有说的完全对,这么做也是没有大错的。

它两次判别P1-1的状态,中间Delay(10),不过是防抖而已,就是说,第一次检测到按键按下,并且过了一段时间发现还是按下状态,那么就认为是按键动作,而不是干扰造成的。

一般来说这个时间是100ms左右到150ms左右,所以要有这么一段时间被delay做不了事情。但是这种干扰不多,所以大部分时间是跳过的。需要按键触发的程序,100ms左右不做事情也不是什么大事。

但真要求严格了,比如说每50ms必须有些事儿要处理,不能有这种干扰,那么就放在中断里,什么中断?原文没讲清楚,应该是时间中断,比如每隔100ms一次。中断里写

if(P1_0 == 0)

if(i == 0)
i = 1;
else 你的代码

else

i = 0;

这样就行了。从初始化到判别,到误判清零都有了,i应该是个全局变量,中断可以访问的,或者是个const的也可以

但是这么做也并不很好,所以更高效的是做一个自己的时间系统,即

中断每1ms一次,有个变量做时间,假设叫做MyTimer,那么中断里

MyTimer ++; 就可以

然后你判别的程序就写成,

if(p1_1 == 0)

if(pressed == 0)
LastTime = MyTimer;
else
if(MyTimer - LastTimer > 100)

your code


else
pressed = 0;


当然你的代码里要处理状态复位,清零,停止判别P1_1等具体事情,而MyTimer-LastTimer也要处理时间反卷等具体细节了。

你问题里的原文不用看了,有诸多问题。而我写的这个是多年经验总结实践而来。虽然写的简单,原理是相当正确的。你可以参考
时间触发嵌入式系统设计模式 一书,里面针对这种问题讲的非常深入。作者是Pont Michael J.追问

if(p1_1 == 0)


if(pressed == 0)
LastTime = MyTimer;
else
if(MyTimer - LastTimer > 100)

your code


else
pressed = 0;

很感谢,可以解释一下这程序么?

追答

if(p1_1 == 0) 简单解释下。 如果按键貌似被按下

if(pressed == 0) 是不是上次判断时候已经是被按下的?如果不是,
LastTime = MyTimer; 则初始化计时器
else 否则的话
if(MyTimer - LastTimer > 100)就比较下时间,如果和最初被按下比较,连续100ms都是被按下状态,那么认为确实是被按下的动作发生了

your code 用户代码,这里要有清理各项标志,并做一段另外的延时(和Lasttime比较像,但是放在这一整段程序之前),用以在短期内不判断第二次按键动作。


else 如果有任何一次发现按键信息不对,可能是扰动,则清零标志(这样下次进入上面的部分LastTime会被重新赋值,重新开始一个100ms的判断过程。)
pressed = 0;

参考技术A 进入第1个if判断语句后,就进入了Delay(10);再看Delay函数,完全让CPU执行(;空语句),所以在做大的产品或者代码时,这个是非常耗费单片机内部资源的。

----这方法,适合于简单的、单任务的情况。

利用定时(如 10ms)中断,就可以轮流检测各个按键以及其它需要处理的任务的标志。
某一个条件满足了,就执行对应处理程序。
这样就可以实现:多任务操作。

轮流检测,就包括了:“查询按键的标志位”、“查询延时的标志位”。

就是说,检测到按键按下,还不应该立即处理,应该继续查询:它按了多少个10ms。
以此来判断:短按、长按、双击等等。
一般,还要检测到按键释放,再执行该键短按、长按、双击所对应的程序。

可以参考:
http://hi.baidu.com/do_sermon/item/21321b80887395d25e0ec1f8追问

嗯嗯~其实我不太懂如何用定时器中断实现多任务操作,能不能给个C语言编写的多任务操作程序??因为我不懂汇编。很感谢~

参考技术B

#include<reg52.h>



sbit P1_1 = P1^1;


unsigned int num=0,num1=0;

unsigned int delay_flag=0,delay_num;


void delay_ms(unsigned int nms)

delay_flag = 1;

delay_num = nms;

void Init(void)

TMOD=0X01;

TH0=-5000/256;

TL0=-5000%256;

TR0=1;ET0=1;

EA=1;

int main()

Init();

while(1)

if(delay_flag == 0)

delay_ms(1000); 

if(delay_flag == 1&&delay_num==0)

P1_1 = 0; 

delay_flag =0;

if(delay_flag == 0)

delay_ms(1000);

if(delay_flag == 1&&delay_num==0)

P1_1 = 1;

delay_flag =0;

return 0;


void time0() interrupt 1

TH0=-1000/256;

TL0=-1000%256; 

if(delay_flag==1)

delay_num--;


追问

if(delay_flag == 0)
delay_ms(1000);
if(delay_flag == 1&&delay_num==0)

P1_1 = 0;
delay_flag =0;

if(delay_flag == 0)
delay_ms(1000);
if(delay_flag == 1&&delay_num==0)

P1_1 = 1;
delay_flag =0;

可以帮我解释一下吗?这里

追答

嗯!这部分是延时程序!设置有延时标志位!如果程序要延时的话就打开延时标志位!然后定时器减延时!最后延时结束时关闭延时标志

定时器

使用单片机时,编程会高频率用到延时,如led灯闪烁,蜂鸣器长短鸣,秒表应用等等。首先考虑软件延时,但这个时间不精确,占用硬件资源。使用延时函数是,其他函数不能运行。这个方案cut掉。硬件延时,嗯,误差非常小。但成本较高,且参数调节不便。这个也不行。选择采用定时器调节时间,不占用cpu时间,能与CPU并行工作,实现精确的定时和计数。又可以通过编程设置其他工作方式和其他参数,因此使用非常方便。下面介绍定时器的使用。

概述

定时器系统是单片机内部一个独立的硬件部分,它与cpu和晶振通过内部某些控制线连接并相互作用,cpu一旦设置开启定时功能后,定时器便在晶振的作用下自动计时,但定时器的计数器计满后,会产生中断。计数时间一次为12/晶振频率。在晶振频率为11.0595mhz时,计数一次时间约等于1.09us。
89c52单片机定时器系统有三个定时器/计数器,分别是定时器T0,定时器T1,T2定时器。他们既有定时器功能,也有计数器功能。T0,T1有四种工作方式,T2有三种工作模式。

内部结构

技术图片定时器结构
由上图可知,定时器系统有两个寄存器组成,分别是TCON,TMOD。还可看出tcon控制外部中断,tmod控制定时器工作方式。tmod寄存器分为两部分,高四位为t1定时器控制位,低四位为t0定时器控制位。t0定时器与th0,tl0两个8位计数器有关,。t1定时器与th1,tl1两个8位计数器有关。上图信息就这么多,接下来看看两个寄存器相关数据。

 

寄存器TCON

技术图片寄存器TCON
TF1:定时器 1 溢出标志。当定时器/计数器 1 溢出时,由 硬件置位;当主机响应中断,
转向中断服务程序时,由硬件清零。
TR1:定时器 1 运行控制位, 由软件置位/ 复位来开启或关闭定时器/计数器 1。
TF0:定时器 0 溢出标志。当定时器/计数器 0 溢出时,由 硬件置位;当主机响应中断,
转向中断服务程序时,由硬件清零。
TR0:定时器 0 运行控制位,由 软件置位/ 复位来开启或关闭定时器/计数器 0。
IE1:外部中断 1 跳变中断请求标志,当检测到 INT1 发生 1 到 0 的跳变时,由硬件置位;当主机响应中断, 转向中断服务程序时,由硬件清零。
IT1:外部中断 1 触发方式控制位,由 软件置位或清零来选择外部中断 1 的跳变/电平触发中断请求。IT1=0 时,外部中断 1 为电平触发方式,当 INT1 输入低电平时,置位 IE1。
采用电平触发方式时,外部中断源必须保持低电平有效,直到该中断被 CPU 响应,同时在该中断服务程序执行完之前,外部中断源必须被清除,否则将产生另一次中断。IT1=1 时,外部中断 1 为边沿触发方式,在对 INT1 的相邻两次采样中,如果一个周期中为高电平,接下来的周期为低电平,则置位 IE1,表示外部中断 1 正在向 CPU 申请中断。直到该中断被CPU 响应时,才被 硬件清零。
IE0:外部中断 0 跳变中断请求标志,当检测到 INT1 发生 1 到 0 的跳变时,由硬件置位;当主机响应中断, 转向中断服务程序时,由硬件清零。
IT0:外部中断 0 触发方式控制位,应用同 IT1。

 

这个寄存器与中断有关,支持位寻址,就是可以对其每一位进行单独操作。定时器工作就是在一个特定的间隔(与晶振有关)加1,等到加到定时器溢满时,会触发外部中断。这两个定时器都是16位可编程定时器/计数器。最大可装2的16次方。,就是65535.,定时器在晶振为11.0592MHZ时,间隔约等于1.09us。
(关于中断的知识在上一篇文章有详细介绍,在这里就不累赘。)

寄存器TMOD

技术图片寄存器TMOD
GATE:门控制位,当 GATEx=1 时,控制寄存器 TCON 的 TRx=1(x=0 或 1)。当 GATEx=0 时,定时器启动与停止仅受寄存器中的TRx来控制(x=0 或 1)。
C / :定时器、计数器方式选择位,该位为 1 时为计数器,为 0 时为定时器。
M0:定时器/计数器工作模式选择位。
M1:定时器/计数器工作模式选择位。
注:高四位是T1定时器控制位,低四位是T0定时器控制位

 

工作方式如下图所示

技术图片4种工作方式
这个寄存器是控制定时器的工作方式与哪个定时器工作。tmod寄存器支持位寻址,编程时只可以是总线式,不可以单个控制。因为两个定时器命名一样,单个控制会弄混。下面示范写法:
TMOD=0X01; // 0000 0001 
可以看出只有最低位为1,即T0定时器的M0=1;对照上图数据可知,这是使用T0定时器的定时功能中的工作方式1,就是16位定时器。
TMOD=0X20; //0010 0000
这个看数据手册得知是T1定时器的定时功能中的工作方式2,即具有自动重载的8位定时器。

 

附:定时器使用需要用到中断,这里将中断的中断源优先级放在下面。

技术图片中断源优先级

定时器使用

中断函数

在介绍定时器使用时,先介绍中断函数,C51的中断格式如下
void 函数名()iinterrupt 工作组
{
中断内容;
}
中断函数不能返回值,所以前缀为void,函数名可以任意取,但一般建议使用有意义的名字,到时候检查也可以明白是什么函数,interrupt是c语言中的一个关键字--中断,记住就行。工作组就是对应中断源,比如说,使用T1定时器,那中断源就是定时器1中断,这时工作组就是3。下面示范:
void timer_t1() interrupt 3
{
TH1=(0XE0);
TL1=(0X07);
}
上面这个实例很容易理解,对着手册看就知道是T1定时器中断。

定时器初值计算

中断函数明白后,如何定时还是不清楚。开启定时器后,定时器就会开始计数,每次加1的间隔是固定的,而且到达最大值就会溢出,触发中断。这样子的话我们可以设定一个初值,初值到最大值的时间假设为50ms,那样的话定是的效果就达到了。定时器加1时间间隔约等于1.09us,定时器在没有赋值时默认初值为0,最大值为65535,计算可得655351.09us约等于72ms,没有赋初值一次定时最大为72ms。可以设置一个初值,就拿50ms来说,501000/1.09约等于45872,也就是说经过45872次计数时间为50ms,那初值就是65535-45872=19663。

大概了解定时器,来看看如何使用,定时器是由16位可编程寄存器组成,分为高8位,即THX(X=1或X=0)低8位TLX(X=1或X=0)。为了更好定时,肯定会选择赋初值。这里介绍一种简单的方法,不用计算。既然它们分为两部分,可以利用这一特点。举个例子:
选择10ms时间,T1定时器。这里以晶振为12MHZ为准,因为11.0592MHZ计算麻烦,这样计数一次就是1us;
TH1=(65535-10000)/256;//表示初值为55535,/256表示高8位的初值,很好理解,低 8位最多存2的8次方=256个数,每满一次高8位加1, /256表示高8位加了多少次。

TL1=(65535-10000)%256;// %256表示不满256最后留下的数。

使用步骤

计算知道后,来看看定时器使用步骤:

  1. 对TMOD赋值,确定T0和T1的工作方式
  2. 计算初值,赋值TH0,TL0或TH1,TH1
  3. 对IE赋值,启动中断
  4. TR0或TR1置位,启动定时器
  5. 处理中断函数,定时器中断后变成默认值0,要重新赋初值

例程

#inlclude<reg52.h>   //头文件
sbit led=P1^1;   //位定义
mian()  //主函数
{
    unsigned char count;  定义计数次数变量
    TMOD=0X01;   //设置定时器T0 定时器功能 工作模式1
    TH0=(65536-50000)/256;     //赋初值   50ms
    TL0=(65536-50000)%256;
    EA=1;   //打开总中断
    ET0=1;  //开定时器0中断
    TR0=1;  //启动定时器 
}
void timer_t0() intterrupt 1
{
    TH0=(65536-50000)/256;    //重新赋值
    TL0=(65536-50000)%256;
    count++;     //每中断一次加1
    if(count==20)  //count==20时,说明1秒到
    {
        count=0;  //count清零,重新等待1秒的到来
        led=~led;   //led状态取反
    }
}

上面注释已经很清楚,按照步骤来,一步一步设置参数,基本不会出错。在中断函数中设置一个标志位,中断变化,变化成何值时,再状态变化。基本就是这个套路。给个建议,中断函数不要写太多东西,不然会出错。试想一下,假如进入中断需5ms,但在中断函数中命令要运行10ms,命令没有运行完,又进入中断,就会出错。

总结

定时器就这样子,不会很难,一些命令在数据手册都有,忘记了就重新看一下,写多了就会记住·,重要的是记住步骤,记住编程思想,在写之前在脑中想一下步骤,或在纸上把思路画一下,那里不通就会跃然于纸上,再稍加思索一般就行了。





























































以上是关于解决延时函数耗费单片机内部资源的问题。可以将延时函数放在中断中……方法解释一下的主要内容,如果未能解决你的问题,请参考以下文章

单片机中用写delay函数做延时和用定时器做延时有啥区别?

51单片机延时函数计算问题以及如何准确延时

单片机中delay函数精确延时多少ms?

STC89C52RC单片机额外篇 | 05 - 把NOP指令封装成微秒级延时函数

51单片机C语言程序中延时函数delay的原理是啥?

单片机 延时函数