中断使得高低速设备可以协调工作(低速设备完成工作后通过中断的方式通知高速设备一次处理一批数据),中断还可以根据不同的优先级实现嵌套执行。
定时器本质上是个 16 位的自增计数器,当发生溢出时,如果开启了溢出中断,单片机会自动向 CPU 报告这个溢出中断,处理相应的中断任务。
寄存器
TCON 定时器控制寄存器
TCON 的低 4 位用作外部中断,高 4 位用作定时控制。地址是 88H。
TCON 定时器控制寄存器各个位的意义如下:
所在位 bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
名称 | TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
- IT0:外部中断0触发方式。1为低电平触发,0为下降沿触发。
- IE0:外部中断0请求标志位。IE0=1 时表示有中断请求,0则没有。
- IT1:外部中断1触发方式。1为低电平,0为下降沿信号。
- IE1:外部中断1请求标志位。IE0=1 时表示有中断请求,0则没有。
- TR0:定时器/计数器0启动停止控制位。1为启动,0为停止。
- TF0:定时器/计数器0溢出标志位。1表示发生溢出,如果开启了中断,则会触发中断。
- TR1:定时器/计数器1启动停止控制位。1为启动,0为停止。
- TF1:定时器/计数器1溢出标志位。1表示发生溢出,如果开启了中断,则会触发中断。
IE 中断允许控制寄存器
CPU 对中断源的开启或屏蔽的控制,是通过 IE 寄存器来设置的,IE 既可按字节地址寻址(其字节地址为 A8H),又可按位寻址。某个中断对应的位设为 1 则表示允许中断,否则禁止。
IE 寄存器各个位的意义
IE 中断允许控制寄存器各个位的意义如下:
所在位 bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
名称 | EA | - | ET2 | ES | ET1 | EX1 | ET0 | EX0 |
- EX0:外部中断 0 中断允许位
- ET0:定时器/计数器 0 中断允许位
- EX1:外部中断 1 中断允许位
- ET1:定时器/计数器 1 中断允许位
- ES:串口中断允许位
- ET2:定时器/计数器 2 中断允许位(进行52系列)
- EA:中断总开关
定时器工作在中断方式时,当定时器的值计满溢出时,会触发定时器溢出中断。
C 语言示例
只要想使用中断,就必须开启 EA 总中断。例如,如果想使用定时器/计数器0,需要添加下面一段 Keil C51 代码来开启 EA 和 ET0:
EA = 1; // 开启总中断
ET0 = 1; // 开启定时器/计数器0 中断
或者使用字节操作:
IE |= 0x82; // 设置 IE 寄存器为 10000010,即开启总中断和定时器/计数器0中断
汇编语言示例
如果使用汇编语言,开启外部中断 0 的汇编代码,字节操作为:
MOV IE,#81H
;MOV 0A8H,#81H; 这里也可以直接使用 IE 寄存器的地址 A8H
或者使用汇编语言的位操作:
SETB EA
SETB EX0
TMOD 定时器工作模式寄存器
TMOD 用于控制定时器的工作模式,低4位用于 T0,高4位用于 T1。各个位的意义如下:
所在位 bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
名称 | GATE | C/T | M1 | M0 | GATE | C/T | M1 | M0 |
- M0 与 M1:共 4 中组合,对应定时器的 4 中工作模式
- M1 = 0,M0 = 0:模式0,13 位,最大计数范围 8192。TL 的低 5 位和 TH 的高 8 位组成 13 位计数器,用于兼容 48 系列一般不用。
- M1 = 0,M0 = 1:模式1,16 位,最大计数范围 65536
- M1 = 1,M0 = 0:模式2,8 位,最大计数范围 256。高 8 位放预置数,只有低 8 位参与计数。计数溢出后可以自动重新装填预置数,定时精度高。可以用于波特率发生器等精确计时场合。
- M1 = 1,M0 = 1:模式3,8 位,最大计数范围 256。此时 T0 被拆成 2 个独立的定时/计数器。其中 TL0 可以用作 8 位的定时/计数器,TH0 只能用于定时器。TH0 的控制及溢出标志借用 T1 的。一般仅当 T1 工作在模式 2 时,才会让 T0 工作在模式 3。
- C/T:设置为 0 则作为定时器使用,设置为 1 则成为计数器
- GATE:计数脉冲与定时/计数器之间的开关。
- GATE = 0 时,开关仅由 TR0 控制,TR0 = 1 时计数脉冲可以通过,否则无法通过
- GATE = 1 时,开关由 TR0 和 INT0 同时控制,仅在 TR0 = 1 且 INT0 高电平时,计数脉冲才可以通过
IP 中断优先级控制寄存器
所在位 bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
名称 | - | - | PT2 | PS | PT1 | PX1 | PT0 | PX0 |
SCON 串行口控制寄存器
所在位 bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
名称 | SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |
- SM0 和 SM1:串行口方式选择
SM0 | SM1 | 方式 | 说明 | 波特率 |
---|---|---|---|---|
0 | 0 | 0 | 8位数据发送 | fosc/12 |
0 | 1 | 1 | 10位数据发送,包括起始位,停止位 | 可变 |
1 | 0 | 2 | 11位数据发送,包括起始位,停止位 ,校验位 | fosc/64 |
1 | 1 | 3 | 同方式2 |
- SM2:多机通信使能位。在方式2或方式3中,若SM2=1,则只有当接收到的第9位数据(RB8)为1时,才能将接收到的数据送入SBUF,并使接收中断标志RI置位向CPU申请中断,否则数据丢失;若SM2=0,则不论接收到的第9位数据为1还是为0,都将会把前8位数据装入SBUF中,并使接收中断标志RI置位向CPU申请中断。在方式1,如SM2=1,则只有收到有效的停止位时才会使RI置位。在方式0时,SM2必须为0。
- REN:串口数据接收允许位,1允许,0禁止
- TB8:在方式2和方式3中,这位发送的是第9位。在多机通信中,常以该位的状态来表示主机发送的是地址还是数据。通常规定:TB8为“0”表示主机发送的是数据,为“1”表示发送的是地址。
- RB8:在方式2和方式3中,这位发送的是第9位。它和SM2、TB8一起用于通信控制。
- TI:发送中断标志位 ,用完时要用软件清0
- RI:接受中断标志位,用完时要用软件清0
中断
中断源
51单片机有5个中断源,5个中断源分别是:
- 外部中断0,从 P3.2 端口复用
- 外部中断1,从 P3.3 端口复用
- 定时/计数器0溢出中断
- 定时/计数器1溢出中断
- 串口发送或接收中断
中断可以根据优先级实现嵌套,51 系列可以实现 2 级嵌套(对应优先级寄存器 IP),52 系列可以实现 4 级嵌套(对应优先级寄存器 IP 和 IPH)。
中断对应信息
中断名称 | 中断标志位 | 中断号 | 默认优先级 | 中断入口地址 |
---|---|---|---|---|
外部中断0 | IE0 | 0 | 高 | 0003H |
定时/计数器0溢出中断 | TF0 | 2 | ↓ | 000BH |
外部中断1 | IE1 | 1 | ↓ | 0013H |
定时/计数器1溢出中断 | TF1 | 3 | ↓ | 001BH |
串口发送或接收中断 | RI/TI | 4 | 低 | 0023H |
中断处理流程
- 停止主程序运行
- 保护断点,把程序计数器 PC 的值压入堆栈
- 寻找中断入口,每个中断都有不同的程序入口
- 执行中断处理程序
- 中断返回,继续执行主程序
中断的使用
任何中断的使用都要满足 3 个条件:
- 开启总开关:EA
EX = 1
- 开启指定中断的开关,例如要使用外部中断0,则必须设置:
EX0=1
- 发生中断事件
中断系统有一个总的开关 EA(IE 寄存器中),如果想使用中断,必须打开总开关。
每个中断都有一个单独的开关,这些单独的开关跟总开关 EA 一样,都在 IE 寄存器中。
定时器中断使用
初始值的计算
假设我要每计数 24 次触发一次溢出中断,定时器工作在模式 1,则计数初始值为 65536 - 24 = 65512。
Keil C51 代码
外部中断示例代码
下面代码使用了外部中断0,上电后 P1 端口 0 号引脚的 LED 会一直闪烁,首次触发外部中断时,P1 端口所有 LED 点亮,再次触发外部中断时,0号引脚的 LED 再次开始闪烁,以此循环:
#include <reg52.h>
int flag = 1;
void delay() {
unsigned int a = 50000;
while(a--);
}
void main() {
EA = 1;// 开启中断总开关
IT0 = 1;// 设置外部中断0触发方式,下降沿触发
EX0 = 1;// 开启外部中断0
while(1) {
while(flag & 0x01 == 1) {
P1 = 0xfe;
delay();
P1 = 0xff;
delay();
}
P1 = 0x00;
}
}
void externelInterrupt() interrupt 0 {
flag++;
}
电路图:
串口示例代码
/*9600@11.0592M*/
#include <reg52.h>
void InitUART(void) {
TMOD = 0x20;
SCON = 0x50;
TH1 = 0xFD;
TL1 = TH1;
PCON = 0x00;
EA = 1;
ES = 1;
TR1 = 1;
}
void SendOneByte(unsigned char c) {
SBUF = c;
while(!TI);
TI = 0;
}
void main(void) {
InitUART();
}
void UARTInterrupt(void) interrupt 4 {
if(RI) {
RI = 0;
} else
TI = 0;
}