51单片机的中断及其使用方法


什么是中断?

比如说我正在厨房用煤气烧一壶水,这样就只能守在厨房里,苦苦等着水开——如果水溢出来浇灭了煤气,有可能就要发生一场灾难了。

门外忽然又传来了铿锵有力的歌声,我最喜欢的天龙八部要开演了,听着水壶发出“咕嘟咕嘟”的声音,我清楚:除非等到水开,否则没有我享受人生的时候。

这个场景跟中断有什么关系呢?

在这个场景中,我是唯一具有处理能力的主体,不管是烧水、还是看电视,同一个时间点上我只能干一件事情。但是,在我专心致志干一件事情时,总有许多或紧迫或不紧迫的事情突然出现在面前,都需要去关注,有些还需要我停下手头的工作马上去处理。只有在处理完之后,方能回头完成先前的任务。

中断机制不仅赋予了我处理意外情况的能力,如果我能充分发挥这个机制的妙用,就可以“同时”完成多个任务了。

事实上烧水需要10分钟完成,但是提下水壶和关煤气我只要几秒钟就可以完成。为了这几秒,我需要在厨房等候10分钟。如果使用闹钟定时10分钟,10分钟一到,闹钟就会提醒我该去关煤气了,那么我就可以去安心看电视了。

实际上就是用了闹钟这样一个中断信号来提示我完成提水壶和关煤气的任务。

中断优先级的说明

  • 当设置为默认中断固有优先级时:

    当几个中断同时发生时,则先处理中断优先级高的中断程序,在处理任意中断期间发生中断,都不会响应。

  • 当配置了中断优先级,即抢占优先级

    同时发生中断,优先级高的先响应,在处理任意中断时,发生同级别或低级的中断,则不响应,发生优先级更高的中断时,则先处理高优先级中断,处理完毕,再回来处理当前中断。

  • 当设置为默认固有中断优先级时:

    假设“水开”默认优先级高于“门铃”优先级,当先听到“水开”则先处理“水开”这件事,当先听到“门铃”则先处理“门铃”这件事。只有当两件事同时发生时,则会先去处理“水开”,再处理“门铃”。

  • 当人为配置了抢占中断优先级

    配置“水开”为高优先级,则当“水开”事件发生时,直接处理“水开”,在处理“水开”的过程中,即使“门铃”响了,也不会去理会。当“门铃”响了,正在走向门口时,这个时候,“水开”事件又发生了,那么从门口转向,先去处理“水开”,处理好了后,再回头到门口,处理“门铃”事件。

在51单片机中使用中断

51单片机中断源

51单片机共有6个中断源,分别如下:

INT0——外部中断0,由P3_2端口引入,低电平或者下降沿引起;中断级别最高;C语言使用序号为0;

T0——定时器/计数器0,由T0计数器计数回零引起;中断级别第二;C语言使用序号为1;

INT1——外部中断1,由P3_3端口引入,低电平或者下降沿引起;中断级别第三;C语言使用序号为2;

T1——定时器/计数器1,由T1计数器计数回零引起;中断级别第四;C语言使用序号为3;

TI/RI——串行口中断,由串行口完成一帧字符发送/接受后引起;中断级别第五;C语言使用序号为4;

T2——定时器/计数器2,由T2计数器计数回零引起;中断级别第六;C语言使用序号为5;

51单片机中断的例子

