当前课程知识点:智能车制作:嵌入式系统 >  第三章 MCU基础 >  3.5 中断子程的概念和编程 >  Video

返回《智能车制作:嵌入式系统》慕课在线视频课程列表

Video在线视频

Video

下一节:Video

返回《智能车制作:嵌入式系统》慕课在线视频列表

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

第三章 MCU基础

-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

第四章 MCU外设与开发

-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

第五章 MCU与嵌入式系统设计

-5 智能车视角的嵌入式设计

--Video

Video笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。