[求助] 请教由独立键盘程序联想到的中断概念

qrswll   2012-4-11 15:56 楼主

郭天祥教程的练习题,三个独立键盘实现按下第一个时计时停止,按下第二个时计时开始,按下第三个是计数值清零从头开始。这是我自己编的程序,可以正常实现功能。但是我在自己调试程序排错的时候,想到了中断的概念。比如这里按key3键是计数值从0开始显示,我在想如果定时器中断时间到,刚好在bb=0;bai=bb/100;shi=bb%100/10;ge=bb%10;这几句话后面进入中断程序(程序中用红色标出了),进入中断后,bb被加到1,中断程序执行完回到主程序,此时个,十,百位分别是1,0,0,这样不是和我们程序的要求不一致了么(程序要求复位从0开始计时)。但是我把程序下载到板子上面没有出现按key3是1的情况,都是符合题目要求的0。请前辈指点,我的概念哪里出错了?

#include<reg52.h>
sbit dula=P2^6;
sbit wela=P2^7;
sbit key1=P3^4;
sbit key2=P3^5;
sbit key3=P3^6;
sbit key4=P3^7;
unsigned char aa,ge,shi,bai;
unsigned int bb;
void init();
void display(unsigned char,unsigned char,unsigned char);
void delay(unsigned int);
unsigned char code table[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71};
void main()
{
 init();
 while(1)
 {
 if(key1==0)
 {
  delay(5);
  if(key1==0)
  {
   TR0=0;
  }
 }
 if(key2==0)
 {
  if(key2==0)
  {
   TR0=1;
  }
 }  
 if(key3==0)      
 {
  bb=0;
  bai=bb/100;
  shi=bb%100/10;
  ge=bb%10;       //如果在这句话后,定时器中断定的时间到,进入定时器中断,那么bb被加1,个位被刷新为1,此时这个复位键复位到的是1??那么数码管也显示1?和要求不符,但实际下载程序到板子上的结果却是每次按这个键下去都是复位到0.
  if(key1==1){TR0=1;}
 }
 display(bai,shi,ge);
 }
}
void init()                         //初始化函数
{ wela=1;
 P0=0xff;
 wela=0;
 dula=1;
 P0=0x00;
 dula=0;
 aa=0;
 bb=0;
 P3=0xff;
 TMOD=0x01;
 TH0=(65536-10000)/256;
 TL0=(65536-10000)%256;
 EA=1;
 ET0=1;
 TR0=1;
}
void timer0() interrupt 1              //定时器0中断
{
 TH0=(65536-10000)/256;
 TL0=(65536-10000)%256;
 aa++;
 if(aa==1)
 {
  aa=0;
  bb++;
 }
 bai=bb/100;
 shi=bb%100/10;
 ge=bb%10;
 if(bb==999)
 {
  bb=0;
 }
}
void display(unsigned char one,unsigned char two,unsigned char three)       //显示函数
{
 dula=1;
 P0=table[one];
 dula=0;
 P0=0xff;
 wela=1;
 P0=0xfe;
 wela=0;
 delay(1);

 dula=1;
 P0=table[two];
 dula=0;
 P0=0xff;
 wela=1;
 P0=0xfd;
 wela=0;
 delay(1);

 dula=1;
 P0=table[three];
 dula=0;
 P0=0xff;
 wela=1;
 P0=0xfb;
 wela=0;
 delay(1);
}
void delay(unsigned int z)       //延时程序
{
 unsigned int x,y;
 for(x=z;x>0;x--)
  for(y=110;y>0;y--);
}

回复评论 (22)

顶起来
点赞  2012-4-12 07:58
点赞  2012-4-13 07:55
LZ,你的程序能正常运行么,就是实现那三个按键的正常功能。

另外,你在开头基本把问题说明白了,但是,有点乱,有个小建议,每个函数前面大致注释一下这个函数的功能。
形参 和 返回值,这样方便看。

另外就是你这个程序有的地方写得很莫名其妙,还没全找,只是看到一个地方——先不说全局变量 延时那些。

