当前课程知识点:智能车制作:嵌入式系统 >  第四章 MCU外设与开发 >  4.5 嵌入式开发的进阶知识 >  Video

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

Video在线视频

Video

下一节:Video

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

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

第三章 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笔记与讨论

也许你还感兴趣的课程:

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