【ARM-Cortex-M3/4】汇编基础与常用指令集
令用于产生软中断,从而实现在用户模式变换到特权模式,CPSR保存到管理模式的SPSR 中,执行转移到SWI向量,在其它模式下也可使用SWI 指令,处理同样地切换到特权模式。恢复寄存器列表, 先出栈的数据(即原先最后入栈的r0)保存到r0, 最后出栈的数据(即原先最先入栈的r14)保存到r14。即C语言extern,指示编译器当前的符号不是在本源文件中定义的,而是在其他源文件中定义的,在本源文件中可
CortexM0、Cortex-M0+和Cortex-M1处理器只支持多数16位指令和部分32位指令,Cortex-M3支持的32位指令更多。Cortex-M4处理器支持剩下的SIMD等DSP提升指令集可选的浮点指令。
1 ARM汇编语言基础
1.1 基本语法
标号
操作码{cond}{S} 操作数1, 操作数2, … ;注释
标号
:可选,必须顶格写,作用是让汇编器计算程序转移的地址(可以是C函数名)cond
:可选,即指令执行条件,如果不写则使用默认条件AL
(无条件执行)。S
:可选,表示指令执行后,会修改PSR寄存器操作码
:即指令助记符,前面必须至少有一个空格操作数
:第1个操作数一般为本指令的执行结果存储处。形成操作数的有效地址的方法称为操作数的寻址方式,分为:- 隐含寻址:不是明显地给出操作数的地址。而是在指令中隐含着操作数的地址
- 立即寻址:立即寻址方式的操作数,即操作数本身即为地址,即立即数,必须以
#
开头,且立即数不能作为指令中的第一操作数(目的操作数),如:MOV R1, #'A'
。 - (寄存器)直接寻址:在指令中直接给出有效地址,操作数的地址是直接给出的,如:
LDR R0, R1
,将源寄存器R1
的地址加载到目的寄存器R0
- (寄存器)间接寻址:操作数中存放的值作为其有效地址,此时必须在操作数两端加方括号
[]
,如:LDR R0, [R1]
,将源寄存器R1
中保存的值加载到目的寄存器R0
中(类似C指针) - 相对寻址:相对当前指令的地址(程序计数器PC中的内容±常量)
- 寄存器偏移寻址:当第2操作数是寄存器偏移方式时,第2个寄存器操作数在与第1个操作数结合之前,选择进行移位操作。如:
MOV R0, R2, LSL #3
,将R2的左移3位,结果放入R0,即R0 = R2 * 8 - 基址寻址:将基址寄存器的内容与指令中给出的偏移量相加,形成操作数的有效地址,基址寻址用于访问基址附近的存储单元,常用于查表,数组操作,功能部件寄存器访问等。如:
LDR R2, [R3,#0x0F]
将R3中的值+0x0f作为地址,加载到R2中 - 多寄存器寻址(块拷贝寻址):一次可以传送几个寄存器值。如:
LDMIA R1!,{R2-R7,R12}
,将R1单元中的数据读出到R2-R7,R12,R1自动+1(!
);STMIA R0!,{R3-R6,R10}
;将R3-R6, R10 中的数据保存到R0指向的地址,R0自动+1。(使用多寄存器寻址指令时,寄存器子集的顺序时由小到大的顺序排列,连续的寄存器可用-
连接,否则,用,
分隔书写。) - 堆栈寻址:栈操作。
注释
:;
开头
注意:
- 立即数不可以是任意数值,必须由一个8位的常数循环移位偶数位得到,如:8位数0xbe左移8位 -> 0x0000 be00。可以使用伪指令操作立即数。
- 绝大多数
16
位指令只能访问R0-R7
;32
位Thumb-2
指令则可以随意访问R0-R15
。
1.2 指令后缀
具体的条件码为:
- 对于Thumb指令集,只有转移指令(
B
指令)才可随意使用。而对于其它指令,CM3引入了IF-THEN
指令块,在这个块中才可以加后缀,且必须加以后缀。 S
后缀可以与条件后缀一起使用。
条件码应用示例:
- C代码
if (a > b) a++;
else b++;
- 对应的ARM指令(R0-a, R1-b)
1.3 ARM编译器与GCC编译器语法差异
armasm
即为ARM汇编编译器语法GUN
即为GCC汇编编译器语法
ARM 系列目前支持三大主流的工具链,即ARM RealView (armcc), IAR EWARM (iccarm), and GNU Compiler Collection (gcc).
在core_cm3.h
中有如下定义:
/* define compiler specific symbols */
#if defined ( __CC_ARM )
#define __ASM __asm /*!< asm keyword for armcc */
#define __INLINE __inline /*!< inline keyword for armcc */
#elif defined ( ICCARM )
#define __ASM __asm /*!< asm keyword for iarcc */
#define __INLINE inline /*!< inline keyword for iarcc. Only
avaiable in High optimization mode! */</span>
#define __nop __no_operation /*!< no operation intrinsic in iarcc */
#elif defined ( GNUC )
#define __ASM asm /*!< asm keyword for gcc */
#define __INLINE inline /*!< inline keyword for gcc
#endif
1.4 ARM UAL模拟器VisUAL
1.4.1 内存映射
-
不模拟外设
-
较低的内存地址是用来保存指令的,因此程序计数器PC从地址
0x0
处的指令1开始。 -
执行期间,指令执行内存区域(
Instruction Memory Execute Accesss
)不可读写,即为ROM。(这是软件的局限,实际ROM区域只读) -
指令执行内存区域默认大小
0x10000
字节,允许模拟16,384
行代码 -
VisUAL 支持两种内存访问模式:
Open
和Strict
。- 在
Open
访问模式下,所有数据内存地址都具有读/写访问权限(默认启用) - 在
Strict
访问模式下,只有使用 DCD、 DCB 或 FILL 指令定义的明确数据内存地址才具有写访问权,其他地址只有读访问权限。
- 在
1.4.2 支持的指令
在软件中可以鼠标点击指令,然后按ctrl+space
查看用法:
关于指令的限制参考:instructions note
2 常用指令集
指令语法中带有
{}
的表示可选!
2.1 存储器访问指令
名字 | 功能 |
---|---|
LDR | 从存储器中加载(Load)字到一个寄存器(Register)中 |
LDRH | 从存储器中加载半(Half)字到一个寄存器中 |
LDRB | 从存储器中加载字节(Byte)到一个寄存器中 |
LDRSH | 从存储器中加载半字,再经过带符号扩展后存储一个寄存器中 |
LDRSB | 从存储器中加载字节,再经过带符号扩展后存储一个寄存器中 |
STR | 把一个寄存器按字存储(Store)到存储器中 |
STRH | 把一个寄存器存器的低半字存储到存储器中 |
STRB | 把一个寄存器的低字节存储到存储器中 |
LDMIA | 加载多个字,并且在加载后自增基址寄存器 |
STMIA | 存储多个字,并且在存储后自增基址寄存器 |
PUSH | 压入多个寄存器到栈中 |
POP | 从栈中弹出多个值到寄存器中 |
STR&LDR示例:
MOV R0, #0x20000
MOV R1, #0x08
MOV R2, #0x34
STR R2, [R0] ; R2的值存到R0所示地址
STR R2, [R0, #4] ; R2的值存到R0+4所示地址
STR R2, [R0, #8]! ; R2的值存到R0+8所示地址, R0=R0+8
STR R2, [R0, R1] ; R2的值存到R0+R1所示地址
STR R2, [R0, R1, LSL #4] ; R2的值存到R0+(R1<<4)所示地址
MOV R2, #0x12
STR R2, [R0], #0x20 ; R2的值存到R0所示地址, R0=R0+0x20
LDR R3, [R0], +R1, LSL #1 ; R0+(R1<<1)所示地址的值存到R3
在旧的ARM架构中,不能对内存进行非对齐的字访问。LDR和STR指令的字地址必须是4的倍数。
2.1.1 LDRD/STRD
CM3支持64
位整数,该指令用于传送64位整数的数据。
LDRD{cond} Rt, Rt2, [{Rn},+/-{Rm}]{!}
LDRD{cond} Rt, Rt2, [{Rn}],+/-{Rm}
STRD{cond} Rt, Rt2, [Rn {,#+/-<imm>}]
STRD{cond} Rt, Rt2, [<Rn>, #+/-<imm>
STRD{cond} Rt, Rt2, [<Rn>, #+/-<imm>]!
STRD{cond} Rt, Rt2, [{Rn},+/-{Rm}]{!}
STRD{cond} Rt, Rt2, [{Rn}],+/-{Rm}
- Rt是第一个源寄存器,t必须是偶数(但不能为R14)
- Rt2是第二个源寄存器,Rt2 = R(t+1)
- Rm为要加载/存储的寄存器
注:Rt和Rt2顺序可互换。
【例】
记 (0x1000)= 0x1234_5678_ABCD_EF00,则:
LDR R2, =0x1000 ;
LDRD R0, R1, [R2]
; R0= 0xABCD_EF00(低32位), R1=0x1234_5678(高32位)
同理,用STRD存储64位整数:
STRD.W R1, R0, [R2]
; (0x1000)=0xABCD_EF00_1234_5678
实现了双字的字序反转操作。
2.1.2 LDM:Load Multiple Register
语法:LDM{addr_mode}{cond} Rn{!},reglist{^}
2.1.3 STM:Store Multiple Register
语法:STM{addr_mode}{cond} Rn{!},reglist{^}
-
addr_mode
- IA - Increment After, 每次传输后才增加Rn的值(默认,可省)
- IB - Increment Before, 每次传输前就增加Rn的值(ARM only)
- DA - Decrement After, 每次传输后才减小Rn的值(ARM only)
- DB - Decrement Before, 每次传输前就减小Rn的值
-
!
: 表示修改后的Rn值会写入Rn寄存器, 如果没有!
, 指令执行完后Rn恢复/保持原值 -
^
: 在特权级模式下会影响CPSR- 数据传输到用户模式寄存器而不是当前模式寄存器(Reglist 不包含 PC)
- 发生正常的多寄存器传输,并将 SPSR 复制到 CPSR 中,这用于从异常处理程序返回。(Reglist 包含 PC)
低标号的寄存器Rn存放在低地址
2.1.4 栈操作
LDM & STM 实现
F-满, E-空,D-减, I-增, B-前, A-后
根据LDM/STM指令4种操作地址的方式,可以得出4种类型的栈:
- FD 满递减堆栈:堆栈通过减小存储器的地址向下增长,SP指向最后压入堆栈的数据(先调整SP再存数据),指令如
LDMFD
,STMFD
等; - ED 空递减堆栈:堆栈通过减小存储器的地址向下增长,SP指向下一个要放入的空位置(先存数据再调整SP),指令如
LDMED
,STMED
等; - FA 满递增堆栈:堆栈通过增大存储器的地址向上增长,SP指向最后压入堆栈的数据(先调整SP再存数据),指令如
LDMFA
,STMFA
等; - EA 空递增堆栈:堆栈通过增大存储器的地址向上增长,SP指向下一个要放入的空位置(先存数据再调整SP),指令如
LDMEA
,STMEA
等。
对于**满减栈
**:
- 压栈时:先减小SP,再存数据;STMDB <==> STMFD
- 弹栈时:先读数据,再增大SP;LDMIA <==> LDMFD
STMFD R13!,{R0,R4-R12,LR} ;将寄存器列表中的寄存器(R0,R4到R12,LR)存入堆栈。
LDMFD R13!,{R0,R4-R12,PC} ;将堆栈内容恢复到寄存器(R0,R4到R12,LR)。
示例:
MOV R1, #1
MOV R2, #2
MOV R3, #3
MOV R0, #0x20000
STMFD SP!, {R1-R3} ; 压栈 R3,R2,R1分别存入SP-4, SP-8,SP-12地址处,SP=SP-12
MOV R1, #0
MOV R2, #0
MOV R3, #0
LDMFD SP!, {R1-R3} ; 弹栈 R1,R2,R3分别得到SP, SP+4,SP+8地址处的值,SP=SP+12
- 压栈:SP先减后存,R3-0x1FFFC, R2-0x1FFF8, R1-0x1FFF4
- 弹栈:SP先读后增,R1-0x1FFF8, R2-0x1FFFC, R3-0x20000
PUSH
& POP
下面语法解释都是基于向下增长的满栈
PUSH {R0} ; *(--SP) = R0, SP为 long*指针
POP {R0} ; R0 = *SP++
subroutine_1
PUSH {R0-R7, R12, R14} ; 保存寄存器列表, r14先入栈, r0最后入栈
… ; 执行处理
POP {R0-R7, R12, R14} ; 恢复寄存器列表, 先出栈的数据(即原先最后入栈的r0)保存到r0, 最后出栈的数据(即原先最先入栈的r14)保存到r14
BX R14 ; 返回到主调函数
注意:寄存器的PUSH
和POP
操作永远都是4字节对齐
2.2 数据操作指令
2.2.1 数据传输指令
指令必须加条件后缀才会更新CPSR!
- MOV
MOV{S}{cond} Rn, <Operand2>
(若Rd 或Rm 是高寄存器(R8~R15),则标志不受影响,若Rd 或Rm 都是低寄存器(R0~R7),则会更新N和Z,且清除标志C和V)
MOV{cond} Rd, #imm16
(指令会更新N 和Z 标志,对标志C 和V 无影响)
imm16 is an immediate value in the range 0-65535.
MOV只能在寄存器之间移动数据,或者把立即数移动到寄存器中,不能读写内存!
MOV R1,#0x10 ;R1=0x10
MOV R0,R8 ;R0=R8
MOV PC,LR ;PC=LR,子程序返回
- MVN
MVN Rd, Op2 ; Rd = ~Op2
(指令会更新N 和Z 标志,对标志C 和V 无影响)
数据非传送指令.将寄存器Rm按位取反后传送到目标寄存器(Rd)
MVN R1, #0x1 ; R1 = ~0x01
- NEG
指令会更新N,Z,C,V 标志
NEG R1,R0, ; R1 = -R0
2.2.2 算术逻辑运算指令
四则运算
加法
在加法指令中(包括比较指令CMN
),当结果产生了进位,则C = 1
,表示无符号数运算发生上溢出;其他情况C = 0
。
- 加法指令
ADD
ADD R1, R2, R3 ; R1 = R2 + R3
ADD R1, R2, #0x12 ; R1 = R2 + 0x12
ADD R3, R8 ; R3 = R3 + R8
- 带进位加法指令
ADC
ADC Rd, Rn,Op2 ;Rd = Rn + Op2 + C
(CPSR的C位)
一般用于超过32位的整数相加,实现64 位加法
ADDS R0, R1, R2 ; R0=R1+R2, ADDS中S表示把进位结果写入CPSR
ADC R5, R3, R4 ; R5=R3+R4+C, ADD相加溢出时 C = 1
- 带12位立即数的常规加法
ADDW
ADDW Rd, #imm12 ; Rd += imm12
减法
在减法指令中(包括比较指令CMP
),当结果发生错位,则C = 0
,表示无符号数运算发生下溢出;其他情况C = 1
。
- 减法指令
SUB
SUB R1, R2, R3 ; R1 = R2 - R3
SUB R1, R2, #0x12 ; R1 = R2 - 0x12
- 带进位减法指令
SBC
SBC Rd, Rn, Op2 ;Rd = Rn – Op2 - !C
使用SBC实现64 位减法
subs r2, r0, r1
sbc r5, r3, r4 ; r5 = r3 - r4 - !c
- 逆向减法指令
RSB
RSB Rd, Rn, Op2 ;Rd = Op2 – Rn
- 带进位逆向减法指令
RSC
RSC Rd, Rn, Op2 ;Rd = Op2 – Rn - !C
乘法
- 乘法指令
MUL
(32位)
对于32位乘法指令操作是单周期的
MUL R1, R2, R3 ; R1 = R2 * R3
VisUAL不支持该指令
- 乘加
MLA
MLA Rd, Rm, Rn, Ra ;Rd = Ra+Rm*Rn
- 乘减
MLS
MLS Rd, Rm, Rn, Ra ;Rd = Ra-Rm*Rn
除法(硬件支持-CM3前卫指令)
- 无符号除法
UDIV
UDIV Rd, Rn, Rm ;Rd = Rn/Rm
- 有符号除法
SDIV
SDIV Rd, Rn, Rm ;Rd = Rn/Rm
位操作
VisUAL里不支持(1<<4)这样的写法,写成:0x10
- 逻辑与
AND
AND R1, R2, #(1<<4) ; 位与,R1 = R2 & (1<<4)
AND R1, R2, R3 ; 位与,R1 = R2 & R3
- 逻辑或
ORR
ORR R1, R2, R3 ; 逻辑或 R1 = R2|R3
- 位清除
BIC
BIC R1, R2, #(1<<4) ; 清除某位,R1 = R2 & ~(1<<4)
BIC R1, R2, R3 ; 清除某位,R1 = R2 & ~R3
- 逻辑异或
EOR
EOR R3, R2, ; 逻辑异或 R3=R3^R2
移位运算
LSL
:逻辑左移(Logical Shift Left),寄存器中字的低端空出的位补0LSR
:逻辑右移(Logical Shift Right),寄存器中字的高端空出的位补0ROR
:循环右移(Rotate Right),由字的低端移出的位填入字的高端空出的位ASR
:算术右移(Arithmetic Shift Right),移位过程中保持符号位不变,即如果源操作数为正数,则字的高端空出的位补0,否则补1RRX
:带扩展的循环右移,操作数右移一位,高端空出的位用原C标志值填充。
MOV r0, #0x1
MOV r1, #0x3
MOV r2, r0, LSL r1 ; r2 = r0 << r1
LSL r1, r0, #3 ; r1 = r0 << 3
注意:仅将寄存器的移位结果作为操作数,而寄存器保存的值不变。
2.2.3 比较指令
以下指令会更新CPSR
寄存器的N、Z、C 和V 标志
- 比较指令
CMP
比较两数相减的结果:
为0:Z = 1
不为0:Z = 0
CMP R0, R1 ; 比较R0-R1的结果
CMP R0, #0x12 ; 比较R0-0x12的结果
- 负数比较指令
CMN
比较两数相加的结果:
为0(溢出进位):Z = 1
不为0:Z = 0
LDR R1, =0xFFFFFFFF
MOV R2, #0x1
CMN R1, R2 ;NZCV 0110
- 位测试指令
TST
第一操作数Rn最低位为0:Z = 1
第一操作数Rn最低位不为0:Z = 0
TST R0, R1 ; 测试 R0 & R1的结果
TST R0, #(1<<4) ; 测试 R0 & (1<<4)的结果
- 相等测试指令
TEQ
(32位指令)
比较两数按位异或结果:
为0:Z = 1
不为0:Z = 0
TEQ R0, R1 ; 测试R0 ^ R1结果
2.3 跳转指令
B
:Branch,跳转
B{cond}{.W} label
.w
表示跳转到Thumb2指令集中的32位指令
BL
:Branch with Link,跳转前先把返回地址保持在LR寄存器中
BL{cond} label
BX
:Branch and eXchange,根据跳转地址的BIT0切换为ARM或Thumb状态(0:ARM状态,1:Thumb状态)
BX{cond} Rm
Rm bit[0] = 0 ARM state.
Rm bit[0] = 1 Thumb state.
BLX
:Branch with Link and eXchange, 根据跳转地址的BIT0切换为ARM或Thumb状态(0:ARM状态,1:Thumb状态)
BLX{cond} label
BLX{cond} Rm
Rm bit[0] = 0 ARM state.
Rm bit[0] = 1 Thumb state.
X:表示带状态;L:表示带返回
- **
LDR
**伪指令直接给PC赋值
BL
示例
BL delay
MOV R1, #1 ; BL指令的下一条指令的地址赋给LR
delay
MOV R0, #5
loop
SUBS R0, R0, #1
;CMP R0, #0 ; CMP指令会更新Z标志
BNE loop ; not equal(Z=0)执行
MOV PC, LR
LDR
与LDR
示例
ADR LR, return
ADR PC, delay
; LDR LR, =return
; LDR PC, =delay
return
MOV R1, #1
delay
MOV R0, #5
loop
SUBS R0, R0, #1
;CMP R0, #0 ; CMP指令会更新Z标志
BNE loop ; not equal(Z=0)执行
MOV PC, LR
注意:BL
和BLX
会将当前跳转指令的下一条指令的地址赋给LR
,如果不使用这些指令,那么当前子程序的LR
为第一条指令的地址。
2.4 ARM杂项指令
- 软中断指令
SWI
令用于产生软中断,从而实现在用户模式变换到特权模式,CPSR保存到管理模式的SPSR 中,执行转移到SWI向量,在其它模式下也可使用SWI 指令,处理同样地切换到特权模式。
CM3中该指令名为SVC
- 读特殊功能寄存器指令
MRS
加载(读)特殊功能寄存器的值到通用寄存器
MRS{cond} Rd, SReg
Rd: 目标寄存器 不允许为R15
- 写特殊功能寄存器指令
MSR
存储(写)通用寄存器的值到特殊功能寄存器
MSR{cond} Sreg, Rn
MRS R0, PSR ; 读组合程序状态寄存器值
MSR PSR, R0 ; 写组合程序状态寄存器值
-----------------------------------
MRS R0, APSR ; 将状态标志读入到RO
MRS R0, IPSR ; 读取异常/中断状态
MSR APSR, R0 ; 写状态标志
注:除了APSR可以在用户级访问外,MRS/MSR必须在特权级下使用
2.5 伪指令
2.5.1 Thumb伪指令
ADR
基于PC相对偏移的地址值读取到寄存器中。没有=
号
ADR register,expr
expr:地址表达式。偏移量必须是正数并小于1KB,且必须局部定义,不能被导入
; 标号
LOOP
ADD R0, R0, #1
ADR R0, Loop ; 伪指令
; 编译器转换的真实指令
ADD R0, PC, #val ; val链接时确定, 相对寻址方式得出LOOP地址
LDR
用于加载32位的立即数或一个地址值到指定寄存器。LDR
作为伪指令时,指令中必须有一个=
:
LDR register,=expr/label_expr
register: 加载的目标寄存器
expr: 32位立即数
label_exp: 基于PC的地址表达式或外部表达式
立即数:
LDR R0, =0x12 ; 0x12是立即数,ARM编译器替换为:MOV R0, #0x12
地址值:
LDR R0, =0x12345678
; 0x12345678不是立即数,ARM编译器替换为:
Label DCD 0x12345678 ; 编译器在程序某个地方保持该值
LDR R0, [PC,#offset] ; offset链接时确定,从内存中读取该值并加载到R0中
LDR
与ADR
区别:LDR
通常是把要加载的数值预先定义,再使用一条PC相对加载指令来取出。而ADR
则对PC作算术加法或减法来取得立即数。因此ADR
未必总能求出需要的立即数,一般是为了取出附近某条指令或者变量的地址,而LDR
则是取出一个通用的32位整数。(ADR效率比LDR高)
NOP
空操作伪指令,一般用于延时。
2.5.2 数据定义伪指令
MAP
定义一个结构化的内存表的首地址,^
与MAP 同义
MAP expr,{base_register}
expr 数字表达式或程序中的标号
base_register 当指令中没有base_register 时,expr 即为结构化内存表的首地址;否则首地址为expr与base_register和
MAP 0x01,R9 ; 内存表的首地址为R9+0x01
MAP 0x40003000 ; 内存表的首地址为0x40003000
FIELD
定义一个结构化内存表中的数据域,#
与FIELD 同义
{tabel} FIELD expr
label 当指令中包含其时,label 的值为当前内存表的位置计数器{VAR}的值,汇编器处理这条FIELD伪指令后,内存表计数器的值将加上expr
expr 表示本数据域在内存表中所占用的字节数
count1 FIELD 4 ;定义数据域count1,长度为4 字节
2.5.3 数据分配伪指令
SPACE
分配一块内存单元,并用0初始化。%
与SPACE 同义
{label} SPACE expr
label 内存块起始地址标号
expr 所要分配的内存字节数
DataBuf SPACE 1000 ;分配1000 字节空间
DCB
分配一段字节内存单元,初始化为expr。=
与DCB 同义
{label} DCB expr{,expr}{,expr}…
label 内存块起始地址标号
expr 可以为**-128~255的数值或字符串**,内存分配的字节数由expr个数决定
DISPTAB DCB 0x33,0x43,0x76,0x12
DCB -120,20,36,55
ERRSTR DCB “Send,data is error!”,0
DCD
和DCDU
申请一个字(32bit)的内存空间,初始化为expr。&
与DCD 同义。
DCD
需要字对齐,DCDU
则不用。
{label} DCD expr{,expr}{,expr}…
{label} DCDU expr{,expr}{,expr}…
label 内存块起始地址标号
expr 常数表达式或程序中的标号,内存分配字节数由expr个数决定
Stack_Size EQU 0x00000400 ;1KB
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;分配栈空间 不初始化 可读写 2^3字节对齐
Stack_Mem SPACE Stack_Size
__initial_sp ;栈顶地址
__Vectors DCD __initial_sp ; Top of Stack 栈顶指针
DCD Reset_Handler ; Reset Handler 复位中断
2.5.4 汇编控制伪指令
IF
、ELSE
和ENDIF
IF :DEF:__MICROLIB ; 即 use MicroLib
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory ;由用户自己实现
EXPORT __user_initial_stackheap
ALIGN
ENDIF
MACRO
和MEND
MACRO 和MEND 伪指令用于宏定义。MACRO 标识宏定义的开始,MEND 标识宏定义久的结束,用MACRO 及MEND 定义的一段代码,称为宏定义体
WHIL
和WEND
WHILE logical_expr
;指令或伪指令代码段
WEND
2.5.5 杂项伪指令
ALIGN
使当前位置满足一定的字节对齐方式。
ALIGN {expr{,offset}}
expr 用于指定对齐的方式,取值为2的n次幂(没有指定,默认字对齐)
offset 当前位置对齐到下面形式的地址处:offset+n*expr
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;分配栈空间 不初始化 可读写 2^3字节对齐
ALIGN
AREA
定义一个代码段或数据段。
AREA sectionname{,attr}{,attr}…
sectionname 所定义的代码段或数据段的名称。如果该名称是数据开头的或数据段(.text
、.bss
、.data
),则两边必须用|
括起来
attr 该代码段或数据段的属性 (逗号隔开)
attr
属性具体说明:
-
ALIGN = expr:ELF的代码段和数据段默认4字节对齐,使用该属性可以2^expr字节对齐(expr为0~31),但是代码段的expr不能为0和1。
-
ASSOC = section:指定与本段相关的ELF段(
.text
、.bss
、.data
),任何时候连接section 段也必须包括sectionname段 -
DODE:定义代码段,默认属性为
READONLY
-
COMDEF:定义一个通用的段,可以包含代码或者数据,在其它源文件中,同名的COMDEF 段必须相同;
-
COMMON:定义一个通用的段,不可以包含代码或者数据,连接器将其初始化为此,各源文件中同名的COMMON 段共用同样的内存单元,连接器为其分配合适的尺寸;
-
DATA:定义数据段,默认属性为
READWRITE
-
NOINIT:不初始化,或初始化为0
-
READONLY:指定本段为只读
-
READWRITE:指定本段为可读写
AREA |.text|, CODE, READONLY ; 定义.text段位代码段,属性设为只读
注意:使用AREA将程序分为多个ELF 格式的段,段名称可以相同,这时同名的段被放在同一个ELF 段中
END
指示汇编编译器源文件已结束。
ENDP
指示汇编文件中的程序已结束。
PROC
定义子程序,与 ENDP
成对使用。
EQU
定义数字常量(#define)
name EQU expr{,type}
type:当expr 为32 位常量时,可用type 指示expr 表示的数据类型(CODE16、CODE32、DATA)
Stack_Size EQU 0x00000400 ;1KB
EXPORT
和GLOBAL
声明一个符号可以被其它文件引用,相当于声明了一个全局变量
EXPORT symbol{[WEAK]}
GLOBAL symbol{[WEAK]}
symbol 要声明的符号名称
[WEAK] 弱定义
EXPORT __Vectors
IMPORT
和EXTERN
即C语言extern,指示编译器当前的符号不是在本源文件中定义的,而是在其他源文件中定义的,在本源文件中可能引用该符号
IMPORT symbol{[WEAK]}
EXTERN symbol{[WEAK]}
IMPORT __main ;C main函数
IMPORT SystemInit ;初始化系统时钟函数
KEEP
表示编译器保留符号表中的局部符号
KEEP {symbol}
symbol 要保留的局部标号(若没有该项,除了基于寄存器处的所有符号将包含在目标文件的符号表)
PEQUIRE8
和PRESERVE8
PEQUIRE8: 当前文件请求堆栈为8 字节对齐
PRESERVE8:当前文件保持堆栈为8 字节对齐
PRESERVE8 ;当前堆栈以8字节对齐
3 CMSIS内核接口函数
Instruction | CMSIS intrinsic function |
---|---|
CPSIE I | void __enable_irq(void) |
CPSID I | void __disable_irq(void) |
CPSIE F | void __enable_fault_irq(void) |
CPSID F | void __disable_fault_irq(void) |
ISB | void __ISB(void) |
DSB | void __DSB(void) |
DMB | void __DMB(void) |
REV | uint32_t __REV(uint32_t int value) |
REV16 | uint32_t __REV16(uint32_t int value) |
REVSH | uint32_t __REVSH(uint32_t int value) |
RBIT | uint32_t __RBIT(uint32_t int value) |
SEV | void __SEV(void) |
WFE | void __WFE(void) |
WFI | void __WFI(void) |
CMSIS还提供了一些使用MRS
和MSR
指令访问特殊寄存器的函数:
Special register | Access | CMSIS function |
---|---|---|
PRIMASK | Read | uint32_t __get_PRIMASK (void) |
Write | void __set_PRIMASK (uint32_t value) | |
FAULTMASK | Read | uint32_t __get_FAULTMASK(void) |
Write | void __set_FAULTMASK (uint32_t value) | |
BASEPRI | Read | uint32_t __get_BASEPRI (void) |
Write | void __set_BASEPRI (uint32_t value) | |
CONTROL | Read | uint32_t __get_CONTROL (void) |
Write | void __set_CONTROL (uint32_t value) | |
MSP | Read | uint32_t __get_MSP (void) |
Write | void __set_MSP (uint32_t TopOfMainStack) | |
PSP | Read | uint32_t __get_PSP (void) |
Write | void __set_PSP (uint32_t TopOfProcStack) |
END
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)