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