比如你的定时中断里,你要对bb计数,你就直接计数bb,何必再来一个aa等1再清零的多余动作?
强者为尊,弱者,死无葬身之地
点赞  2012-4-13 09:16
你得考虑很有道理,但是,从我看这个程度的角度来看,是因为你按KEY1 KEY2这两个动作,KEY1是关定时器,KEY2是开定时器,也许是因为你在这之前有过一个KEY1关定时器的动作,所以既然关了,也就不会再计数。

建议下次用 代码 这个工具来贴代码,我刚才就差点看错行了,以为是 要在KEY1有动作的情况下才刷新数码管
强者为尊,弱者,死无葬身之地
点赞  2012-4-13 09:20
额,更可能的原因是,根本没到定时器来加1,你复位后到显示这段时间里,根本还没来一次定时器。
你的定时器长度是 10000,晶振是多少?
定时长度是多久?

从你的主程序来看,比较简单,整个执行过程很可能才上百条机器周期而已,当定时器长度和这个时间相比而言差别比较大时,你的这种考虑就不可能出现。
强者为尊,弱者,死无葬身之地
点赞  2012-4-13 09:26

引用: 原帖由 辛昕 于 2012-4-13 09:26 发表 额,更可能的原因是,根本没到定时器来加1,你复位后到显示这段时间里,根本还没来一次定时器。你的定时器长度是 10000,晶振是多少?定时长度是多久? 从你的主程序来看,比较简单,整个执行过程很可能才上百 ...
非常感谢前辈的指点。

1.前辈在第一条回复中说的对,我那个aa纯属多余,现在总结起来是自己陷入了思维定势,一般定时时间长的时候总是设一个中间变量,但是这里需要的是1%S,所以就不需要了。同时我这样贴代码确实是有问题的,昨天我也看了一些专业人士写的代码,注释很全,很规整,下次问问题的时候我会把代码写得更加规范。

 

2.前辈的第二条回复解决了我疑惑中的其中一种情况:按下key1后,再按key3复位。因为之前按下key1,定时器已经被关掉,所以按key3的时候肯定是0,不会往上加。

 

3.我用的晶振是11.0592MHz,定时器长度是10ms~     我的疑惑中的另一种情况是,没有按下key1,直接按key3的复位。我仔细想了一下这种情况,如果我按住key3不松手(中断结束了我还没松手),那么主程序因为while(1);循环执行,根据程序长度和机器周期,每循环一次的时间是us级的(不包括display),所以即使某一次中断刚好进入了 bai=bb/100; shi=bb%100/10; ge=bb%10;的后面,将bb变成1,可是由于key3键我还没松手,循环一次之后,bb又变成0,0----1-----0,数字变化中间间隔us级是非常短的,人眼察觉不到。

如果刚好发生了非常小概率的事件:

  bb=0;
  bai=bb/100;
  shi=bb%100/10;
  ge=bb%10;

这段之后进入中断,无论是在中断结束刚好松手key3,还是中断正在进行时松手key3,中断程序依旧执行至结束,回到主程序后,执行 if(key1==1){TR0=1;},显示1,过10ms显示2,依次递增。这时候好像就不符合要求了(从1开始复位的)。。。不过这个概率好像很小。。。?

以上是我的一些理解,可能逻辑上存在一些错误。。。还请前辈指正。。。

前辈的第三个回复:当定时器长度和这个时间(程序执行时间)相比而言差别比较大时,你的这种考虑就不可能出现。

我有些理解不过来。。。为什么定时器长度和程序执行时间相差较大的时候,我这种情况就不会出现呢?中断不会进入 bai=bb/100; shi=bb%100/10; ge=bb%10;的后面么?

 

[ 本帖最后由 qrswll 于 2012-4-14 09:16 编辑 ]
点赞  2012-4-14 09:13

额,别把我叫的这么老,我还没找到女朋友呢

让我们来简单理一下这个程序的执行路线,或者说流程也行
1 开机初始化一次。
2 循环检测3个按键,其检测顺序是 key1 key2 key3
   key1按下,则关闭定时器,即不再计数
   key2按下,反之
   key3按下,清零过后,还要保证key1没按下,才不会关闭定时器。
3 同时循环扫描数码管。

