外部中断奇怪现象

wangkun2046   2009-6-1 09:28 楼主
单片机使用外部中断0,发现一个奇怪的现象,不知道什么原因引起的。
程序执行的时候,如果没有按键按下(没触发中断),程序运行良好。当有按键按下后(触发中断),程序进入到中断响应函数,也算运行良好。
但是,当程序运行完中断响应函数后,就停在了中断响应函数的最后一条语句上了(一条显示函数,),竟然不回到主程序中去。
程序是死了吗?再按键试试,发现程序又再次执行中断响应函数一次,仍然停在中断响应函数的最后一条语句上,这也说明程序没有死啊!

那么会是什么问题呢?

按键采用独立中断连接方式,也就是8个按键跟P1口直接连接,并“与”后连接中断。

我想实现的功能是通过中断查找哪个按键被按下了。

部分程序如下:

void InitInterrupt()
{
EA=1;     //中断允许
   
EX0=1;   //外部中断0允许
PX0=1;   //设置中断优先级
IT0=0;   //设置外部中断0触发方式:0为低电平有效,1为从高到底的负跳变有效
}

//*****************************************************************************
//按键扫描程序
//*****************************************************************************
BYTE GetKey()
{  
   BYTE k;
   delay(20);   //延时防抖
  
   k=P1;

     if(k==0xFE)//11111110
     {
   return 1;
     }

  else if(k==0xFD)//11111101
     {
      return 2;
     }

  else if(k==0xFB)//11111011
     {
      return 3;
     }

  else if(k==0xF7)//11110111
     {
      return 4;
     }

  else if(k==0xEF)//11101111
     {
      return 5;
     }

  
  else if(k==0xDF)//11011111
     {
      return 6;
     }
  
  else if(k==0xBF)//10111111
     {
      return 7;
     }
  
  else if(k==0x7F)//01111111
     {
   return 8;
     }
  else
  {
   return 0;
  }

  
}


//*****************************************************************************
//外部中断响应程序
//*****************************************************************************
Int0_process() interrupt 0 using 0
{
    BYTE k;
    EA=0; //屏蔽所有中断

  key1=GetKey();
  key++;


     while(1)  //等待按键释放
    {
      k=P1;
         if(k!=0xFF)
        {
           _nop_();
        }
       else
    break;
      }

EA=1;
lcd_wcmd(0x01);//清屏
       lcd_pos(0x00);
     lcd_wdat(key1+48);
     lcd_pos(0x40);
     lcd_wdat(key+48);
         }
     delay(500);
  }

//*************************************************************************
//主函数
//*************************************************************************
void main (void)
{
   BYTE i,k;
   key=0;//按键值的初始状态,在有按键按下时,该值改变,对应的按键响应完成后,key值要恢复为0
   key1=0;
  
   InitInterrupt();

   lcd_init();

  k=0;
  while(1)
  {

if(k==0)
{
   lcd_wcmd(0x01);//清屏
     lcd_pos(0x00);
   lcd_wdat(key1+48);

   lcd_pos(0x40);
   i=0;
      while(diss0!='\0')
      {
         lcd_wdat(diss1);
         i++;
       }
    delay(500);
    k=1;
     }
  else if(k==1)
    {
  lcd_wcmd(0x01);//清屏
       lcd_pos(0x00);
     lcd_wdat(key1+48);
  lcd_pos(0x40);
     i=0;
        while(diss0!='\0')
         {
          lcd_wdat(diss2);
          i++;
         }
     delay(500);
  k=0;
    }
   }
}

回复评论 (15)

还没仔细看程序。
1. 采用电平触发中断可能有问题;
2. 中断标记有没有在ISR中清掉。
点赞  2009-6-1 09:52
按键中断我觉得用电平触发要可靠些,而且我以前用电平触发也没问题的。
当采用电平触发方式时,没个机器周期都采样INTO引脚,若INTO引脚为低电平,则置"1"IE0,否则清“0”IE0.也就是说中断请求标志位由单片机自动完成,不需要我来清掉的。
点赞  2009-6-1 10:04
触发用沿比较可靠。
lcd显示不要放在中断处理函数中进行。
去掉显示相关函数调用,看看是不是还停在最后一步。
点赞  2009-6-1 10:15
引用: 引用 3 楼 shuiyan 的回复:
触发用沿比较可靠。
lcd显示不要放在中断处理函数中进行。
去掉显示相关函数调用,看看是不是还停在最后一步。


