当前课程知识点:智能车制作:嵌入式系统 >  第四章 MCU外设与开发 >  4.7 嵌入式开发中的C语言(下) >  Video

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

Video在线视频

Video

下一节:Video

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

Video课程教案、知识点、字幕

大家好

我是清华大学工程物理系的曾鸣老师

欢迎大家继续回到我们ARM微控制器

与嵌入式系统的慕课课堂

继续我们的嵌入式之旅 那么在这一章节

我们进入到了嵌入式开发

C语言这样一个主题 上一节课我们讲了

C语言的一些历史渊源 背后的故事

以及C语言在嵌入式开发里头的两个最简单的

小知识点 那么接下来我们再来讨论

C语言在嵌入式开发里头的剩下的两三个topic

最后我们再归总为从C语言视角看待嵌入式开发的

一些总结和注意事项

那么第一个topic呢 是C语言如何实现对硬件的控制

这是很多同学可能特别好奇 特别想知道的

比如说我们在这章的第一小节

我们最后把那个点灯的这样一个简单程序

写出来的时候 大家会发现我们写的语句是

GPIOD_PDDR寄存器这个名字等于多少

GPIOD_PDOR寄存器这个名字等于多少

来给寄存器赋值 或者我判断寄存器的值是多少

把它拿来读 那么因为用寄存器的名字

好像一个变量一样给它赋值

我们只用五六句话在main函数里

就实现了点灯这样一个功能

那么这些语句为什么这么简单

它究竟怎么发挥作用 实现对我们硬件的控制和访问呢

这件事情我们应该认真地探讨探讨

也就是C语言的语句怎么访问寄存器的

我们回来回头看我们寄存器的本质是什么

寄存器在嵌入式系统里就像这张表一样

它是一个映射在一个统一地址映射上

不同地址的一些电路实现

看起来像一个存储单元

你按地址可以访问它 每个寄存器8个比特

或者32个比特 那么实际上它在C语言视角

访问的是存储 而存储的0或者1背后

连接的是电路 用它的电压值高或者低

来发挥电路控制作用 所以对于我们

一个简单的点灯GPIO来讲 每一组IO

最多32个引脚就对应了这样123456

6个不同的寄存器 对应在不同的地址

GPIOD_PDDR GPIOD_PDOR GPIOD_PDIR 以及我们说的

批量控制寄存器 那么这些寄存器

一组一组一套一套的 每一组IO都有六个

我们在前面做main函数编程的时候

使用的是寄存器的名称 而我们从芯片手册知道

每个寄存器的本质是它映射在这里的

400开头的这样一个32位的物理地址

那么我们再看看这个寄存器 比如说最简单

GPIOD_PDOR 这个寄存器回顾一下它的定义

非常简单 映射在一个地址上占了四个字节

这四个字节呢 一共是刚好32个比特

每一个比特对应一个芯片周围实际存在的

物理引脚 一个金属引脚 这个比特为一

在设为输出的时候

输出的值就是高电平3.3伏这个比特为0

引脚对应的电压就变成0伏

很简单的一个寄存器跟硬件的对应

那么我们实际上

编程的本质给这个寄存器赋值

就是要让这些比特为0为1 那么这件操作

我们在编程里怎么做的呢

或者那个语句的背后是什么

我们先回顾一个概念 在C语言里头叫做

强制类型转化forced type conversion

那么很简单

我们看这么一段代码 从背景上来讲就是说

在不做额外的变量类型声明的时候

我们可以强制把一个类型的变量

当成另外一种类型来对待和处理

这叫称为强制类型转化

比如说我有一个unsigned int类型的variable

一个变量 我这里没有写声明我只用它的名字

来体现它的差异 值是0x1234 大家知道

十六进制四个数就是两个字节 16比特

对吧它是个整型变量

那么我同时还有一个unsigned的

char类型的变量

这个变量是个8比特一个字节

它的值赋值我怎么赋呢

把刚才这个unsigned int

这样一个变量前面打个括号

写个unsigned char反括号后面跟变量