对你的第三个问题,我们只考虑key1 key3的情况,key2跟它们没关系,如果key2按下了,给关了,那啥事都没了。
如果你一直按着key3不松手。并且保证key1是松开的。
这个时候将不断计数。
同时,数码管也不断扫描最新的时间值。

你所担心的问题是,如果bb已经被清零,偏偏在它到执行数码管显示那一刻中间,偏偏被中断打断了,刚好加了1,那就会很惨的出现从1开始。
这的确是有可能的,然而也如同你所说的,如果你一直按着key3——其实从程序执行的速度来说,就算你只不过是按了一下key3,相对于这个没经过消抖处理的key3来说,那也等于说,一直按着key3,我的意思是,反复进入key3清零。

我当时说如果定时长度和这段代码执行的时间相差太远,则这种担心是不必要的,意思正是
不管你的定时中断是否有那么巧刚好在中间打断了。
如你所说,定时中断执行完加1马上就会回来,然后经由数码管显示(这里面,数码管显示程序的刷新频率到底有多快,对你这个问题就很有意义。)
如果数码管的刷新频率快的人眼无法察觉,那么,这个加1是没机会让人看到的,理由正如你所说的,加完1,主循环又马上检测到key3按着,再次清零。
这回,可就没那么容易被定时器打断了,试想,定时器的周期有10个毫秒,而你这段代码(当然我没考虑你那几个delay到底有多长——这种延迟方法本身是有问题的,以后再谈)
假设说,这几个delay没多长,至少不要在毫秒级的级别,也就是说,整个主循环执行一次估计才几个毫秒,那么,就意味着,这个key3清零,频率在几个毫秒,这种水平,人眼是很难看到的,至多看到出现有时冒出个不清晰的1.

请注意,这一切分析,都以主流程和定时器长度的相对比例而言,一般的程序,像这种主流程,本该是很短的,别说10个毫秒,就是1个毫秒相比,也显得很长,故而,你的担心是没必要的。

但是,这个程序写的很是粗糙,首先它在主循环里用循环等待这种方法延时,你那几个delay都是消抖,既然是消抖,时间肯定少不过几个到10个毫秒,而数码管的刷新水平,其中的延迟还是这种办法,这就导致了这个主流程实际跑起来的时间较长,和定时器相比,反而要长好多,那么,你说的这种情况,也许就会出现,偶尔能看到一个1闪过。
强者为尊,弱者,死无葬身之地
点赞  2012-4-15 01:42
说到了这个delay。
建议楼主学习一下其他方式的延时方法,这种纯粹靠循环递减方式的延时方法,只有在这种学习的代码中才能见到,真正实用的程序中这样写,再高的CPU也会让你弄得非常迟钝。
强者为尊,弱者,死无葬身之地
点赞  2012-4-15 01:46
引用: 原帖由 辛昕 于 2012-4-15 01:46 发表 说到了这个delay。建议楼主学习一下其他方式的延时方法,这种纯粹靠循环递减方式的延时方法,只有在这种学习的代码中才能见到,真正实用的程序中这样写,再高的CPU也会让你弄得非常迟钝。
:carnation: 前辈回答得好耐心=。=不对...以后就叫前辈学长了~~ 仔细研读了学长的回答,觉得如果偶尔复位出现了1,想下次刚好还在那个位置出现1的话...说不定可以去买彩票了.......我看的是郭天祥的视频,暂时学到的只是delay().......看来在实际应用中,应该少用delay()......其他的延时方法,是指定时器么?还是有一些更好的方法呢?
点赞  2012-4-16 11:06

回复 10楼 qrswll 的帖子

郭老师的视频对入门很有帮助,但入门以后还要学习更多更规范,看看其他人不同的思路。
强者为尊,弱者,死无葬身之地
点赞  2012-4-17 15:39

回复 10楼 qrswll 的帖子

不要乱叫前辈,我很年轻。
要是找不到媳妇,你给我找啊?
强者为尊,弱者,死无葬身之地
点赞  2012-4-17 15:41
引用: 原帖由 辛昕 于 2012-4-17 15:41 发表 不要乱叫前辈,我很年轻。要是找不到媳妇,你给我找啊?
学长=。=
点赞  2012-4-17 15:44

