【笔记】中断

前言

中断学习笔记
注意:本文所有代码均按照sdcc编译器代码规范编写,如果使用的Keli编译器,需要自行修改代码

定义

  • 中断:打断CPU当前工作的一种事件
  • 中断源:所有可以引发中断的事件的集合
    • 标准51单片机的中断源(5种/3类)
      • 外部中断:由单片机引脚输入的中断
        • 外部中断0
        • 外部中断1
      • 定时/计数器中断:由定时/计数器引起的中断
        • 定时/计数器0中断
        • 定时/计数器1中断
      • 串行中断:由串行通讯(发送串行数据、接收串行数据)引起的中断
        • 串行中断
  • 中断向量:中断函数的入口地址
    • 在C51代码中,通常不直接使用中断向量,而是根据头文件中指定的指针变量来使用中断向量

外部中断

数字信号的种类(4种/2类)

  • 电平信号
    • 高电平信号
    • 低电平信号
  • 边沿信号
    • 上升沿信号
    • 下降沿信号

外部中断请求类型

  • 标准51单片机支持的外部中断请求类型
    • 低电平请求
    • 下降沿请求

STC12C5A60S2使用外部中断

  1. 开中断:STC12C5A60S2的12引脚默认为P3.2端口、13引脚默认为P3.3端口。如果需要开中断,需要将12引脚切换为INT0端口、将13引脚设置为INT1端口
  2. 指定中断请求类型(低电平请求、下降沿请求):虽然标准51单片机支持2种中断请求,但是使用时只能设置1种,每次需要中断时,通过这1种中断请求实现中断
  3. 指定中断处理函数:当检测到中断请求后,立即执行中断处理函数

开中断

  • 通过IE寄存器实现开中断
  • IE寄存器是可以位寻址的
  • IE寄存器从第8位(最高位)到第1位(最低位)分别是:EAESET1EX1ET0EX0
    • EA:中断总开(1)关(0)
    • EX0:外部中断0的开(1)关(0)
    • EX1:外部中断1的开(1)关(0)
    • ET0:定时/计数器0的开(1)关(0)
    • ET1:定时/计数器1的开(1)关(0)
    • ES:串行中断的开(1)关(0)
关闭所有中断
通过字节寻址
1
IE = 0x00;
通过位寻址
1
EA = 0;
开启外部中断0
通过字节寻址
1
IE = 0x81;
通过位寻址
1
2
EA = 1;
EX0 = 1;
开启外部中断1
通过字节寻址
1
IE = 0x84;
通过位寻址
1
2
EA = 1;
EX1 = 1;
同时开启外部中断0和外部中断1
通过字节寻址
1
IE = 0x85;
通过位寻址
1
2
3
EA = 1;
EX0 = 1;
EX1 = 1;

指定中断请求类型

  • 通过TCON(Timer Config)寄存器的低4位指定中断请求的类型

  • TCON寄存器是可以位寻址的

  • TCON寄存器从第4位到第1位分别是:IE1IT1IE0IT0

    • IT0:设置外部中断0的请求类型为低电平请求(0)或下降沿请求(1)
    • IT1:设置外部中断1的请求类型为低电平请求(0)或下降沿请求(1)
    • IE0:外部中断0的使能信号。当外部中断0接收到中断信号后,CPU会自动将IE0设置为1;当外部中断0的中断信号处理结束后,CPU会自动将IE0设置为0
    • IE1:外部中断1的使能信号。当外部中断1接收到中断信号后,CPU会自动将IE1设置为1;当外部中断0的中断信号处理结束后,CPU会自动将IE1设置为0
  • 中断按键:将一个普通按键,一端接GND,另一端接INT0或INT1

中断请求类型设置为低电平请求(一个时刻)
  • 这种方式如果中断按键按下时间过长,会导致中断处理函数执行多次
1
IT0 = 0;
中断请求类型设置为下降沿请求(一个瞬间)
  • 这种方式如果中断按键按下出现抖动,会导致中断处理函数执行多次
1
IT0 = 1;