也就是说我们括号里头的到一个变量类型

加在后面一个变量上

这种语法我们称为强制类型转换

它的语义是说

对于后面这个变量强制当作括号里的变量类型

来读取和处理完成对前面这个变量的赋值

那么我的问题是经过这样一个操作

unsigned char variable这个变量

它的值是多少呢

一个16比特变量

被强制当成8比特一字节变量

所以它的高8位被舍去了 只留下了0X34

是我们拿到的值

所以这样一种强制类型转换的背后

实际上是对这个变量所存储的地址

按照我目标的这个变量类型

也就是强制类型转换的

这个类型去进行读写访问

取出我所要的字长

我所要的值进行使用

这是强制类型转换的本质意思

如果对于强制类型转换 经过这一小段代码

有所理解的同学

我们再看另外一个写法 说我们如果

有一个unsigned char variable型的变量

一个8比特这个就不讨论了

等于 *( (int*) (0x400FF0C0) )

大家看这个400FF0C0是什么呢

如果我们回过去

看刚才那张寄存器的这个变量表就会发现

哦这就是GPIOD_PDOR寄存器

在统一地址映射上的实际地址 物理地址

那么 400FF0C0

是这个寄存器的实际的地址它是个值

这个数是它的地址值

然后我前面打个括号写int *

大家想想这是什么意思 你会发现int *

实际上是一个指向整型变量

指向32比特数的一个指针 对不对

我把一个400FF0C0这个数的强制类型转换

指定成了一个int *型的指针

那么 再把它括起来

最外面这个大括号里头这个int *小括号

加上这个数它含义是什么呢

是整个这个部分是一个指针

它被强制类型

转换成了一个指向int型类型变量的指针

指针是什么

指针是一个指向地址的数据类型

指针的值是什么呢

是强类型转换之前

我们后面这个数

我们把这个数变成了指针

那么换言之这个指针的值就是400FF0C0

对吧 那么我们再回顾下C语言

指针的值实际上是什么

指针的值是指针所指向的地址的这个地址

也就是说我们实际上获得了一个

指向400FF0C0这个地址的指针

它在访问这个地址的时候

按照int型变量来进行读写读取四个字节

是不是我们强制地获得了一个指向这个地址的指针

那么如果我们按现在这个写法

把它们再括起来打个* 是什么意思呢

*运算符作用到一个指针的时候

是根据指针所指向的地址

把这个地址上的数 给读出来

就是根据指针取这个值

这一段话有点绕

那么我们打一个类比的比方

说我们这栋楼里有很多报箱

每个报箱对应一个房间

101房间102房间103房间

我们把这个101 102

也就是我们现在这个地址值强制类型转换

生成了一个指针 这个指针指向这个报箱

我们再做一个*运算符

取这个报箱里的值出来

是不是就是根据

指向一个地址的指针取这个地址里头的值

那么我们如果把它写成一个表达式

一个变量等于这样一个表达式

是不是就相当于把这个地址上32比特的值

取出来赋给这个变量了

那么大家想想它的本质是什么

它的本质就是

我们对这个寄存器

对 GPIOD_PDOR这个寄存器

完成了一次读操作

所以如果没有CodeWarrior来帮我们生成代码

我们自己拿到一个芯片手册

知道一个寄存器在一个地址

我们真正C语言的写法应该是什么样的呢

应该这样写 把地址变成指向地址的指针

再对这个指针做操作

把它的值取出来

我们就可以进行读

那反过来如果我把这个表达式

写在等号的的左边

这个东西等于1个值

是不是我完成了我们刚才所说的

对一个寄存器赋值

如果这个表达式

大家这样听起来还有点不明白的同学

我建议大家可以重复听一遍

再来理解这件事

那么我们反过来一个更好理解的概念

假设说正向说我们直接声明一个int*型的指针

它的变量名是p 小p开头

表示指针 GDIOD_PDOR

这个变量在声明的时候我给它赋个初值

它的值是400FF0C0

是不是意思 这几个大家比较好理解