引用: 原帖由 辛昕 于 2012-4-17 15:39 发表 郭老师的视频对入门很有帮助,但入门以后还要学习更多更规范,看看其他人不同的思路。

概述:一个掉电计数数据保存的程序,第一次上电初值为00,(计数值++,加至99归0),以后每次断电再次上电后,能接着断电前的计数值继续计数。

 

学长,这两天我学习了IIC总线存储器EEPROM的读写方法,视频教程老师说了一道题,要求实现掉电计数保持功能,即用定时器计数,每次数字改变都将变化的数字存入EEPROM,这样即使掉电了,再次上电立即读取EEPROM里面的值,可以在上次计数值的基础上继续往上加。

我编的程序是在主函数里面写了一个利用IIC总线读写EEPROM的程序,这样可以完成掉电保持,但是如果是这样,当我第一次启动程序时(此时从未对EEPROM相应的存储地址里写过值),应该是一个随机值,或者是根据说明书全1或者全0,我想把第一次程序启动时的值固定为00,但是想不出有什么办法。考虑了两种情况:

1.如果在主函数里面初始化令计数初值为0的话,掉电后上电,就不能读出上次的值了,不能完成功能。

2.如果计数初值赋0后,再读EEPROM里存的值,那么赋值的0被覆盖,毫无意义。。。

想了好久现在还没有想出有什么办法可以解决,求学长指点=。=

[ 本帖最后由 qrswll 于 2012-4-17 16:06 编辑 ]
点赞  2012-4-17 15:59

回复 14楼 qrswll 的帖子

这事我干过类似的。
第一次我没办法,但第二次我有办法。

上电之初,不要直接在程序里把该单元清零。

在程序运行时,做一个子函数,当某个按键或者遇到什么条件时,就把该单元清零,从此,它就是零了。
这样以后,它就是0了,以后重上电既不会变成随机数,而是保持在上一次的数值里。

那个清零的操作,可以作为一个系统人工初始设置动作来完成。

这是我最近项目里的操作手法,不知道是否符合你的意图。

换句话来说,你想通过程序来实现的话,我突然想到的一个方法是:
在最开始写一段代码。
开辟多一个EEPROM里的单元,在你第一次初始化这个值为零以后,同时把这个值设置为一个很特别很特别的数值,就是说,非0非0xff,非任何可能的随机数——说是随机,一般来说,都是有一定规律的吧?
写一个特别点的数的话,就很难碰到巧合了——这里说的特别,说的是 二进制上0和1的排列,诸如什么0x55,0xaa之类的。

然后,以后你就判断,如果这个单元是这个你设置的值,那你就不会再去进行这个清零。否则就会清零。

我写个代码简单示意一下,关于eeprom的读写操作我不知道具体如何操作,我只写个示意的

U8 ClearFlag;            //我说的那个标志
U8 Counter;               //你想要的那个计数器

if(ClearFlag != 0x55)
{
    Counter = 0;
    ClearFlag = 0x55;
    /*
     Operation on write Counter & ClearFlag into EEPROM
   */
}

[ 本帖最后由 辛昕 于 2012-4-19 02:05 编辑 ]
强者为尊,弱者,死无葬身之地
点赞  2012-4-19 02:00
他妈的发段代码真狗血,,,
几次都不对劲,我再发一次

[ 本帖最后由 辛昕 于 2012-4-19 02:07 编辑 ]
强者为尊,弱者,死无葬身之地
点赞  2012-4-19 02:06
U8 ClearFlag; //我说的那个标志
U8 Counter; //你想要的那个计数器


/* Operation on read out ClearFlag from EEPROM */

if(ClearFlag != 0x55)
{
Counter = 0;
ClearFlag = 0x55;

/* Operation on write Counter & ClearFlag into EEPROM */
}
else
/* Operation on read out Counter from EEPROM */
强者为尊,弱者,死无葬身之地
点赞  2012-4-19 02:08

回复 16楼 辛昕 的帖子

可以用编辑器里的代码功能试试
加油!在电子行业默默贡献自己的力量!:)
点赞  2012-4-19 08:36

