解决延时函数耗费单片机内部资源的问题。可以将延时函数放在中断中……方法解释一下
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.直接在中断中查询按键的标志位.
这两种解决方法我没看懂,
举个例解释一下,用定时器中断就能不浪费单片机内部资源吗?噢噢~~用中断的时候单片机可以做其他事情?百度是可以得到例子,但是要能解释清楚。谢谢。
还有~
什么叫查询按键的标志位?
什么叫查询延时的标志位?
原文并没有说的完全对,这么做也是没有大错的。
它两次判别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;
----这方法,适合于简单的、单任务的情况。
利用定时(如 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最后留下的数。
使用步骤
计算知道后,来看看定时器使用步骤:
- 对TMOD赋值,确定T0和T1的工作方式
- 计算初值,赋值TH0,TL0或TH1,TH1
- 对IE赋值,启动中断
- TR0或TR1置位,启动定时器
- 处理中断函数,定时器中断后变成默认值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,命令没有运行完,又进入中断,就会出错。
总结
定时器就这样子,不会很难,一些命令在数据手册都有,忘记了就重新看一下,写多了就会记住·,重要的是记住步骤,记住编程思想,在写之前在脑中想一下步骤,或在纸上把思路画一下,那里不通就会跃然于纸上,再稍加思索一般就行了。
以上是关于解决延时函数耗费单片机内部资源的问题。可以将延时函数放在中断中……方法解释一下的主要内容,如果未能解决你的问题,请参考以下文章