layout: post
title: “指令集”
date: 2024-1-16 15:39:08 +0800
tags: Cotex-M3 Cotex-M3权威指南笔记
标号
操作码操作数1, 操作数2, ... ;注释
标号是可选的,如果有,它必须顶格写。标号的作用是让汇编器来计算程序转移的地址。
操作码是指令的助记符,它的前面必须有至少一个空白符,通常使用一至二个“Tab”键来产生。操作码后面往往跟随若干个操作数,而第1个操作数,通常都给出本指令的执行结果存储处。不同指令需要不同数目的操作数
立即数必须以“#”开头
MOV R0, #0x12 ; R0 <- 0x12
MOV R1, #’A’ ; R1 <- 字母A的ASCII码
还可以使用EQU指示字来定义常数
NVIC_IRQ_SETEN0 EQU 0xE000E100 ;注意:常数定义必须顶格写
宏定义
MOV R1, #NVIC_IRQ0_ENABLE ; 把立即数传送到R1中
如果汇编器不能识别某些特殊指令的助记符,你就要“手工汇编”——查出该指令的确切二进制机器码,然后使用DCI编译器指示字。例如,BKPT指令的机器码是0xBE00,即可以按如下格式书写:
DCI 0xBE00 ; 断点(BKPT),这是一个16位指令
可以使用DCB来定义一串字节常数,字节常数还允许以字符串的形式来表达;还可以使用DCD来定义一串32位整数
Cortex-M3中,对条件后缀的使用有很大的限制:只有转移指令(B指令)才可随意使用。
S后缀可以和条件后缀在一起使用
MOV R8, R3
使用!表示自增或者自减, 如果不加!的话寄存器里面的值不会变
还可以用于更新寄存器的标志,
LDR.W R0, [R1, #20]!
, 先执行R1 = R1 + 20, 之后把R1位置的值放到R0(预索引)
后索引
先把寄存器地址里面的值取出来以后再进行更新寄存器
STR.W R0, [R1], #-12 ;后索引
**注: **使用寄存器作为偏移的时候不能使用前后索引
16位指令MOV支持8位立即数加载
MOV R0, #0x12
32位指令MOVW和MOVT可以支持16位立即数加载。
要加载32位立即数怎么办呢?如果要直来直去,当前是要用两条指令来完成了。通过组合使用MOVW和MOVT就能产生32位立即数, 必须先使用MOVW,再使用MOVT。这种顺序是不能颠倒的,因为MOVW会清零高16位
更流行的是另一种方法:使用汇编器提供的”LDR Rd, = imm32”伪指令
如果加载的是程序地址, 会把最低位设置为1, 加载的是数据不会设置为1
ADR指令不会设置为1
B Label ;跳转到Label处对应的地址
BX reg ;跳转到由寄存器reg给出的地址
reg的最低位指示出在转移后将进入的状态:是ARM(LSB=0),还是Thumb(LSB=1)。CM3只在Thumb中运行,就必须保证reg的LSB=1
BL Label ;跳转到Label对应的地址,并且把跳转前的 下条指令地址保存到LR
BLX reg ;跳转到由寄存器reg给出的地址,并根据REG的LSB切换处理器状态, ;还要把转移前的下条指令地址保存到LR
只能保存一级, 所以在进行多层调用的时候需要对LR寄存器的值进行并保存
主要用于跳转指令条件以及IF-Then指令的依据
CMP指令。CMP指令在内部做两个数的减法,并根据差来设置标志位,但是不把差写回
CMP R0, R1 ; 计算R0- R1的差,并且根据结果更新标志位 CMP R0, 0x12 ; 计算R0- 0x12的差,并且根据结果更新标志位
CMN是CMP的一个孪生姊妹,只是它在内部做两个数的加法(相当于减去减数的相反数)
CMN R0, R1 ; 计算R0+R1的和,并根据结果更新标志位 CMN R0, 0x12 ; 计算R0+0x12的和,并根据结果更新标志位
TST指令的内部其实就是AND指令,只是不写回运算结果,它也无条件更新标志位。
TST R0, R1 ; 计算R0 & R1,并根据结果更新标志位 TST R0, 0x12 ; 计算R0 & 0x12,并根据结果更新标志位
TEQ指令的内部其实就是EOR指令,只是不写回运算结果,它也无条件更新标志位。
TEQ R0, R1 ; 计算R0 ^ R1,并根据结果更新标志位 TEQ R0, 0x12 ; 计算R0 ^ 0x12,并根据结果更新标志位
在进行存储器映射关系改变或者保护区的改变以后, 需要一个数据同步指令DSB
一般情况下主要使用前两条
第三条可以在更新代码以后使用用来清空流水线
主要用于进行削顶失真, 原因是数字溢出以后, 会从最大变成最小, 最小变为最大
使用饱和运算以后会在到达极值以后保持极值
举例来说,如果要把一个32位(带符号)整数饱和到12位带符号整数(-2048至2047),则可以如下使用SSAT指令
SSAT{.W} R1, #12,
如果需要把32位整数饱和到无符号的12位整数(0-4095),则可以如下使用USAT指令
USAT{.W} R1, #12, R0
访问特殊功能寄存器的“专用通道”, 必须在特权级下使用,除了APSR可以在用户级下访问外。
围起一个块,里面最多有4条指令,它里面的指令可以条件执行
IT指令已经带了一个“T”,因此还可以最多再带3个“T”或者“E”。 并 且对T和E的顺序没有要求。其中T对应条件成立时执行的语句,E对应条件不成立时执行的语句。在If-Then块中的指令必须加上条件后缀,且T对应的指令必须使用和IT指令中相同的条件,E对应的指令必须使用和IT指令中相反的条件。
IT的使用形式总结如下:IT ;围起1条指令的IF- THEN块
IT ;围起2条指令的IF- THEN块
IT ;围起3条指令的IF- THEN块
IT ;围起4条指令的IF- THEN块
比较并条件跳转指令专为循环结构的优化而设,它只能做前向跳转
BZ ,
while (R0!=0) {
Function1();
}
变成
Loop
CBZ R0, LoopExit
BL Function1
B Loop
LoopExit:
...
CBZ/CBNZ不会更新标志位
REV反转32位整数中的字节序,REVH则以半字为单位反转,且只反转低半字。语法格式为
REV Rd, Rm
REVH Rd, Rm
REV16 Rd, Rm REVSH Rd,
记R0=0x12345678
REV R1, R0
REVH R2, R0
REV16 R3, R0
执行后R1=0x78563412,R2=0x12347856,R3=0x34127856
专门服务于小端模式和大端模式的转换
REVSH在REVH的基础上,还把转换后的半字做带符号扩展
比前面的REV之流更精细,它是按位反转的,相当于把32位整数的二进制表示法水平旋转180度。其格式为:RBIT.W Rd, Rn
例如,记R1=0xB4E10C23(二进制数值为 1011,0100,1110,0001,0000,1100,0010,0011),``RBIT.W R0, R1`
R0=0xC430872D(二进制数值为1100,0100,0011,0000,1000,0111,0010,1101)
为了体贴C语言的强制数据类型转换而设的,把数据宽度转换成处理器喜欢的32位长度
对于SXTB/SXTH,数据带符号位扩展成32位整数。对于UXTB/UXTH,高位清零
SXTB R1, R0 ; R1=0x00000065
SXTH R1, R0 ; R1=0xffff8765
UXTB R1, R0 ; R1=0x00000065
UXTH R1, R0 ; R1=0x00008765
BFC(位段清零)指令把32位整数中任意一段连续的2进制位s清0,语法格式为
BFC.W Rd, #lsb, #width
LDR R0, =0x1234FFFF
BFC R0, #4, #10 R0= 0x1234C00F
位段不支持首尾拼接。例如, BFC R0, #27, #9将产生不可预料的结果
BFI(位段插入指令),把某个寄存器按LSB对齐的数值,拷贝到另一个寄存器的某个位段中,
BFI .W Rd, Rn, #lsb, #width
LDR R0, =0x12345678
LDR R1, =0xAABBCCDD
BFI.W R1, R0, #8,
UBFX/SBFX都是位段提取指令
UBFX.W Rd, Rn, #lsb, #width
SBFX.W Rd, Rn, #lsb, #width
UBFX从Rn中取出任一个位段,执行零扩展后放到Rd中
SBFX也抽取任意的位段,但是以带符号的方式进行扩展。
TBB(查表跳转字节范围的偏移量)指令和TBH(查表跳转半字范围的偏移量)指令,分别用于从一个字节数组表中查找转移地址,和从半字数组表中查找转移地址。TBH的转移范围已经足以应付任何臭长的switch结构。如果写出的switch连TBH都搞不定,只能说那人有严重自虐倾向。
因为CM3的指令至少是按半字对齐的,表中的数值都是在左移一位后才作为前向跳转的偏移量的。又因为PC的值为当前地址+4,故TBB的跳转范围可达255*2+4=514
TBH的跳转范围更可高达65535*2+4=128KB+2
只能作前向跳转,也就是说偏移量是一个无符号整数。
TBB.W [Rn,Rm]; PC+= Rn[Rm]*2
TBH的操作原理与TBB相同,只不过跳转表中的每个元素都是16位的。故而下标为Rm的元素要从Rn+2*Rm处去找。