导图社区 ARM编程思维导图
计算机原理:四、ARM编程。这部分内容对于很多人来说都是拦路虎,这个由我亲自制作的思维导图希望会对你的学习有所帮助!喜欢的话请给我点个赞吧!
编辑于2021-06-16 19:53:48ARM 编程
• 本章以Cortex-M4处理器(具体为 STM32F4xx)为核心讲述 ARM 编程,分别介绍了: • ARM汇编程序设计 • C与汇编混合程序设计
ARM汇编语言程序格式
一个完整的 ARM 汇编语言程序包含若干条汇编语言 语句,每条汇编语言语句由标号、指令、伪指令、汇编 指示命令和注释组成,语句格式如下:  以程序段为单位组织代码,包括若干代码段和数据段,至少有一个代码段。
数据常量定义汇编指示命令
数据常量定义伪指令EQU用于为程序中的常量、标号等定义一个等效的字符名称, 类似于C语言中的#define。 EQU伪指令定义的字符名称,当表达式为32位常量时,可指定表达式的数据类型, 有三种类型:CODE16、CODE32和DATA 。
名称 EQU 表达式{,类型 } ;其中EQU可用“*”代替
数据变量定义汇编指示命令
数据变量定义汇编指示命令用于定义ARM汇编程序中的变量、对变量赋值以及定义寄存器的别名等操作。
GBLA、GBLL和GBLS
语法格式: GBLA ( GBLL 或 GBLS ) 全局变量名 GBLA Test1;定义一个全局的数字变量,并初始化为 0,变量名Test1 GBLL Test2;定义一个全局的逻辑变量,并初始化F(假) GBLS Test3;定义一个全局的字符串变量,并初始化为空
LCLA、LCLL和LCLS
LCLA、LCLL和LCLS用于定义一个ARM程序中的局部变量,并将其初始化,变量的作用范围以其所在宏的特定实例为限。 语法格式: LCLA(LCLL或LCLS) 局部变量名 LCLA Test4 ; 定义一个局部的数字变量,并初始化为 0 LCLL Test5 ; 定义一个局部的逻辑变量,并初始化为F(假) LCLS Test6 ; 定义一个局部的字符串变量,并初始化为空
SETA、SETL和SETS
语法格式: 变量名 SETA(SETL或SETS) 表达式 SETA、SETL和SETS给一个已定义的全局变量或局部变量赋值 Test1 SETA 0xaa ;将该变量赋值为0xaa Test2 SETL {TRUE} ;将该变量赋值为真 Test3 SETS “Testing” ;将该变量赋值为“Testing”
RLIST
语法格式: 名称 RLIST {寄存器列表} RLIST可用于对一个通用寄存器列表定义名称,定义的名称可在 ARM指令LDM/STM中使用。在LDM/STM指令中,列表中的寄存器访问次序为根据寄存器的编号由低到高,而与列表中的寄存器排列次序无关。 
内存分配汇编指示命令
内存分配汇编指示命令一般用于为特定的数据分配存储单元,同时完成已分配存储单元的初始化。
DCB: 标号 DCB 表达式
DCB分配一片连续的字节存储单元,并用指定的表达式初始化。 表达式可以为0~255的数字或字符串。DCB也可用等号(=)代替。
DCW(或DCWU): 标号 DCW(或DCWU) 表达式
DCW或DCWU分配一片连续的半字存储单元。表达式为程序标号或数字表达式。DCW分配的字是半字对齐的,DCWU分配的字并不严格半字对齐。 DataTest DCW 1 ,2 ,3 ;分配一片连续的半字存储单元并初始化
DCD(或DCDU): 标号 DCD(或DCDU) 表达式
DCD或DCDU用于分配一片连续的字存储单元。表达式可以是程序 标号或数字表达式。DCD也可用“&”代替。用DCD分配的字存储单元 是字对齐的,而用DCDU分配的字存储单元并不严格字对齐。 DataTest DCD 4 ,5 ,6 ;分配一片连续的字存储单元并初始化
DCQ(或DCQU):标号 DCQ(或DCQU) 表达式
DCQ或DCQU用于分配一片以8字节为单位的连续存储区域,并用指定 的表达式初始化。用DCQ分配的存储单元是字对齐的,而用DCQU分配的存 储单元并不严格字对齐。 注意:DCQ不能给字符串分配空间。
DCFS(或DCFSU):标号 DCFS(或DCFSU) 表达式
DCFS或DCFSU用于为单精度的浮点数分配一片连续的字存储单元。每 个单精度的浮点数占据一个字单元。用DCFS分配的字存储单元是字对齐的,而用DCFSU分配的字存储单元并不严格字对齐。
DCFD(或DCFDU):标号 DCFD(或DCFDU) 表达式
DCFD或DCFDU用于为双精度的浮点数分配一片连续的字存储单元。每个双精度的浮点数占据两个字单元。用DCFD分配的宇存储单元是字对齐的,而用DCFDU分配的字存储单元并不严格字对齐。
SPACE:标号 SPACE 表达式
用于分配一片连续的存储区域并初始化为0。SPACE可用“%”代替。
MAP:MAP 表达式 {,基址寄存器}
定义一个结构化的内存表的首地址。MAP可用“^”代替。基址寄存器为可选 项,当基址寄存器选项不存在时,表达式的值即为内存表的首地址,当该选项存 在时,内存表的首地址为表达式的值与基址寄存器的和。 MAP通常与FIELD配合使用来定义结构化的内存表。
FIELD:标号 FIELD 表达式
定义一个结构化内存表中的数据域。FIELD可用“#”代替。表达式的值为当 前数据域在内存表中所占的字节数。 FIELD常与MAP配合使用来定义结构化的内存表。MAP定义内存表的首地址, FIELD定义内存表中的各个数据域。 注意:MAP和FIELD仅用于定义数据结构,并不实际分配存储单元。
汇编控制汇编指示命令
IF、ELSE、ENDIF
IF逻辑表达式 指令序列1 ELSE 指令序列2 ENDIF IF、ELSE、ENDIF能根据条件的成立与否决定是否执行某个指令序列。当IF后 面的逻辑表达式为真,则执行指令序列1,否则执行指令序列2。
WHILE 、WEND
WHILE 逻辑表达式 指令序列 WEND 当WHILE后面的逻辑表达式为真,则执行指令序列一直到逻辑表达式为假。
MACRO、MENO
MACRO $标号宏名 $参数1,$参数2, …… 指令序列 MEND MACRO、MEND将一段代码定义为一个整体,程序中通过宏指令多次调 用该段代码。$标号在宏指令被展开时,标号会被替换为用户定义的符号, 宏指令可用一个或多个参数,宏指令被调用时,参数被相应的值替换。在代码较短且需传递参数较多时,可使用宏指 令。
MEXIT:MEXIT用于从宏定义中跳转出去
子程序结构设计
BL funcname
BL指令完成两个操作: 1) 将子程序的返回地址放到LR寄存器中; 2) 将PC寄存器的值设置为目标子程序的第一条指令地址,即跳转到子程序。 在子程序中,可以通过将LR寄存器的值传送到PC寄存器来实现子程序 返回,具体指令可以为: BX LR 或 MOV PC, LR 或 STMFD SP!, {R0-R7,LR} ; 把要保护的寄存器内容压入堆栈 LDMFD SP!, {R0-R7,PC} ; 恢复寄存器内容,原LR值传送到PC
两个问题
1) 保护现场的问题
保护现场就是保护子程序调用时的CPU资源的状态,特别是寄存器 资源的状态。CPU寄存器对所有子程序都是可访问的,如果不适当保护, 程序运行逻辑可能会出错。 保护现场一般是通过批量存储指令 STMFD 和批量加载指令 LDMFD 完成。 在进入子程序后调用STMFD把要保护的寄存器内容压入堆栈, 在子程序返回时调用LDMFD恢复寄存器内容。
2) 参数传递的问题
参数传递可以通过寄存器传递,也可以用堆栈传递。
ARM可执行映像文件的构成以及各个段在存储器中的位置
ARM映像文件就是可执行文件,也称为 Image 文件或 ELF(Executable and Linkable Forma) 文件,包括bin或hex两种格式,可以直接烧入ROM(Flash)中。 在调试过程中,我们调试的是.axf文件,这也是一种映像文件,它只是在bin文件中加了一个文件头和一些调试信息。
ARM可执行映像文件的构成
映像文件一般由域(Region)组成,域最多由三个输出段组成(RO,RW,ZI)组成,输出段又由输入段对应生成。
加载域:是指程序烧入ROM中的状态,一般来说ROM里的整个映像文件所在的地址空间就是加载域;
运行域:是指程序运行时的状态,在运行时,程序中的RW段和ZI段必须装载到可读写的RAM中。
各个段在存储器中的位置
对于加载域中输出段的地址位置,一般来说RO段后 面紧跟着RW段,通常是地址连续的。
对于运行域中输出段的地址位置,一般来说RO段在 Flash地址空间,RW和ZI段在RAM地址空间,RO段与 RW/ZI段地址不连续,但RW和ZI一定是连续的。
RW段和ZI段初始化代码
C与汇编程序的相互调用
方式: 把C语言代码封装为函数,在汇编代码中调用 把汇编代码封装为函数(子程序),在C代码中调用 在C代码中使用嵌入汇编(Embedded assembler ) 在C代码中使用内联汇编(inline assembler)
AAPCS标准
ARM公司推出的 AAPCS (ARM Archtecture Procedure Call Standard) 标准,规定了一些函数间 调用的规则,这些规则包括函数调用过程中: 寄存器的使用规则, 数据栈的使用规则, 参数的传递规则。 有了这些规则之后,C语言程序就可以和汇编程 序相互调用。
一
1.父函数与子函数间的入口参数依次通过R0~R3这4个寄存器传递。父函数在调用子函数前先将参数存入到R0~R3中, 若只有一个参数则使用R0传递,2个则使用R0和R1传递, 依次类推,当超过4个参数时,其它参数通过栈传递。当子函数运行时,根据自身参数个数自动从R0~R3或者栈中读取参数。
二
2.子函数通过R0寄存器将返回值传递给父函数。子函数返回时,将返回值存入R0,当返回到父函数时,父函数读取R0 获得返回值。
三
3.发生函数调用时,R0~R3是传递参数的寄存器,即使是父函数没有参数需要传递,子函数也可以任意更改R0~R3寄 存器,无需考虑会破坏它们在父函数中保存的数值,返回 父函数前无需恢复其值。AAPCS规定,发生函数调用前,由父函数将R0~R3中有用的数据压栈,然后才能调用子函数,以防止父函数R0~R3中的有用数据被子函数破坏
四
4. R4~R11为普通的通用寄存器,若子函数需要使用这些寄存 器,则需要将这些寄存器先压栈然后再使用,以免破坏了 这些寄存器中保存的父函数的数值,子函数返回父函数前 需要先出栈恢复其数值,然后再返回父函数。AAPCS规定, 发生函数调用时,父函数无需对这些寄存器进行压栈处理 ,若子函数需要使用R4~R11 ,则由子函数负责压栈,以防止父函数R4~R11中的数据被破坏。
五
5.编译器在编译时就确定了函数间的调用关系,它会使函数 间的调用遵守3、4条规定。但编译器无法预知中断函数的调用,被中断的函数无法提前对R0~R3进行压栈处理,因此需要在中断函数里对它所使用的R0~R11压栈。对于中断函数,不遵守第3条规定,遵守第5条规定。
六
6. R12寄存器在某些版本的编译器下另有它用,用户程序不能使用,因此我们在编写汇编函数时也必须对它进行压栈处理,确保它的数值不能被破坏。
七
7.R13寄存器是堆栈寄存器(SP),用来保存堆栈的当前指针
八
8.R14寄存器是链接寄存器(LR),用来保存函数的返回地址。
九
9.R15寄存器是程序寄存器(PC),指向程序当前的地址。
C中写汇编
嵌入汇编
主流ARM工具链的C编译器支持嵌入汇编特性,它可以在C程序中实现汇编函数( 子程序),具体做法是在函数声明前增加 __asm 关键字。
例程

内联汇编
主流ARM工具链的C编译器支持内联汇编特性,它可以在C程序中嵌入汇编程序, 实现一些高级语言没有的功能,提高程序执行效率。要加入__asm
例程

内部函数
有些情况下,需要使用无法用普通C代码实现的特殊功能操作。 除了使用汇编函数(子程序)、内联汇编、嵌入汇编来进行汇编语言编程外 ,实现特殊功能操作的另一个方法是使用内部函数(Intrinsic functions)。特殊功能操作包括系统初始化、外设访问等
CMSIS
CMSIS(Cortex Microcontroller Software Interface Standard)是ARM公司为统一软件结构而为Cortex微控制器制定的软件接口标准。
CMSIS-CORE
CMSIS-RTOS
CMSIS-DSP
编译器相关的内部函数
指令分类
伪指令:在汇编阶段被翻译成执行指令的组合 汇编
指示指令:由汇编器负责解释执行,仅在汇编过程 中起作用的用于完成准备工作的指令。
输入段
ARM汇编语言程序源文件中一般有代码段和数据段等多个段,这就是所谓的输入段 输入段经过 汇编处理链接后就变成了映像文件中的RO段和RW段, 还有ZI段,也就是三个输出段(RO,RW,ZI)。