文章目录
- 一、keil软件介绍
- 1.1 创建工程
- 1.2 解析start.s文件(重点)
- 1.3 乱码解决
- 1.4 更换背景颜色
- 1.5 C语言内存分布
- 1.6 解析map.lds文件(重点)
- 1.7 常见错误信息
- 1.8 仿真
- 二、汇编三种符号
- 2.1 汇编指令
- 2.2 伪指令
- 2.3 伪操作
- 三、汇编指令格式
- 3.1 格式
- 3.2 注意事项
- 四、数据操作指令
- 4.1 数据搬移指令 mov mvn
- 4.2 立即数
- 4.3 伪指令 ldr
- 4.4 移位操作指令
- 4.5 位运算操作指令
- 4.5.1 代码1
- 4.5.2 代码2
- 4.6 算数运算指令
- 4.7 比较指令
- 五、跳转指令
- 5.1 指令码
- 作业1
- 作业2
- 总结
一、keil软件介绍
1.1 创建工程
跟着文档进行创建就可以,注意不要出现中文路径
1.2 解析start.s文件(重点)
.text @文本段
.global _start @ 声明一个_start函数入口
_start: @ _start标签,相当于C语言中函数
mov r0,#0x1 @ 一条汇编指令
stop: @ stop标签,相当于C语言中函数
b stop @ 跳转到stop标签下的第一条指令执行,相当于C语言中while(1)
.end @结束标志
1.3 乱码解决
1.4 更换背景颜色
将群里下发global.prop文件替换到C:\Keil_v5\UV4,重启keil软件
1.5 C语言内存分布
1.6 解析map.lds文件(重点)
map.lds文件:链接脚本文件
作用:给编译器进行使用,告诉编译器对各个段,如何进行分布
/*输出格式:32位可执行程序,小端对齐*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
/*输出架构:arm架构*/
OUTPUT_ARCH(arm)
/*入口:_start*/
ENTRY(_start)
/*段*/
SECTIONS
{
. = 0x00000000;/*入口地址*/
. = ALIGN(4);/*4字节对齐*/
.text :
{
./Objects/start.o(.text) /*第一个文件存放start.o文件,指定start.o位置*/
*(.text) /*其余文件没有要求,编译器随便放*/
}
. = ALIGN(4);
.rodata : /*只读数据段*/
{ *(.rodata) }
. = ALIGN(4);
.data : /*数据段*/
{ *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : /*.bss段*/
{ *(.bss) }
__bss_end__ = .;
}
1.7 常见错误信息
c:/program files (x86)/codesourcery/ld.exe: cannot find ./start.o =====> 不能找到./start.o
解决方法:
1)在当前工程下,查看start.o位置 ======> 编译器决定start.o在哪个位置
2)修改map.lds文件
1.8 仿真
二、汇编三种符号
2.1 汇编指令
编译器将一条汇编指令编译生成机器码,占用代码段空间
2.2 伪指令
伪指令本身不是一条指令,编译器可以将其编译生成多条指令,共同完成一条指令功能
2.3 伪操作
指导编译器对代码如何进行编译,所有以.开头的为伪操作,伪操作不占用代码段空间
三、汇编指令格式
3.1 格式
{cond}{s} Rd,Rn,#oprand2
:指令码
{cond}:条件码
加条件码:指令有条件执行
不加条件码:指令默认无条件执行
{s}:状态位
加s:影响CPSR寄存器
不加s:不影响CPSR寄存器
Rd:目标寄存器
Rn:第一操作寄存器
#oprand2:第二操作数
1)立即数
2)寄存器
3)有效数:将一个数按位取反之后,如果这个数为立即数,说明这个数为有效数
3.2 注意事项
1、{cond}{s}需要连在一起编写
2、Rd,Rn,#oprand2需要用逗号分隔开
3、{cond}{s}和Rd,Rn,#oprand2需要用空格隔开
4、一条汇编指令占用一行,并且没有分号
5、汇编中不区分大小写
四、数据操作指令
4.1 数据搬移指令 mov mvn
指令码:mov mvn
指令格式:{cond}{s} Rd,#oprand2
mov ====> 将第二操作数进行赋值
mvn ====> 将第二操作数,按位进行取反之后,进行赋值
mov r0,#0xf @ r0 = 0xf ====> 立即数
mov r1,#0xff @ r1 = 0xff ====> 立即数
mov r8,r1 @ r8 = r1 = 0xff ====> 寄存器
@ mov r2,#0xfff @ r2 = 0xfff ====> error
@ mov r3,#0xffff @ r3 = 0xfffff ====> error
@ mov r4,#0xfffff @ r4 = 0xfffff ====> error
mov r5,#0xffffff @ r5 = 0xffffff = ~r5 = 0xff000000 ====> 有效数
mov r6,#0xfffffff @ r6 = 0xfffffff = ~r6 = 0xf0000000 ====> 有效数
mov r7,#0xffffffff @ r7 = 0xffffffff = ~r7 = 0 ====> 有效数
mov r0,#0xff @ r0 = 0xff
mvn r1,#0xff @ r1 = ~ r1 = 0xffffff00
4.2 立即数
1、从判断的数中,找到0~0xff之间的数 ===> 判断的这个数所有1包含
2、将找到的0~0xff之间的数,循环右移偶数位 ===> 低位移出,补到高位
3、如果能够得到你要判断的那个数,说明这个数就是立即数
判断的数0 ~ 0xff之间数循环右移偶数是否立即数
判断的数 | 0 ~ 0xff之间数 | 循环右移偶数 | 是否立即数 |
0xf | 0xf | 0 | 是 |
0xf000000f | 0xff | 4 | 是 |
0xff000000 | 0xff | 8 | 是 |
0x000000ff | 0xff | 0 | 是 |
0x1fe00000 | 0xff | 11 | 不是 |
0x1f800000 | 0x7E | 10 | 是 |
0x0000000f =====> 判断的数
0000 0000 0000 0000 0000 0000 0000 1111 =====> 判断的数
0 ~ 0xff之间数:0xf
循环右移偶数: 0
0xf000000f =====> 判断的数
1111 0000 0000 0000 0000 0000 0000 1111 =====> 判断的数
0000 0000 0000 0000 0000 0000 1111 1111 =====> 0 ~ 0xff之间数
0 ~ 0xff之间数: 0xff
循环右移偶数: 4
0xff000000 =====> 判断的数
1111 1111 0000 0000 0000 0000 0000 0000 =====> 判断的数
0000 0000 0000 0000 0000 0000 1111 1111 =====> 0 ~ 0xff之间数
0 ~ 0xff之间数:0xff
循环右移偶数: 8
0x000000ff =====> 判断的数
0000 0000 0000 0000 0000 0000 1111 1111 =====> 判断的数
0000 0000 0000 0000 0000 0000 1111 1111 =====> 0 ~ 0xff之间数
0 ~ 0xff之间数:0xff
循环右移偶数: 0
0x1fe00000=====> 判断的数
0001 1111 1110 0000 0000 0000 0000 0000 =====> 判断的数
0000 0000 0000 0000 0000 0000 1111 1111 =====> 0 ~ 0xff之间数
0 ~ 0xff之间数:0xff
循环右移偶数: 11
0x1f800000 =====> 判断的数
0001 1111 1000 0000 0000 0000 0000 0000 =====> 判断的数
0000 0000 0000 0000 0000 0000 0111 1110 =====> 0x7E
0 ~ 0xff之间数:0x7E
循环右移偶数: 10
4.3 伪指令 ldr
思考:在0~4G空间,有很多数都不是立即数,那么如何进行赋值呢?
指令码:ldr
格式:ldr 寄存器,=值
ldr r0,=0xfff @ r0 = 0xfff
ldr r1,=0xffff @ r1 = 0xffff
ldr r2,=0xfffff @ r2 = 0xfffff
4.4 移位操作指令
指令码:lsl lsr ror asr
指令格式:{cond}{s} Rd,Rn,#oprand2
lsl:逻辑左移 =====> 特点:无符号数左移,高位移出,低位补0
lsr:逻辑右移 =====> 特点:无符号数右移,低位移出,高位补0
ror:循环右移 =====> 特点:低位移出,补到高位
asr:算数右移 =====> 特点:低位移出,高位补符号位
mov r0,#0xff @ r0 = 0xff = 0000 0000 0000 0000 0000 0000 1111 1111
@1.将r0寄存器中的值,逻辑左移4位,并且存放到目标寄存器r1
@ 0000 0000 0000 0000 0000 0000 1111 1111
@ 0000 0000 0000 0000 0000 1111 1111 0000
lsl r1,r0,#0x4 @ r1 = r0 << 4 = 0xff0
@2.将r1寄存器中的值,逻辑右移4位,并且存放到目标寄存器r2
@ 0000 0000 0000 0000 0000 1111 1111 0000
@ 0000 0000 0000 0000 0000 0000 1111 1111
lsr r2,r1,#0x4 @ r2 = r1 >> 4 = 0x000000ff
@3.将r2寄存器中的值,循环右移4位,并且存放到目标寄存器r3
@ 0000 0000 0000 0000 0000 0000 1111 1111
@ 1111 0000 0000 0000 0000 0000 0000 1111
ror r3,r2,#0x4 @ r3 = 0xf000000f
ldr r4,=0x8000000f
@4.将r4寄存器中的值,算数右移4位,并且存放到目标寄存器r5
@ 1000 0000 0000 0000 0000 0000 0000 1111
@ 1111 1000 0000 0000 0000 0000 0000 0000
asr r5,r4,#0x4 @ r5 = 0xf8000000
4.5 位运算操作指令
指令码:and orr eor bic
格式:{cond}{s} Rd,Rn,#oprand2
and:按位与 =====> 与0清0,与1不变
orr:按位或 =====> 或0不变,或1置1
eor:按位异或 =====> 异或0不变,异或1取反(相同为0,相异为1)
bic:按位清零 =====> 第二操作哪一位写1,对应位进行清0
真值表
1 ^ 1 = 0
0 ^ 1 = 1
1 ^ 0 = 1
0 ^ 0 = 0
4.5.1 代码1
/*
mov r0,#0xff @ r0 = 0xff
@ 31 4 3210
@ **** **** **** **** **** **** **** ****
@ 0000 0000 0000 0000 0000 0000 1111 1111
@ 1111 1111 1111 1111 1111 1111 1110 1111
@将r0寄存器中第4位进行清0,保证其他位不变,并且存放到目标寄存器r0中
@ 第一种方法:
and r0,r0,#0xffffffef
@ 第二种方法:推荐使用
and r0,r0,#(~(0x1 << 4))
@ 备注:当目标寄存器和第一操作寄存器相同,可以合并 and r0,#(~(0x1 << 4))
@ 第三种方法
bic r0,r0,#(0x1 << 4)
*/
mov r0,#0xEf @ r0 = 0xEf
@ 0000 0000 0000 0000 0000 0000 1110 1111
@ 1 1110 1111
@将r0寄存器中第8位进行置1,保证其他位不变,并且存放到目标寄存器r1中
orr r1,r0,#(0x1 << 8) @ r1 = 0x1ef
mov r2,#0xf @ r0 = 0xf
@ 0000 0000 0000 0000 0000 0000 0000 1111
@ 0111
@将r2寄存器中第3位进行取反,保证其他位不变,并且存放到目标寄存器r3中
eor r3,r2,#(0x1 << 3) @ r3 = 0x7
4.5.2 代码2
ldr r0,=0x12345678
@ 1> 将R0寄存器中的第[4]位清0,保持其他位不变
and r0,r0,#(~(0x1 << 4))
@ 2> 将R0寄存器中的第[7]位置1,保持其他位不变
orr r0,r0,#(0x1 << 7)
@ 3> 将R0寄存器中的第[31:28]位清0,保持其他位不变
and r0,r0,#(~(0xf << 28))
@ 4> 将R0寄存器中的第[7:4]位置1,保持其他位不变
orr r0,r0,#(0xf << 4)
@ 5> 将R0寄存器中的第[15:11]位修改位10101,保持其他位不变
@ 先清零
bic r0,r0,#(0x1f << 11)
@ 在置1
orr r0,r0,#(0x15 << 11)
4.6 算数运算指令
指令码:add adc sub sbc mul
指令格式:{cond}{s} Rd,Rn,#oprand2
add:普通加法指令
adc:带进位加法指令 ====> CPSR寄存器中C位标志位
sub:普通减法指令
sbc:带借位减法指令
mul:乘法指令 ====> 没有第二操作数,{cond}{s} Rd,Rn
/*
练习题1:实现两个64位数相加
第一个64位数:高32位r0 = 0x4 低32位r1 = 0xffffffff
第二个64位数:高32位r2 = 0x1 低32位r3 = 0x1
两个64位数进行相加目标高32位r4表示=0x6,低32位r5表示=00000000
mov r0,#0x4
mov r1,#0xffffffff
mov r2,#0x1
mov r3,#0x1
adds r5,r1,r3 @ r5 = r1 + r3
adc r4,r0,r2 @ r4 = r0 + r2 + c = 0x4 + 0x1 + 1 = 0x6
*/
/*
练习题2:实现两个64位数相减
第一个64位数:高32位r0 = 0x4 低32位r1 = 0x4
第二个64位数:高32位r2 = 0x1 低32位r3 = 0x5
两个64位数进行相减目标高32位r4表示=0x2,低32位r5表示=0xffffffff
*/
mov r0,#0x4
mov r1,#0x4
mov r2,#0x1
mov r3,#0x5
subs r5,r1,r3 @ r5 = r1 - r3
sbc r4,r0,r2 @ r4 = r0 - r2 - !c = 0x4 - 0x1 - 1 = 0x2
/*乘法指令 ====> 没有第二操作数,<opcode>{cond}{s} Rd,Rn*/
mov r0,#0x4
mov r1,#0x5
mul r0,r1 @ r0 = r0 * r1 = 0x14
4.7 比较指令
指令码:cmp
指令格式:{cond} Rn,#oprand2
注意点:
1)比较指令没有目标寄存器
2)比较指令本质做减法运算
3)比较指令的执行结果,会影响CPSR寄存器的NZCV位,并且不需要加s
4)比较指令和条件码搭配使用
5)前面我们所有学习的指令,都是默认无条件执行,比较指令有条件指令
mov r0,#0x4
mov r1,#0x5
cmp r0,r1 @比较r0和r1寄存器中的值
subhi r0,r0,r1 @如果r0 > r1 r0 = r0 - r1
subcc r1,r1,r0 @如果r0 < r1 r1 = r1 - r0
五、跳转指令
5.1 指令码
指令码:b / bl
指令格式:b / bl{cond} 标签 ====> 跳转到标签下,第一条指令执行
b:有去无回,不会保存函数返回地址到LR寄存器中
b:有去有回,会保存函数返回地址到LR寄存器中
mov r0,#0x1
mov r1,#0x2
@ b add_func @有去无回,不会保存函数返回地址到LR寄存器中
bl add_func @有去有回,会保存函数返回地址到LR寄存器中
mov r3,#0x4
b stop
add_func:
add r0,r0,r1 @ r0 = r0 + r1 = 0x1 + 0x2 = 0x3
mov pc,lr @ 恢复现场 pc = lr
作业1
作业2
用for循环实现1~100之间和5050
for(i=1;i<=100;i++)
{
sum = sum + i;
}
总结
特殊功能寄存器:sp lr pc cpsr spsr
基本格式:<opcode>{cond}{s} Rd,Rn,#oprand2
数据操作指令
1> 数据搬移指令 mov mvn ldr
2> 移位操作指令 lsl lsr asr ror
3> 算数运算指令 add adc sub sbc
4> 位运算操作指令 and orr eor bic
5> 比较指令 cmp
跳转指令 b / bl