就是说这个指针指向的就是这个地址

那我们任何时候拿这个指针加个*号

是不是就可以访问这个地址

就可以读写操作这个寄存器了

所以这两种写法实际上都可以完成

对硬件寄存器的地址访问

那么我们想想我们实际写的代码是什么样的

我们点个灯前面的例程

实际上main函数看起来非常的漂亮

GDIOD_PDOR 值或一下

把IO设成输出

GDIOD_PDOR 寄存器与一下

设成这个0

让灯点亮 然后for循环delay

每次把GDIOD_PDOR寄存器的值翻转

让这个灯一会亮一会灭

这是我们在main函数里

用C语言 用寄存器的名称所写出来的语句

那么如果我们用刚才的东西

说它的本质是什么呢

实际上是 *((int*)400FF0D4)

这是我们PDOR寄存器的地址

我们对这个地址

生成一个指针变量

对这个指针变量做取它的值的操作 给它赋值

然后同样的

我们一系列这样的指针操作

刚才的代码写成这个形式

就是严格的C语言 通过灵活的C语言的精髓

指针 来访问地址

实现寄存器控制

那么这段如果大家能够

把自己C语言基础补一补

加以理解的话

你会发现我们所写的代码背后是这样的

那么为什么我们能用左边这个方式写呢

能直接非常舒服的

有了集成开发环境帮我们做了这件事

我们拿个变量名

就可以写这样一段代码呢

我们去看一看我们CodeWarrior生成的代码

我们在CodeWarrior建一个project的时候

总会指定我们当前这个project

针对的是哪一款哪个型号的芯片

我们指定的是ARM Cortex的Kinetis KL25

这个系列的芯片

它会生成一个project

可以自动include这样一个MKL25Z4.h的头文件

把这个头文件打开你会发现它非常非常长

里头大量的篇幅

就是在帮我们声明这些寄存器的名称

但它用了一个非常有意思的语法

如果愿意 C语言更扎实的同学

可以花点时间读这段代码

它把我们的每一组IO所使用的六个寄存器

放在一起声明成了一个结构体

再把这个结构体强制声明成了一个

指向这个结构体的指针类型变量

于是呢 它用这个结构体的指针类型变量

指向每六个寄存器的起始地址

使用结构体里访问元素的 ->

这样一个运算符

来获得其中每一个寄存器的地址值

然后再用define和typedef

把我们所要的这个

变量的寄存器的名称定义成结构体的指针

指向了这个寄存器的地址

实现使用变量名称就可以对对应地址

赋值的功能 这套语法因为掺杂了结构体

掺杂了指向结构体的指针

读起来好像要更复杂

但是它的本质语法

就是我们刚才所举的这个例子

通过强制类型转换

实现对一个特定地址的指针访问

直接实现对硬件的读写

这就是C语言强大的地方

所以我讲这个概念有两层意义

一方面通过指针的深入理解

大家掌握C语言的强大之处

掌握如何用C语言

在编译开发环境不支持的情况下

能够访问硬件

理解我们所写的代码

实际上是直接对物理地址进行访问

其次呢 就是我们应该要知其然知其所以然

假设没有集成开发环境

假设我们只有一个编译器

这个头文件我们可以自己写

没有头文件拿到一个新出的芯片

我们可以自己来写这个代码

写这个头文件 来把它玩起来

这就是深入学习嵌入式系统

应该掌握的一些技能和知识

那么这是第一个话题

然后下面呢还有一个话题

就是我们如何让C语言知道

我们所访问的这个变量名GDIOD_PDOR

这个名称是一个寄存器

那么同学就会反问了

我们需要让C语言知道它是个寄存器吗

这是个好问题 我们在讨论这件事的时候

可以慢慢往下讨论

那些先说使用这个时候

我们用到了C语言标准关键字

叫Volatile 关键字

那么这个关键字如果我们查一下

C语言标准的语法

或者一些最简单查查词典

这个词的基本含义是易失的 可变的

那么在C语言的语法里面

这个词的定义是什么呢