指定中断处理函数

  • 无参数列表
  • 无返回值
  • 无需声明,通常放在整个程序最后
  • 函数名自定义
  • 必须指定中断向量号
  • 中断处理函数不能手动调用

中断向量:用于控制中断的内存地址
中断向量号:用于标记中断的编号,方便编程

interrupt <num>:指定中断向量号,取值范围为0~4

0:外部中断0
1:定时/计数器中断0
2:外部中断1
3:定时/计数器中断1
4:串行终端

1
2
3
4
void int0(void) interrupt <num>
{
...
}
阻止中断的接收
  • 当一个中断处理函数正在执行时,阻止中断的接收
  • 阻止中断的接收可以用来消除抖动
1
2
3
4
5
6
7
8
9
void int0(void) __interrupt 0
{
// 阻止中断的接收
EX0 = 0;
// 业务代码
...
// 还原中断的接收
EX0 = 1;
}

完整代码示例

  • 利用外部中断0实现中断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<8052.h>

void init()
{
// 指定开启外部中断0
IE = 0x81;
// 指定外部中断0的终端请求类型为下降沿请求
IT0 = 1;
}

void main(void)
{
init();
while (1)
{
...
}
}

// 指定外部中断0的中断函数
void int0(void) __interrupt 0
{
// 阻止中断的接收
EX0 = 0;
// 业务代码
...
// 还原中断的接收
EX0 = 1;
}

定时/计数器中断

  • 在标准51单片机上有2个16位的定时/计数器

  • 计数值:计数的总次数

  • 计数初值:开始计数的起点

  • 计数最大值:可以计数的上限

    • 51单片机做多可以计16位的数

定时/计数器的计数方式

  • 减计数:从计数初值(计数值)开始减到0。x86的PC使用的就是减计数的计数器
  • 加计数:从计数初值(最大值-计数值)开始加到计数器能力最大值。标准51单片机使用的就是加计数的计数器

定时/计数器的计数内容

  • 定时器:数内部的时基(时间基准)
  • 计数器:数外部的脉冲信号周期(每个周期一高一低)

定时/计数器的时间计算

  • 时钟周期:1个时钟周期 = 1 / 晶振的频率
    • 如果51单片机接了一个12MHz的晶振,那么时钟周期为1/12μs
  • 机器周期:1个机器周期 = 12 * 时钟周期
    • 如果51单片机接了一个12MHz的晶振,那么机器周期为1μs

STC12C5A60S2使用计数器中断

  1. 开中断
  2. 指定模式
  3. 写入计数初值
  4. 开启定时/计数器
  5. 指定中断处理函数:当检测到中断请求后,立即执行中断处理函数

指定模式

  • 通过TMOD(Timer Model)寄存器指定模式
  • TMOD寄存器是不可以位寻址的
  • TMOD寄存器从第4位到第1位是用来设置定时/计数器0的,分别是:GATE、C/TM1M0
  • TMOD寄存器从第8位到第5位是用来设置定时/计数器1的,分别是:GATE、C/TM1M0
    • GATE:门控信号,用来指定定时/计数器的开关的方式,1表示硬件开启,0表示软件开启
    • C/T:1表示工作在计数模式,0表示工作在定时模式
    • M1:用来设置工作方式的高位
    • M0:用来设置工作方式的低位
模式 M1 M0 寄存器位数 TH寄存器功能 TL寄存器功能 特点
工作方式0 0 0 13位 - - 为了向下兼容
工作方式1 0 1 16位 存放计数初值的高位 存放计数初值的低位 只能手动载入初值
工作方式2 1 0 8位 存放计数初值的备份 存放计数初值 可以自动载入初值
工作方式3 1 1 8位 作为定时器 作为计数器 可以同时作为定时器和计数器的寄存器

写入计数初值

  • 用2个初值寄存器存放初值,最大可以存放16位
    • 定时/计数器0:高8位用TH0寄存器,低8位用TL0寄存器
    • 定时/计数器1:高8位用TH1寄存器,低8位用TL1寄存器