LCD显示放在中断中是为了观察是否进入了中断。
如果把这个现实函数去掉的话,程序就会死在按键时的状态。(我的初始状态是两个字符串交替显示,但是按键后就是当前显示的哪个字符串就一直显示,跟死了一样)
点赞  2009-6-1 10:35
//*****************************************************************************
//?????????ì?????ò
//*****************************************************************************
Int0_process() interrupt 0 using 0
{
    BYTE k;
    EA=0; //??±??ù??????
    key1=GetKey();
    key++;
    while(1)  
    {
            k=P1;
        if(k!=0xFF)
        {
            _nop_();
        }
              else
              {
                    break;
              }

        EA=1; // if(k!=0xFF), 应该执行到这里吧,这是还是低电平,是不是又该进中断了?  
        lcd_wcmd(0x01);
              lcd_pos(0x00);
            lcd_wdat(key1+48);
            lcd_pos(0x40);
            lcd_wdat(key+48);
    }
     
    delay(500);
  }
点赞  2009-6-1 10:55
//*****************************************************************************
//?????????ì?????ò
//*****************************************************************************
Int0_process() interrupt 0 using 0
{
    BYTE k;
    EA=0; //??±??ù??????
    key1=GetKey();
    key++;
    while(1)  
    {
            k=P1;
        if(k!=0xFF)
        {
            _nop_();
        }
              else
              {
                    break;
              }

        EA=1; // if(k!=0xFF), 应该执行到这里吧,这是还是低电平,是不是又该进中断了?  
        lcd_wcmd(0x01);
              lcd_pos(0x00);
            lcd_wdat(key1+48);
            lcd_pos(0x40);
            lcd_wdat(key+48);
    }
     
    delay(500);
  }
点赞  2009-6-1 10:56
//*****************************************************************************
//?????????ì?????ò
//*****************************************************************************
Int0_process() interrupt 0 using 0
{
    BYTE k;
    EA=0;
    key1=GetKey();
    key++;
    while(1)  
    {
            k=P1;
        if(k!=0xFF)
        {
            _nop_();
        }
              else
              {
                    break;
              }

        EA=1; // if(k!=0xFF), 应该执行到这里吧,这是还是低电平,是不是又该进中断了?  
        lcd_wcmd(0x01);
              lcd_pos(0x00);
            lcd_wdat(key1+48);
            lcd_pos(0x40);
            lcd_wdat(key+48);
    }
     
    delay(500);
  }
点赞  2009-6-1 10:56
首先很感谢楼上的回复!
EA=1; // if(k!=0xFF), 应该执行到这里吧,这是还是低电平,是不是又该进中断了?
在上面的while语句就是判断是否释放按键,只有变为高电平之后才能执行到EA=1。
实际上在程序中,EA=1这条语句就是作为中断处理函数的最后一条语句的。下面的显示语句只是我为了观察多加上去的。
点赞  2009-6-1 11:56
我觉得在哪好像差别不是太大,也就是说:如果(K!= 0xFF), 那么后面EA=1,这时又会不断进入中断!只有当按键释放掉之后才会退出中断。这样处理中断不大妥当吧。FYI。
点赞  2009-6-1 12:17

  1.   key1=GetKey();
  2.   key++;


  3.     while(1)  //等待按键释放
  4.     {
  5.       k=P1;
  6.         if(k!=0xFF)
  7.         {
  8.           _nop_();
  9.         }
  10.       else
  11.     break;
  12.       }


  13. lcd_wcmd(0x01);
  14.       lcd_pos(0x00);
  15.     lcd_wdat(key1+48);
  16.     lcd_pos(0x40);
  17.     lcd_wdat(key+48);
  18.     }
  19.    
  20.     delay(500);
  21. //放到这里.
  22. EA=1; // if(k!=0xFF), 应该执行到这里吧,这是还是低电平,是不是又该进中断了?

是不是EA 后里面又来中断了
点赞  2009-6-1 13:13
按键产生中断,当按键释放后,应该不会再来中断了啊!

现在通过实验,发现问题出在delay函数上。调用这个函数就会出错,但是我自己在响应函数中写delay函数的函数体又可以了。真是奇怪!

