前言:
最近从学校实验室拿到了WarShip STM32F1的开发板,想着趁着疫情被封在宿舍里系统得学习下STM32的Cortex-M3。众所周知,学单片机如果不去学汇编等于没学。在我看《Cortex-M3权威指南》这本书的时候,完全被32位机器指令系统的复杂程度给震撼到了。
我原来只学过51单片机,所以在学Cortex-M3架构时感到非常吃力,于是就想像51单片机一样在电脑上测试Cortex-M3(以下简称为CM3)的指令。我在网上找了好多教程,但都是没有说出完整的流程。我在仔细研究了各位先驱的方法和官方给的start-up文件后,找到了下面这个方法。
本文内容完全属于个人学习总结以及个人的理解和看法,在学习过程中借鉴了很多来自于网络的资料。我自己的水平有限,里面难免回出现错误,如遇到还请您不吝赐教。
我默认您对CM3有一定的了解,并且具备一定的基于CM3的C语言开发经验。文中所使用的芯片为STM32F103ZE,所使用的环境为keil5。
1.创建第一个汇编工程
创建工程的步骤与平常创建工程的步骤并没有太大的区别。对于工程的目录,大家都有不同的习惯,我这里就不再赘述。
工程名称随便写一个就行。
接下来选择对应的芯片。
注意在接下来这个配置环境窗口,要按照以下勾选,否则在编译时会报错。勾选上之后点下方的ok即可。
接下来我们要对可以了的debug模式进行设置,以免在编译完成后无法正常的进入的debug模式。
首先点击这个魔法棒的标志.
在新打开的窗口中找到debug标签,然后将Use Simulator 选项给勾选上,下方的Run to main()也勾选上。下方的Dialog DLL:中的内容换成DARMSTM.DLL,将Parameter:下的内容换成-pSTM32F103ZE。如下图所示
2.编写汇编程序
接下来我们就能在上面写代码了。在这里我想先放上一个乍一看正确但是确实错误的写法。了解这个写法为什么是错误的对于我们理解CM3芯片的启动过程有着不可或缺的作用。
AREA REST,CODE,READONLY
ENTRY
START PROC
EXPORT START
MOV R0,#1
MOV R1,#2
MOV R2,#3
ENDP
END
在这里解释下上面这短短的几行汇编代码。
1.第一行AREA REST,CODE,READONLY
这一行在汇编中其实应该被称为伪指令,这几行代码的意思是定义一个段【section】,这个段是汇编语言组织代码的基本单位(应该只是在STM32上才有的,写51汇编时完全没有这一说),这个段有点类似于C语言中的函数。AREA为伪指令,表示声明一个段,这个段的名称为RESET。而RESET段是默认的入口,所以在汇编程序中有且只有一个RESET段。CODE表示这个段的属性,表示当前的段是代码段。相应的,表示段的属性的还有DATA、STACK、HEAP等,它们分别表示数据、栈和堆。READONLY表示这个代码段的访问属性为只读,当然还有表示允许读写的READWRITE。
2.第二行ENTRY
该表示程序的入口,与C语言的main相同,但是汇编允许程序有多个入口,这个以后再说。
3.第三行START PROC
这一行的START PROC 与第十行的ENDP成对出现,表示定义的一个子程序,也可以说是子函数。START为子程序的标号,你可以叫任何名字,但是标号一定要顶格写,编译器会将任何顶格写的当做子函数的入口。这里简单介绍下ARM的汇编规定,ARM汇编规定:标号一定要顶格写,而指令、伪指令、伪操作等指令码的前面一定要有前导空格。在书写是我们为了方便还有美观,一般有一个或是多个TAB代替。
4.第五行EXPORT START
这一行是一个链接属性的声明的语句,表示START这个标号和其对应的子函数在其他的汇编语言文件中也能够被调用。调用的时需要对这个标号进行导入,导入的方法为IMPORT START。EXPORT与IMPORT一般也是成对出现的,只有用EXPORT声明过后的标号才能使用IMPORT调用。
5.第十二行END
END也是个伪指令,用处是告诉编译器,END之后的所有都不在编译。一般用END表示该汇编文件的结束。
现在我们对我们刚才写好的汇编程序进行编译了。
这时会报出一个错误,编译器说我们没有指定主函数,这个简单我们再添加一个C语言代码,在代码中写上个空白的主函数。就像下面这样:
此时要注意,还记得我们在汇编程序中写的 ENTRY吗,ENTRY就是程序入口,现在在C语言中还有一个main,这就代表着我们定义了多个入口。这时候,我们将汇编文件中的ENTRY语句给删除。再进行编译。
这时候编译器就不再报错了,但我们是否真的写对了还需要验证。接下来打开debug仿真。
然后我们就发现我们的程序跑飞了,我们想修改的寄存器的值都不正确。这时候就需要我们仔细研究下CM3的启动以及程序执行过程了。
3.CM3的启动过程
对于51单片机,在启动是直接从0x0000H开始执行的,然后直接读到一个跳转指令,直接跳到主函数中进行执行。大部分芯片也是这样的,从0X00000000H进行执行,进入系统得RESET中断,进行一些初始化工作,然后跳转到主函数进行执行。但是,CM3却不是这样的。
上图为CM3的中断向量表,我们可以看到,CM3在0X00H中存放的不是RESET中断的入口,而是其他的一些东西。里面存放的为MSP即为系统的主堆栈指针。在复位后CPU会首先将0X00000000H的值给主堆栈指针MSP。我们都知道这个CM3有两个堆栈MSP和PSP,这两个堆栈指针的寄存器的名字都为R13,只是在处理器切换模式时自动切换堆栈。那么在我们按下复位后CPU为什么要这样做。这是因为,在CM3架构的CPU复位后系统进入的为特权级别的Handler模式,Handler模式有些必须使用MSP。为了能够正常初始化,所以CPU先在0X00000000H处将MSP进行初始化。这也就是说我们的程序将PC指针赋予了错误的值,为了解决这个问题,我们需要看下start_up.sl里面的中断向量表。
我们发现,在复位后,CPU执行了两个操作。一个是SystemInit另一个是完全不存在的__main程序(或者说是空白的)而不是去执行我们写的START函数。既然CPU在执行完SystemInit之后去执行__main,那么我们在__main中多怕用我们自己写的函数或者是说直接把我们的函数的标号改成__main就行了。这时候,我们的汇编文件中就要表明了主函数的入口,那么C语言写的空白的主函数就完全没有任何用处了,直接删掉就行了。在最后一行记得加上B .以防代码跑飞。
AREA REST,CODE,READONLY
ENTRY
__main
EXPORT __main
MOV R1,#1
MOV R2,#2
MOV R3,#3
B .
ENDP
END
结果如下,这时寄存器的值就是我们想要的正确值了。