单片机CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理(中断发生);CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务);待CPU将事件B处理完毕后,再回到原来事件A被中断的地方继续处理事件A(中断返回),这一过程称为中断。
例如
当你正在洗衣时,突然手机响了(中断发生),你暂时中断洗衣的工作,转去接电话(中断响应和中断服务),待你接完后,再回来继续洗衣(中断返回),这一过程就是中断。
单片机中断分为内部中断和外部中断两大类,外部中断由单片机外部设备产生,中断产生后通过单片机的外部管脚传递给单片机,传递这个中断信号最简单的方法就是规定单片机的管脚在什么状态下有外部中断产生,这样单片机通常是有一个或多个IO口当在输入状态时可以用来检测外部中断信号。有外部中断产生的条件通常也就是这五种:IO口输入为高、IO口输入为低、IO口输入由高变为低、IO口输入由低变为高、IO口输入由高变低或者由低变高。
一个连接到单片机的外部设备,如果想要使用单片机的外部中断,就必须在自己请求单片机中断响应的时候给单片机提供单片机在这五种信号中所支持的类型来触发单片机中断。程序运转中,一个中断不是只产生一次,一般都会间隔持续产生,这五种外部中断触发信号前四种都有一个问题,就是外设发出请求中断信号后如果信号请求线状态不改变,外设会无法向单片机提供下一次中断请求信号。让我们来看看以单片机和外部设备采用负跳变触发中断为例的触发情况。
外部设备以负跳变触发单片机中断,第一次中断请求外部设备的中断请求输出脚可以从高变低,触发单片机中断,第一次中断请求发生后中断请求脚保持输出低,外部设备无法产生第二次中断的触发负跳变信号。
图1 外设只能产生一次中断请求信号示意图
将外部设备的中断请求信号做出修改,原来为需要中断时只是输出从高到低变化,现在改为输出先从高变到低,经过一小段时间后自己从低变回高,这样就可以每次需要中断时都能向单片机输出负跳变触发信号。
图2 外设可连续产生中断请求信号示意图一
或者是由外部设备提供某种接口,单片机通过该接口可以对外部设备进行中断清除操作,中断清除操作可以让外部设备的中断请求输出脚恢复到高。
图3 外设可连续产生中断请求信号示意图二
外部中断触发还有一些特殊方式,比如外部脉冲宽度测量、外部脉冲计数等,这些方式都是在前面几种基本触发方式上进行功能扩展得来的,外部脉冲宽度测量就是当中断信号线跳变时会启动内部一个计时器,到下一次中断信号线跳变时通过计时器得到脉冲宽度并重新启动计时器,这些方式很少会使用到,不做详述。
内部中断是指单片机内部的功能模块产生中断信号,只要是单片机内部在CPU外围能独立工作的功能模块都会提供中断功能,常见的内部中断类型有时钟Timer、串口UART、模数转换ADC等。内部中断的工作流程和外部中断没太多区别,只是中断请求信号是在单片机内部进行传输,中断信号不是管脚上的电平状态,而是一个寄存器里面的相应标志位,通常当某个内部中断产生中断请求时就会将相应标志位置为1,CPU响应中断时将这个标志位清0。
图4 内部中断触发示意图
单片机对中断标志位的处理方法没有统一标准,具体的约定方法要看单片机文档。大部分是标志位为1有中断产生,但有少数单片机是标志位为0有中断产生;有的单片机对中断标志位是CPU写入什么就是给改写成什么,有的则是规定必须通过写1或写0来实现清除操作,还有少数只要读一下中断标志位就会自动清除掉该标志位。如果单片机不想被外部中断触发,大不了将用于连接外部中断触发信号的管脚接成不会触发中断的电压状态就可以,但内部中断无法去改变内部连线,所以单片机为了可以选择中断是否可以被除法,在其内部会有相关的寄存器来进行选择,通过里面的控制标志位开发人员可以根据实际情况决定是否使用中断。通常单片机里面有一个总控制位,这个位可以控制所有中断的开与关,然后每一种中断自己还有一个独立的控制位决定自己的开与关,如果想使用某个中断,就需要将总中断开关和对应中断的开关都打开。
当单片机有中断信号产生时,就会触发对应中断,不同的中断源会需要不同的响应方法,也就是说不同的中断产生的时候,需要单片机程序依照不同的中断源做出不同的响应,这就是中断服务程序。如果是UART收到新数据产生中断,应该是UART中断服务程序将数据读回来并做处理,如果是ADC转换完成产生的中断,需要的则是ADC中断服务程序将数据读回来并做处理。如果需要清中断标志位动作,一般都是在中断服务程序里面完成。
不同的中断源需要与之对应的中断服务程序,实际开发中并不是所有的中断都会被用到,开发人员为了节约程序代码空间会只写出自己要使用到的中断服务程序,也就是说会有一些中断没有与之对应的中断服务程序,如果触发了这样的中断,单片机程序会运行出错,前面中断各自独立的控制位就排上用场,将这些控制位关掉,相应中断就不会被触发。
单片机开始上电的时候,如果控制中断是否被打开的寄存器控制标志位被打开,可能会出现中断被误触发的情况,而这个中断如果没有与之相对应的中断服务程序的话程序就会跑飞,所以单片机上电的时候一般会自动将这些寄存器里面的标志位都关掉,以免误触发。
中断服务程序是单片机程序的一部分,具体内容由开发人员决定,这样中断服务程序的大小在单片机程序中的位置就不固定,当单片机的中断被触发后,单片机需要知道中断服务程序在什么位置才能执行它,单片机通过中断跳转表(中断向量表)来解决这个问题。
虽然中断服务程序的大小和在整个程序中的位置会不固定,但程序只要被烧进单片机系统,对于这个程序来说其中断服务程序的大小和在整个程序中的位置就会被固定下来,如果对单片机程序空间分配我们做出一些约定,将一个绝对固定地址专门分配给中断使用,程序编译时会将中断服务程序的起始地址(或者是跳转到中断服务程序的指令)填到这个绝对固定地址所在的空间,当中断产生时候,单片机先将绝对固定地址所在位置里面的内容读出,根据所读内容就可以跳转到中断服务程序。
图5 中断响应示意图
简单的单片机所提供的中断种类有限,为了简化程序,会给每一个中断分配一个用来存放中断服务程序地址的地址空间,这种方法其实没什么不好的地方,只是单片机技术发展到现在遇到了瓶颈,高端单片机越来越复杂,于是一些专业厂商开始合作共享技术资源,例如ARM公司利用他们在CPU架构体系上的技术优势专门给另外的厂商提供CPU内核,另外的厂商在ARM内核的CPU外围增加功能模块,这些功能模块大都支持中断。
图6 ARM内核单片机架构图
不同厂家在相同CPU内核基础上设计出来的单片机外围的功能模块会各不相同,从而中断的种类和个数也各不相同,而CPU处理中断的方法是一样的,如果延续简单的单片机给每个中断都分配一个地址空间的做法显然有问题,CPU无法知道到底有多少种中断需要支持,这些中断又分别对应什么模块,于是采用另外一种中断处理方法,将所有中断地址都指向同一个,并将所有中断依次编号,中断产生时候CPU会告诉中断服务程序当前中断编号是多少,然后中断服务程序根据中断编号做出相应响应。
图7 公用中断入口中断响应流程图
图8 独立中断入口中断响应流程图
所有中断使用同一个中断向量地址然后通过中断号判断中断类别的方法虽然解决了通用CPU内核中断不能直接对应中断向量地址的问题,但把它中断处理的流程和具有独立中断向量表的单片机相比就会发现中断的响应速度会变慢。具有独立中断向量表的单片机只要一条跳转指令就可以直接进入中断程序,而没有独立中断向量表的单片机需要先跳转到中断公共入口,然后通过代码判定中断类别,确定中断类别后才跳转到真正的中断程序中去。C语言的代码会让这种情况更加恶化,所以如果是没有独立中断向量表的单片机一般采用汇编查表的方法加快中断响应速度。
图9 汇编中断快速跳转表
中断程序执行完毕后回返回继续执行主程序,这样就要求中断不改变主程序的运行状态,所以中断响应时需要将程序当前运行的状态信息保存起来,比如程序运行到什么位置、当前CPU状态寄存器的状态等信息。当中断程序执行完毕,可以通过这些信息将CPU状态寄存器恢复原来状态,并能返回原程序继续执行。不同的单片机对此的处理方式也会有不同,一种是完全由硬件来完成,并不需要程序来进行管理;另外一种是将状态信息用相应指令保存在特定位置,返回时再用相应指令恢复原来状态。
单片机中断还有中断优先级和中断嵌套的概念,但不是所有的单片机都会支持这两种功能。中断优先级是不同的中断会有不同的优先级别,如果同时有两个中断产生,单片机会先响应优先级高的中断。中断嵌套是指在中断响应当中又有新的中断产生,单片机可以暂停当前的中断程序执行去响应新的中断,新中断程序执行完以后在接着执行当前中断程序。一般中断嵌套是高优先级的中断可以插入低优先级中断响应程序,同级或低级的中断不能插入当前中断响应程序。
图10 中断嵌套示意图
中断步骤说明:
步骤①保存主程序现场,执行中断1服务程序
步骤②保存中断1服务程序现场,执行中断2服务程序
步骤③恢复中断1服务程序现场,继续执行中断1服务程序
步骤④恢复主程序现场,准备继续执行主程序,有新中断不能继续执行主程序
步骤⑤保存主程序现场,执行中断3服务程序
步骤⑥恢复主程序现场,准备继续执行主程序,有新中断不能继续执行主程序
步骤⑦保存主程序现场,执行中断4服务程序
步骤⑧恢复主程序现场,无中断产生继续执行主程序
有的单片机一进入中断函数就会自动将中断的总控制位关掉,需要开发人员在中断程序中用程序再次打开,否则一次中断后所有的中断就不能继续使用。对于中断标志位,在写单片机程序的时候要依据单片机文档进行清除标志为操作,不然有可能会一旦产生某个中断就会连续不停的反复响应这个中断,导致主程序不能继续运行。