引用: 原帖由 辛昕 于 2012-4-19 02:00 发表 这事我干过类似的。第一次我没办法,但第二次我有办法。 上电之初,不要直接在程序里把该单元清零。 在程序运行时,做一个子函数,当某个按键或者遇到什么条件时,就把该单元清零,从此,它就是零了。这样 ...
谢谢学长的指点。学长这段话一开始说的项目里的做法我觉得比较符合实际的使用情况,因为这又不是做定时炸弹,非要在第一次上电的时候取初值为0(而且是关电源计时会停止的炸弹,计数还是往上加的...一般都是倒计时=。=)

在实际应用中,可以设一个按键,按下时将计数值清零,是很不错的选择。如果需要在第一次上电就为0,再按一下清零键就好了。

学长后面想到的方法是一种低概率出错的方法,这里是8位存储数据,那么出错的概率就是1/256,在调试程序的时候,我每次都要把判断的标志位的值改一下,因为是存在EEPROM中的,所以上一次写入的值被保存在了同一个存储空间,想在下一次中调试就跳过了00初值的赋值了=。=(实际上觉得换个地址存也可以~)。刚开始的时候遇到好多问题,一开始是IIC总线读和写的时候,总是得不到想要的效果,我估计是程序执行得太快了,造成时序冲突,于是在两次写之间加了一个delay(10),想要的效果出来了,上电第一次会有初值,后面断电后,在原基础累加。

但是还是有些瑕疵,就是第一次上电的时候先是显示一个乱码,然后才跳成00。这我就不太懂了,调试了好多次,误打误撞把这个错误解决了,就是在解决之后我到现在都没明白是为什么。。。我在程序中用定时器中断进行计时,定时器中装入10ms,取变量aa==100的时候,得到1S的定时,完成两位数的数码管时间显示。其中有一句话是

if(num==100){num=0;}我把判断条件变成if(num>=100){num=0;},一开始的乱码就没有了。我估计是一开始还是读了EEPROM存储器里面的值。。。num设定的是unsigned char类型的。num通过num/10和num%10分别得到个位和十位显示的值。这是我的第一个疑问=。=

第二个疑问是这样发现的:在我基本实现了功能要求后,多次尝试修改标志位的值,但是发现了一个BUG.......在我设标志位为0x96和0x94的时候,断电再上电后计数的值并没有按照递增的顺序,而是从23开始往上加,无论当前计数值是多少,断电再上电都是23........不知道是什么原因

求学长指点 估计三言两语也说不清=。=代码贴在楼下~

