CortexM3汇编实践.docx
- 文档编号:29860052
- 上传时间:2023-07-27
- 格式:DOCX
- 页数:27
- 大小:1.65MB
CortexM3汇编实践.docx
《CortexM3汇编实践.docx》由会员分享,可在线阅读,更多相关《CortexM3汇编实践.docx(27页珍藏版)》请在冰豆网上搜索。
CortexM3汇编实践
CORTEX-M3汇编语言实践
编程
基于STM32F103系列MCU
2015-1-9
三两二锅头duck8815@
Cortex-M3汇编语言实践编程
写在前面:
接触Cortex-M3已经有一段时间了,大大小小也做了几个项目,可以说对这个系列
的片子有了一定的了解。
相对于8位单片机来说CM3给我的感觉实在是强了太多,其中
比较明显的感觉是存储上的扩充,这让我在编程的时候不必为了节省几十个字节的内存而
大费周章。
还记得当初用ATmega16写一个项目的时候,申请了一个比较大的缓冲区之后
要好多个模块共用,搞得程序结构非常乱而且还容易出错,当然这还只是一个小惊喜。
更
大的优势在于CM3先进高效的中断机制以及它丰富的外围接口和强大的片内功能,这些
让我在开发的过程中深切的体会到CM3相对于8位单片机的优越性。
而相对于高端的
ARM芯片(ARM7、ARM9等)来讲CM3又以它精巧灵活的特性让我深深的喜欢上了这个
系列的芯片,尤其是新的指令集给人感觉耳目一新,让原来繁杂的芯片初始化工作最终浓
缩到数十行汇编代码中,CM3以它的精简和易用再一次吸引了我。
经过一段时间的学习和使用,个人觉得如果想把一款芯片用的得心应手一些必要的
理论知识还是值得花时间去学习和研究的,所以在工作之余就有了这个文档。
本文主要从
汇编语言的角度去阐述和学习Cortex-M3的体系结构以及基本工作原理,实现对一些片内
功能的配置与应用同时还包括一些简单的外设应用。
本文重点不在于深究汇编指令码,而
是通过使用汇编语言让读者从计算机的角度出发去思考问题,了解计算机的工作原理和步
骤,所以汇编指令码的细节内容在这里则不会深入讨论。
声明:
本文内容完全属于个人学习总结以及个人的理解和看法,在学习过程中借鉴过很多
资料包括来源于网络的资料,如果文中内容让您感觉不适请联系作者删除。
由于作者水平有限文中难免出现错误,如遇到错误烦请您不吝指正,并欢迎您与作
者进行交流沟通duck8815@。
作者默认您对CM3有一定的了解,并且具备基于CM3芯片的C语言开发经验。
文
中使用的芯片是STM32F103ZE,软件开发环境用的是***(因为涉及到版权问题本来考虑
在Linux平台下进行编写代码但是考虑到大家可能很少有人用Linux开发程序,所以选了
和大家一样的开发环境)。
下面作者将从一个空的汇编工程展开本文。
-1-
Cortex-M3汇编语言实践编程
1第一个汇编工程
我们创建工程的时候IDE会提示我们是否导入启动文件,而这个启动文件就是用汇编语言
编写的,在这里简单说一下汇编工程的创建步骤:
新建一个工程
创建工程的步骤和平时创建工程一样,之余工作目录之类的东西相信各位自己也都有自己
的习惯这里就不再赘述。
Figure1-1新建工程
工程名字自己随便写一个就行
Figure1-2工程名字
选择对应芯片,在出现是否创建启动文件时选择不创建。
Figure1-3
-2-
Cortex-M3汇编语言实践编程
配置工程
修改工程目录以及配置目标文件输出位置,保证工程目录文件便于管理。
Figure1-4创建工程目录
单击途中1号位置→单击2号位→选择obj目录→4好位置选中创建HEX文件
Figure1-5配置目标文件输出位置
-3-
Cortex-M3汇编语言实践编程
按照图中序号顺序完成后续配置
修改部分工程信息,1、2两处改成自己对应的工程名与组名
Figure1-6修改工程信息
-4-
Cortex-M3汇编语言实践编程
新建一个文本文件,并保存为汇编源程序文件
Figure1-7新建文件
Figure1-8保存文件
将保存好的文件添加到工程,添加完毕效果如下:
Figure1-9添加到工程
-5-
Cortex-M3汇编语言实践编程
编写程序
Figure1-10第一段汇编程序
对于上面的这段程序需要详细的解释一下:
1.正文第1行以分号(;)开始,在ARM汇编里表示单行注释,ARM汇编不支持
多行注释。
2.第三行代码的内容在ARM汇编中被称之为伪代码,这行代码的意思是定义一
个段[section],段是汇编语言组织代码的基本单位,功能与C语言中的函数类
似,代码写在段的外面也允许,但是会出一些问题。
这一行代码中AREA伪指
令表示声明一个段,RESET是段的名字,在当前平台上RESET段也是系统默认的
入口,所以在代码中有且只有一个RESET段。
CODE表示短的属性,代表当前
段为代码段,和CODE功能类似的还有DATA、STACK、HEAP等分别表示数据
段、栈和堆。
READONLY是当前段的访问属性表示只读,还有READWRITE表示
可读可写。
3.第4行代码只有一个单词ENTRY,表示程序入口,即CPU会从ENTRY标记的这
一行代码处开始执行程序,类似与C语言中的main函数,但是与C语言不同
的事ARM汇编允许一个程序有多个入口,但是用户需要指定一个入口作为主
-6-
Cortex-M3汇编语言实践编程
入口。
关于入口的定义你还可以在MDK参考手册上找到如下解释:
Figure1-11ENTRY官方解释
4.第6行代码“STARTPROC”与第12行代码“ENDP”通常成对出现,表示定义一个
子程序,其中START是子程序名或者叫做标号,在ARM汇编当中代表当前子
程序的入口地址,相当于C语言中的函数名的作用。
同时大家应该注意到这个
START的位置是顶格写的,这是因为在ARM汇编中规定:
标号必须要顶格写,
而指令、伪指令、伪操作等指令码(或者称为关键字例如MOV、ADD等属于
指令、LDR、ADR等属于伪指令、DCD、IF/ENDIF等属于伪操作)在书写的时候
需要有前导空格,一般我们用一个或者多个TAB代替。
例如我们把第8行内容
变成如下形式:
-7-
Cortex-M3汇编语言实践编程
编译程序我们会发现如下错误:
事实上编译器是把第8行指令的MOV当成标号来处理,而把R0当成的指令操
作码,因为无论是指令、伪指令、还是伪操作中都不包含R0这条指令码,所
以编译器会报出上述错误。
同理如果把第6行的START加上前导空格也会报出
类似的错误,在这里就不重复展示了。
5.第7行EXPORTSTART是一条链接属性声明语句,表示START这个子程序(确
切的说是这个标号或叫标识符)在其他汇编语言文件中也可以被调用,但是如
果其他文件中想调用START还需要将这个标号进行导入,导入的方法
是”IMPORTSTART“,EXPORT与IMPORT是一对操作符,用EXPORT声明的标号
只有通过IMPORT导入以后才能正常使用。
6.最后14行END表示当前汇编语言源文件的结束,ARM汇编编译器在编译期对
预处理过的汇编语言源文件进行逐行扫描,当遇到END关键字的时候表示当前
文件扫描结束,编译器将不会处理写在END之后的任何内容。
经过上面的介绍我们得出一些结论,这些结论是关于编写一个ARM汇编程序的必须因
素,它们是:
1.一个汇编语言程序至少要有一个代码段,否则芯片将不知道执行什么内容。
2.一个汇编语言程序至少要有一个入口,否则芯片将不知道该从什么地方开始执
行程序。
3.一个汇编语言源文件应该以END进行标记结束。
4.在当前IDE环境下系统默认的段名字叫RESET,这里没有明确说RESET段必须
是代码段,所以也可以是其他属性的段。
-8-
Cortex-M3汇编语言实践编程
编译、调试程序
接下来我们把刚刚写好的程序进行编译,我们会发现这里有个警告:
Figure1-12第一个程序编译警告
双击这个警告会跳出如下内容:
Figure1-13警告对应内容
自习查看我们会发现这个以.sct结尾的文件实际是编译器在编译期生成的一个中间文件,
警告内容说无法匹配*(InRoot$$Sections)这个段,实际上这个段就是编译器在编译期给C语
言中的main函数定义的一个别名,因为我们没有写.C文件更没有写main函数所以编译器
自然就不能匹配到main函数了,这个问题属于编译器的原因造成的,即编译器在编译程
序的时候需要有main函数,否则将会报出警告。
处理的办法是在工程当中添加一个C语
言源文件并写入一个空的main函数。
-9-
Cortex-M3汇编语言实践编程
Figure1-14
将上述写好的文件保存并添加到工程后编译:
Figure1-15
这个时候又变成了另外一个警告,意思是有多个程序入口点被定义,但是没有指定一个主
入口,原因是main函数本身也是一个程序的默认入口与我们子汇编文件中写的ENTRY意
思是一样的,所以我们需要指定一个主入口或者只保留一个入口就可以了,因为程序中不
需要很多入口所以只保留main删除ENTRY。
Figure1-16
-10-
Cortex-M3汇编语言实践编程
修改后再次编译就没有错误了,但是只有这几行代码芯片还是无法正常工作的,所以暂时
还不能使用设备,那我们只好借助仿真器来调试一下看一看程序的结果,首先配置调试目
标,需要去掉Runtomain这个选项,让程序从汇编代码开始执行:
Figure1-17调试配置
点击调试选项则会出现以下情况,看看反汇编器以及寄存器的值,完全不知道程序执行到
哪里了。
Figure1-18
-11-
Cortex-M3汇编语言实践编程
点击复位之后可以看到以下界面,鼠标选中一行代码,反汇编器会自动跳到对应的位
置。
Figure1-19
首先观察2号标记,也就是PC寄存器的值是0x0102F04E,同时注意我们在程序第7
行的位置已经添加了断点,断点处代码对应的地址应该是0X08000000,图中3号所标
记的地址,但是这个PC的地址很明显说明是程序跑飞了,也就是说程序根本没有按照
我们预想的顺序去执行。
再观察3和4说明在地址0x8000000处存放的正好是我们编写的第一行代码”MOV
R0,#1“,而这条汇编指令对应的二进制机器码是0xF04F0001。
实际上一般来讲系统
代码都应该是从0x00地址处开始的这里为什么是0x08000000呢?
那是因为这里的地
址(0x08000000)是硬件的物理地址,而那个0x00地址只是逻辑地址,二者之间是有
一个映射关系的,IDE在这里已经帮我们把映射之后的地址显示出来了,所以我们在这
里看到的实际的物理地址(0x08000000)而不是逻辑地址(0x00000000)。
这一点也
可以通过下图来证明
-12-
Cortex-M3汇编语言实践编程
Figure1-20
图中的配置选项里分别给出了内存中只读存储区(READONLY,经常用来存放代码或者
常量)与读写存储区(READWRITE,用于存放数据、变量等)的起始地址与大小,这
个地址是系统默认给出的我们一般不会做修改。
我们的代码段的访问熟属性是
READONLY,那第一行代码的地址自然是从0X08000000处开始。
如果我们再观察一下
CM3的存储空间分配则不难发现0X08000000恰好属于代码段,而0X20000000恰好是
SRAM的起始地址。
Figure1-21
-13-
Cortex-M3汇编语言实践编程
到这里我们仍然没有解决一个问题,那就是为什么程序会跑飞了呢?
CM3的启动过程
如果想彻底解决程序跑飞的问题我们就需要好好的研究一下CM3在启动的时候都做了哪
些事情。
一般来讲芯片启动后都是从0x00000000地址处开始执行,然后直接进入系统的RESET异
常处理,在RESET处理中可能做一些堆栈初始化、寄存器复位等工作,处理完毕之后最终
跳转到C语言编写的main函数当中执行C语言程序。
CM3也不例外,同样是从
0x00000000地址处开始执行程序,但是它和其他的芯片有一点差别,那就是Cortex-M3放
在0x00000000地址处的并不是RESET的入口地址而是其他的东西,是什么呢?
请看下表
(表格出自《Cortex-M3权威指南》英文版第40页):
Figure1-22Cortex-M3向量表
通过观察上表我们不难发现,CM3的向量表0x00地址处存放的并不是RESET程序的入口
而是系统主堆栈的入口地址,而0x04地址处存放的才是真正的RESET程序入口地址。
-14-
Cortex-M3汇编语言实践编程
原来CM3启动时虽然是从0x00000000地址处开始但是并没有直接进入RESET程序,而是
先把存放在0x00000000地址处的数据赋值给主堆栈指针(也就是MSP寄存器,因为主堆
栈和线程堆栈是绑定地址的,两个寄存器的名字都是R13,只是在处理器模式进行切换的
时候由硬件自动切换堆栈,所以我们在编写程序的时候无论是访问主堆栈
线程堆栈
芯片这么做
的目的是因为默认开机后芯片处于特权级别下的Handler模式,该模式下有一些操作必须
要用的主堆栈(MSP),所以系统在启动的时候必须要先设置主堆栈,然后再进入RESET
过程。
那如何来证明上面所说的是正确的呢?
我们先来看这样一张图:
Figure1-23内存查看器
Figure1-24中显示的是0X08000000处的内存信息,这个地址是存储器的物理地址,也是
经过映射之后的0x00000000地址处。
再结合Figure1-25中3号和4号旁边的信息可以确
定上图中红色矩形框中所选中的数据正是指令”MOVR0,#1“和”MOVR1,#2“的二进制机器
码。
而R13(SP)里面的内容是0x0001F04F(是0x08000000处的内容,小端模式),R15
(PC)的内容是0x0102F04E(是0x08000004处的内容,小端模式。
Thumb2指令至少是
半字对齐的,所以PC寄存器的最低有效位
0x0102F04E而不是内存中存放的0x0102F04F)。
根据上面的解释,PC在程序开始前被赋值为0x0102F04E,在这个地址里存放的内容我们
根本不知道是什么东西,所以程序跑飞也就成了必然事件。
解释了程序为什么会跑飞那接下来就来解决这个问题。
其实程序跑飞的主要原因就是在系
统启动的时候PC被赋予了错误的值,想解决这个问题其实很简单,把正确的值丢给PC就
行了。
那么如何才能把正确的值给PC呢?
这里我们就不得不提到CM3的异常向量表也就是
Figure1-26中所展示的内容,这张表中标记处了CM3在遇到各种系统异常时候对应的处理
程序的入口地址,当然也包括了RESET(系统复位)时候的入口。
所以解决PC赋值最合
适的办法就是在我们的程序中构建一张和Figure1-27内容一模一样的表,而且这张表的起
始地址应该是逻辑的0x00000000。
因为编译器在链接期间会默认把命名为”RESET“的段放
到镜像的起始地址处也就是0x00地址,所以我们需要在RESET段中构建向量表。
因为也
-15-
Cortex-M3汇编语言实践编程
没有比start_up.s里面定义的更好的结构和书写方式,所以这里直接借用IDE自动生成的
向量表部分的代码。
这个向量表应该是只读属性,而且应该是一个数据段(因为向量表中
存放的都是一些程序的入口,类似于一个函数指针数组或转移表,所以是数据段)所以之
前写的代码就要作一番改动了,具体如下:
Figure1-28代码实现向量表
上图中的代码就是系统向量表的实现。
其中第4行是向量表的定义,向量表直接处于
RESET段的最开始处,段的属性为只读数据段,这个段在编译器链接程序的时候将会被放
在镜像文件的最开始处。
第5、6、7行表示将向量表的属性(起始地址、大小)进行全局
声明,第24行是在向量表的尾部添加部分空数据用来保护表结构不被意外修改。
表中的
数据要么是异常处理子程序的入口要么是数据段的入口,所以既然这里用到了这些标号那
在程序中就要有对应的定义。
接下来补全这些定义,先添加系统堆栈定义:
Figure1-29系统堆栈定义
-16-
Cortex-M3汇编语言实践编程
在RESET段之前添加堆栈定义,这里需要对MSP和PSP进行分别定义,而且要注意这个
段的属性是可读可写的数据段,NOINIT表示数据段是未初始化的或初始化为零。
其只包
含零初始化的空间保留命令SPACE或DCB、DCD、DCDU、DCQ、DCQU、DCW或DCWU。
可以决定在链接时AREA是未初始化的还是零初始化的。
这里要注意,上图中第10行和第18行中两个MSP_TOP,第9行表示定义一个大小为
MSP_SIZE的连续空间,因为CM3的堆栈操作方式固定为递减堆栈,所以栈顶应该在数据
之后也就是第10行的位置。
第18行是向量表的首个元素也就是MSP的入口地址,这里
应该对应的就是我们之前定义的MSP的栈顶即MSP_TOP。
接下来是异常处理子程序入口定义,这里只列出部分代码:
Figure1-30异常处理子程序
上图中Reset_Handler这个子程序就是系统启动后开始执行代码的位置了,这里的|.TEXT|
又是一个默认设置,这个标号是系统默认代码段的名字。
一般在这个子程序里做一些必要
的初始化工作之后就可以跳到C语言的main函数执行了,但是我们现在是写汇编程序所
以暂时先不进行跳转。
那么接下来就可以把之前的代码写在Reset_Handler中测试一下了,程序中添加如下代
码:
Figure1-31编写测试代码
-17-
Cortex-M3汇编语言实践编程
为了不让程序跑飞,在50行处添加死循环,编译:
Figure1-32编译程序
调试:
Figure1-33调试代码
点击单步执行,三行代码执行完毕后对应寄存器中的内容已经发生了变化,到此我们的第
一个汇编程序就结束了。
-18-
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- CortexM3 汇编 实践
![提示](https://static.bdocx.com/images/bang_tan.gif)