开启定时/计数器

  • 通过TCON(Timer Config)寄存器的高4位指定定时/计数器的开启
  • TCON寄存器从第8位到第5位分别是:TF1TR1TF0TR0
    • TR0:定时/计数器0的软开(1)关(0)
    • TR1:定时/计数器1的软开(1)关(0)
    • TF0:定时/计数器的计数溢出标识,标识计数是(1)否(0)已完成
    • TF1:定时/计数器的计数溢出标识,标识计数是(1)否(0)已完成

完整代码示例

  • 通过按键,接收脉冲信号,累计5次开始中断

    • 在计数模式下,需要利用T0T1引脚来接收脉冲信号,初始是高电平
    • 可以将T0T1引脚接按键,实现发出脉冲信号
  • 定时/计数器完成计数后自动中断

  • 进入中断后溢出标识会自动置0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<8052.h>

void init()
{
// 指定开启定时计数器0中断
IE = 0x82;
// 为定时/计数器0指定模式
// 指定门控信号为软件开启
// 指定工作在计数模式
// 指定工作方式1
TMOD = 0x05;
// 为定时/计数器0指定计数初值
TH0 = (65536 - 5) / 256;
TL0 = (65536 - 5) % 256;
// 为定时/计数器0开启计数
TR0 = 1;
}

void main(void)
{
init();
while (1)
{

}
}

void timer0(void) __interrupt 1
{
// 阻止计数
TR0 = 0;

// 业务代码
...

// 重新指定计数初值
TH0 = (65536 - 5) / 256;
TL0 = (65536 - 5) % 256;
// 恢复计数
TR0 = 1;
}

STC12C5A60S2使用定时器中断

  1. 开中断
  2. 指定模式
  3. 写入计数初值
  4. 开启定时/计数器
  5. 指定中断处理函数:当检测到中断请求后,立即执行中断处理函数

完整代码示例

小于等于65536μs的定时
  • 在定时时实际上是通过晶振的振动频率来数数,以达到定时的效果,12MHz的晶振机器周期为1μs

  • 由于在标准51单片机上有2个16位的定时/计数器,没个定时/计数器最大只能存储65536个数,所以每次计时只能累计65536μs

  • 设置一个50ms的定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<8052.h>

void init()
{
// 指定开启定时计数器0中断
IE = 0x82;
// 为定时/计数器0指定模式
// 指定门控信号为软件开启
// 指定工作在定时模式
// 指定工作方式1
TMOD = 0x01;
// 为定时/计数器0指定时间初值(1ms=1000次)
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256;
// 为定时/计数器0开启计数
TR0 = 1;
}

void main(void)
{
init();
while (1)
{

}
}

void timer0(void) __interrupt 1
{
// 阻止计数
TR0 = 0;

// 业务代码
...

// 重新指定计数初值
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256;
// 恢复计数
TR0 = 1;
}
大于65536μs的定时
  • 虽然每次计时最多只能累计65536μs,但是通过多次计时能实现更多时间的累计,其中每次计时的时间为时间基值

  • 设置一个1s的定时器

    • 通过在时间基值为50ms的定时器,累计定时20次,实现1s的定时器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<8052.h>

void init()
{
// 指定开启定时计数器0中断
IE = 0x82;
// 为定时/计数器0指定模式
// 指定门控信号为软件开启
// 指定工作在定时模式
// 指定工作方式1
TMOD = 0x01;
// 为定时/计数器0指定时间初值(1ms=1000次)
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256;
// 为定时/计数器0开启计数
TR0 = 1;
}

void main(void)
{
init();
while (1)
{

}
}

void timer0(void) __interrupt 1
{
// 累加器
static unsigned char i = 0;

// 阻止计数
TR0 = 0;

if (++i == 20)
{
// 业务代码
...

// 累加器置0
i = 0;
}

// 重新指定计数初值
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256;
// 恢复计数
TR0 = 1;
}
只改变标识
  • 如果一个定时器需要反复使用,在定时器完成定时后,不立即执行业务代码,而是改为只改变预先设定的标识,这样能减小定时器的误差
  • 通过bit关键字可以定义一个布尔值,只能存放1或0,bit不能用unsigned关键字修饰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<8052.h>