[ 本帖最后由 qrswll 于 2012-4-20 08:33 编辑 ]
点赞  2012-4-19 17:48
引用: 原帖由 辛昕 于 2012-4-19 02:08 发表 U8 ClearFlag; //我说的那个标志 U8 Counter; //你想要的那个计数器 /* Operation on read out ClearFlag from EEPROM */ if(ClearFlag != 0x55) { Counter = 0; ClearFlag = 0x55; /* Operation on ...
  1. #include<reg52.h>
    sbit dula=P2^6; //数码管段选
    sbit wela=P2^7; //数码位选选
    sbit SDA=P2^0; //IIC数据线
    sbit SCL=P2^1; //IIC时钟线
    unsigned char aa,bb,shi,ge,flag,num1;
    //-------数码管数字显示表----//
    unsigned char code table[]={
    0x3f,0x06,0x5b,0x4f,
    0x66,0x6d,0x7d,0x07,
    0x7f,0x6f,0x77,0x7c,
    0x39,0x5e,0x79,0x71};
    //------uS级延时函数----//
    void delay()
    {;;}
    //-----mS级延时函数----//
    void delay1(unsigned char x)
    {
    unsigned char a,b;
    for(a=x;a>0;a--)
    for(b=100;b>0;b--);
    }
    //--------IIC总线函数----//
    void start() //IIC总线起始
    {
    SDA=1;
    delay();
    SCL=1;
    delay();
    SDA=0;
    delay();
    }
    void writebyte(unsigned char writedata) //IIC写字节
    {
    unsigned char temp,i;
    temp=writedata;
    for(i=0;i<8;i++)
    {
    SCL=0;
    delay();
    temp=temp<<1;
    SDA=CY;
    delay();
    SCL=1;
    delay();
    }
    SCL=0;
    delay();
    SDA=1;
    delay();

    }

    unsigned char readbyte() //IIC读字节
    {
    unsigned char i,j;
    for(i=0;i<8;i++)
    {
    SCL=0;
    delay();
    j=(j<<1)|SDA;
    delay();
    SCL=1;
    delay();
    }
    return j;

    }
    void ACK() //IIC应答
    {
    unsigned char i;
    SCL=0;
    delay();
    if(SDA==1&&i<250)
    {
    i++;
    }
    SCL=1;
    delay();
    SCL=0;
    delay();
    }
    void stop() //IIC总线结束
    {
    SDA=0;
    delay();
    SCL=1;
    delay();
    SDA=1;
    }

    //-------数码管显示函数--------//
    void display(unsigned char one,unsigned char two)
    {
    if(bb==1)
    {
    P0=0x00; //消隐
    dula=1;
    P0=table[one];
    dula=0;
    P0=0xff;
    wela=1;
    P0=0xfe;
    wela=0;
    }
    if (bb==2)
    {
    dula=1;
    P0=table[two];
    dula=0;
    P=0xff;
    wela=1;
    P0=0xfd;
    wela=0;
    bb=0;
    }
    }
    //---------初始化函数----------//
    void init()
    {
    TMOD=0x11; //开定时器0和1
    TH0=(65536-10000)/256;
    TL0=(65536-10000)%256;
    TH1=(65536-1000)/256;
    TL1=(65536-1000)%256;
    EA=1;
    ET0=1;
    ET1=1;
    TR0=1;
    TR1=1;

    start(); //读EEPROM的0x03地址空间,即标志位的值存放的空间
    writebyte(0xa0);
    ACK();
    writebyte(0x03);
    ACK();
    start();
    writebyte(0xa1);
    ACK();
    flag=readbyte();
    stop();

    if(flag!=0x99) //标志位flag,判断,如果是第一次上电,给0x08地址初始化值0x00;疑问发源地2。
    {
    start();
    writebyte(0xa0);
    ACK();
    writebyte(0x08);
    ACK();
    writebyte(0x00);
    ACK();
    stop();

    delay1(10);

    start(); //同时给标志位flag赋值=判断的值,防止下次上电再初始化00.
    writebyte(0xa0);
    ACK();
    writebyte(0x03);
    ACK();
    writebyte(0x99); //标志位的判断换成0x96,0x94之类的都会有BUG,重新上电后总是从23开始计数
    ACK();
    stop();

    }

    start(); //如果不是第一次上电,则读取0x08里面的值num1(0x08是真正数据存储地址,不是标志位地址)
    writebyte(0xa0);
    ACK();
    writebyte(0x08);
    ACK();
    start();
    writebyte(0xa1);
    ACK();
    num1=readbyte();
    stop();
    }
    //-----主函数--------//
    void main()
    {
    init();
    while(1)
    {
    start(); //每执行一次while循环都把计数值num1写入0x08地址中,方便重新上电后读取,继续计数。
    writebyte(0xa0);
    ACK();
    writebyte(0x08);
    ACK();
    writebyte(num1);
    ACK();
    stop();
    shi=num1/10; //两位数码管十位个位值的计算
    ge=num1%10;
    display(shi,ge);
    }
    }
    //-----定时器0中断,负责计数值加加以及清零-----//
    void timer0 () interrupt 1
    {
    TH0=(65536-10000)/256;
    TL0=(65536-10000)%256;
    aa++;
    if(aa==100)
    {
    aa=0;
    num1++;
    }
    if(num1>=100) //疑问发源地1。。。
    {
    num1=0;
    }
    }
    //-----定时器1中断,负责数码管现实中需要的延时,满足视觉暂留------//
    void timer1 () interrupt 3
    {
    TH1=(65536-1000)/256;
    TL1=(65536-1000)%256;
    bb++;
    }
[ 本帖最后由 qrswll 于 2012-4-20 10:10 编辑 ]
点赞  2012-4-19 17:50
12下一页
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复