用一句非常简单话来说

它是一个variable 一个变量

然后呢 叫做什么呢

may be changed outside the normal program flow

就是在正常程序的流程之外

有可能会自己发生改变的这样一种变量

有同学觉得这句话不好理解

那我们用人类的语言来翻译它一下

什么叫正常的程序流程

大家应该有一个概念

说我们如果编写一个计算机程序

我们慢慢建立这样一种感觉

就是我们是当前这个计算机的上帝

他做的每个step 每一个时序

是根据我们作为开发人员

所写的程序来决定的

我举个很简单例子

大多数情况下

我们用int 用char类型声明一个变量

这个变量我给他赋了个值等于3或者等于5

如果程序往下执行的语句当中

我没有给这个变量再赋新值

是不是这个变量的值不应该变

它应该还是3还是5

否则就出问题了对吧

所以这种情况下我们叫做 normal program flow

就是我们逐句执行

我们程序的这个流程

是我们的程序运行流程

它没有改变变量值的话

它不应该变 可是寄存器不是这样的

为什么呢

我们在C语言语法里会说

这样几种情况

一个寄存器最最直观的事情

比如说GPIOD_PDIR寄存器

它的值反应的不是一个数值 一个变量

是一个地址映射像一个存储空间

但它并不是存储器

它的值是电路 外面接的是引脚

它反应的是外面一个引脚接了一个按键

被按下或者没有被按下

电压是高还是低

它的值是跟着外部电路变的

所以如果我们写一句话

说对一个外部的输入引脚读它的值

检查外面一个按键有没有被按下

然后循环一段时间再读一下

如果编程的人员

都很理解我们写这个程序的目的是什么

就是定时检查人有没有按这个键

比如我们写了一个小游戏

每5毫秒查这个人有没有按下

那么如果把这句话写在我们的C语言里头

我们觉得我们的功能是非常清晰的

如果C语言 它很stupid

它的编译器不知道这是一个 Volatile

或者叫一个寄存器

那么我们把我们编译器的优化选项打开

开到最高一档的优化 什么叫优化

就是让我的代码尽可能简短

用的空间尽可能的小

执行速度尽可能快

然后编译器就会非常smart

认为这是个变量

这个人为什么在给变量没有赋新值的时候

读它一下

然后delay一段时间又读它一下

那一个变量你没有赋新值

读两次这是个浪费的操作

编译器就会把它优化掉

这个时候你看编译器生成的汇编代码

我举个例子

你就会非常清晰地发现 只剩下了一次读取

于是我们的程序运行就不正常

所以我们会使用Volatile这样一个关键字

放在我们这个变量的声明之前

就像这样一个语法 然后呢使编译器知道

这是一个在程序流程里

如果没有改变值他自身会变的

易失型的变量

所以我在优化的时候

对于这一类的优化应该跳过去

所以Volatile关键字

加上我们前面讲的灵活的指针访问

能够确保我们在C语言的视角里

正确地进行嵌入式的开发

访问跟硬件关联的寄存器

这就是涉及到底层电路的C语言编程

和台式机计算机操作系统上C语言编程的差异

然后呢

第三个topic C语言编程

我们前面讲了中断的概念

隐约的提了一下中断

是要有中断服务子程序的

那么中断服务子程序的C语言如何实现

那么中断服务的子程序 如果我们看的话

它会有若干个特点 我们这里不展开讲

只简单重复 我们会发现中断服务子程序

它看起来像一个普通的C语言函数

但它有若干个差异 有哪些差异呢

首先它最重要的一条

它没有caller 没有呼叫

为什么呢 因为中断服务子程序

从来不在我们的正常的程序流程里被呼叫

为什么

它的机制是硬件设定好一个条件满足

比如外部一个按键按下了 通讯收到了一个字节

定时的一个时间到了

由CPU收到中断后自动去调用这个函数

所以这个函数虽然写成了中断服务子程序

但它从来不由我们自己写的程序在main函数里

在某个函数里去调用它 所以no caller

其次呢因为它不被调用