而且现在扫描键盘,有得时候按键一次,但是发现单片机认为扫描了两次。这个应该就是抖动引起的吧。但是不知道该怎么处理这个防抖好。

改为下降沿触发中断也试过了,效果差不多。
点赞  2009-6-1 13:44
各位,大家好!这几天公司不是太忙,就上eeworld来看看,让我思念以前在学校的生活……同时跟大家交流交流,分享生活!

读了你的程序和文字描述,思路很清晰,方法也很有创意,首先值得肯定!

恩,下面我仅提供一些小小的个人建议,请跟大家讨论:

1 在中断响应程序中多了一个“}”。

2 在主函数中InitInterrupt()的后面分号错误为中文输入的分号。

3 关于P1口的读取问题
  请查看P1口内部结构,要求在读P1某IO口之前,需要先写“1”到该IO口,如:
  P1 = 0xff;
  P1 = 0xff; //可重复一边以确保可靠
  k = P1;

4 关于中断里调用delay()的问题
  在响应程序中调用GetKey()函数,GetKey()函数又调用了delay(20);另外为了在中断看显示还调用了有关显示函数,之后delay(500)。
  其实不管出于什么理由,都要尽量避免在中断里调用delay(),想想为什么不让MCU在延时期间去做其它事情了,以免白白浪费时间?!

5 关于函数重载的问题
  在main()主函数中有delay(500),那么很有可能正在delay(500)期间发生中断,而中断响应程序中又重新调用delay()函数,这就是典型地出现了函数重载。
  要说清除函数重载,还要了解编译器和汇编,这里我尽量简化说明:
  我们在第一次调用delay()时,用到了某些变量(变量地址由编译器给定),而中断响应程序中又重新调用delay(),也要用到某些变量。如果不做特殊处理,那么前后两次调用delay()用到的变量地址空间可能发生冲突,程序也就可能死循环或跑飞!
  解决这一问题的办法就是在创建delay()函数时明确表明其是重载函数,如:
  void delay(unsigned int a) reentrant
  {
    unsigned int i;
    for(i=0; i<=a; i++);
  }

6 INT0的中断
  楼上也有好友提过,有关INT0的触发方式,这里再归纳一下需要注意的地方。
(1)当IT0=1时采用负边沿触发方式,中断引脚上的负脉冲宽度至少保持12个振荡周期(如晶振频率为6MHz,则宽度为2us)。
(2)当IT0=0时采用电平触发方式,则输入到INT0的外部中断源必须保持低电平有效,直到该中断被相应;同时,在中断返回前必须使得电平变高,否则将会再次产生中断。
      针对你的程序,在中断响应程序中,打开EA后,显示和延时了500,那么怎么保证“在中断返回前必须使得电平变高,否则将会再次产生中断”呢?

7 软件架构问题
  根据你的信息,该项目只需要完成两件事情:(1)按键;(2)显示
  以下架构供参考(主要特点是模块化):

//=============================================================================
// test.c
//=============================================================================

#include "reg51.h"

#ifndef         TRUE
#define                TRUE                1
#endif

#ifndef         FALSE
#define                FALSE                0
#endif


#define                DISPLAY_REFURBISH_TIME        50

//For key
#define                KEY_NONE                0
#define                KEY_A                        1
#define                KEY_B                        2
#define                KEY_C                        3
#define                KEY_D                        4
#define                KEY_E                        5
#define                KEY_F                        6
#define                KEY_G                        7
#define                KEY_H                        9

typedef                char                        BYTE;
typedef                unsigned char        UBYTE;
typedef                int                                WORD;
typedef                unsigned int        UWORD;
typedef                long                        DWORD;
typedef                unsigned long        UDWORD;


void InitInterrupt(void);
void lcd_init(void);
//void delay(UWORD a);
void KeyScan(void);
void KeyProg(void);
void Display(void);


bit                TimeTickFlag = FALSE;
UWORD        DisTimeTickCount = 0;

//For key
struct         KEY_STATUS
{
        UBYTE        bKeyShortDown        :1;                        //短按标志
        UBYTE        bKeyLongDown        :1;                        //长按标志
        UBYTE        bKeySeriesDown        :1;                        //连按标志
        UBYTE        bKeyShortUp                :1;                        //短按抬标志
        UBYTE        bKeyLongUp                :1;                        //长按抬标志
        UBYTE                                        :1;                        //
        UBYTE                                        :1;                        //
        UBYTE                                        :1;                        //
};