下面先简单写一个不用中断实行的数码管秒表程序,定时器的使用可以参考我的这边文章: (51单片机定时器使用)[https://www.fkomm.cn/article/2018/9/19/45.html]

# include <8052.h>

# define ADDR0 P1_0
# define ADDR1 P1_1
# define ADDR2 P1_2
# define ADDR3 P1_3
# define ENLED P1_4

unsigned char __code LedChar[] = { //数码管显示字符转换表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = {  //数码管显示缓冲区,初值0xFF确保启动时都不亮
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

void main() {

    unsigned char i = 0;    //动态扫描的索引
    unsigned int  cnt = 0;  //记录T1中断次数
    unsigned long sec = 0;  //记录经过的秒数

    ENLED = 0;
    ADDR3 = 1;

    TMOD = 0x01;                //设置T1为模式1
    TH1 = 0xCF;                 //为T1赋初值0xCF67,为1ms
    TL1 = 0x67;
    TR1 = 1;                    //启动T1

    while(1) {              
        if (TF1 == 1) {         //判断T1是否溢出

            TF1 = 0;            //T1溢出后,清零中断标志,并重新赋值
            TH1 = 0xCF;
            TL1 = 0x67;
            TR1 = 1;

            cnt ++;             //计数值自加1

            if (cnt >= 1000) {  //判断T1溢出是否达到1000次
                cnt = 0;        //达到1000次后计数值清零
                sec ++;         //秒计数自加1

                /*以下代码将sec按十进制位从低到高依次提取并转为数码管显示字符*/
                LedBuff[0] = LedChar[sec%10];
                LedBuff[1] = LedChar[sec/10%10];
                LedBuff[2] = LedChar[sec/100%10];
                LedBuff[3] = LedChar[sec/1000%10];
                LedBuff[4] = LedChar[sec/10000%10];
                LedBuff[5] = LedChar[sec/100000%10];
            }
        }

        P0 = 0xFF;              //数码管消隐
        /*以下代码完成数码管动态扫描刷新*/
        switch(i) {
            case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
            case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
            case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
            case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
            case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
            case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
            default: break;
        }
    }
}

下面我们将使用中断来实现

# include <8052.h>


# define ADDR0 P1_0
# define ADDR1 P1_1
# define ADDR2 P1_2
# define ADDR3 P1_3
# define ENLED P1_4

unsigned char __code LedChar[] = {  //数码管显示字符转换表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = {  //数码管显示缓冲区,初值0xFF确保启动时都不亮
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

unsigned char i = 0;    //动态扫描的索引
unsigned char flag1s = 0;//1秒定时标志
unsigned int  cnt = 0;  //记录T1中断次数

void main() {
    unsigned long sec = 0;//记录经过的秒数

    EA = 1;             //使能总中断
    ENLED = 0;          //使能U3
    ADDR3 = 1;          //因为需要动态改变ADDR0-2的值,所以不需要再初始化了
    TMOD = 0x10;        //设置T1为模式1
    TH1  = 0xFC;        //为T1赋初值0xFC67,定时1ms
    TL1  = 0x67;
    ET1  = 1;           //使能T1中断
    TR1  = 1;           //启动T1

    while(1) {
        if (flag1s == 1) { //判断1秒定时标志

            flag1s = 0;   //1秒定时标志清零
            sec ++;       //秒计数自减1

            /*以下代码将sec按十进制位从低到高依次提取并转为数码管显示字符*/
            LedBuff[0] = LedChar[sec%10];
            LedBuff[1] = LedChar[sec/10%10];
            LedBuff[2] = LedChar[sec/100%10];
            LedBuff[3] = LedChar[sec/1000%10];
            LedBuff[4] = LedChar[sec/10000%10];
            LedBuff[5] = LedChar[sec/100000%10];
        }
    }
}

/* 定时器1中断服务函数 */
void InterruptTime1() __interrupt 3 {

    TH1 = 0xCF;             //重新加载初值
    TL1 = 0x67;

    cnt++;                  //中断次数计数值加1

    if (cnt >= 1000) {      //中断1000次即1秒
        cnt = 0;            //清零计数值以重新开始下1秒计时
        flag1s = 1;         //设置1秒定时标志为1
    }

    /*以下代码完成数码管动态扫描刷新*/
    P0 = 0xFF;   //显示消隐

    switch(i) {
        case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
        case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
        case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
        case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
        case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
        case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
        default: break;
    }
}