所以他没有传递参数

也不会有返回值 因为没有人去调用它

不在程序流程调用 所以你无从传递参数

也无从让它向别的地方返回值

它是void类型

void参数的C语言函数

那么在别的CPU构架

我们这门课讲的是ARM

在别的CPU构架上

往往还有一个特点就是

中断返回的指令

跟普通C语言函数返回指令

这个return的指令是不同的CPU指令

而在我们的ARM里头呢

有一个非常好的事情

就是它用的是同一个指令

所以在别的CPU嵌入式开发的时候

我们讲通用知识

你往往会需要使用类似interrupt的关键字

来声明你这个函数

放在你的函数类型前面

告诉编译器这是一个中断函数

它的返回指令在编译的时候应该有所不同

而在我们ARM Cortex的M

这样一个构架里头

它非常聪明的是通过每一个函数进函数的时候

用两字节对齐的这样一个偶数地址

不用的那个最低位为0为1

来区分是函数调用还是中断调用

所以它的返回指令用的是同一个CPU指令

指令根据这个零或者1

来决定怎么返回 所以呢

它的返回指令

跟普通的C语言函数是一模一样的

那有同学说这个我没完全听懂啊

那他反映到我们C语言编程是什么特点呢

结论就是在ARM的CPU编程里头

ARM的嵌入式系统开发里头

我们的中断函数只用写成一个普通的void类型

加void返回值的中断函数

在里头去注意开关中断这样的一些基本的语句流程

就可以了 而在这个非ARM的CPU

或者在一些以往的0812这些CPU上面

我们注意中断程序要加上一个interrupt的关键字

这个取决于你的编译器

然后呢我们最后再来看看 这个

从ARM的微控制器启动一直到main函数

这样一个topic 我经常会说在嵌入式开发里头

如果理顺了前面这些流程 理顺了中断的开发

你会体会到一个非常棒的感觉

就是从这个硬件的启动的第一个时序开始

一直到进入main函数 你非常清楚这个CPU

这个MCU它在干什么你是它的上帝

我们来回顾一下这个启动流程

从ARM启动的时候

我们会从中断向量表里头去读取它的中断向量

读取它的堆栈指针寄存器

分别放在CPU内部的堆栈寄存器

把堆栈指向内存的最低地址

让堆栈可用 PC指针

指向程序代码的第一条指令让第一条指令可用

那么第一条指令是什么呢

我们很多同学都会认为是我写的main函数

因为这是个MCU嘛

一个单片机 微型计算机我写了个main函数

就干这个功能

它又没有操作系统

所以上来就执行我的代码 那么实际上呢不是

在绝大多数计算机系统里头

都会有一小段 Startup Code

那么比如说我们用的台式机

我们都知道他上电启动是直接进Windows嘛

实际上不是的

他至少要经过2到3个步骤

首先要进主板上的BIOS

把基础输入输出设备初始化完成整个电脑的自检

然后交给磁盘的引导扇区

引导扇区有时候会加一个boot loader

运行一小段代码

最后再跳转到操作系统的loader入口

再进入我们的windows或者linux操作系统

这是计算机的启动流程 比较复杂

也不太被编程人员所能接触到

嵌入式系统不同了

我们其实可以知道从上电的每一个步骤

所以我们是要上电完了以后

堆栈指针寄存器和PC指针寄存器

如果在别的构架的CPU上

我们堆栈指针是没有初始化的 在PC指针

在ARM这个构架上

PC指针指向了一段代码的初始地址

这段代码一般来讲是CodeWarrior帮我们生成的

很神秘 看上去一大堆C语言和汇编代码

那么如果你在CodeWarrior生成project 把左边这些目录打开

你会看到有一些 sysinit.c这样的程序

那么这段代码看起来很复杂 我们来读一读

是不是我们自己不能做

实际上通常情况下Startup code

在一个嵌入式的计算机平台上最少要做五件事

哪五件事情呢

第一件事情 把堆栈指针寄存器

或者是帧寄存器frame(如果有的话)

