STC89C52RC 外部中断INT0和INT1 操作
1 外部中断
1.1 外部中断
单片机外部中断是指由外部设备或事件触发的中断信号,它可以打断单片机正在执行的程序,转而执行中断服务程序来处理特定的外部事件。外部中断是单片机与外部世界进行交互的重要方式之一,可以用于处理各种外部输入、事件或信号,如按键输入、传感器触发、通信接收等。
外部中断的处理过程通常涉及以下几个关键步骤:
- 配置中断触发条件:单片机需要通过相应的寄存器或配置寄存器设置外部中断的触发条件,如上升沿触发、下降沿触发、边沿触发或电平触发等。这些触发条件决定了何时触发外部中断。
- 使能中断:在需要使用外部中断时,单片机需要使能相应的中断源,以便在触发条件满足时产生中断请求信号。
- 编写中断服务程序(ISR):中断服务程序是用于处理中断事件的函数,它会在中断触发时被自动调用。在中断服务程序中,可以编写处理外部中断事件的代码,如读取外部输入、处理传感器数据、发送响应信号等。
- 中断向量表:单片机通常使用中断向量表来管理不同的中断源和相应的中断服务程序。中断向量表是一个包含各个中断服务程序地址的表格,当中断触发时,单片机会自动跳转到相应的中断服务程序执行。
在具体的单片机平台和开发环境中,外部中断的配置和处理方法可能会有所不同。不同的单片机可能会提供不同的寄存器和库函数来支持外部中断功能。因此,您需要参考所使用单片机的器件手册和开发环境的文档,以了解如何正确配置和处理外部中断。
1.2 中断介绍
TC89C52是一款常用的单片机型号,它基于8051内核,并具有一些扩展的功能和特性。以下是关于STC89C52中断的介绍:
- 中断源:STC89C52支持多种中断源,包括外部中断、定时器中断、串口中断和其他特定硬件中断。具体的中断源数量和类型取决于单片机的具体型号和配置。
- 中断向量表:STC89C52使用中断向量表来管理中断服务例程。中断向量表是一组特定的内存地址,每个地址对应一个中断源的中断服务例程入口点。当中断触发时,单片机会根据中断源的标识符查找相应的中断向量表地址,并跳转到对应的中断服务例程。
- 中断优先级:STC89C52支持中断优先级的设置,以决定各个中断源的触发顺序和优先级。具有更高优先级的中断源将在较低优先级的中断源之前得到处理。中断优先级可以通过设置特定的寄存器来实现,通常是通过IP(Interrupt Priority)寄存器来配置。
- 中断控制和使能:STC89C52提供了相关的寄存器用于控制和使能中断。例如,EA(全局中断使能)位用于启用或禁用所有中断,INT0、INT1等位用于控制特定外部中断的使能,定时器相关寄存器用于配置定时器中断等。
- 中断服务例程(ISR):对于每个中断源,需要编写相应的中断服务例程来处理中断事件。中断服务例程应位于中断向量表所指定的地址处,它们负责执行与中断相关的任务和操作。在中断服务例程中,可以执行特定的代码、读取和处理相关寄存器、更新状态等。
- 中断返回:在STC89C52中,中断服务例程的返回是通过执行中断返回指令(RETI)来实现的。RETI指令会将程序的执行流程返回到中断触发的地方,并继续执行主程序的下一条指令。
中断寄存器介绍:
示例中断服务函数写法:void Timer2_isr(void) interrupt 5 using 1
5:代表中断向量号 (详见中断查询次序,timer2是第六个所以为5)
1:就是指定在执行函数时切换为使用第1组
根据以下分析,我们建议不要使用using,避免操作不当对程序运行造成影响。
以下是网上总结的:原文链接:http://www.51hei.com/mcu/766.html
using关键字的作用就是指定某个函数在执行时切换寄存器组的:
51中有四个寄存器组,每个组有R0-R7这8个寄存器,用于CPU的数据处理,一般主函数main()默认使用第0组,因为PSW寄存器的初始值的第3、4位是00嘛,即默认指定了第0组。using 0就是指定在执行函数时切换为使用第0组,不加using关键字的话,一般都默认使用第0组,但这样的话在调用其他函数(包括中断服务函数)的时候,就会加入一些压栈指令以保护原来的R0-R7寄存器的(这样的话,程序执行会效率会低一点,因为会产生很多个指令是用来压栈出栈的),照这样说,只是加了using 0,使用的也是第0组又不是其它的组,程序就不应该有问题了吧,但是就是因为加了using 0,编译了一次,才发现在定时器中断函数t0()的入口中并没有发现把R0-R7的代码压入栈呀,就是说没有保护好R7呀,那当然就是在执行完之后回来R7不能回复原来的值啦,接下来的事情。。。。我就不说啦,这就是问题的根源,去掉using 0就可以了,编译器就自动帮你将R0-R7压栈,手动加了using 0,就是让编译器以为之前用的并不是第0组,而现在执行这个中断函数时就切换到第0组,而省去了将R0-R7这8个寄存器压入栈的指令了,这样虽然看起来是快了,然而对于这个程序来说却是致命的问题!!!因为根本没有保护好R0-R7,而没有保护R7并不是我预期发生的!!!这不是编译器的问题,是自己没有了解好、用好using的问题啊!还有,其实也可以在编译器选项里有选项来硬性规定编译器统一不直接使用寄存器而是使用间接寻址的办法来改变循环变量分配的地址的,或者使用
#pragma NOAREGS
定义函数
#pragma AREGS
来规定某个函数的是这样子。
当然,这样的规定和使用using本身并不冲突,只是使用了这个关键字后就可能会间接地产生一系列的问题,让人郁闷了这么久,其实本来如果有详细地看整个程序的反汇编代码,或许当时就会发现发现少了那段压栈指令了,而不是说推断为编译器分配空间的问题了。。。。。。。。希望我的这次教训对各位有所帮助!!!
另外,using的用法,其实就是手动指定函数使用的寄存器组,用得不好,如果在中断里还有调用其它函数,用得不好会出现函数传递出错的,不信可以反汇编看看,建议如果对这个关键字用法和C51的结构及汇编不熟的话,请还是让C51编译器帮你好了,不要胡乱使用,因为会比较容易出错的,要切记哦!!!其实用using关键到底对在编译后会造成什么影响,建议自己亲自去查看汇编程序。。。
2 STC89C52外部中断
2.1 外部中断引脚
中断 AT89S52 有 6 个中断源:两个外部中断(INT0 和 INT1),三个定时中断(定时器 0、1、 2)和一个串行中断。这些中断如图 10 所示 每个中断源都可以通过置位或清除特殊寄存器 IE 中的相关中断允许控制位分别使得中 断源有效或无效。IE 还包括一个中断允许总控制位 EA,它能一次禁止所有中断。 如表 5 所示,IE.6 位是不可用的。对于 AT89S52,IE.5 位也是不能用的。用户软件不应 给这些位写 1。它们为 AT89 系列新产品预留。 定时器 2 可以被寄存器 T2CON 中的 TF2 和 EXF2 的或逻辑触发。程序进入中断服务后, 这些标志位都可以由硬件清 0。实际上,中断服务程序必须判定是否是 TF2 或 EXF2 激 活中断,标志位也必须由软件清 0。 定时器 0 和定时器 1 标志位 TF0 和 TF1 在计数溢出的那个周期的 S5P2 被置位。它们的 值一直到下一个周期被电路捕捉下来。然而,定时器 2 的标志位 TF2 在计数溢出的那 个周期的 S2P2 被置位,在同一个周期被电路捕捉下来。
STC89C52外部中断引脚在P32和P33两个引脚,分别对应INT0和INT1
如图所示:
2.2 外部中断寄存器说明
(1)在 8051 单片机中,TCON(Timer Control)寄存器用于控制定时器/计数器的操作和中断。TCON 寄存器是一个 8 位寄存器,包含了定时器/计数器相关的控制位和中断标志位。下面是 TCON 寄存器的位分配:
apache
复制
Bit 7 6 5 4 3 2 1 0
TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0
以下是各个位的功能解释:
• TF1 (Timer 1 Overflow Flag):定时器 1 溢出标志位。当定时器 1 溢出时,该位会置 1。需要手动清除该标志位以允许下一次溢出中断。
• TR1 (Timer 1 Run Control):定时器 1 运行控制位。设置为 1 时,定时器 1 开始运行;设置为 0 时,定时器 1 停止。
• TF0 (Timer 0 Overflow Flag):定时器 0 溢出标志位。当定时器 0 溢出时,该位会置 1。需要手动清除该标志位以允许下一次溢出中断。
• TR0 (Timer 0 Run Control):定时器 0 运行控制位。设置为 1 时,定时器 0 开始运行;设置为 0 时,定时器 0 停止。
• IE1 (External Interrupt 1 Enable):外部中断 1 使能位。设置为 1 时,允许外部中断 1 的触发;设置为 0 时,禁止外部中断 1。
• IT1 (External Interrupt 1 Type Control):外部中断 1 触发类型控制位。当 IT1 为 0 时,外部中断 1 通过电平触发;当 IT1 为 1 时,外部中断 1 通过边沿触发。
• IE0 (External Interrupt 0 Enable):外部中断 0 使能位。设置为 1 时,允许外部中断 0 的触发;设置为 0 时,禁止外部中断 0。
• IT0 (External Interrupt 0 Type Control):外部中断 0 触发类型控制位。当 IT0 为 0 时,外部中断 0 通过电平触发;当 IT0 为 1 时,外部中断 0 通过边沿触发。
通过配置 TCON 寄存器的位,可以实现以下功能:
• 控制定时器 1 和定时器 0 的启停操作。
• 选择外部中断 1 和外部中断 0 的触发类型(电平触发或边沿触发)。
• 允许或禁止外部中断 1 和外部中断 0 的触发。
需要注意的是,TCON 寄存器是特殊功能寄存器(SFR),对应的地址为 0x88。在程序中,可以使用特定的指令来读取和写入 TCON 寄存器的值,例如 MOV 指令。
总结起来,TCON 寄存器在 8051 单片机中用于控制定时器/计数器的操作和中断,以及外部中断的触发和使能。通过配置 TCON 寄存器的位,可以实现定时器/计数器的启停控制和中断的配置。
(2)根据触发源的逻辑框图可知,外部中断由低电平触发或者下降沿触发,如图所示
(3)中断使能控制位
3 STC89C52外部中断演示
工程整理,首先,我们将定时器看门狗使能都屏蔽掉,并将全局中断使能放到主程序中。然后新建中断的c和h文件添加到工程。
3.1 电平触发外部中断
工程中所有代码如下所示:
//main.c文件
#include "includes.h"
/*------------------------------------------------
延时子程序
------------------------------------------------*/
void delay(unsigned int cnt)
{
while(--cnt);
}
/*------------------------------------------------
主函数
------------------------------------------------*/
void main (void)
{
EA=0;
//初始化定时器0
sys_timer0_init();
//初始化定时器1
sys_timer1_init();
//初始化定时器2
sys_timer2_init();
sys_wdog_init();
//外部中断初始化
sys_exit_init();
//8个指示灯的操作
sys_led();
sys_led_test();
sys_led_test1();
sys_ledtube_on1();
sys_ledtube_on2();
//主循环中添加其他需要一直工作的程序
sys_keynum_ledon(sys_key_single());
sys_keynum_ledon(sys_key_board());
EA=1;
P1=0x55; //P1口初始值,预设指示灯状态,测试引脚
while (1)
{
clr_wdg();
}
}
//c51_gpio.c文件
#include "includes.h"
void sys_led(void)
{
P1 = 0xFF; //P1口全部为高电平,对应的LED灯全灭掉,ff换算成二进制是 1111 1111
P1 = 0x00; //P1口全部为低电平,对应的LED灯全亮起,ff换算成二进制是 0000 0000
}
/********************************************************
函数名称:sys_led_test
函数功能:IO口高低电平测试
入口参数:
出口参数:
修 改:
内 容:
********************************************************/
void sys_led_test(void)
{
bit tmp = 0;//中间变量用于获取io口状态
//控制4个引脚输出
P10 = 1;
P11 = 0;
P12 = 0;
P13 = 1;
//用另外四个IO口获取状态并测试(指示灯显示)
tmp = P10;
P14 = tmp;
tmp = P11;
P15 = tmp;
tmp = P12;
P16 = tmp;
tmp = P13;
P17 = tmp;
}
/********************************************************
函数名称:sys_led_test
函数功能:led流水灯
入口参数:
出口参数:
修 改:
内 容:
********************************************************/
void sys_led_test1(void)
{
// delay(30000);//延时程序
P1<<=1; //左移一位 该语句等效于 P1=P1<<1
P1|=0x01; //最后一位补1,该语句等效于 P1=P1|0x01 符号"|"表示"或"
if(P1==0xFF) //检测是否移到最左端?"=="表示检测符号2端的值是否相等
{
// delay(30000);
P1=0xfe; //重新赋值
}
}
//c51_ledtube.c文件
#include "includes.h"
// 显示段码值01234567,可对应原理图查看显示不同图形对应的引脚高点电平配置状态
unsigned char const EL[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,\
0x77,0x7c,0x39,0x5e,0x79,0x71};//0-F
/********************************************************
函数名称:sys_ledtube_on1
函数功能:点亮一个数码管全为亮起来
入口参数:
出口参数:
修 改:
内 容:
********************************************************/
void sys_ledtube_on1(void)
{
//根据原理图,将P0口全部输出高电平,P2选择0号数码管
P0=0xFF;//取显示数据,段码
P2=0; //取位码
}
/********************************************************
函数名称:sys_ledtube_on2
函数功能:显示一组数据
入口参数:
出口参数:
修 改:
内 容:
********************************************************/
static unsigned char ledtube_cnt = 0;
void sys_ledtube_on2(void)
{
ledtube_cnt++;
if(ledtube_cnt>7)
{
ledtube_cnt = 0;
}
P0 = 0x00; //防止切换数码管瞬间有虚影出现
P2 = 0x00;
P0 = EL[ledtube_cnt]; //取显示数据,段码
P2 = ledtube_cnt; //取位码
//根据人眼适应虚影缓冲时间为50ms左右
//我们调整delay在500以下可以看到明显的看起来是一串数据一起显示
delay(100);
}
/********************************************************
函数名称:sys_keynum_ledon
函数功能:显示按键数值
入口参数:按键数值
出口参数:
修 改:
内 容:
********************************************************/
void sys_keynum_ledon(unsigned char num)
{
//根据原理图,将P0口全部输出高电平,P2选择0号数码管
P0=EL[num];//取显示数据,段码
P2=0; //取位码
}
//c51_key.c文件
#include "includes.h"
bit key1=0; //定义按键位置
bit key2=0;
bit key3=0;
bit key4=0;
/********************************************************
函数名称:sys_key_single
函数功能:按键检测,带有消抖策略
入口参数:
出口参数:按键数值
修 改:
内 容:
********************************************************/
static unsigned char key1_history = 0;//缓存上一次按键的结果
unsigned char sys_key_single(void)
{
key1=P30; //定义按键位置
key2=P31;
key3=P32;
key4=P33;
if(!key1) //按下相应的按键,数码管显示相应的码值
{
delay(1000);
if(!key1)
{
key1_history = 1;
return 1;
}
else
{
return key1_history;
}
}
else if(!key2)
{
delay(1000);
if(!key2)
{
key1_history = 2;
return 2;
}
else
{
return key1_history;
}
}
else if(!key3)
{
delay(1000);
if(!key3)
{
key1_history = 3;
return 3;
}
else
{
return key1_history;
}
}
else if(!key4)
{
delay(1000);
if(!key4)
{
key1_history = 4;
return 4;
}
else
{
return key1_history;
}
}
else
{
return key1_history;
}
}
unsigned char sys_key_board(void)
{
unsigned char key = 0x00;
unsigned char num = 0x00;
key=keyscan(); //调用键盘扫描
if(key == 0xFF)
{
num = key1_history;
}
else
{
switch(key)
{
case 0xee:num = 0x0;break;//0按下相应的键显示相对应的码值
case 0xde:num = 0x1;break;//1 按下相应的键显示相对应的码值
case 0xbe:num = 0x2;break;//2
case 0x7e:num = 0x3;break;//3
case 0xed:num = 0x4;break;//4
case 0xdd:num = 0x5;break;//5
case 0xbd:num = 0x6;break;//6
case 0x7d:num = 0x7;break;//7
case 0xeb:num = 0x8;break;//8
case 0xdb:num = 0x9;break;//9
case 0xbb:num = 0xA;break;//a
case 0x7b:num = 0xB;break;//b
case 0xe7:num = 0xC;break;//c
case 0xd7:num = 0xD;break;//d
case 0xb7:num = 0xE;break;//e
case 0x77:num = 0xF;break;//f
default:num = key1_history; break;
}
key1_history = num;
}
return num;
}
/*------------------------------------------------
键盘扫描程序
------------------------------------------------*/
unsigned char keyscan(void) //键盘扫描函数,使用行列反转扫描法
{
unsigned char cord_h,cord_l;//行列值中间变量
P3=0x0f; //行线输出全为0
cord_h=P3&0x0f; //读入列线值
if(cord_h!=0x0f) //先检测有无按键按下
{
delay(100); //去抖
if(cord_h!=0x0f)
{
cord_h=P3&0x0f; //读入列线值
P3=cord_h|0xf0; //输出当前列线值
cord_l=P3&0xf0; //读入行线值
return(cord_h+cord_l);//键盘最后组合码值
}
}
return(0xff); //返回该值
}
//c51_timer.c文件
#include "includes.h"
/*------------------------------------------------
定时器初始化子程序
------------------------------------------------*/
void sys_timer0_init(void)
{
TMOD |= 0x01; //使用模式1,16位定时器,使用"|"符号可以在使用多个定时器时不受影响
TH0=0x00; //给定初值,这里使用定时器最大值从0开始计数一直到65535溢出
TL0=0x00;
//EA=1; //总中断打开 等最后一个中断打开
//ET0=1; //定时器中断打开
TR0=1; //定时器开关打开
}
/*------------------------------------------------
定时器初始化子程序
------------------------------------------------*/
void sys_timer1_init(void)
{
TMOD |= 0x20; //使用模式2,
TH1=0x05; //给定初值,这里使用定时器最大值从5开始计数一直到255溢出
TL1=0x00;
//EA=1; //总中断打开
//ET1=1; //定时器中断打开
TR1=1; //定时器开关打开
}
/*------------------------------------------------
定时器初始化子程序
------------------------------------------------*/
void sys_timer2_init(void)
{
RCAP2H = 0/256;//
RCAP2L = 0/256;
//ET2=1; //打开定时器中断
//EA=1; //打开总中断
TR2=1; //打开定时器开关
}
void sys_wdog_init(void)
{
//WDT_CONTR = 0x35;
}
void clr_wdg(void)
{
//WDT_CONTR = 0x35;
}
/*------------------------------------------------
定时器中断子程序
------------------------------------------------*/
void Timer0_isr(void) interrupt 1
{
TH0=0x00; //重新赋值
TL0=0x00;
//sys_led_test1(); //流水灯操作
}
/*------------------------------------------------
定时器中断子程序
------------------------------------------------*/
void Timer1_isr(void) interrupt 3
{
//sys_led_test1(); //流水灯操作
}
/*------------------------------------------------
定时器中断子程序
------------------------------------------------*/
void Timer2_isr(void) interrupt 5//定时器2中断
{
TF2=0;
//sys_led_test1(); //流水灯操作
}
//c51_exit.c文件
#include "includes.h"
void sys_exit_init(void)
{
EX0=1; //外部中断0开
IT0=0; //电平触发
//IT0=1; //边沿触发
EX1=1; //外部中断1开
IT1=0; //电平触发
//IT1=1; //边沿触发,IT1=0表示电平触发
}
/*------------------------------------------------
外部中断程序
------------------------------------------------*/
void Exit0_isr(void) interrupt 0
{
//在此处可以添加去抖动程序,防止按键抖动造成错误
P1=~P1;
}
/*------------------------------------------------
外部中断程序
------------------------------------------------*/
void Exit1_isr(void) interrupt 2
{
//在此处可以添加去抖动程序,防止按键抖动造成错误
P1=~P1;
}
//c51_ledtube.h文件
#ifndef __C51_LEDTUBE_H__
#define __C51_LEDTUBE_H__
extern unsigned char const EL[];
extern void sys_ledtube_on1(void);
extern void sys_ledtube_on2(void);
extern void sys_keynum_ledon(unsigned char num);
#endif
//includes.h文件
#ifndef __INCLUDES_H__
#define __INCLUDES_H__
//#include<reg52.h>
//包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义
#include "STC89C5xRC_RDP.h"
//应用层头文件
#include "c51_gpio.h"
#include "c51_ledtube.h"
#include "c51_key.h"
#include "c51_timer.h"
#include "c51_exit.h"
extern void delay(unsigned int cnt);
#endif
//c51_gpio.h文件
#ifndef __C51_GPIO_H__
#define __C51_GPIO_H__
extern void sys_led(void);
extern void sys_led_test(void);
extern void sys_led_test1(void);
#endif
//c51_key.h文件
#ifndef __C51_KEY_H__
#define __C51_KEY_H__
extern bit key1; //定义按键位置
extern bit key2;
extern bit key3;
extern bit key4;
extern unsigned char sys_key_single(void);
extern unsigned char sys_key_board(void);
extern unsigned char keyscan(void); //键盘扫描函数,使用行列反转扫描法
#endif
//c51_timer.h文件
#ifndef __C51_TIMER_H__
#define __C51_TIMER_H__
extern void sys_timer0_init(void);
extern void Timer0_isr(void);
extern void sys_timer1_init(void);
extern void Timer1_isr(void);
extern void sys_timer2_init(void);
extern void Timer2_isr(void);
extern void sys_wdog_init(void);
extern void clr_wdg(void);
#endif
//c51_exit.h文件
#ifndef __C51_EXIT_H__
#define __C51_EXIT_H__
extern void sys_exit_init(void);
extern void Exit0_isr(void);
extern void Exit1_isr(void);
#endif
3.2 边沿触发外部中断
更改//c51_exit.c文件中代码
void sys_exit_init(void)
{
EX0=1; //外部中断0开
//IT0=0; //电平触发
IT0=1; //边沿触发
EX1=1; //外部中断1开
//IT1=0; //电平触发
IT1=1; //边沿触发,IT1=0表示电平触发
}
3.3 Protues仿真
增加了选择开关,用于模仿外部电平信号的变化,SW45用于兼容此引脚用作其他功能时断开。
4 外部中断总结
单片机外部中断具有广泛的应用用途,常见的包括:
- 按键输入:外部中断可用于处理按键输入。通过配置外部中断触发条件为按键的下降沿或上升沿,当用户按下或释放按键时触发中断,可以及时响应按键操作并执行相应的处理逻辑。
- 传感器触发:许多传感器(如光敏传感器、温度传感器、加速度传感器等)可以通过外部中断与单片机连接。当传感器检测到特定事件或条件变化时,触发外部中断,单片机可以及时采集传感器数据并进行相应的处理。
- 通信接收:外部中断可用于处理串口通信或其他通信接口的数据接收。当单片机接收到外部设备发送的数据时,触发中断以及时处理接收到的数据,例如解析命令或响应数据等。
- 定时器溢出:单片机的定时器通常具有溢出中断功能。通过配置定时器溢出中断,可以实现定时任务的执行,例如定时采集数据、定时发送信号等。
- 外部事件触发:除了按键和传感器触发外,外部中断还可以处理其他外部事件,如外部信号的变化、外部设备的状态改变等。通过配置相应的触发条件和中断服务程序,可以进行灵活的外部事件处理。
通过合理应用外部中断,可以提高单片机系统的实时性和响应能力,实现与外部设备或事件的高效交互。具体使用方式和应用场景取决于单片机型号、外部设备的特性以及系统需求。