很高兴你能走到这一步,与我的设想差不多,进入到课程中期将会筛选掉至少一半的人,祝贺你,存活到了现在。假如此章学习个差不多,你的51单片机就完成了70%的学习内容了。我可以告诉你的是,这也是学校大三一个学期(共计16周每周两节课的教学内容的70% (所以说上课没用,你用了一周时间就啃完了这些))
增加延迟之后
还记得我们S2.3用的流水灯代码吗,不知道也没关系。我重新贴给你,而且这回我们将把nothing函数称为delay函数,且增大他的延迟。
#include <reg51.h>
#include <intrins.h>
// sbit 用于标定单个io,方便记号
sbit key = P1^0;
unsigned char state = 0xf8;
void delay(){
unsigned int i,j = 0;
for(i=0;i<300;i++)
for(j=0;j<300;j++);
}
void main(){
P0 = state; //提供初始值
while(1){
//程序陷入while死循环 一直检测执行while里的代码
state = _crol_(state, 1); //循环移位
P0 = state;
delay();
}
}
现在我们将在 P2.0 添加一个绿色的led灯,那么现在的电路图就是这样的。

现在,我们想要在流水灯运行的过程当中按下按钮切换这颗绿色灯的闪烁,你可能会把代码改成这样。
#include <reg51.h>
#include <intrins.h>
// sbit 用于标定单个io,方便记号
sbit key = P3^2;
sbit greenLed = P2^0;
unsigned char state = 0xf8;
void delay(){
unsigned int i,j = 0;
for(i=0;i<300;i++)
for(j=0;j<300;j++);
}
void main(){
P0 = state; //提供初始值
key = 1;
greenLed = 1;
while(1){
//程序陷入while死循环 一直检测执行while里的代码
state = _crol_(state, 1); //循环移位
P0 = state;
delay();
if(key == 0){
greenLed = ~greenLed;
key = 1;
delay();
}
}
}
现在你发现,当按键按下时有时候能够切换新增led的亮灭,有时又会失效。而当你快速点击时,这个效果更加明显。你今天的第一个任务就是想,这是什么原因造成的?
中断
那么,我们该如何避免这种问题呢?我们不得不引入单片机中一个 十分重要的概念 – 中断。
也许你还记得上一章作业示例的其中一部分
//你可以认为计数器计满后运行一下这个函数
void Timer0_ISR(void) interrupt 1 {
TH0 = 0x79; // 将初值置回
TL0 = 0xA0;
TF0 = 0; // 清除溢出标志
if(toggle) {
P0 = 0xFF; // 设定P0输出高电平
toggle = 0;
} else {
P0 = 0x00; // 设定P0输出低电平
toggle = 1;
}
}
在这个函数后出现了 interrupt 1 ,这就是我们这期想要讲解的内容、单片机的中断。
在刚才的示例中,我们发现按下按键 “有时有用,有时不好用” 这是因为单片机执行程序时是一条一条执行的。(这与你的多核电脑并不一样)假如你按下按键的那一个时刻碰巧一直没运行到 if(key == 0) ,那自然就是什么什么都没有发生了。换句话说,当你使用 delay() 时,是占用cpu资源做无意义计算来实现的延迟,在这个期间你按下按钮自然就没有反应了。
但假如你把按键设定为中断后你就会发现,无论你何时按下按钮,单片机都会立刻响应且不会出错。这就是我们需要中断的原因,中断就是优先处理高优先级的事情。

你可以简单地认为你在玩电脑,当你的妈妈叫你来吃饭时你将游戏暂停,先优先完成吃饭的任务后,再继续进行游戏。
中断寄存器
中断相关的寄存器我们简单介绍下面的:
EA: 全局中断使能位。设置为1时允许所有已启用的中断;设置为0则禁止所有中断。
ES: 串行口中断使能位。
ET1: 定时器/计数器1中断使能位。 如果使用定时/计数器,则拉高他
EX1: 外部中断1使能位。如果使用外部引脚,则拉高他
ET0: 定时器/计数器0中断使能位。
EX0: 外部中断0使能位。
还记得我们上一期中的寄存器设置吗,我想现在你应该能看懂了
/*可以暂时忽略这两个寄存器设置*/
ET0 = 1; // 使能定时器0中断
EA = 1; // 开启总中断
中断的序号
现在我们知道了中断是什么意思了,也知道如何设置中断寄存器了,但后面的 interrupt 1后面的 “1” 是什么呢?这就是中断的信号,我们要给中断分配他对应的序号,例如 1号就是对应定时器0的中断信号。具体如下表:
中断序号0 表示的是外部中断0的中断序号
中断序号1 表示的是定时计数器0的中断序号
中断序号2 表示的是外部中断1的中断序号
中断序号3 表示的是定时计数器1的中断序号
中断序号4 表示的是串行口的中断序号
中断序号5 表示的是定时计数器2的中断序号
对于之前的定时器0,我们使用的是interrupt 1, 假如我们想通过中断对应引脚(P3.2 P3.3)来触发外部中断,就需要使用 interrupt 0 与 interrupt 2了。
示例
电路图如下

示例代码
#include <reg51.h>
#include <intrins.h>
sbit greenLed = P2^0;
unsigned char state = 0xf8;
void delay(){
unsigned int i,j = 0;
for(i=0;i<300;i++)
for(j=0;j<300;j++);
}
// 外部中断0对应中断序号0
void changeLED() interrupt 0{
greenLed = ~greenLed;
delay();
}
void main(){
EA = 1; //开启总中断
EX0 = 1; //开启中断0的外部触发
P0 = state; //提供初始值
greenLed = 1;
while(1){
//程序陷入while死循环 一直检测执行while里的代码
state = _crol_(state, 1); //循环移位
P0 = state;
delay();
}
}
我们发现,无论如何点击按键,高优先级的切换绿色灯泡一直都会生效,同时流水灯也能正常运行。如果快速点击,也是先进行绿色灯切换,而蓝色流水灯会暂停。
你知道吗,两个中断有着不同的优先级,终端彼此之间也存在嵌套关系,也就是中断的中断。这作为课外内容需要你自己了解。
作业
单片机没有多线程,要想类似并行实现任务基本都靠中断。现在我们用如上的电路图设计一个儿童玩具。
我需要你将其改成默认流水灯从上至下移动,当按钮按下后变为从下至上运动,当按钮再次按下,所有灯泡变成闪烁状态,在你完成后将视频上传到实验室邮箱(<姓名> - 中断作业)。