初始到内存的某个地址

那我们都很清楚

堆栈是比如说从底部往上先入先出这么用的

那我应该把堆栈指针寄存器

指向可用内存空间的底部

方便之后一旦出现要使用堆栈的时候函数调用

中断调用的时候 能够由底往上使用内存

而堆呢由顶往下使用堆

这样内存空间变得可用

所以堆栈可用的

标志或者叫初始化堆栈

就一句话

堆栈指针寄存器指向一个可用的内存

这是第一个步骤 第二个步骤呢

我们为了严谨起见

应该把我们所使用的所有内存空间里头的

内存的初始值清零一遍

让它的初值都是零

第三呢就是我们如果有全局变量

我们大家有时候会喜欢C语言写个变量声明

在main函数外头放在最上头对吧

而且还喜欢在写它的时候直接等号

给它赋个初值

那么大家有没有想过这个变量的值谁来给你赋

因为程序一旦进入main函数就逐条执行

并没有一句话去给它赋值

所以这个初值是由Startup code

帮你把它赋上的

对应这变量所在内存空间把值给copy进去

如果大家使用了C++

还会有一个全局的这个构造函数

或者构造调用constructor来把这个初始化

最后一个步骤才是call main函数

跳转到我们大家熟悉的

你自己编写的main函数

这是这五个步骤

那么逐步来看看在我们现在所学习的

嵌入式的ARM开发里头

这几个步骤还有没有用

或者有没有能省掉的 我们会发现

堆栈指针寄存器在ARM Cortex M的构架里头

零号中断存的就是堆栈的指针的初始地址

所以如果你没有额外想把它指向的内存空间的话

我们在编程烧写程序的时候

给中断向量表的第0号中断这个格子

烧入我们内存的最低地址

这样芯片在上电的时候

硬件帮你初始化了堆栈指针

就像初始化PC指针指向第一条指令一样

在ARM构架里这两件事是同时自动做的

对吧 然后第二步骤把内存要清零

如果我们声明的变量

能够做到C语言的standard

说变量在没有赋初值之前 我不用

那我每次使用的时候都是用我自己赋的初值

那它原始是不是零其实就没有关系

然后包括全局变量赋初值

如果我们养成了一个习惯

在顶部声明的全局变量不在后面直接加等号

赋个初值 而是main函数里头再赋初值

这个步骤也可有可无

然后如果我们不使用C++全局构造也是可以跳过的

那还剩下什么呢 实际上就是跳转到了main函数

所以如果没有CodeWarrior这样的集成开发环境

给大家了一个C语言编译器

比如今天我拿到一个特别新的CPU

或者因为各种地震火灾战争各种原因

我们拿这个CPU只有一个编译器 自己要把它玩起来

你会发现把如果我深入学习我是可以做的

对于寄存器的访问

我可以用指针轻松地实现对着手册就可以了

对于函数如何加载到main函数

在ARM构架上非常简单 前面四个步骤

其实都可以省略 跳转到main函数也是可以的

这段代码自己写 对吧

所以我们会发现知其然知其所以然

讨论了刚才这五个 六个topic以后

我们会发现我们对于C语言

在嵌入式开发上的理解稍微深了一点

那我们来小结一下

微控制器的C语言编程有什么特点 我想有这样

七到八个特点 第一 我们是在一个有限RAM

有限ROM或者大家不熟的同志

把它映射到有限内存

有限硬盘类似于这样的存储空间上

计算机系统上开发

我们所使用的芯片

它的RAM它的存储内存8K到32K

甚至大一点128K 然后它的ROM

也就相当于硬盘这样的

非丢失存储器 从32K到128K到1M撑死了

所以大家会发现这个容量是很小的

我们随便点个图片都是多少K

所以我们必须优化我们的程序

量入为出地使用我们的变量类型

使用库函数 能不用的就不用

不要把内存浪费掉 然后呢还有什么呢

就是我们的编程 是针对一个具体的

微控制器型号的 不同型号的微控制器

它内部的CPU有不同的构架 同构架的CPU

