当前课程知识点:智能车制作:嵌入式系统 > 第四章 MCU外设与开发 > 4.5 嵌入式开发的进阶知识 > Video
各位同学 大家好
我是清华大学工程物理系的曾鸣老师
欢迎大家回到我们
ARM微控制器与嵌入式系统的课堂
我们的学习已经进入了第四个章节
也就是开发
并且在这个开发这个章节
也学习了若干个小节的知识了
那么前面我们花了几个小节学习IO模块
这样一个最简单外设的编程模型
也看了看怎么在main函数里头
用几句话实现一个点灯的基本功能
然后呢 刚才又用一个小节 然后
我们学习了嵌入式开发里头一些基本概念
基本流程 大概知道了从嵌入式开发的角度
我们得有一个板 我们得有一个开发工具
得写个程序 得编译得调试 得把它放进去
让它运行 那么现在我开始讲一讲
嵌入式开发里头更加深层
或者更加高阶一点的问题
其实恰好是这个部分我认为
是嵌入开发里头比较爽的部分
我们都说过
嵌入式开发最大的特点
就是软件跟硬件有一个紧密的结合
我们在计算机上编程的时候呢
往往你写一个main函数
然后放在这个操作系统里运行
printf你看到一句话
WINDOWS你看到一个窗口
其实很多同学会很好奇 说我写的程序
这个程序是怎么运行起来的呢
好像是个黑盒子 而对于嵌入式系统
最大的好处就是那种当上帝的感觉
你可以从你的嵌入式微处理器嵌入式计算机
这个CPU 它从上电的每一个周期
每一个CLOCK在干什么
你可以知道的一清二楚 那么换言之
当你做一个产品设计的时候
你可以从硬件的角度知道它在每一个毫秒
每一个微秒 或者根据你的时钟周期
每一个十纳秒在干什么
这种感觉是非常非常好的
那么我们在前面学习了一个嵌入式系统
它的中断它的这个上电复位向量
以及Flash RAM存储器的概念
我们回顾一下它上电的过程
那么在我们嵌入式的地址映射里
根据ARM公司
或者是根据Freescale NXP这样的设计厂商的定义
我们会有一个统一的地址映射
那么这个统一的地址映射
它的最高位置是我们的 中断向量
而中断向量在ARM Cortex的
微处理器核里头
最上面的一个向量呢
是我们的堆栈指针向量
第二个向量就是我们的PC指针
也就是第一条指令所存的地址
一旦完成上电复位以后
在我们之前烧写的程序里
除了程序本身 程序的入口地址
有的同学会说入口地址是什么
我们很多同学之前会脑补是main函数
或者是别的代码段 anyway 是入口地址
会填在PC指针这儿 所以CPU会自动的
把这个堆栈的初始地址和这个PC指针地址
加载到CPU里
于是很显然我们CPU就从我们的
程序的第一条指令开始逐条地往下执行
执行的过程当中一个时钟周期
一个时钟周期的运行
它又会访问我们的存储器
在我们的内存里读读写写去访问
我们所说的变量 数组 可能会从底部
来使用我们的堆栈 在函数调用
或者是在进行一些参数传递的时候
使用堆栈的内存 而在发挥实际的功能
要与外围发生关联的时候 比如要控制个灯
要读取一个光线传感器
要读取个电机的转速要调整一下舵机的时候
它就会读写我们的底部的寄存器的地址空间
通过映射在寄存器地址空间的这些存储地址
这些寄存器 来实现对外围电路硬件的控制
那么这样一个流程的周而反复
那么那个上帝是谁呢
是我们这些在座的同学 是编程人员
你写的程序控制这些事情究竟完成什么功能
这就是我们整个微控制器从上电
到运行的完整过程 那么一个好的程序员
我这是第一次说这件事可能后面还会重复
就是你必须很清楚知道你写的程序在做什么
你的微处理器 你的CPU在你的指挥下
在干什么 而反过来 它给你的反馈是
你是他的上帝 对吧
那么如果我们从另外一个角度来讲讲
我们嵌入式开发深层的背后的东西
我很喜欢借用美国麻省这个伍斯特大学
这样一个PPT里头这样一张图
这张图非常有意思 它有两页
第一张图说我们在编程的时候会想一想
为什么计算机很酷 或者微处理器很酷
微控制器很酷这样的嵌入式系统很酷
因为它跟普通数字电路不一样
它不是个简单状态机
一个简单状态机你写好了以后
你可能说它是个交通灯红绿灯
或者说它是一个电控闸刀
你的功能是定死的
而一个微控制器驱导的嵌入式系统
它是一个复杂的状态机 你可以给它编程
它做什么功能是由你写的程序来决定的
同样一个芯片今天可以是机器人
明天可以是智能车
那么从fantasy从幻想的角度来看
我们是在一个人眼看不见的领域
写了一个程序 比如说printf("Hello World!");
让它通过编译通过汇编通过链接
好像得到了一个可执行的程序
从reality从现实的角度来讲
实际上这个程序得放到存储器里
然后被我的CPU读取
CPU在读取工作的时候就像我刚才所讲
它有控制一个外设 这个外设
可能是个通讯接口
也可能是一个对于显示器的这个显示接口
跟显示器之间通讯
最后根据我们程序的内容Hello World!
这几个字母
在屏幕上对应的位置打出对应的光斑点
最后变成人类可以阅读的Hello World!
这几个字母
那么这是一个程序运行的
这样一个完整的流程 那么同样的
在这当中有另外一张图
就是计算机看上去好像很神奇
写什么程序它就能干什么事儿
但是我们作为开发人员
必须跳出这种神奇的陶醉感
很清楚知道一些什么事呢
就是我们的微处理器我们所用的微控制器
它只是一个笨笨的傻傻的Ugly的
State Machine一个状态机
它并不懂什么C语言 也不懂什么汇编语言
这些语言是给人类看的 给我们人类看的
它能够懂的是那些0和1的机器码
我们所有语言写成的程序 通过了编译汇编
链接烧写到计算机的存储器里
是由一个一个地址格子对应的0和1的序列
组成的一张表
这些0和1的序列就是指令 指令的流水
就是程序 而CPU是一个Ugly的状态机
笨笨的 在一个时钟
相当于一个动物心跳的驱动下
一个CLOCK 一个CLOCK的
沿着地址的跳转去读取这些指令
那么逐条指令的执行 获得一个结果
向外设输出0或者1输出电压
实际上在运行过程中 CPU某种意义上讲
完全不知道它自己在做什么 因为它没有智能
但是这些程序的 流水
这些指令的累计会完成各种各样复杂的功能
讲到这里 大家也许会想到了什么
对你首先会想到
这是一个典型的数字时序电路
学过数字电路的同学对吧
所以我们会很多时候都说要把时钟打开
CLOCK非常重要 时序非常重要
智能车你数这个轮子的速度
你的脉宽是多少很重要 这是第一个
第二个 大家会想到什么 大家会想到
我们在学习第二章节一开始的时候
向一位先辈致敬 这位先辈就是图灵
大家会发现这样一个逐条滚动读取
执行0或者1 输出0或者1
这样一种模型说明了我们今天计算机
仍然是在图灵所说的图灵机的构架上
进行执行 所以先驱或者先哲
就是能把一个领域
上升到一个哲学的高度
提出一个原型
哪怕经过几十年近百年的发展
它仍然还在这样一个原型里
所以这就是我们
计算机嵌入式开发的本质
好 这个本质更偏哲学
我们再往更现实的层面讲
我们在做现实的嵌入式开发的时候
不管你手上有工具链
还是有CodeWarrior IDE开发环境
我们往往会通过这样一些步骤的编程
在有意或者无意当中
使用到各种各样的文件资源
我们来梳理一下它的关系
大家最最喜欢的东西
特别学习过C语言基础的同学
你最喜欢的是c语言的.c和.h文件
你看到它觉得很亲切 这个我不怵 ok
我们最简单的同学
不会用多个文件之间的相互调用
我会写一个c文件
然后用一个.h头文件来声明它的变量
然后在.c文件里写个main
anyway我会有些C语言的源代码
那么这些源代码会给谁呢
会给一个叫做编译器的东西
那么同时高阶一点的同学
或者说有志于学习点汇编的同学
你可能会手动写一点汇编代码
对吧你会写成程序段或者用可规范的方式
把它写成与C语言
可互相调用的函数或者程序段
写成.asm的汇编语言程序
这都是我们编程所编写的东西
这两类程序加以编写以后
在实际的开发流程中怎么办
我们的C语言会经过了编译器
compiler的编译 我们刚才讲过
绝大多数编译器会选择以汇编
作为一个中间步骤 生成汇编代码
而它与我们直接写出来的汇编代码
会放在一个锅里一起来汇 给谁呢
给汇编器
也就是assembler assembler既是汇编器
英文直译也叫组装器对吧
assemble 组装
那么它的基本功能是把我们的
每一个汇编代码每一个人类可阅读代码
根据它与机器码指令之间的一一映射关系
加以翻译和组装 变成0和1的指令序列
那么这些指令序列是连续的 0和1的指令集
指令段 所以就是程序段 这些程序段
是可以重新定位的
也就是说每一个函数
连续的指令它构成了一段一段
但是在这个时候 它们之间的相互 调用关系
相互存放的地址还没有最终定死 所以呢
这些程序段又构成了我们的所谓的目标文件
是一些片段
那么我们往往在做程序编程的时候
我们会发现我们并不是所有的程序
都是自己写出来的 比如说
大家会调用的printf函数
有同学还可能会调用一个数学函数对吧
那么这个时候我们会发现
我们还会有一些代码 有些代码段
是来自于我们的库文件
C语言的standard里头会有很多很多的库
这些库我们一旦使用就可以省去很多
造轮子的功能 但是高阶同学如果用C++
你们可能会还会用模板用各种算法库
那么这些代码会从库里直接拿出来
与我们的程序一起变成一个有机的整体 ok
我们用了这么多的程序段
有这么多的孤立的函数和库
如何形成一个可以有机的整体
让他们互相调用 定义他们的最终关系呢
如果这是由一个人来做
我们得需要一张装配图对不对
告诉他这些代码可以放在哪
互相之间怎么定义连接关系
这个时候我们还会有一个.ld文件
是一个链接脚本语言写成的链接文件
链接文件里头待会会给大家看一个例子
会告诉我们在我们所使用的计算机平台
比如说到我的微处理器 那就更简单了
哪个地址段是flash可以放程序
哪个地址段是RAM可以放变量
哪个地址段是RAM的底部可以是堆栈
这些装配图定义死了以后
我们再通过一个工具
也就刚才工具链所讲的linker
把代码按照装配图的指示
放到可以放配的空间里组合起来
就形成了一个最终 具备互相调用关系
在我们的目标计算机平台上
可以使用的执行文件 执行文件大家都很熟
很多同学说在windows平台里头
就是.exe文件 .com文件
那么这是历史上遗留下来
用不同语言编写的可执行文件
那么实际上在这个linux平台里头
它可能不是这个名字 不叫可能
它完全就不是这个名字 它可能是个脚本
它也有可能是个.elf的二进制文件
而在我们嵌入式行业里头
它可能是一个binary文件
或者是个ascrecord也就是从摩托罗拉时代
下载 传递下来的这种ASCII码的脚本文件
或者是在Intel的体系价构里的hex文件
那本质上都是一样的 它是记录了详细地址
与函数代码段之间地址相互关系的
一个完整的映射关系的这样一个代码文件
它最终可以拥有足够的信息 指导我们的
程序编程器 烧写器把它烧写到我们芯片的
正确位置确保芯片上电能够正确执行
那么这样一个完整的流程 我们拉了一下
就会发现其实呢 好像很简单 但好像又很复杂
我们用C语言只写了个main函数
但是居然惊动了这么多的文件 这么多的人
大家共同参与 得到了一个可执行的文件
那么我们真正从后面看使用起来的时候
并没有那么复杂 因为由于工具链的存在
非常简单 你还是只是写了一个main函数
那么这个工具链里头 我们还会有些副产品
高阶的同学愿意学得深一点的同学
可以简单的了解 其中一个副产品
就是在我们最终的这个链接环节
生成可执行文件同时一般会生成一个
叫.map文件 也就是一个映射文件
这个文件非常有意思他详细记录了
我们在链接的时候声明的每一个变量
每个固定使用内存的变量
每一个函数体
它所使用的地址空间
在我们的嵌入式微处理器从多少地址
到多少地址是哪一个函数
从多少地址到多少地址是哪一个变量
我们都可以在这个文件里找着
调试的时候 我们可以试试用我们调试工具
到对应的地址去看它 所以它是一个装配总图
那么还有一个很有意思的文件呢
是在C语言编译器 在很多编译器里头
它可能会把中间步骤帮助我们生成一个
类似的文件 也就是C语言对应的汇编代码
所以有很多同学在
学习汇编的时候会觉得很难
我认为可以逆向学习
你可以通过看看你的
c语言生成的汇编代码是什么
来一方面学习汇编语言
一方面作为一个底层的硬件工程师
知道你的C语言程序究竟要花几个周期
几条指令 是不是如你所想象的那样在执行
这是一个非常重要的能力
那么我们再用一张简图来说一说
刚才这些东西之间对应关系
这张图是我们整理的一个抽象图
未必非常准确 但我认为非常的形象
我们在进行开发的时候
会涉及到一系列的文件 而最终
我们所有编程的结果
都会烧写到我们最终的芯片里
那么他们之间怎么形成这样一种逻辑的联系
不是光光烧写的联系
我们可能会有个区间
定义了所有外设寄存器
就像我们在第一节里学的 IO的那些
控制IO方向 点灯用的输出值的寄存器
我们会使用C语言里头文件的概念
把这些寄存器都适当的声明出来
然后让我们方便地在编程的时候可以使用
其次呢我们会有一些flash的空间
和RAM的空间 在这个里头我们
通过我们编程的函数代码
还有我们的程序启动的Startup Code这些
会放到我的flash保证我们上电
就能够不丢失的 每次都执行
而在我们编程的过程当中 会声明很多变量
会声明很多数据结构 而且我们函数的调用
会使用堆栈 这些东西 会去到RAM里头
从顶部使用堆 从底部使用栈
来使用我们的内存 所以呢这样一系列的
映射关系就使我们在开发环境当中
遇到的所有的头文件 宏定义 代码段 数据段
全部应该放到右边芯片里头
而我们的装配总图就是刚才所说的
链接器的这样一个链接脚本文件.ld文件
我在这里给大家看了一个在
CodeWarrior开放环境里头MKL25Z128
一个自动生成的链接脚本文件
当然我给大家看的这个版本
是我经过一点点简化 把一些冗余的语法去掉
但我们仍然可以看出这个脚本
初学的同学可能会觉得好复杂 我不想看
但是你静下心来去读 这个是很多编程
都是通过这种方式来学习 然后你会发现
其实它已经很简单了
它分为了几个section几个段
然后第一个段里头
有个非常明确的地方 是这个stack pointer
也就是我们的堆栈指针的起始地址
它放在了内存的最底部 然后这个值
又赋给了一个下划线起头的变量名称
如果有兴趣的同学在整个project搜索
你会发现有一个代码段 会把这个值
放在我们中断向量表的第一项 也就是我们的
中断向量表的那个
堆栈指针寄存器的初始值那里
也就是说跑正了我们的堆栈指针
在上电复位的时候指向了内存的底端
让堆栈就位
然后底下呢定义了一个栈的大小
大家注意这个值以后深度学习的时候
并不是个约束条件 它只能在编译的时候
进行堆栈大小的检查 而在运行时
如果堆栈溢出这两句话起不到保护作用
我们只能通过程序动态的来检查
那么堆栈的部分跳开下面部分更加简单明了
我们发现每一个变量名后面
都是一个数据的起始地址
那么指定了一段地址段 给它起了个名字
那么这些名字分别是中断向量表
vector table或者是flash或者RAM
那么如果你对这些段的起始地址存有疑虑
我们不妨回过来看看我们刚才所说的
这张内存映射表 那么我们会发现
在这个内存映射表里 根据芯片手册
已经定义好了 从哪个地址到哪个地址
分别是flash 分别是RAM
所以这个编译脚本文件
只是如实地把手册 我们刚才开头
大家注意MKL25Z128这个型号的MCU
它的地址段用脚本的方式写出来
然后最后一段呢 是一个编译器的规定动作
所谓的.text
是我们编译出来的可执行代码段
当然还有我们的variable
还有我们的常量分别应该放在哪儿
那显然我们的常量 我们的字符串我们的代码
都应该放在flash的掉电不能丢失
下次上电执行的时候 这些值应该还在
而我们的堆栈 我们的全局变量
我们的这个stack 都应该在RAM段
所以这样一种 逻辑关系写下来 这个ld文件
虽然有它自身的语法 有兴趣的同学
可以去加以学习 但是它是非常可读的
你会发现
它其实就是老老实实的说了这么一件事
那有的同学说我想把flash或者RAM
空出一段来不要让编译器用
以后我的程序要用怎么办呢
相信所有聪明的同学这个时候你可以学会
自己改这个脚本文件 那么万幸的是
这样一个脚本文件在CodeWarrior里头
不用我们每一个人自己去写
你一旦在建project的时候选好了型号
它是自动生成的 所以我在这里给大家讲了
不是开发有那么复杂 而是开发有这么简单
因为这些事情集成开发环境都帮你做了
那么在这里小结一下我们这节课
讲了这么多嵌入式开发的背后的知识和概念
我们来小结一下
嵌入式开发的精髓
之前我说过就是存储器
为什么说嵌入式开发的精髓是存储器呢
因为我们的代码是保存在存储器里的
我们代码执行的时候它的变量它的堆栈
是放在存储器里的 虽然这个存储器
是flash是RAM
而且最重要的是在嵌入式
在微控制器的领域
我们所有与外围电路的功能性的东西
也是通过映射为存储器
的寄存器来加以访问和控制的
所以我们编程的对象全是存储器
那么微控制器开发的特点
跟计算机比有什么差别呢
我想大概最简单的来讲有这么几个差别
首先它有一个平坦的存储器模型
特别是在没有分页没有保护模式
这么简单的微控制器层面上
它就是彻底的一维的一个平坦的结构
从0x0000 0000到0xFFFF FFFF地址的编值
中间有若干地址可以不用
但是我所有要用的功能
都映射到这个地址段里 每个地址段
哪个是flash 哪个是RAM 哪个是外设
都是非常清楚的 第二个概念呢
就是我们的存储空间是非常有限的
因为我没有使用保护模式
没有采用分页和虚拟的这些存储
我们的存储 不管是flash还是RAM
它最终都有有限的大小
所以我们后面在编程的时候会讲要量入为出
最后就是我们在访问的时候
我们的存储器的类型是略有区别的
虽然说精髓在存储
但是大家对它要有个概念 flash
我们可以通过开发的调试工具一次性的写入
但是我们在编程环境当中不做特殊的编程
无法去随便更改它的值 RAM呢
就跟普通内存的概念一样 可以随时的使用
涂改 编写 改变值 但是掉电它会丢失 寄存器呢
表面上访问的是存储器写的是0和1
但是在它的功能上 它最终是与电路关联的
是0和1所代表的高电压低电压来发挥作用
又根据外设的时序电路 我们后面
会一个一个外设的学习 产生花样百变的
各种功能 最终让这个嵌入式系统
可以非常灵活地玩起来
那么讲完了这个小节的内容
今天的课就到这结束 下一节课
我会给大家专门讲一讲
在这样一个嵌入开发里头使用c语言开发
与计算机上有哪些不同
以及有哪些秘密和技巧
-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