struct        KEY_VAR
{
        UBYTE                                        KeyNumOld;        //之前键值
        UBYTE                                        KeyNum;                //当前键值
        struct KEY_STATUS                 KeyStatus;        //按键状态
        UBYTE                                        KeyEnergy;        //按键能量,用于驱抖动
        UBYTE                                        KeyHold;        //按键保持时间
}KeyVar={0};

//=============================================================================
// Interrupt
//=============================================================================
void T0_process(void) interrupt 1 using 0 //定时10ms
{
        TL0=(65536-10000)%256;
        TH0=(65536-10000)/256; //定时器初值重载,定时10ms

        TimeTickFlag = TRUE;
}


//=============================================================================
// main
//=============================================================================
void main(void)
{
        lcd_init();
        InitInterrupt();

        while(1)
        {
                //以下每10ms运行一次
                if(TimeTickFlag==TRUE)
                {
                        EA = 0;
                        TimeTickFlag = FALSE;
                        EA = 1;

                        KeyScan();
                        Display();
                       
                        //可以在这里添加其它任务处理
                        KeyProg();
                       
                }
        }
       
}


//=============================================================================
// Initial
//=============================================================================
void InitInterrupt(void)
{
        TMOD=0X01; //选择定时器0方式1

        TH0=(65536-10000)/256; //定时器置初值,定时10ms
        TL0=(65536-10000)%256;

        TR0=1; //启动定时器T0
          ET0=1; //开定时器TO中断

        EA=1; //中断允许
}


void lcd_init(void)
{
}

/*
//不存在函数重载问题,故这里不需要定义为 reentrant
void delay(UWORD a)
{
        UWORD i;
    for(i=0; i<=a; i++);
}
*/

//=============================================================================
// LCD
//=============================================================================
void lcd_wcmd(UBYTE a)
{
        a = a;
}

void lcd_pos(UBYTE a)
{
        a = a;
}

void lcd_wdat(UBYTE a)
{
        a = a;
}

//每DISPLAY_REFURBISH_TIME*10ms=500ms刷新一次
void Display(void)
{
        DisTimeTickCount++;
        if(DisTimeTickCount>=DISPLAY_REFURBISH_TIME)
        {
                DisTimeTickCount -= DISPLAY_REFURBISH_TIME;

                //以下是刷新显示
                lcd_wcmd(0x01);
            lcd_pos(0x00);
                  lcd_wdat(KeyVar.KeyNum+48);
                  lcd_pos(0x40);
        }
}


//=============================================================================
// Key
//=============================================================================
//每10ms按键扫描一次
void KeyScan(void)
{
        UBYTE        temp;

        P1 = 0xff; //先写入"1"才能读
        P1 = 0xff; //为了可靠重复一遍

        temp = P1;
        switch(temp)
        {
                case 0x01:
                        KeyVar.KeyNum = KEY_A;
                        break;
                case 0x02:
                        KeyVar.KeyNum = KEY_B;
                        break;
                case 0x04:
                        KeyVar.KeyNum = KEY_C;
                        break;
                case 0x08:
                        KeyVar.KeyNum = KEY_D;
                        break;
                case 0x10:
                        KeyVar.KeyNum = KEY_E;
                        break;
                case 0x20:
                        KeyVar.KeyNum = KEY_F;
                        break;
                case 0x40:
                        KeyVar.KeyNum = KEY_G;
                        break;
                case 0x80:
                        KeyVar.KeyNum = KEY_H;
                        break;
                default:
                        KeyVar.KeyNum = KEY_NONE;
                        break;
        }

        //下面还有利用KeyVar结构体的代码处理

}

//按键处理
void KeyProg(void)
{
       
}


//=============================================================================

8 关于按键扫描的问题:完整的全面的按键扫描,可以另外讨论。

下班了,暂且写这么多吧,希望与大家共同进步,感谢各位!

点赞  2009-6-1 18:18
不要老是盯着程序看,要查查datasheet,能请几个问题哦
1 看中断时电平还是边沿
2 要是电平触发,看是否可以自动恢复
点赞  2009-6-1 21:42
进入中断程序之后,关中断试一下,判断一下问题的位置。
点赞  2009-6-1 22:40
中断方式跟多个中断嵌套的问题?
点赞  2009-6-3 17:09
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复