单片机使用外部中断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;
}
}
}
还没仔细看程序。
1. 采用电平触发中断可能有问题;
2. 中断标记有没有在ISR中清掉。
按键中断我觉得用电平触发要可靠些,而且我以前用电平触发也没问题的。
当采用电平触发方式时,没个机器周期都采样INTO引脚,若INTO引脚为低电平,则置"1"IE0,否则清“0”IE0.也就是说中断请求标志位由单片机自动完成,不需要我来清掉的。
触发用沿比较可靠。
lcd显示不要放在中断处理函数中进行。
去掉显示相关函数调用,看看是不是还停在最后一步。
引用: 引用 3 楼 shuiyan 的回复:
触发用沿比较可靠。
lcd显示不要放在中断处理函数中进行。
去掉显示相关函数调用,看看是不是还停在最后一步。
LCD显示放在中断中是为了观察是否进入了中断。
如果把这个现实函数去掉的话,程序就会死在按键时的状态。(我的初始状态是两个字符串交替显示,但是按键后就是当前显示的哪个字符串就一直显示,跟死了一样)
//*****************************************************************************
//?????????ì?????ò
//*****************************************************************************
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);
}
//*****************************************************************************
//?????????ì?????ò
//*****************************************************************************
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);
}
//*****************************************************************************
//?????????ì?????ò
//*****************************************************************************
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);
}
首先很感谢楼上的回复!
EA=1; // if(k!=0xFF), 应该执行到这里吧,这是还是低电平,是不是又该进中断了?
在上面的while语句就是判断是否释放按键,只有变为高电平之后才能执行到EA=1。
实际上在程序中,EA=1这条语句就是作为中断处理函数的最后一条语句的。下面的显示语句只是我为了观察多加上去的。
我觉得在哪好像差别不是太大,也就是说:如果(K!= 0xFF), 那么后面EA=1,这时又会不断进入中断!只有当按键释放掉之后才会退出中断。这样处理中断不大妥当吧。FYI。
- key1=GetKey();
- key++;
- while(1) //等待按键释放
- {
- k=P1;
- if(k!=0xFF)
- {
- _nop_();
- }
- else
- break;
- }
- lcd_wcmd(0x01);
- lcd_pos(0x00);
- lcd_wdat(key1+48);
- lcd_pos(0x40);
- lcd_wdat(key+48);
- }
-
- delay(500);
- //放到这里.
- EA=1; // if(k!=0xFF), 应该执行到这里吧,这是还是低电平,是不是又该进中断了?
是不是EA 后里面又来中断了
按键产生中断,当按键释放后,应该不会再来中断了啊!
现在通过实验,发现问题出在delay函数上。调用这个函数就会出错,但是我自己在响应函数中写delay函数的函数体又可以了。真是奇怪!
而且现在扫描键盘,有得时候按键一次,但是发现单片机认为扫描了两次。这个应该就是抖动引起的吧。但是不知道该怎么处理这个防抖好。
改为下降沿触发中断也试过了,效果差不多。
各位,大家好!这几天公司不是太忙,就上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 关于按键扫描的问题:完整的全面的按键扫描,可以另外讨论。
下班了,暂且写这么多吧,希望与大家共同进步,感谢各位!
不要老是盯着程序看,要查查datasheet,能请几个问题哦
1 看中断时电平还是边沿
2 要是电平触发,看是否可以自动恢复
进入中断程序之后,关中断试一下,判断一下问题的位置。