当前课程知识点:ARM微控制器与嵌入式系统 > 第五章 ARM微控制器的各种外设 > 5.3.2 ARM微控制器外设:IO的中断编程(下) > 5.3.2 ARM微控制器外设:IO的中断编程(下)
各位同学大家好
我是清华大学工程物理系的曾鸣老师
欢迎大家继续回到我们
ARM微控制器与嵌入式系统MOOC课堂
那么我们在这个单元里呢
继续挑战我们外设学习当中第一次的中断编程
我们仍然是拿最简单一类外设IO的中断来开刀
那么在前半个单元
前半个部分呢
我们花了很多很多口舌带着大家来回忆
中断是怎么样一种机制
然后这个中断是怎么工作起来的
以及中断向量表如何打开总开关
如何打开子开关等等这些内容
那么在这个单元
我们接着进行我们编程开发的剩下三个步骤
来真真正正把这个程序给写出来
在这个过程当中我带着大家
继续回忆和梳理加深理解
对于中断的这样一种程序的
运行机制和编程模式
这是我们学习的一个难点
但是大家学会了一定会觉得受益无穷
那么第二个步骤呢
就是把我们对应的中断源给配置好
就是 Configure the corresponding Interrupt Source
那么我们在第一个步骤打开了中断的总开关
那就不区分是哪个中断
同时打开了中断的子开关
如果大家认真回忆我们打开的那个子开关呢
是PORTA的子开关
也就是RORTA这个模块
这个引脚单元的中断源的子开关
我们已经打开了
换言之我们接下来应该配置PORTA
可是最多能管32个引脚哦
PTA0到PTA31
究竟哪一个引脚在什么情况下
可以向CPU产生中断呢
这是需要配置的
所以接下来我们要进行这样一个配置
对于每个引脚的配置
我不知道大家还有没有深刻的印象
我们都会有一个专门的寄存器
之前做IO编程的时候用过很多次
就是PORTx_PCRn这个寄存器
这个寄存器是每一个物理引脚
对应都有一个独立的寄存器
比如PTA1对应的就是PORTA_PCR1这个寄存器
我们之前对它的所有编程
都是对于这个寄存器下面这几个比特
特别是比特8、9、10这三个比特设值
设成000到111的这八个值来确定
这个引脚给哪个功能用
比如说我们一般会配置成001当IO用对不对
学UART的时候
我们会配置成UART这个串行通讯模块
所要的功能的值
这是我们做的一件事
那么在实验1的时候
我们如果对照电路图把这个引脚
希望配置成一个按键电路
我们还多配置了一位呢是这个比特1
也就是0X000...02这个配置
把这一位置1
那么这一位一旦置1
我们启用了这个IO的上拉电阻
那么对应我们的电路图呢
就是片内有一个上拉电阻
片外是一个接地的按键
于是当这个键不按的话
在上拉电阻作用下我们输入的时候
读到的逻辑1
键按下读到的是逻辑0
所以我们当时给的语句是PORTA_PCR1
也就是PTA这个引脚接了一个按键
我们赋了一个值
等于0X0102
就是给IO用
给上拉电阻
不知道大家回忆起来没有
那么涉及到中断源
一个PORTA我们设置了中断子开关打开
我们希望其中比如说按键PORTA_PTA1这个引脚
对应的按键成为我们的中断源
每次键按下告诉CPU发生了一个中断
我们应该怎么配置呢
在这个寄存器的上面几个比特
进行进一步的编程
比特16、17、18、19这四个比特
它们的值可以是0000到1111这16个值
那么其中1000、1001、1010和1111这四个值
都是将这个引脚配置成可以产生中断
而这四个值对应的是这个引脚
在发生什么样的电平变化的时候产生中断
如果我们读一下我们的电路我们会发现
我们的键平时是悬着的
内部上拉电阻作用下引脚读到的值
是逻辑高电平逻辑1
3.3伏
键按下的时候与地短路
我们引脚读到的值变成了0伏
也就是逻辑0
由1向0的跳变
是我们键按下的一瞬间
所以如果我们把这四个比特的值
配置成1010这个组合
也就是按照芯片手册第183页告诉我们的含义
那么把这个引脚配置成了
发生由1到0的下降沿的时候可以触发中断
那反过来有的同学说
老师 那我希望是这个键每次按下去不作用
一撒手发生作用
很简单
改这四个值
改成刚才这张表里的
由0到1上升沿的时候产生中断
就可以了对不对
所以如果我们完成这个中断源的配置
把某一个引脚配置成可以产生中断
就是PORTA_PCR1赋值这个语句稍微改一改
变成等于0X0A0102
是不是就配置成了下降沿可以产生中断
同时当IO用
并且启动上拉电阻
是不是跟我们电路就契合了
这就完成了这样一个中断源的配置
那么多说一句的是
如果大家仔细再看这个寄存器
我打出红框的比特24这一个比特
这个比特是我们这个引脚
对应的这个中断发生以后的中断标志位
中断标志位我们称为flag
也就是刚才我们配置这件事情一旦发生
它就置位产生1
这个1与我们中断总开关的开关的允许
大家记不记得大大的与号
三个条件都满足的时候
就会向CPU打出一个中断
那么换言之从操作上来讲
每当一个中断响应完了
我们应该清除这个中断标志位
以便在下一个按键
下一次跳变沿的时候
能够正确的再一次触发中断
这叫清除中断标志位
怎么清
大家看这个芯片手册这个图上
写了一个小小的字符串
对这一位的解释显示W1C
就是write one to clear
也就是你对它写一次1
就可以清掉标志位
也就是我们对这个比特要置一次1
怎么置1啊
把它跟有1这个比特或一下对不对
所以这是如何清中断标志位
在这个地方我们讲了两件事
如何配这个引脚成为中断源
以及日后操作当中如何清除中断标志位
所以把它对照我们的电路图大家就会发现
我们一共有八个引脚都接在PORTA上
比如说我们把第一个引脚PTA1这个IO
配置成我们的中断源
回头希望这个按键按下的时候
能够向我们的CPU发出一个中断请求
告诉CPU一件事发生了
也就是执行我们刚才所说的这样一个配置语句
放到我们主程序里大家会发现
就是比刚才的汇编语言打开总开关
C语言对NVIC寄存器置位打开子开关
又加了一句PORTA的PCR1等于0X0102
下降沿产生中断
设置为IO用和上拉电阻
我不再重复
那么这样我们就完成了前两个步骤
打开中断的总开关和配置这个中断这个
大大的PORTA能用的32个引脚里头
究竟哪一个或者哪几个引脚
可以以什么方式触发中断
那么外设具备 开关打开了
这个中断的条件也具备了
是不是这件事情一旦发生
这个键一旦被按下去
就会有一个请求从电路上打开我们的CPU
CPU收到这么一个请求
那么接下来两个编程大家回顾我们的思想
就是CPU在完全硬件的触发下
它得到这个请求
它应该能够正确的去调用一个函数
执行我们希望这个键按下以后所做的操作
也就是找到对应的corresponding的
ISR中断服务子程序
那么这我们的第三个步骤
那么这中断服务子程序怎么写呢
我们来回顾一下基本概念
在讲中断的时候我们讲
中断服务子程序是跟每一个中断源
每一个中断请求所对应的
要做的那样一个操作
我们把它写成一个函数
这个函数我们可以用C语言来写
特别是在ARM Cortex M0的CPU上
它跟普通的C语言函数没有任何区别
因为CPU在设计的时候
对于中断的返回指令该做哪些操作
已经自己在CPU的机制上加以了处理
在return指令的某一个比特上加以了标示
所以我们对于C语言
ARM的C语言编写的时候非常简单
就是写成了一个void的参数
void的返回值的这样一种函数类型就可以了
那么对于编程者大家要理解的是
这一个函数
是由我们前面设置的中断条件满足的时候
由CPU自动调用的
所以它写好了以后放在那里
它任何时候都不在我们main函数里被调用
所以它是在CPU条件符合的时候自动调用
这个概念大家一定要强
后面我们看整个程序
还会带着大家梳理这个概念
那么这个函数怎么写呢
它的原型就像我们这么说的
一个空的参数
void参数
一个void返回值的函数
里头要做do something
也就是我们这个中断发生了我们希望它做的事
最后记得清除中断标志位
以便下一次中断的发生
那写到我们这个IO的这个函数里头
非常清晰
PORTA如果发生了一次中断
这个中断源我们已经配置好了
是某一个或者某几个按键
能够触发这个中断
那么这个函数怎么写
在这样一个函数void类型
void的返回值里头
首先判断一下PORTA的PDIR寄存器
跟某个值与一下来判断
是哪一个按键按了引起了PORTA这个中断
比如说我们现在只配置了一个引脚
能够产生中断
但我们仍然进来做一次检查
检查完了确实是这个按键触发了中断
我们就做跟它对应的操作
什么操作呢
注意我们把bMode这个全局变量
在这里仍然取了下反
这是一个在堆上的全局变量
在中断函数里感觉它的值在main函数里
能够看得到它的变化对不对
然后我们把原来main函数里做的那个操作
把两个灯的状态进行一次归零
是归成两个灯同亮同灭
还是归成一亮一灭
是不是就对灯的闪烁模式进行了切换呢
那么在主程序那边它怎么做
我们待会看整个程序会再看
最后这个事做完了
每次按键切换了灯的闪烁模式在退出之前
我们是不是要清一下对应的中断标志位
也就是我们原来所说的PORTA_PCR1寄存器
我们要把它跟第24比特那个1或一下
对它写一次1
把它标志位清掉
这就是一个中断函数的写法
那么做完这件事
我们就完成了我们的第三个步骤
接下来 我们应该看的是第四个步骤
如何把这样一个中断函数
写好的函数跟我们的中断向量表关联起来
让我们CPU
能够在对应的中断发生的时候找到它
以及把刚才讲的所有内容关联起来
让大家理解这个中断函数究竟是怎么工作的
这是第四个步骤
那么在这个步骤里头我们再回忆一下
中断向量表是一段连续的存储空间
你可以把它认为是一个由指针构成的表
每一个指针都指向一个函数的入口地址
所以每当一个中断发生的时候
CPU就到这张表里头去读一个指针的值
从而使PC寄存器
跳转到一个对应的函数的入口地址
开始执行对应的函数操作
执行这个中断对应所需要做的那些操作
那么如何填表呢
其实我们中断向量表一共有48项
前16项是ARM公司设计的内部的异常
后面32项就是我们刚才所讲的从0到31
32个外设中断
那么我们如果再回忆一下
我们用到的PORTA这个中断
在外设这32个中断里排位第几号呢
我们前面做中断的这个子开关打开的时候
查中断表已经做过
PORTA是第30号
也就是第倒数第二项
那么换言之
我们就应该在这张表的倒数第二项
那四个字节这个表项里
填入我们刚才所写的这个函数的入口地址
第一条指令的地址
这样CPU在这个对应的中断发生了之后
到这个表里
把它的值搂出来
加载给PC指针
从而跳转到那个地方进行执行
这件事情说起来很清晰
很多同学听了很多基本概念以后
已经知道是什么含义
仍然不知道怎么做对不对
那么CodeWarrior帮我们做了很多基础的操作
如果我们绕开了这样一个简单的赋值
和对flash里头某一个地址填值的
这样一个操作的话
在CodeWarrior里头非常简单
我们新建一个project
指明了我们用的芯片型号的时候
如果大家打开CodeWarrior生成的代码的
最左边那个列
会有一个文件叫kinetis_sysinit.c
大家往下拉会发现CodeWarrior
自动的根据我们的型号
生成这么一个数据结构
这个数据结构里头是一系列函数的名称
构成的一个结构体
那么我们大家一定从C语言要记住这个概念是
跟数组一样
函数的名称也是一个指针
它的值就是函数的第一条指令
所以这个结构体里头
每一个函数名就是每一个函数的入口地址
所以我们顺着往下找
找到倒数第二发现这个函数名称已经填在这儿了
就是PORTA_IRQHandler这么一个函数名称
那么我们有两种做法
可以把我们刚才写的函数
跟这个地址关联起来
第一种这就是一个空函数
我们写我们的函数的时候
就把我们函数起成这个名字就可以了
它在编译的时候会自动的链接在一起
第二种就是我们给我们的中断函数
刚才写了一个不同的名字
我们将我们写的函数名
替换到这个结构体里然后加以编译和build
那么就回头我们函数入口地址
就会自动的填写到中断向量表这个位置
那么如果我们把刚才所有东西串联起来
得到的代码就是个屏幕所示
我们对于中断的这次编程
不是单独的一次实验
而是大家自己根据代码把它编写出来加以体验
体会它运行的机制
所以这里我多说两句
我们在我们的main函数里
完成了原来所有的打开时钟配置引脚的IO用
以及配置引脚的初始方向和值这些操作
增加了三句话
一句话是打开中断的总开关
一句话是打开中断PORTA的子开关
一句话是把PORTA里头的某一个
或者以后的某几个引脚
配置成在什么条件下
比如在下降沿下能够产生中断
就这么三句话
那么我们把我们对应的
中断源按键按下的操作拎出来
成了一个中断子函数大家看一看
是PORTA_IRQHandler这个函数
那么这个函数是void参数 void的返回值
并且这个函数在我们的main函数里头
任何时候都没有被调用
那么我们的main函数里头最后的for循环
只剩下了delay和让PORTA反转灯闪烁
那么大家一定要脑袋想清楚这样一种
中断工作的机制
用一句话或者用几句话来加以描述就是
我们设置好了一种中断触发的条件
并且让它可以发生就是这个键被按
一旦键被按一个指令
一个硬件的信号打给了CPU
CPU根据中断向量表
自动去调用IRQHandler这个函数
切换完了我们灯闪烁的基础模式
同时清理下中断标志位
以便下一次按键能够响应
这个函数就退出了
继续回到我们的主程序执行
而在主程序里for循环变得非常简单
只是延时闪烁
延时闪烁
换言之我们的中断函数
并不是被main函数调用
是在按键的时候
在人的操作下CPU自动调用
它可能发生在main函数这个循环体
无休无止的循环当中的任意时刻
而对我们灯的闪烁模式完成切换
所以这样一种中断的工作模式万变不离其宗
回到最开始大家想想最开始那张打电话的图
我们的主程序就是你正在做作业
正在做你的工作
而我们的中断就是一个拨进来的电话
每当拨进来一个电话你接起的时候
CPU切换了一下灯的闪烁模式
挂掉电话主程序继续周而复始的闪烁
这样的一种模式
使我们的程序变得可靠又简单
它从编程上好像难以理解
但是从程序的执行乃至结构
乃至可读性
乃至日后的维护上
都得到了极大的提升
所以通过这两个单元的学习
我相信大家第一次掌握了
如何真真正正编写一个中断程序
如果大家理解了
是不是觉得它不是那么难
如果大家没有理解没有关系
我们在下面的几个单元
还会有定时器等等模式
再让大家反复加深
对于中断这样一个难点理解
今天这节课就到这儿
-1.1 课程概览
--1.1 课程概览
-1.2 如何学好嵌入式系统
-2.1 计算机的基本概念、发展历史
-2.2 从晶体管到CPU
-2.3 概念CPU、微控制器MCU和嵌入式系统
-2.4 八卦计算机史
-2.5 不同领域、不同系列的嵌入式系统
-2.6 ARM历史与MKL25Z128 MCU
--2.6 ARM历史与MKL25Z128 MCU【习题】
-3.1 CPU的基本结构和运行机制
-3.2.1 堆栈的概念
--3.2.1 堆栈的概念【习题】
-3.2.2 堆栈的概念-头脑体操
-3.3.1 ARM的体系结构
--3.3.1 ARM的体系结构【习题】
-3.3.2 ARM的体系结构-头脑体操
-3.4.1 中断的概念和机制
-3.4.2 中断子程的概念和编程
-3.5 复位、时钟、存储器和总线
--3.5 复位、时钟、存储器和总线【习题】
-3.6 小结:MCU的总体结构和程序运行机制
--3.6 小结:MCU的总体结构和程序运行机制【习题】
-4.1 第一种外设:IO
-4.2 IO外设的编程实操-点亮LED
-4.3 IO外设的进阶知识
-4.4 嵌入式开发的基本概念与工具链
-4.5 嵌入式开发的进阶知识
-4.6 嵌入式开发中的C语言(上)
--4.6 嵌入式开发中的C语言(上)【习题】
-4.7 嵌入式开发中的C语言(下)
--4.7 嵌入式开发中的C语言(下)【习题】
-E0.1 实验零 开发板的初步认识与工具链的安装
-E0.2 实验零 体验一个例程的编译与下载
-E0.3 实验零 编写第一个程序:点亮核心板LED
-E1 实验一 点灯秘籍
-5.1 ARM微控制器外设学习概述
-5.2.1 ARM微控制器外设:通讯
-5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)
--5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)
--5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)【习题】
-5.2.3 ARM微控制器外设:异步串行通讯UART的原理(下)
--5.2.3 ARM微控制器的外设:异步串行通讯UART的原理(下)
--5.2.3 ARM微控制器外设:异步串行通讯UART的原理(下)【习题】
-5.2.4 ARM微控制器外设:RS-232串口与USB虚拟串口
--5.2.4 ARM微控制器外设:RS-232串口与USB虚拟串口
-5.2.5 ARM微控制器外设:UART的寄存器编程(上)
--5.2.5 ARM微控制器外设:UART的寄存器编程(上)
-5.2.6 ARM微控制器外设:UART的寄存器编程(下)
--5.2.6 ARM微控制器外设:UART的寄存器编程(下)
--5.2.6 ARM微控制器外设:UART的寄存器编程(下)【习题】
-E2 实验二 UART编程实操
-5.3.1 ARM微控制器外设:IO的中断编程(上)
-5.3.2 ARM微控制器外设:IO的中断编程(下)
-5.4.1 ARM微控制器外设:定时器的原理
-5.4.2 ARM微控制器外设:定时器的编程
--5.4.2 ARM微控制器外设:定时器的编程【习题】
-E3 实验三 定时器中断编程实操
-5.5.1 ARM微控制器外设:PWM的原理
-5.5.2 ARM微控制器外设:PWM寄存器与编程
-5.5.3 ARM微控制器外设:PWM编程实例—电子音乐
--5.5.3 ARM微控制器外设:PWM编程实例—电子音乐
-E4 实验四 数码管显示编程实操
-5.6.1 ARM微控制器外设:SPI通讯简介
--5.6.1 ARM微控制器外设:SPI通讯简介【习题】
-5.6.2 ARM微控制器外设:SPI寄存器与编程
-5.6.3 ARM微控制器外设:SPI编程实例—OLED显示屏驱动
--5.6.3 ARM微控制器外设:SPI编程实例—OLED显示屏驱动
-5.7.1 ARM微控制器外设:I2C通讯简介
-5.7.2 ARM微控制器外设:I2C的通讯协议
-5.7.3 ARM微控制器外设:I2C寄存器与编程
--5.7.3 ARM微控制器外设:I2C寄存器与编程【习题】
-5.7.4 ARM微控制器外设:I2C编程实例—加速度传感器
--5.7.4 ARM微控制器外设:I2C编程实例—加速度传感器
-5.8.1 ARM微控制器外设:ADC简介
-5.8.2 ARM微控制器外设:ADC基础
-5.8.3 ARM微控制器外设:ADC寄存器与编程
-E5 实验五 ADC编程实操
-E6 挑战实验
--E6 挑战实验
-6.1 嵌入式系统的接口与设计
-6.2 嵌入式系统的实例