当前课程知识点:ARM微控制器与嵌入式系统 > 第五章 ARM微控制器的各种外设 > 5.4.2 ARM微控制器外设:定时器的编程 > 5.4.2 ARM微控制器外设:定时器的编程
各位同学大家好
我是清华大学工程物理系的曾鸣老师
欢迎大家回到
ARM微控制器与嵌入式的MOOC课堂
我们在这个单元里呢
接着进行定时器
特别是Sys Tick这样一类
在ARM微控制器里头
最最简单的定时器的编程和实现
前面讲了它的原理
和它最最简单那三四个寄存器
其实大家会已经发现这个单元
没有想象中那么难
但是难点在于
我们第二次进行一个中断程序的开发
我希望通过这样一次又一次的挑战
怎么来做中断
来加深大家对于这样一种编程思想的理解
那么回到我们的任务
我们如果把Sys Tick这个模块用起来
写一个跟它对应的定时的中断程序
来改进我们刚才那个按键
改变灯闪烁的程序
要几个步骤呢
其实从程序的改造或者程序编写的角度来讲
还是跟那个按键做中断一样是四个步骤
头两个步骤把这个中断的开关打开
把这个中断的中断源配置好
就是配置好它的条件
比如放在定时器多长时间产生一次中断
那么这件事我们在这里用一个部分来讲
接下来呢
是如何写它的中断服务程序
如果把它填到中断向量表
更重要的是
我们最后会拎出来给大家讲
经过这样一种改造我们的程序
究竟工作在了怎样一种模式和思想下
先看前两个步骤
把中断打开
也包括几件事
首先是要把中断的总开关打开
这个在上一个章节里头我们已经讲过
中断的总开关打开就是CPU内核的
一个寄存器的一个比特
PM这个比特置位
它对应的就还是那一条汇编指令
所以我们这里就不重复讲了
然后中断的子开关打开
当我们在用IO的PORTA这个中断的时候
它是一个外部的中断
也就是在IRQ 0到31
32个外部中断里
所以它的开关在NVIC内核模块当中的
那个一个寄存器32个比特当中的一个比特
而我们现在这个章节所使用的Sys Tick
是ARM公司设计的ARM内核的一个特有的中断
或者一个interception
所以它是在我们中断向量表里头
0到15那前16个
是我们称为exception的那16个内核中断
或者叫exception当中的一个
所以它的子开关不再像原来那样
在NVIC模块里
而是就在我们的Sys Tick模块的寄存器里
在哪呢
就是上一个单元我们讲的
Control status register里头那一个比特打开中断
就是它的子开关了
所以我们对于它的中断进行打开
其实呢
就只用中断总开关的汇编语言指令
和中断子开关的
Sys Tick寄存器置位enable就可以了
那么第二个问题说我们那个步骤二
如何配置好这个时钟源呢
其实在上个单元讲完简简单单三个寄存器
给了个步骤
已经用英语或者用人类的语言
告诉了我们这三件事应该怎么做
那我现在的问题是
它的寄存器的值应该赋成多少呢
是不是大家得做个简单计算
我们的时钟源用的core clock
也就是20.97兆
那如果我们希望得到一个每0.5秒
发生一次中断的时钟
我们应该如何设置
我们这三个寄存器当中的那个
reload value的值对不对
那么肯定要用20.97兆怎么着啊
乘或者除这个0.5秒
算一算寄存器的计数值
应该设成多少
也就20.97兆的频率
数数数多少个是0.5秒
而它的误差就是20.97兆分之一
那也就是那个误差差不多是50个纳秒
我不知道大家想明白这件事没有
那么另外我要提醒大家注意的一件事是
如果你算出这么一个值
想要设给我们的寄存器
请你在设置之前keep in mind脑袋当中
想一想这件事
就是我们这个寄存器
我们这个计数器是多少比特的
是24比特的
也就是它最多你能给它赋值
能够赋上去的就是24比特二进制数
多于他的就没有发生作用
数数数到24比特的时候就溢出归零
所以你算出来的你要设的这个值
你是不是应该自己把它换成二进制看看
有没有超过24个比特
换言之你要定时的这个长度
是不是这个定时器这个timer直接可以实现的
这是大家一定要注意的
比如下次我让大家把这个定时间隔设成5秒
设成10秒
你再算一算可不可以做得到
清楚了这件事
将我刚才用人类的语言所讲的这些内容
转换成我们的代码
添加到我们的main函数里头
我们来看看我们的main函数程序
在main函数的开始部分跟原来一样
仍然是把对应的引脚所用到的时钟打开
引脚指定为IO用
指定好IO的方向和高低电平
然后在上一个章节里我们教大家
把IO工作在中断模式的时候
把中断的总开关打开
把IO的中断配置好
然后配置好对应的那一个按键的引脚
在什么情况下可以产生中断
这是左下角的这个红代码给大家提示的
我们添加这样子右上角这三个
红色的这三行
是不是我们刚才所讲的
对于Sys Tick这样一种典型timer的设置
那么这件事情
我们做了刚才
英文所讲的那三个步骤
刚好是第一个步骤
我们给Reload Value register
赋了一个长长的16进制数
也就是二进制数
它对应的是按20.97兆数0.5秒
所要Reload的那个值
决定了我们定时器产生的周期
第二句话呢是给Current Value写了一次操作
这次写操作将Current Value
这个计数寄存器的值清零
同时保证了中断标志寄存器也被清零了
最后呢我们给
Sys Tick Control and status register赋了个初值
这个初值我非常容易的加以解释
就是打开Sys Tick时钟模块
打开使能这个Sys Tick的中断
把子开关打开
同时指定时钟源是CoreClock也就是20.97兆
这样就完成了这样一个Sys Tick模块的初始化
把中断打开了
把中断每多长时间发生一次也配置好
那回到我们的编程思想
我们已经完成了步骤1和步骤2
剩下什么呢
是步骤3
我们如何写一个定的这个时间
达到的中断服务函数
去做我们希望在这个中断里应该做的事
当然最后还有一个步骤是
我们把它如何填到中断向量表里
那么这个时候涉及到了我们编程思想
我们都知道中断服务函数
是在我们设置的条件每当它发生的时候
自动硬件通知CPU
由CPU自动调用的一个函数
现在我们使用的是定时器和定时中断
换言之每当0.5秒的时间
比较精确的达到的时候
就会有一个中断通知CPU
CPU应该响应它去做一件我们希望它做的事
这是它的编程思想
放回我们刚才的程序里头
我们需要写一个void的类型
void的返回值的中断服务函数
仍然在里头do something
并且清中断标志位
以便下一次中断能够发生
而体现到我们现在这个程序里头
我们每0.5秒的定时中断费这么大劲配置好了
其实要干什么
其实就是要让灯闪一下对不对
原来亮的变灭 原来灭的变亮
把这个GPIOC
PORTC寄存器的这些值翻转一下
同时保证这个开关不要动
那么最后清一下标志位
这些我说的话体现成C语言代码
就是这么一个简简单单四行的中断服务函数
每当0.5秒达到的时候请调用这个函数
让这个灯的值
引脚的值翻转一下
就这么简单
那么完成了这么一个中断服务函数
第四个步骤
最后一个步骤
在我们去看整个程序框架之前
我们先把这个步骤做完
就是要把这样一个中断函数
它的入口地址
第一条指令的地址
填入我们的中断向量表
或者说把这样一个函数
跟我们前面配置好的中断源关联起来
在它发生通知CPU的时候
CPU能够根据中断的来源
找到这个函数去加以执行
是不是就这么个意思
那么这件事情我们在
上一个章节已经做过一次
之前也讲过无数次原理
就看我们中断向量表
从上电默认的0000地址开始
我们有16个表项是对应ARM的
CPU内部的中断源或者exception
后面还有32个表项是对应ARM开放给
外设的中断源
我们上一次做IO的按键输入的中断呢
是底下32表项当中的第30项
也就是倒数第二项
那么这一次我们现在用的是Sys Tick模块
它在哪呢
它在我们这个表格里头的前16项里头
大家可以看到有个Sys Tick
所以我们仍然回到我们CodeWarrior
帮我们生成的kinetis_sysinit.c这个文件里头
我们之前看到这个数据结构
它用一系列的函数名称
或者叫指向函数第一条指令的指针
构成一个结构体
那么我们顺着这样一个结构体往下找
看它的前16个表向我们就会找到
Sys Tick_Handler这么一个函数名称
这就是CodeWarrior帮我们填写好中断向量表
已经写好的空函数的函数名
所以我们可以把我们刚才的
ISR中断服务函数的名称就命名为这个名称
编译的时候让他们自动链接在一起
也可以自己另外起个名称
用我们自己所用的函数名填充在这个位置
让我们CodeWarrior来自己进行编译和链接
这两者操作是一样的
所以如果我们完成了这样一个步骤
我们Sys Tick_Handler这样一个中断服务函数
这样一个让灯翻转这个函数
就已经填到了中断向量表
那么在接下来看完整代码之前
我们必须脑袋很清楚
我们究竟做了一件什么事
我们完成了把Sys Tick这样一个模块用起来
设置了让它每0.5秒钟发生一次中断
允许了这个中断可以通知CPU
告诉了CPU每当这个中断发生的时候
应该根据中断向量表
调用我们所写的这个函数
在这个函数里面
我们每次会让外面的灯完成一次翻转
接着中断函数返回
CPU接着执行自己的流程
那么回过头来看
经过我们这样的改造
我们程序变成什么样的
我们会发现我们的main函数变的非常的简洁
完成了一系列刚才所说的初始化
和两个不同中断源的配置
这个时候我们的for函数已经变空了
待会再讨论它变空的问题
我们想想原来在for函数里头做了哪些事
一件事情是检查键按了没有
一件事情是在delay下
不管键按了没有
我们要去让这个灯定期翻转
那么这件事情现在已经完全被拎出来
体现为了接下来这两个中断函数来完成
每当键被按下的时候
进按键的中断
改变两个灯的当前值
使他们变成互相不一样
或者互相一样
这是改变这个灯闪烁的模式
每当定时中断0.5秒钟发生一次的时候
进定时中断
去让当前引脚上的值翻转一下
也就是说让灯闪烁一下
那么回到我们的主程序我们会发现
我们其实在for循环
在最后一直是在那儿空转
每当按键的时候去改变灯的模式
每当定时0.5秒的时候
不管for循环里头现在是空的
还是以后再会写一些代码做一些别的任务
我们都会在0.5秒的时候
CPU准时收到一个中断进入函数
去执行让一个灯翻转的任务
于是在这样一种思想下
我们之前main函数做的两个
特别要求时间响应
比如人输入了一定要响应
我们定时时间到了灯一定要闪
都拆分成了两个独立的中断
在外面加以执行
这个时候我们会发现我们程序
整个编程思想发生了一个根本的变化
所以在这种情况下
我们的主程序的main函数
变得空空如也
我们可以在里头仍然象征性的放个delay
也可以什么都不做
当然日后编程的时候
我们也可以把一些不需要立即响应的
不那么着急的任务
放在main函数里头慢慢的做
既不用担心它们的执行
使我们无法响应人的按键
也不用担心它们的执行
使我们灯闪烁的时间间隔不再准确
然而在这个程序里头
我们其实也可以选择在main函数
最后这个for循环里头
放入一个让系统进入低功耗的休眠的函数
大家想一想我们可能每半秒钟
才需要做一次操作
人甚至很长时间才做一次按键
而每当进入我们这两个中断函数的时候
人的按键只用6到7条C语言语句
十几条指令就返回退出
而对于点灯的这个定时中断
只用一到两条C语言语句
也就是5、6个汇编指令就返回
所以这两个函数的执行时间
都在微秒以下或者微秒量级
而如果我们每半秒钟才进行一次这个操作
也就是我们CPU真正做有用操作的时间
只有几十万分之一
而剩下的时间如果我们在main函数里头
放一个不影响中断的低功耗函数
我们的主程序将在绝大多数时间里
都是低功耗的
于是我们整个系统如果用一个电池供电
它的待机时间可能会扩充几千倍
乃至几万倍
这就是中断给我们带来从编程思想上
乃至从功耗和性能上的根本的改变
希望大家通过这个章节的学习
尝试写代码实现它
并且理解这背后的东西
-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 嵌入式系统的实例