当前课程知识点:智能车制作:嵌入式系统 > 第四章 MCU外设与开发 > 4.2 IO外设的编程实操-点亮LED > Video
各位同学大家好
我是清华大学工程物理系的曾鸣老师
欢迎大家回到我们
ARM微控制器与嵌入式系统的慕课课堂
这个章节呢
那我们开始学习ARM微控制器的开发
那么在上一小节
我们进入了第一类最简单的外设IO
以及认识了一个真正的芯片
它是长什么样的
我们应该怎么样把它用起来
那么这个小节
针对上节课最后提出来的我们如何
去控制一个小的led灯
或者读取一个按键开关的人的
输入状态来进行编程的学习
在我们学习的这样一个ARM Kineties KL25
这样一个具体型号上
我们使用哪些寄存器
怎样通过编程把它给使用起来
那么我们上节课最后讲的
这个任务非常简单
我们假设我们这个芯片的PORTD
PTD0到PTD7这八个引脚
而通过外围电路都接着八个LED灯
当然我是略去了这些灯
都应该存在的那一个限流电阻
那么换言之
因为上面接的是电源
所以我们这些引脚
每个引脚输出逻辑一的时候
这些灯应该是灭掉的
输出逻辑零的时候
这些灯因为有电流的存在导通就会亮
那么我们左边给了一个空的main涵数
说怎样通过一个空的没函数
编成让这样一些灯
如我们所愿地闪烁起来呢
但是闪的时候
我们爱玩的同学就可以
各种玩的花样和闪法
比如我让八个灯最简单都亮
或者都灭一起亮一起灭
交替灯走马灯或者呼吸灯
那么这一系列的技巧
都是编程当中我们可以玩的东西
那我们怎么样让它
如我们所愿的实现出来
是这节课所要学习的内容
那么在我们以后的学习当中
经常会给大家开个玩笑
说每一个模块要想使用起来要几个步骤
我说很简单
就跟把一个大象放到冰箱里一样三个步骤
这个开冰箱门大象塞到冰箱里
然后关门
我们使用这么一个模块的时候
也是这么三个步骤
我在这里列出了三个步骤
分别是把我们这个模块
所需要对应的时钟打开
然后呢把我们这些引脚
配置为我们现在所要使用的这个功能
比如我现在学了第一类外设
就是当GPIO来用
最后一个步骤呢对这个
IO模块的寄存器进行编程
设置它如我们所愿的
去进行输入输出的IO功能等三个步骤
那这三个步骤我现在没有按顺序来讲
我讲一个最直观的步骤
也就最后一步骤先讲
我们如何对IO模块进行编程
让这些灯如我们所愿地工作起来
那么在我展开之前
我们回忆回忆我们实际上
刚才介绍IO模块的内部原理的时候
已经隐隐约约讲到了
它是由若干个寄存器来控制的
每一个引脚会对应寄存器里头一个比特
有的寄存器里头一个比特
管这个引脚是输入还是输出
有的寄存器一个比特管这个
引脚输出的值是零或者一
有的寄存器呢
是在这个引脚是输入的时候
来读取这个引脚上的值是
高电压还是低电压
那么我们可以大致想象出
这么一个编程的模型
我们前面在讲编程模型的时候讲到了
在我们这样一个嵌入式的ARM微控制器里头
我们每一个模块
它的控制都会抽象为若干个寄存器
那么对于我们所讲的IO模块实际上呢
抽象出来的是六个寄存器
不仅有刚才提到的方向输入输出这三个
那么在我们这张表格里
我把其中另外三个现在先标灰
因为他们比较的复杂
在我们现在这个基本任务里面先不用
所以大家不用怕只用这三个寄存器
也就是我标为黑色的这三个
那么这些信息在哪里找到
具体到一个芯片的时候
可以在芯片手册找到
比如说我们现在所使用的KL25
这样一个Cortex M0的ARM微控制器
它的芯片手册呢一点也不厚
也就1000多页对吧
那么我们这个寄存器呢
在第41章节的771页可以找到
那么我来带着大家来读它
通过我们前面的学习
我们一个微控制器
他的编程模型从0000地址一直到
FFF 8个F的这个地址
32位的地址空间是统一地址映射
映射成了FLASH RAM和外设寄存器不同的模块
而所有的外设寄存器所分配的地址
这个是由ARM公司在设计
ARM芯片的时候所分配规定的
都在从4000 0000开始的这个地址段
那么相应的我们可以想象我们的IO
也一定在这个地址段里
映射为了某几个地址
是我们刚才所说的寄存器
C语言编程对这些地址写入对应的值
也就能访问这些寄存器
那么换到我们芯片手册所说的771页
我们会发现这些寄存器编成了很多组
我们刚才说每一组的外设的引脚
每一组的芯片的引脚
都会分成一个一个的端口
比如PORTA PORTB PORTC PORTD
当这些引脚对应的使用成IO的时候
我们就可以把它对应的称为
GPIOA GPIOB GPIOC这么很多组
每一组的GPIO都会管若干个引脚
相应的每一组的GPIO
都有自己的一套寄存器
所以从寄存器的地址映射来讲
我们看这个芯片手册就会发现
同样的这一组六个寄存器
从GPIOA若干个地址
GPIOB
再来一遍一模一样若干个地址
一直到我们刚才这个LED灯
所接的GPIOD这一组
也是六个寄存器占了六组地址
那么这六组地址的起始地址
大家从这个表格的最左边一列就可以看到
是400F F0C0这个地址开始
那么在我们之后的编程当中
我们很多时候用C语言编程
可能就会忘记了这个地址
我们直接使用它的名字
来进行编程就可以
在我们这节课
我们也是这么来进行编程
后面的课
我会给大家讲它背后的机制原理
以及它的语法上的讲究
那么我们可以看到在这张表格里
这六个寄存器如我刚才所说
中间有三个我们现在不用
我们最重要关注的是GPIOD Port Data Direction Register
这样一个方向寄存器
以及GPIOD_PDOR GPIOD的Port Data Output Register
还有GPIOD_PDIR
也就是GPIOD的Port Data Input Register
这样三个方向 输入 输出
分别对应的寄存器
他们各自有各自的地址
那么它们的功能和内部结构
是什么样的呢
我们看一看会发现非常简单
比如说GPIO的PDDR寄存器
放在GPIOD这个一组里头
GPIOD_PDDR 寄存器
它是一个32位的寄存器
所以它从它的起始地址开始
一共会占四个字节32个比特
那么我们在对外围的引脚
编组到一个PORT端口里头的时候
我们最多会有32个引脚编组在一个端口
那么换言之在我们这个
GPIOD这个编组里头
也最多会管32个引脚
那么这些引脚不一定在我们的芯片里
都全部引出来了
变成了亮亮的金属腿
但是如果他一旦出现
它一定会一一对应在这个比特里
那么我们非常直观地会发现
这个寄存器有32个比特
每一个比特都对应一个
可能存在的外部的物理引脚
所以对应着一个比特的值是零还是一
就直接对应了我们外围的芯片的引脚
PORTD上的0 1 2 3 4一直到31
这每一个引脚的方向
我们把对应的比特设为一
那么对应的这一个引脚
就会被配置为输出模式
我们把对应的比特设为零
这个引脚就会配置成输入模式
这就是GPIOD
PDDR寄存器的功能和含义
那么与之相应的GPIOD的PDOR寄存器
管PORTD的这一组32个引脚的输出值
那么这32个引脚对应的32个比特
也组成了一个四字节的寄存器
每一个比特对应一个引脚
所以当我们的引脚配置为输出模式的时候
这一个比特设置为一或者零
对应的值就会在引脚上
像我们上节课所讲的示意图一样
送到引脚上出现3.3V的
高电压或者零电压
于是我们就成功地实现了输出
那么与之相应的我们还有GPIOD
当然也可以是GPIO A B C任何一个
PORT的PDIR寄存器
也就是Port Data Input Register
那么这个寄存器我们会发现
它的写入功能
在芯片手册里标为了灰色
为什么呢
因为它是一个输入寄存器
它的值不是我们写给它的
我们任何时候读取它的值
都是由芯片根据外部所获得的
电压值是高还是低来生成的
所以我们对它写是没有什么意义的
而我们对它读 当这个引脚设为输入模式的时候
这里的PDIR寄存器的值
会相应的与引脚上
所接的电压值随之变化
所以引脚得到了高电压
我们从这里可以读到一
引脚变成了低电压
我们就可以读到零
就这么简单
那么这三个寄存器的组合
我们就完成了刚才所讲的
上一节课紧密联系的
最最简单的功能实现
设置方向输出还是输入
输出什么值输入读到什么值
那么回到点灯的任务
我们可以发现
在点灯的任务里面
我们所使用的是
PORTD的零到七的八个引脚
我们接了八个灯
那么我们做一个最简单的编程
我们要把这八个灯给点起来
让它亮或者让它灭甚至让它闪烁
我们应该怎么来写我们的main函数呢
那么很聪明的
有一定C语言基础的同学
我们可以稍微想想就可以发现
我可以这么写
首先我应该把引脚都设为输出
对吧我要控制灯的亮和灭
那么我不管让它亮让它灭
让它输出零还是输出一
我都是输出所以呢
PORTD GPIOD 的PDDR寄存器
它的最低八个比特
比特零到比特七
八个引脚PTD0到PTD7
对应的这八个比特
应该都变成一是输出
所以呢我们把这个寄存器赋了一个初值
等于0x0F(字幕菌注:老湿口误啦~应该是0xFF) 这是一个三十二位寄存器
所以你可以脑补
0xFF实际上是0000 00FF
一共三十二个比特
最后八个比特是1111 1111
写成十六进制是FF
那么后面我不再这样类似地这么复杂解释
比如说我们的初始值输出
我们可以给GPIOD的PDOR寄存器
设一个初始的输出值
那么这个输出值根据我们自己的喜好
我们可以给它最低的
八个比特都赋上零
那么我们输出的就都是低电压
大家可以想象那这个时候
是不是八个灯都亮了对吧
那么如果我们希望初始状态
这八个灯都是灭的呢
我们可以很灵活给这个寄存器赋上的值是
GPIOD的PDOR寄存器等于0xFF
这样最低是八个比特是1111 1111
那么随之这八个引脚都会输出逻辑一
也就是3.3V
那么因为电压等电位没有电流
这八个灯就灭了
那么我们可以通过简单的两句话
控制外面一个灯的亮或者灭
那怎么才能玩起来呢
我们来玩一个最简单的玩法
我们写一个for循环
在里头我们假设我们已经用C语言
写好了一个delay函数
这个delay函数可以跟一个参数是
比如说毫秒为单位
我们称为delay毫秒
参数1000 等于一秒钟
那么这种函数以后大家可以学习怎么写
我给大家提示一个最简单的写法
就是你做一个足够大的死循环
让它循环几百万次上亿次
这是在我们计算机系统里
它就会浪费掉一段时间对吧这就是delay
那么跳开这个delay函数
每次进for循环delay一段时间
我们再做一件什么事情呢
我们要读取我们
GPIOD_PDOR寄存器的值
知道我们之前给这个引脚赋的是什么值
这个灯之前是什么状态
我们给按位取个反
赋回给这个寄存器
那是不是原来是一的变成了零
原来是零的变成了一呀
那这样一句话放在这里
是不是每隔一秒钟我们会发现
我们把这个引脚的值变了一下
那个灯是不是由亮就变灭了
那么如果我们有良好的C语言基础
一定要具备逐行去读这个代码
脑袋跟着他
用脑补的方法来运行程序的
这样一种初步能力
一个for循环完了
回来我们再delay一秒钟
这个时候上次亮的再翻转一次是不是灭了
于是这个程序持续源源不断运行起来
它的流程是初始化方向
给灯一个初值比如说点亮
然后进循环 延迟一秒钟
灯由亮变灭
再回循环 再延迟一秒钟
由灭变亮 如此反复永不退出
那我们就得到了一个
八个灯不停周期闪烁的例程
那么我多说一句 也就是有了这个例程
在我们实验的范畴
在我们这个爱好者的范畴
其实大家就可以花样玩了
比如你希望体现出这个灯
像坠物一样从上面一个一个落下来
越攒越多是什么效果
你希望这个灯跟街头的霓虹灯一样
形成走马灯是怎么做
然后你希望这个灯像呼吸灯一样
看到常亮常灭渐亮渐灭应该怎么做
提示一下 在这里我们可以做人的视觉暂留
所以小小一个点灯当中
我们可以有非常非常多的玩法
可以玩的非常非常的酷
但是反过来我们应该从当中
提炼它最后的数学模式
用编程语言加以体现
这是我们应该掌握的能力
也是跳出任何一个单片机微控制器
具体型号通用的知识
好第一个步骤完成了
那么我们放在这里
说如果像刚才那样写还有什么缺点呢
同学们想一想
如果是有一定基础的同学会说
老师你这个GPIOD和这个PORTD
寄存器有32个比特能管32个引脚
你刚才用的全部是赋值语句
那你把最后八个引脚的值赋了
方向定了 怎么把带上前面24个引脚
也给强行变了值赋了值呢
对 这是不好的
在一个复杂应用的时候
每个引脚都有它特定的功能
如果这些引脚刚好也当IO用
我们这样写程序可能就会引起BUG
严重的时候可以引起我们的系统
没有如我们所期望的去工作
所以呢在真正规范的写法里头
我们会尽量学习C语言的"与"或者"或"
只改变局部几个比特的值的使用
这段代码大家可以自己读和琢磨
我就不展开详细的解释了
那么把大象塞到冰箱里的三个步骤
我们完成了第三个步骤
好像这个灯已经能运行了
那这个程序写进去能干活吗
还不行还缺前面两个步骤
我们来看看前面两个步骤该怎么做
这也是未来的其他模块比较通用的步骤
那么第一个步骤我们用了IO模块
就应该把IO模块的时钟打开
让它不再是低功耗的
而是实实在在运行起来
否则我们如果直接写下面几个语句
在我们的开发环境里
编译会得到一个错误
说这个程序写的不对
那么时钟模块怎么打开呢
我们会看一看在芯片手册的第192页
在别的芯片里你也可以找到类似的地方
叫做系统集成模块
它里头有很多寄存器
如果你仔细的查找
会发现一个系统集成的时钟的门控寄存器
也就是clock的gating寄存器
它的编号是第五个
这个寄存器如果把它的解释内容打开
我们会发现一些很熟悉的地方
它有若干比特
分别写着PORTA PORTB PORTC PORTD PORTE
所以我们一旦用到了这一个端口上的引脚
我们就应该把对应的这个端口的时钟打开
那么刚才回忆一下刚才用到的点灯
是哪个端口呢
是PORTD 对吧
PORTD的 PTD0 到PTD7
所以我们应该把这个PORTD的时钟打开
它就一个比特
那么它的含义也非常简单
默认状态下这个比特是零
始终是关着的是低功耗的
我把这个比特置一
这个时钟就打开了
说起来很麻烦其实非常简单
那么写成语句就是一句话
也就是System Integration Module SIM这个模块里头的
SCGC5这个寄存器
我们把它的值
PORTD对应的这个比特置一
这个时候我采用了一种简单的写法
把一左移十二位与它或
大家回头看看
它是不是对应的
是这个第十二个比特呢
这就是我们把这个时钟打开
所以有了这个步骤我们就完成了
这样一个编程的三个步骤当中
第一个步骤把时钟打开
那么只剩下还有第二个步骤没有做了对不对
第二个步骤是把我们这个PORT上的引脚
配置为我们这个GPIO模块用
而不是给别的模块用
我们在上一节课的概念里讲过
它可以给通讯给ADC给别的模块用有可能
当IO用我们需要配置
回到我们的PORT的概念
我们会发现我们有很多很多引脚
它变成了一个一个的端口
像PORTD PORTD0 PORTD7
它们首先属于PORTD
然后它们自己有个序号分别是0 1 2 3 4 5 6 7
因此每一个物理存在的引脚
一个物理上存在的引脚
它都有自己一个独立对应的寄存器
来配置它的功能
这跟刚才不一样
刚才是所有的引脚都在一个寄存器里
32个比特 一个比特对应一个
在对引脚的功能进行配置的时候
这是一个非常专门的事情
所以每一个引脚都有一个对应的寄存器
它的名称它的功能
在我们这芯片手册第177页
也是一个独立的章节
叫做Port Contrl and Interrupt模块
那么在这个模块里头
每一个引脚都有一个对应的寄存器
它的名称叫做PORTx
比如A B C D_PCRn
n比如说01234567
所以我们可以想象PTD0对应的就是
PORTD_PCR0寄存器
PORTD1对应的就是PORTD_PCR1寄存器
每个引脚对应一个寄存器
那么它的功能是什么呢
它有32个比特
每一个引脚都由32个比特来配置
标为灰色的部分是这个忽略 保留的比特
标为白色的部分
可以控制这个引脚的很多功能
比如说它的第16到第20个比特这四个比特
是控制这个引脚的中断功能
我们以后会讲
最下面零到七之内的若干个比特
可以控制引脚是否有上拉电阻
输出的电流 大小等等
这些高阶的功能
大家日后可以通过手册
自行的了解和学习 并不复杂
那么它的第八个比特
第九个比特和第十个比特这三个比特
也就是我们中间有个小缺块这个区域
这三个比特大家想想
从000到111可以组合成八种选择
这八种选择的值赋给这个寄存器
就是选择我们这个引脚
使用它的第几个功能
什么叫第几个功能
大家回忆一下
在上一节课最开始的时候
我们拉出了这个芯片手册里
一个芯片的Pinout的表格
大家还记得吗
每一个名称为PTD0 PTD1 PTD2的引脚
它可能都有若干种复用的功能
我们称为ALT1一直到ALT7
那么我们在这些功能的复用里面
我们可以发现PORTD0的所有IO功能
都刚好是它的第一个功能
也就是001号功能
后面它复用的其他功能
还有通讯还有别的
所以对于任何一个引脚
我们应该把刚才寄存器里
对应的这三个比特
赋值为001选用第一个功能
也就是告诉单片机告诉微控制器
这个引脚使用它的功能一
也就是当IO用
这是我们刚才说的
编程的第二个步骤啊
所以这件事情说起来很复杂
操作起来很简单 八句话
PORTD_PCR0这个寄存器赋值等于0x0100
0100就是比特八到比特十这三个比特
正好二进制是001对不对
那么这样的语句重复八遍
从PORTD_PCR0一直到PORTD_PCR7
我们都给它赋值等于0100
也就是告诉我们的程序
这八个引脚请都当第一个功能
也就是IO使用
而不是当别的功能使用
那么我们完成了大象塞在冰箱里的
第二个步骤以后
再回过头来看我们的程序
已经非常的完整了
第一个步骤
我们把这样一个芯片的这个IO
我们用到的PORTD它的时钟打开
第二个们步骤呢用八句话
把PORTD0到PTD7这八个引脚
指定为给IO使用
而不是给别的功能使用
最后一个步骤呢到了IO模块里头
根据我刚才前面所讲的
我们把这八个引脚
它的对应的输入和输出进行编程和配置
把它们设置为输出
赋上零的初值
让灯亮起来接着在一个循环里
我们每延迟和delay一段时间
然后让这个引脚的值进行一次翻转
于是我们会看到
这外面的八个灯已经成功的被我们点亮
它们一起变亮
然后隔一段时间一起变灭
然后开始闪烁
于是我们就完成了这样一个
IO点灯的一个最基本的编程
熟悉了刚才所提到的
这若干个步骤当中寄存器的使用
而有了这样一个基础
首先我们大家会感觉什么呢
会感觉IO的编程小菜一碟
其次呢我们还觉得
我们其实还可以有更多可以玩的
比如说我可以把这延迟的时间改的更短
看看会发现什么情况
改到短到你眼睛无法分辨
然后我们在这个for循环里头增加更多的结构
让灯的闪烁可以变得更加的花样
我们可以尝试着把这当中
我们想显示的每一种模式
提炼出它的数学的模型
数学的模式再用编程的语言加以实现
乃至实现类似于像iPhone上的呼吸灯
这样的效果
这样大家就可以放手去玩
好我今天这个编程的内容呢到此结束
下一个节课我会给大家介绍一下
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