它有不同的外设

寄存器映射的地址可能有差异

往往我们需要正确的头文件

去正确地访问对应的这个寄存器

然后正确的形成链接得到可用的程序

那么有了IDE集成开发环境 比如说CodeWarrior

比如说IAR 比如说Keil

它最重要的一个功能

就是帮你把它整合 让你的开发变得简单

最后呢 还有一条

就是说我们的操作系统是没有的

在嵌入式系统里头我们可以有操作系统

但从底层开始开发的时候 绝大多数场合

是没有操作系统的

所以这就意味the developer 开发者

在座的诸位你们应该清楚你们在干什么

从我刚才讲的例子 从上电的第一个时序

跳转到main函数我做了什么没做什么

其实是完全由自己的程序决定的 然后呢

它还会偶尔涉及到中断程序的编写

这是一类在计算机上开发很多时候是碰不到的

由一个设定好的硬件条件 时间到了

键按下去了自动调用一个约定好的函数

来执行一件事形成一个中断的工作机制

然后呢 在这个C语言编程里头另外这个特点

我们所使用的函数库在集成开发环境

大家可能感觉不到它是个精简的函数库

会有一些数学函数或一些标准输入输出函数

它可能是没有的或者不完整的

比如说printf scanf

如果我们在计算机上编程的时候

我们很清楚standard I/O output是显示器

input是键盘

在一个嵌入的MCU上其实这些都没有对吧

所以我们要使用这一类函数

我们首先会define一个底层函数

帮它完成指明标准输入输出设备是什么

这些函数就可以用了

然后再就比如说这个文件系统函数

存储一个文件打开一个文件

因为操作系统都没有何来文件

再比如内存的管理函数

我们malloc一个内存空间

大家要注意

内存的分配和释放是要操作系统支持的

这些事情在嵌入式开发没有操作系统的时候

都是没有的

如果我们有朝一日给它移植了μC/OS

这样的嵌入式实时操作系统

我们会逐步地扩充这种功能

最后还有一条呢

就是在底层的8位 16位 32位

ARM Cortex M这样的MCU上

我们是统一地址映射

从0000地址一直到ff地址

没有操作系统

所以我们的程序是

Not relocatable 就是不可重定位的

定死了是哪个地址段函数在那个位置相互调用

它是一个不可重定地址的这样一种

运行和开发模式 最后一个特别小的点

要提示大家的就是我们的

嵌入式开发的这个函数

在没有操作系统的情况下

never return from main函数

所以就是这个main函数 我们在计算机上开发的时候

总会底下有个return main函数退出 退到哪

在dos下运行 它退到dos系统

在windows下运行的

退到windows操作系统

而实际上在计算机嵌入式系统里头

从main函数一旦退出外面

没有操作系统上电就是main函数

所以PC指针就会顺着往下跑 就跑飞了

所以我们任何一个嵌入式程序

在没有操作系统的情况下

一定会在最后放一个

死循环 endless loop

让它在那循环程序不要跑飞

那么这些要点我这么顺着拉下来

可能有的同学觉得讲的很快

无法完全理解

没有关系在这里点出来

我们有两个概念

第一 在日后的嵌入式开发里

会时不时地体会到这样一些特点

来加深你对开发的认识的

和你应该注意的事项

其次经过我们讲一讲

C语言背后的这些拔高的东西

有的同学能理解

有的同学已经很熟悉

这都没有关系

是告诉我们集成开发环境并没有那么神秘

拿着一个IDE我们能干

我没有IDE有个编译器

有个芯片手册我们也能干活

这就说明这个嵌入系统大家学到了一定的深度

是一个比较高阶的

然后C语言的掌握

不能说是无止境的

但是确实有很多可以深挖的东西

我这里推荐一本书 虽然作者我不熟

叫做这个 C语言深度剖析

有兴趣的同学可以学学

加深自己C语言的功底

对于嵌入式的开发会有很多的使用

所以到这节课

我们对于开发里头的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笔记与讨论

也许你还感兴趣的课程:

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