一、中断概念
对于单片机来说,中断是指CPU正在处理某一任务A时,发生了另外一件事件B,需要CPU迅速去处理(中断发生),CPU暂停当前的A任务(中断响应),转而去处理事件B(中断服务),待CPU将事件B处理完毕后,再回到原来任务A被中断的地方继续处理任务A(中断返回),这一套流程即称为“中断”。来实现这样一个场景:四个LED依次亮灭,时间间隔最小0.5秒,最大5秒,要求精确延时。使用两个按键分别控制间隔时间的增减,每按一次增或减0.5秒。精确延时用定时器中断实现,按键响应使用外部中断实现。
AT89C51单片机内部共有6个中断源,从上图可以看出,中断服务程序是可以再次被中断的,这就涉及到中断优先级问题,中断都可以打断正在执行的非中断任务,高优先级中断可以打断正在执行的比自己优先级低的中断服务程序,低优先级中断无法打断正在执行的比自己优先级高的中断服务程序,中断无法打断优先级相同的中断服务程序。AT89C51中断源如下表所示:
中断源 | 说明 | 默认中断优先级 | 序号(C语言使用) | 入口地址(汇编使用) |
INIT0 | 外部中断0,由P3.2端口线引入,低电平或下降沿触发 | 最高 | 0 | 0003H |
T0 | 定时器/计数器0中断,由T0计数器计满回零触发 | 第2 | 1 | 000BH |
INIT1 | 外部中断1,由P3.3端口线引入,低电平或下降沿触发 | 第3 | 2 | 0013H |
T1 | 定时器/计数器1中断,由T1计数器计满回零触发 | 第4 | 3 | 001BH |
TI/RI | 串口中断,串口完成一帧数据发送或接收时触发 | 第5 | 4 | 0023H |
T2 | 定时器/计数器2中断,由T2计数器计满回零触发 | 最低 | 5 | 002BH |
中断允许寄存器IE:
IE是一个八位寄存器,可以位寻址,每一位可进行单独操作,实现以下功能:
位序号 | 位符号 | 功能描述 |
D7 | EA | 全局中断允许位。1:打开全局中断控制 0:关闭全部中断。 |
D6 | -- | 保留 |
D5 | ET2 | 定时器/计数器2中断允许位。1:打开T2中断。0:关闭T2中断。 |
D4 | ES | 串口中断允许位。1:打开串口中断。0:关闭串口中断。 |
D3 | ET1 | 定时器/计数器1中断允许位。1:打开T1中断。0:关闭T1中断 |
D2 | EX1 | 外部中断1中断允许位。1:打开外部中断1中断。0:关闭外部中断1中断 |
D1 | ET0 | 定时器/计数器0中断允许位。1:打开T0中断。0:关闭T0中断 |
D0 | EX0 | 外部中断0中断允许位。1:打开外部中断0中断。0:关闭外部中断0中断 |
中断优先级寄存器IP:
IP是一个八位寄存器,用来设定中断的优先级,具体可阅读对应的芯片手册。本次实验场景比较简单,不再对中断优先级做设定,使用默认的优先级。
定时器/计数器工作方式寄存器TMOD:
TMOD是一个八位寄存器,不能位寻址,用来确定定时器的工作方式及功能选择,复位时整个寄存器清零。每一位定义如下所示。低四位控制定时器/计数器T0,高四位控制T1。而T2的工作方式控制器为T2MOD,详见芯片手册,本场景中未使用到。
位序号 | 位符号 | 功能描述 |
D7 | GATE | 门控制位。GATE=0时,定时器/计数器启停仅受TCON寄存器中TRA(X=0/1)控制。GATE=1时,定时器/计数器启停仅受TCON寄存器中TRA(X=0/1)和外部中断引脚(INT0或INT1)上的电平状态来共同控制。 |
D6 | C/T | 定时器模式/计数器模式选择位。 |
D5 | M1 | M1M0工作方式选择位。00:方式0,为13位定时器/计数器。01:方式1,为16位定时器/计数器。10:方式2,8位初值自动重装的8位定时器/计数器。11:仅适用于T0,分成两个8位计数器,T1停止计数。 |
D4 | M0 | |
D3 | GATE | 同上 |
D2 | C/T | 同上 |
D1 | M1 | 同上 |
D0 | M0 | 同上 |
定时器/计数器控制寄存器TCON:
IP也是一个八位寄存器,用来设定中断的优先级,具体可阅读对应的芯片手册。本次实验场景比较简单,不再对中断优先级做设定,使用默认的优先级。
根据定时时长设置定时器寄存器值:
对于T0来说,由TL0寄存器作为低8位、TH0寄存器作为高8位,组成16位的加1计数器。定时器一旦启动,它就会在原来的数值上开始加1计数,若程序开始时,没有设置TH0和TL0,则默认值是0,假设始终频率为12MHz,12个时钟周期为一个机器周期,此时及其周期是1us,计满TH0和TL0就需要2^16-1个数,再来一个脉冲计数器就会溢出,随即触发中断。因此从0开始到中断发生需要65536us,约65.5ms。如果需要50ms,就需要给TH0和TL0设置一个初值,初值基础上计数50000次,就会消耗约50ms。65536-50000=15536,即TH0和TL0中应装入的总数为15536。TH0为高8位,即15536/256。TL0为低8位,即15536%256。以后接触高等级的芯片,关于寄存器的设置会更复杂一些。本实验中采用的是11.0592M的晶振(为什么没有直接用12M的,是因为后面做串口实验的时候,11.0592M更有利于串口通信频率的设定),机器周期=12*(1/11059200)≈1.09us,若需要每个时间周期t=50ms,则N(计数周期)=50000/1.09≈45872。高8位TH0装入(65536-45872)/256,低8位TL0装入(65536-45872)%256。
外部中断控制:
外部中断的控制比较简单,打开中断,设置触发方式就可以了,包括低电平触发和下降沿触发。此处为了防止重复触发(抖动或者低电平持续时一直触发中断),采用了下降沿触发,为了消除抖动,下降沿触发后关闭外部中断,延时一段时间后再次打开。真实应用场景中,极少有按键直接连接触发外部中断引脚的情况,此处只为演示外部中断触发。
二、仿真电路
LED模块的接法和《从51到ARM裸机开发实验(003) AT89C51 GPIO实验》中的接法相同。按键TIME+、TIME-分别接了P3.2、P3.3,即外部中断0和外部中断1。
三、程序设计
1、驱动程序
interrupt.h
#ifndef _INTERRUPT_H_
#define _INTERRUPT_H_
void set_interrupt_callback(int interrupt_num, void* isr_callback);
void timer0_isr();
void external0_isr();
void external1_isr();
#endif
interrupt.c
#include <reg52.h>
#include "interrupt.h"
void (*callback0)(); // 函数指针
void (*callback1)(); // 函数指针
void (*callback2)(); // 函数指针
//设置中断回调函数
void set_interrupt_callback(int interrupt_num, void* isr_callback){
if(interrupt_num == 0){
callback0 = isr_callback;
}else if(interrupt_num == 1){
callback1 = isr_callback;
}else if(interrupt_num == 2){
callback2 = isr_callback;
}
}
//定时器0中断
void timer0_isr() interrupt 1
{
callback1();
}
//外部中断0
void external0_isr() interrupt 0
{
callback0();
}
//外部中断1
void external1_isr() interrupt 2
{
callback2();
}
delay.h
#ifndef _DELAY_H_
#define _DELAY_H_
void delayms(unsigned int xms);
#endif
delay.c
#include "delay.h"
void delayms(unsigned int xms){
unsigned int i,j;
for(i=xms;i>0;i--){
for(j=110;j>0;j--);
}
}
2、应用程序
application.h
#ifndef _APPLICATION_H_
#define _APPLICATION_H_
void timer0_callback();
void external0_callback();
void external1_callback();
#endif
application.c
#include <reg52.h>
#include "application.h"
#include "interrupt.h"
#include "delay.h"
#define uchar unsigned char
#define uint unsigned int
sbit led1 = P0^0;
sbit led2 = P0^1;
sbit led3 = P0^2;
sbit led4 = P0^3;
#define MAX_CYCLE 100
#define MIN_CYCLE 10
uchar num;
uchar time_cycle = 10;
//主函数
void main(){
set_interrupt_callback(0, &external0_callback);
set_interrupt_callback(1, &timer0_callback);
set_interrupt_callback(2, &external1_callback);
TMOD = 0x01; //设置定时器0为工作方式1(M1M0为01)
TH0=(65536-45872)/256;
TL0=(65536-45872)%256;
EA=1; //开总中断
EX0=1; //打开外部中断0
EX1=1; //打开外部中断1
IT0=1; //INT0下降沿触发方式
IT1=1; //INT1下降沿触发方式
ET0=1; //开定时器0中断
TR0=1; //启动定时器0
led1=0;
while(1);
}
//定时器0中断回调函数
void timer0_callback(){
TH0=(65536-45872)/256;
TL0=(65536-45872)%256;
num++;
if(num>=time_cycle){
num=0;
if(led1 == 0){
led1 = 1;
led2 = 0;
}else if(led2 == 0){
led2 = 1;
led3 = 0;
}else if(led3 == 0){
led3 = 1;
led4 = 0;
}else if(led4 == 0){
led4 = 1;
led1 = 0;
}
}
}
//外部中断0回调函数
void external0_callback(){
EX0=0; //关闭外部中断0
delayms(10); //延时
if(time_cycle < MAX_CYCLE){
time_cycle = time_cycle + 10;
}
EX0=1; //打开外部中断0
}
//外部中断1回调函数
void external1_callback(){
EX1=0; //关闭外部中断1
delayms(10); //延时
if(time_cycle > MIN_CYCLE){
time_cycle = time_cycle - 10;
}
EX1=1; //打开外部中断1
}
四、资料下载
源码与仿真电路下载地址:https://download.youkuaiyun.com/download/qq_54140018/87761758