当前课程知识点:智能车制作:嵌入式系统 > 第三章 MCU基础 > 3.5 中断子程的概念和编程 > Video
各位同学大家好
我是清华大学工程物理系的曾鸣老师
欢迎大家继续回到我们
ARM微控制器与嵌入式系统的MOOC课堂
我们继续进行第三章基础知识当中
关于中断这样一个难点的学习
在上一个单元里
我们讲了讲
什么是中断什么是轮询
给大家留下了一个初步的概念
有同学听了可能会比较晕
没有关系
讲了讲中断最重要的几个概念
它是由硬件和电路
通知CPU发生的约定好的外部事件
而CPU去保留好一个现场的条件下
打断现有程序的运行
去执行一段中断程序
最终它运行完毕后
好像什么事都没有发生一样
还原这个现场回到主程序的运行
因为它具备这样一种保存现场
还原现场的能力
所以呼应了中断可以在任何时候
由外部的信号
来产生和打断主程序这样一种特点
注意它是发生在任意时刻
那么我们这个单元来看看
究竟刚才讲了半天的
这个针对一个中断的中断子程
有什么特点怎么编写
应该注意什么东西
展开了讲详细的定义
中断子程 SR的缩写
一般叫做Interrupt Service Routine
那么我们有时候
说它是一个子程
有时候说它是一个function
对于C语言编程它就是一个特殊的函数
我们呆会会讲
那么先树立几个基本的概念
对于一个CPU对于一个微控制器
其实我们会有若干种不同的中断源
我们按了一个键
收到了一个通讯
都是不同的
通知CPU的可能的中断信号
在一个CPU里
根据我们的编程的需要
我们可能会设置
允许这当中一种
或者几种中断源通知CPU
或者允许它们的发生
而每一种中断源的发生
根据我们所要实现的功能
我们应该都对于他对应的
有相应的中断服务子程
来完成这样一件事情发生后所应该做的处理
比如说按键发生了
我们应该点个灯通讯来了
我们应该做个回复
这都是中断子程一一对应
应该所做的事情
那么从编程的角度来讲
这样一种中断服务子程在一些CPU里
它的特点是
可能跟C语言的一个普通函数会略有区别
而在我们ARM Cortex M的处理器上
它的编写方式在我们开发环境里
就是普通的C语言函数
那么为什么会有这种差异
是因为在有一些CPU上面
中断服务子程的返回指令在编译的时候
会以普通函数的返回指令是不同的指令
所以必须在C语言编程上告知编译器
这是一个中断服务函数
比如使用Interrupt关键字
而在我们的ARM Cortex M的处理器上面
它用了一种非常巧妙的机制
在中断发生的时候
会在Link Register R14里保存一个特殊值
从而在使用一个普通的函数返回指令的时候
CPU根据返回的这个的Link Register里的值
知晓自己是一个中断返回
从而比普通的C语言函数的返回
做那些中断所需要的特殊的操作是哪些操作
就是上一个单元我们所讲到的
把堆栈里那一大堆保存的寄存器的值
逐一弹出去还原那个现场对不对
大家想一想普通的函数返回
只弹出一个返回地址
而如果中断返回
要把整个CPU里绝大多数计算器的值给还原
这是它们的差异
所以 规而简之
在我们ARM的微处理器
ARM Cortex M里我们的中断子程
就是个C语言函数
虽然它只是一个C语言函数
在编程的时候
它还有一些共同的特殊性
或者说我们做为C语言
已经懂了的同学
在学习的时候一定要注意的特点
第一个特点
中断服务子程
虽然被写成了一个C语言函数
但是它从来不在我们的程序主程序代码当中
由我们人写的代码来加以调用
我不知道大家明不明白这件事
我换一句话来说
比如说我们设定好一个按键的中断
约定好这个中断是通知CPU可以发生的中断
而这个中断一旦发生人按了键
我们约定好它需要执行的功能
是点亮一盏灯
那么我们一定会设置按键中断发生
并且写一个函数
它的内容就是点亮一盏灯
那这个函数就是中断服务函数
但是这个函数一旦写完
它会有函数名称
这个函数按照正常来讲
我们不应该在
我们的主程序当中的main函数
或者任何位置去调用它
那有的同学会说
这不有问题吗
我们学C语言的时候
写的函数就一定是要调用的
如果写了函数从来没有被调用
这个函数就是个废函数是多余的
这就是中断理解的一个难点
因为中断的工作机制是
我们在我们的主程序里
设置好一个中断发生的条件
比如 打开全局中断允许按键中断发生
并且设定好按键中断
哪一个影响 哪一个中断
可以触发中断
这样一旦设定好以后
我们按了键 按键通过电路
通过电路的连接
以信号的方式通知CPU
这个中断发生了
而CPU在任意时刻获知这个中断发生
会自动的去调用
我们写好的一个相应的函数
来完成这个中断的处理
所以在这个流程当中
这个函数并不是在我们的代码里被调用的
并不是我们通过写它的函数名来调用的
它是由于一个外部的中断事件
直接通知CPU 被CPU自动调用的
所以这是它的第一个特征
它是自动调用不由程序代码来调用
另外一个特征刚才讲过很多次的
CPU每次调用这个函数的时候
与普通的C语言函数不一样
它会做大量的保留现场的入栈和出栈的操作
那么为什么要做这样一种操作
就呼应到了它的第三个特点
也就是说
这样的一个函数一旦中断的使能
中断的总开关被打开后
它可能在任意一个时间结点上被调用
这件事情有个难点
我在这里先讲大家试着去理解
我们编程里头所写的每一个函数
一旦写好
我调用它就会在我的编程语言的
某一句某一行去调用它
所以从程序的流程上来讲
它调用的时间点是确定的
所以在编译的时候
就会确定它使用那些寄存器
不使用那些寄存器
而中断的函数
我们一旦加以编写
它由硬件来调用
对于我们主程序main函数
比方说 这样一个流程来讲
它可能在任意一条语句之间
任意一条指令之间
由于外部事件的发生而被调用
所以它必须能够具备保留现场的条件
前面说过
那反过来讲
这样一种中断函数的特点
就是它可能会在任意时间点上
自动的被调用
而不由我们编程人员决定
而是某种上由用户
由我们程序的通讯
由我们程序的运行来决定
最后一条
就这样一种函数大家会发现
因为它不被我们主程序流程所调用
是不是它的参数和返回值
都是void
你会发现他是个
void类型 void参数的函数
因为它不被主程序调用
没有程序可以向它传递参数
它也不必向任何程序传递返回值
所以它必然是一个
void类型
void返回值的特殊函数
那么最后在这个函数的编写当中
待会儿会讲到
我们会注意中断的标志位
如果允许中断的嵌套
或者中断的下一次发生
我们可能要清除标志位
然后我们会考虑在中断里
是否要关闭或者保持中断的总开关
是打开的
那么这是一些高阶的内容
那么大家直接会想到的另外一个问题
就是说如果在一个CPU里头
我设置好了一个中断能发生能通知CPU
我又写好了一些函数
能够跟这些中断呼应
处理它所对应的功能
比如说按键和点灯
我怎么告诉CPU
这些函数与这些中断信号之间的对应关系
或者更编程的语言讲
当一个信号发生
通知CPU的时候
CPU如何正确的找到这个函数去加以执行
这会一个新的概念
就是中断向量表
中断向量表是一段在CPU里头
保留的连续的存储空间
那么这个时候
大家又浮现出前面几课讲过这个概念
有很多格子
每个格子里有个地址
每个格子里可以存储一些值
那么中断向量表在上电复位后
在CPU里头的一个默认地址
比如在我们的ARM Cortex M的处理器里头
就在它最前面的地址
就是00的这个地址段里头
那么每一个中断源
每一个能够通知CPU发生中断
不管是按键还是通讯
还是定时都会拥有自己
在这个表里头的一个表项
这是在CPU设计的时候就设计好了的
而每一个表项在我们的中断向量表里
就是一个格子
那么这个格子里头
这个表项决定了这个格子对应哪个中断源
这个格子里存储的值
应该是我们所对应的这样一个
服务函数的入口地址
大家注意函数的入口地址是什么
是个指针式指向函数的第一条指令的地址
如果C语言基础好的同学
我再拔高一点讲
函数的函数名在C语言里头
就是指向函数的第一条指令的地址的指针
没看明白的同学可以仔细的再想一想
那么所以我们把我们所对应的
函数的入口地址
这样一个指针填在这样一个中断向量表里
中断向量表的每一个表象
对应了不同的中断源
表里填的内容是函数的入口地址
所以CPU得到通知的时候
只要根据发生中断源的项去查这张表
找到这个入口地址
跳转过去给PC指针寄存器
是不是程序就去执行这样一个函数了
这就是中断向量表
那么如果我们打开我们的芯片手册
比如说打开我们所学习的
大家还记不记得学习的那一个型号
MKL25这样一个型号的MCU
打开它的手册
比如说我们翻到它的第52页
就能看到这样一张中断向量表
是由芯片设计厂商所提供的
那么这张向量表里
对于每一个中断源
包括CPU内部错误所处理的这个叫异常源
都给了一个表项
每一个表项
从00地址开始04 08开始往下码
一共在这张表里有48个表项
每个表项占4个字节
可以存储一个32比特的地址
所以我们把每一种中断所发生
对应要处理的函数的入口地址填在这里
CPU就能自动地找到对应的
中断函数加以调用和处理
那么对于这个表项的事情
我们日后再展开讲
那么我们再来讲两个拔高的概念
一个是中断的优先级
那么在多个中断
如果同时出现CPU怎么办呢
它都得响应对不对
所以会有个优先级的概念
高优先级的中断优先得到响应
那么如果中断的优先级
在有些CPU里头是固定的
比如最简单的像12
它就以中断在这个表里的顺序号作为优先级
越往前的越优先
而我们所学习的ARM Cortex M处理器呢
优先级是可以设置的
用两个比特
最多可以给中断设置四种不同的优先级
谁设置的优先级高
谁就优先得到响应
而如果优先级相同的中断一起发生了怎么办呢
就按先后顺序来处理
那么有了优先级的概念
还会有一个中断的概念
是大家在学习当中
要加以理解的就是中断的嵌套
那么中断在一次中断响应
正在执行这个中断函数的过程中间
如果又发生了一个中断怎么办呢
这会有两种情况
如果新发生的中断优先级
没有当前的中断的优先级高
那显然要把现在的中断这个函数处理完
等它执行完了
再去处理刚才新发生的这个中断
而万一新发生的中断
比现在正在执行的中断优先级更高
而中断又是开着的
就是全局中断总开关是开着的
那么势必会打断当前中断
跳转到一个新的中断里去执行
那么抽象出来呢
就是像这张图所示
主程序执行的过程当中发生一次中断
压栈保留一个现场执行中断函数
中断函数没有执行完
又来了一个更高优先级的中断
只能再次打断它
再保留一个CPU的现场
再占一部分堆栈去执行更高优先级的中断
而退出的时候依次退出和还原现场
那么这就是中断的嵌套
那么所以我们可以非常容易想到的两件事情
一件事情是这样的一种中断的嵌套
是不是会对内存有比较大的开销
所以我们在允许和使用中断嵌套的时候
要想想我内存有多少
否则又会发生我们之前讲堆栈的时候
讲到的堆栈溢出的风险
另外一个呢
就是说我们中断既然要发生嵌套
就势必意味着优先级越来越高
而由于我们ARM Cortex M处理器
其实对于中断只有四个优先级
即使加上前面那些CPU的内核异常
最终也只有七种异常级别
所以嵌套的层数其实是有限的
不可能发生特别多层次的嵌套
那么理解了中断的优先级
中断的嵌套这样一些高阶的概念
有点晕的同学没关系
大家可以反复地理解几次
并在日后的实践中不断地加深认识
我要给大家讲最后一个问题
就是中断的潜在风险
或者叫中断之间交换数据的一种机制
大家会想我写了一个main函数
这个main函数里头
我会做很多操作会有很多变量
如果执行了一个中断函数
如果这个中断函数执行的功能
只是点个灯那么简单
那比较OK
假设说我有一个中断函数
它里头要做些数据的处理
比如说我要记我这个人按键按了多少次
我每按一下调用中断
要把这个次数+1
请问这个次数
应该如何与main函数交换它的这个值呢
很多同学会想到
我要用全局变量对吧
那么有的同学从C语言的感觉上来讲
我声明一个全局变量
哪个函数都能用
所以它们就可以共享这个变量的值
就可以实现数据的交换了
这个想法是对的
高阶一点的同学
听过我们这门课前面几个单元的同学
应该会想到什么
全局变量在堆上对不对
在内存的顶端
是中断函数和主函数都可以访问的一个地址
那么全局变量的使用
会带来中断的一个潜在风险
就是我这段例程
比如说我生一个全局变量
我们同学编程有一个不太好的习惯
变量随便起名字i j k a b c对吧
我声明了一个全局变量
我在主函数里
拿着这个全局变量
当一个流水号去while循环
把一个字符串 一个一个字符给输出来
只要这个全局变量
还比如说大于0我就继续打印这个字符
结果我在中断函数的时候忘了
我也用了这个全局变量
我拿这个全局变量干一个别的功能
里头有一句代码是
这个全局变量++中断每发生一次
这个全局变量的值就会+1
这个时候就呼应刚才我讲过的所有基本概念
大家来想一想
这个中断函数不会在main函数的任何地方
被我们的程序代码调用
而是被我们所设定的那个外部功能
不管是按键还是通讯
跟这个中断函数对应的这个中断源
自动地随时地随机地通知CPU去调用
所以很有可能它在任意位置
打断我们的main函数
比如在我们这个for循环
这个while循环里头
那它一旦发生
是不是在这个while循环里头
被打断了执行了这个中断函数
回来对于main函数
以为什么事情都没有发生
但是要内存里头全局变量的值变了
我们的A这个变量可能++ 多了1
最后我们会发现我们输出这个字符串
这个hello少了个字母
或者多了个字母对不对啊
这就是中断函数使用全局变量所能带来的风险
而它的背后就是这个单元我给大家所讲的
我们现在可能无法深入地理解
中断究竟怎么编程
但是一定要留下的一个基本概念是中断的机制
它是一个独立的为外部源所触发的函数
快速实施响应的一些事情
并且它由硬件的通知
可能在任意位置打断我们的main函数
依赖于堆栈来保留这个现场
但是内存里头的全局变量
是不会被恢复和改写的
所以我们对它的使用必须做到心中有数
那么这两个单元合起来呢
就是一个中断的基本概念
有很多同学
我相信听完这两个单元
会觉得特别的难以理解和困难
但是没有关系
整个嵌入式学习都是动手一起来
就会简单一百倍
在我们的第四第五个章节
我会带着大家实际地从IO和定时器
来真正地做一两个中断的编程
做完了大家就会更加地豁然开朗
会发现其实我弄懂了
-1.1 课程概览
--Video
-1.2 进入嵌入式系统的世界
--Video
-1.3 如何学好嵌入式系统
--Video
-2.1 计算机的基本概念、发展历史
--Video
-2.2 从晶体管到CPU
--Video
-2.3 概念CPU、微控制器MCU和嵌入式系统
--Video
-2.4 八卦计算机史
--Video
-2.5 不同领域、不同系列的嵌入式系统
--Video
-2.6 ARM历史与MKL25Z128 MCU
--Video
-3.1 CPU的基本结构和运行机制
--Video
-3.2.1 堆栈的概念
--Video
-3.2.2 堆栈的概念-头脑体操
--Video
-3.3.1 ARM的体系结构
--Video
-3.3.2 ARM的体系结构-头脑体操
--Video
-3.4 中断的概念和机制
--Video
-3.5 中断子程的概念和编程
--Video
-3.6 复位、时钟、存储器和总线
--Video
-3.7 小结:MCU的总体结构和程序运行机制
--Video
-4.1 第一种外设:IO
--Video
-4.2 IO外设的编程实操-点亮LED
--Video
-4.3 IO外设的进阶知识
--Video
-4.4 嵌入式开发的基本概念与工具链
--Video
-4.5 嵌入式开发的进阶知识
--Video
-4.6 嵌入式开发中的C语言(上)
--Video
-4.7 嵌入式开发中的C语言(下)
--Video
-E0.1 实验零 开发板的初步认识与工具链的安装
--Video
-E0.2 实验零 体验一个例程的编译与下载
--Video
-E0.3 实验零 编写第一个程序:点亮核心板LED
--Video
-E1 实验一 点灯秘籍
--Video
-5 智能车视角的嵌入式设计
--Video