bit flag = 0;

void init()
{
// 指定开启定时计数器0中断
IE = 0x82;
// 为定时/计数器0指定模式
// 指定门控信号为软件开启
// 指定工作在定时模式
// 指定工作方式1
TMOD = 0x01;
// 为定时/计数器0指定时间初值(1ms=1000次)
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256;
// 为定时/计数器0开启计数
TR0 = 1;
}

void main(void)
{
init();
while (1)
{
// 当标识位为1时,表示定时器完成定时
if (flag)
{
// 业务代码
...
// 标识位置0
flag = 0;
}
}
}

void timer0(void) __interrupt 1
{
// 累加器
static unsigned char i = 0;

// 阻止计数
TR0 = 0;

if (++i == 20)
{
// 标识位置1
flag = 1;
// 累加器置0
i = 0;
}

// 重新指定计数初值
TH0 = (65536 - 50000) / 256;
TL0 = (65536 - 50000) % 256;
// 恢复计数
TR0 = 1;
}

总结

中断函数的中断向量号总结

中断向量号 中断类型
0 外部中断0
1 定时/计数器中断0
2 外部中断1
3 定时/计数器中断1
4 串行终端

寄存器总结

寄存器名 用来定义的功能 第8位 第7位 第6位 第5位 第4位 第3位 第2位 第1位
IE 寄存器的总开关 EA - - ES ET1 EX1 ET0 EX0
TCON 外部中断的使能信号、请求类型 - - - - IE1 IT1 IE0 IT0
TCON 定时计数器的溢出标识、软开关 TF1 TR1 TF0 TR0 - - - -
TMOD 定时计数器0的开关方式、定时/计数模式、工作方式 - - - - GATE C/T M1 M0
TMOD 定时计数器1的开关方式、定时/计数模式、工作方式 GATE C/T M1 M0 - - - -
TH1 定时计数器1的计数初值高8位 - - - - - - - -
TL1 定时计数器1的计数初值低8位 - - - - - - - -
TH0 定时计数器0的计数初值高8位 - - - - - - - -
TL0 定时计数器0的计数初值低8位 - - - - - - - -

EA:中断总开(1)关(0)
ES:串行中断的开(1)关(0)
ET1:定时/计数器1的开(1)关(0)
EX1:外部中断1的开(1)关(0)
ET0:定时/计数器0的开(1)关(0)
EX0:外部中断0的开(1)关(0)
IE1:外部中断1的使能信号。当外部中断1接收到中断信号后,CPU会自动将IE1设置为1;当外部中断0的中断信号处理结束后,CPU会自动将IE1设置为0
IT1:设置外部中断1的请求类型为低电平请求(0)或下降沿请求(1)
IE0:外部中断0的使能信号。当外部中断0接收到中断信号后,CPU会自动将IE0设置为1;当外部中断0的中断信号处理结束后,CPU会自动将IE0设置为0
IT0:设置外部中断0的请求类型为低电平请求(0)或下降沿请求(1)
TF1:定时/计数器的计数溢出标识,标识计数是(1)否(0)已完成
TR1:定时/计数器1的软开(1)关(0)
TF0:定时/计数器的计数溢出标识,标识计数是(1)否(0)已完成
TR0:定时/计数器0的软开(1)关(0)
GATE:门控信号,用来指定定时/计数器的开关的方式,1表示硬件开启,0表示软件开启
C/T:1表示工作在计数模式,0表示工作在定时模式
M1:用来设置工作方式的高位
M0:用来设置工作方式的低位

定时计数器工作方式总结

模式 M1 M0 寄存器位数 TH寄存器功能 TL寄存器功能 特点
工作方式0 0 0 13位 - - 为了向下兼容
工作方式1 0 1 16位 存放计数初值的高位 存放计数初值的低位 只能手动载入初值
工作方式2 1 0 8位 存放计数初值的备份 存放计数初值 可以自动载入初值
工作方式3 1 1 8位 作为定时器 作为计数器 可以同时作为定时器和计数器的寄存器

完成