3、汇编
3.1 汇编代码中的主要符号
- 指令 : 一条指令被编译生成32位机器码执行时,完成特定的功能
- 伪指令
本身不是一条指令,编译器会将其编译生成多条指令,完成一条伪指令的功能。
- 伪操作
不占用任何的内存空间(代码段),指导编译器对代码进行编译的。
.xxx
xxx:
.text //将定义符开始的代码编译到代码段(只读)
.data //将定义符开始的代码编译到数据段(可读写)
.end //文件结束
.equ //GPG3CON,0xE03001C0 定义宏(即用GPG3CON代替0xE03001C0)
.byte //定义变量1字节
.byte 0x11,'a',0定义字节数组
.word //定义word变量(4字节,32位机)
.word 0x12344578, 0x33445566
.string //定义字符串 .string "abcd "
.global _start //声明_start为全局符号
- 注释
单行注释: @
多行注释: /* */
.if 0 .else .endif
3.2 最基本的指令语法格式
(opcode){cond}{S} Rd, Rn, oprand2
opode : 指令的名字
{cond} : 条件码
{S} : 状态标志位
指令名后边加S,指令的执行结果影响CPSR寄存器的NZCV位,否则不影响CPSR的NZCV位
movs adds subs
Rd : 目标寄存器
Rn : 第一个操作寄存器
oprand2 : 第二个操作数
1> 可以是一个普通的寄存器
2> 可以是一个立即数或有效数
3> 可以是一个经过移位操作的寄存器
一条汇编指令占一行,指令名和目标寄存器之间使用空格隔开;寄存器之间使用逗号隔开;
(opcode){cond}{S}连到一起写。
汇编中,指令不区分大小写,举例如下
MOV R0, #0xFF ==== mov r0, #0xff
拓展:条件码
3.3 指令分类
3.3.1 数据操作指令
3.3.1.1 数据搬移指令 mov mvn ldr
@== mov/mvn{cond}{s} Rd, oprand2==
mov r0, #0xff @ r0 = 0xff
mov r1, r0 @ r1 = r0
mvn r2, #0xff @ r2 = ~0xff
mvn r3, r2 @ r3 = ~r2
@ mov pc, #0x3 错 //pc寄存器的0,1两位无效
mov r0, #0xFF000000
@ mov r1, #0xFFF 错
@ mov r2, #0xFFFF 错
mov r3, #0xFFFFFF
拓展:立即数和有效数
3.3.1.2 移位操作指令 lsl lsr asr ror
@ 语法格式:
@ opcode{cond}{S} Rd, Rn, Oprand2
mov r0, #0xFF
@ lsl 逻辑左移 :高位移出,低位补0
lsl r1, r0, #0x4 @ r1 = r0 << 4
@ lsr 逻辑右移 :低位移出,高位补0
lsr r2, r0, #0x4 @ r2 = r0 >> 4
@ asr 算数右移 :低位移出,高位符号位
@ ror 循环右移 :低位移出补到高位
mov r3, r0, lsl #4 √
mov r4, #(0xFF << 4) √
mov r4,0xFF,lsl #4 ×
3.3.1.3算数运算指令 add adc sub ***c mul
@ 语法格式:
@ opcode{cond}{S} Rd, Rn, Oprand2
@ add/sub: 普通的加减法指令
@ adc/***c: 待进位借位的加减法指令
@ 案例:
/*
实现两个64位数据的加法运算
第1个64位数,r0表示低32位,r1表示高32位
第2个64位数,r2表示低32位,r3表示高32位
结果,r4存低32位,r5存高32位
*/
mov r0, #0xFFFFFFFE
mov r1, #4
mov r2, #5
mov r3, #3
@ s:指令的执行结果影响cpsr的nzcv位
adds r4, r0, r2 @ r4 = r0 + r2 = 0x3
@ 判断C位的值
adc r5, r1, r3 @ r5 = r1 + r3 + C =0x8
@ 案例:
/*
实现两个64位数据的减法运算
第1个64位数,r0表示低32位,r1表示高32位
第2个64位数,r2表示低32位,r3表示高32位
结果,r4存低32位,r5存高32位
*/
mov r0, #4
mov r1, #8
mov r2, #5
mov r3, #3
subs r4, r0, r2 @ r4 = r0 - r2
***c r5, r1, r3 @ r5 = r1 - R3 - !C
@ mul
mov r7, #3
mov r8, #4
mul r6, r7, r8 r6=r7*r8 √
mul r6, r7,#4 ×
@ mul指令的第二个操作数只能是一个寄存器
3.3.1.4位运算指令 and(与) orr(或) eor(异或) bic(位清除)
@ 语法格式:
@ opcode{cond}{S} Rd, Rn, Oprand2
ldr r0, =0x87654321
@ 1> 将R0寄存器中的值,第[3]位置1,保证其他位不变
@ r0 = r0 | 0x8;
方法1:orr r0, r0, #0x8
方法2:@ orr r0, r0, #(0x1 << 3)
方法3:@ mov r1, #1
@ orr r0, r0, r1, lsl #3
@ 2> 将R0寄存器中的值,第[10]位清0,保证其他位不变
@ r0 = r0 & (~(0x1 << 10));
方法1:and r0, r0, #0xFFFFFBFF
方法2:and r0, r0, #(~0x400)
方法3:and r0, r0, #(~(0x1 << 10))
@ 3> 将R0寄存器中的值,第[7:4]位置1,保证其他位不变
@ r0 = r0 | (0xF << 4);
orr r0, r0, #(0xF << 4)
@ 4> 将R0寄存器中的值,第[23:20]位清0,保证其他位不变
@ r0 = r0 & (~(0xF << 20))
and r0, r0, #(~(0xF << 20))
@ 5> 将R0寄存器中的值,第[15:10]位设置为0b101010,保证其他位不变
1) 全部清0
r0 = r0 & (~(0x3F << 10))
and r0, r0, #(~(0x3F << 10))
2) 把相应的位置1
r0 = r0 | (0x3F << 10)
orr r0, r0, #(0x2A << 10)
@ 6> 将R0寄存器中的值,第[20]位取反,保证其他位不变
@ r0 = r0 ^ (0x1 << 20)
eor r0, r0, #(0x1 << 20)
@ bic 位清除指令
@ 将R0寄存器中的值,第[23:20]位清0,保证其他位不变
@ 本质: r0 = r0 & (~(0xF << 20))
bic r0, r0, #(0xF << 20)
3.3.1.5比较指令 cmp
@ 语法格式:
@ cmp{cond} Rn, oprand2
@ 注:1. 没有目标寄存器
@ 2. 执行的执行结果影响的是cpsr的nzcv位,不需要加s
@ 3. 本质:减法运算
@ 4. 比较指令和条件码结合使用
@ 案例:if (r0>r1) r0 = r0 - r1
@ else r1 = r1 - r0
mov r0, #15
mov r1, #9
cmp r0, r1
subhi r0, r0, r1
subcc r1, r1, r0 //无进位无符号小于
3.3.2 跳转指令 b/bl
@ 语法格式:
@ b/bl{cond} Label(函数名)
@ 汇编中的函数 Label: 指令
@ b : 不保存返回地址到lr寄存器,有去无回
@ bl : 保存返回地址到lr寄存器,有去有回,C语言函数的调用
@ 1. 调用汇编的函数
.if 0
mov r0, #1
mov r1, #2
b add_func
add_func:
add r2, r0, r1
.endif
@ 2. 实现一个0-100相加,结果保存到r0
mov r1, #0
func:
cmp r1, #100
bhi loop //无符号大于
add r0, r0, r1
add r1, r1, #1
b func
mov r6,#3
bl add_100 @ 保存返回地址到lr中
b loop
add_100:
mov r1, #0
stop:
cmp r1, #101
addcc r0, r0, r1
addcc r1, r1, #1
bcc stop
mov pc, lr @ 必须手动返回
loop:
b loop
@ 案例: 求两个数的最大公约数
mov r0, #25
mov r1, #10
mm:
cmp r0, r1
beq loop
subhi r0, r0, r1
subcc r1, r1, r0
bne mm
loop:
b loop
3.3.3 内存读写的操作指令 load/store
2> 多寄存器操作指令
3> 栈操作指令
load/store指令
单寄存器的操作指令 ldr str
@ r : Register
@ 语法格式:
ldr{cond} Rn, [Rm] //从Rm的地址中下载数据到Rn
str{cond} Rn, [Rm] //把Rn的值存到Rm地址里面
Rn : Rn寄存器中的数当成数据处理
[Rm] : Rm寄存器中的数当成内存的地址处理
== ldr : 将[Rm]执向的地址空间中的内容读到Rn寄存器中
str : 将Rn寄存器中的值,写到[Rm]指向的地址空间中。==
ldr r0, =0x40000800 //伪指令
ldr r1, =0x12345678 //伪指令
str r1, [r0]
ldr r2, [r0]
@ int *p = &a
@ p =r0 / *p = [r0]
@ (*p = a) = str
@ (a = *p) =ldr
@ ldrh/strh : 半字内存读写指令
@ h : half
@ strb/ldrb : 单字节内存读写指令
@ b : Byte
@ ldr/str的寻址方式
ldr r0, =0x40000800
ldr r1, =0x11111111
ldr r2, =0x22222222
ldr r3, =0x33333333
//将r1中的值写到r0+4指向的地址空间中,r0指向的地址空间不变
str r1, [r0, #4]
//将r2中的值写到r0执行的地址空间中,同时更新r0指向的地址空间:r0=r0+4
str r2, [r0], #4
//将r3中的值写到r0+4指向的地址空间中,同时更新r0指向的地址空间:r0=r0+4
str r3, [r0, #4]!
//同样ldr strh ldrh strb ldrb也支持以上三种不同的内存寻址方式
//注意:地址偏移量应该是load/store指令所操作内存的最小值的整数倍
2> 多寄存器操作指令 stm ldm
@ m : mutil
@ 语法格式:
stm/ldm{cond} Rm, {寄存器列表}
Rm : Rm中存放的是内存地址
{寄存器列表}:寄存器连续使用"-“隔开
寄存器不连续使用”,"隔开。
ldr r0, =0x40000800
ldr r1, =0x11111111
ldr r2, =0x22222222
ldr r3, =0x33333333
ldr r4, =0x44444444
ldr r5, =0x55555555
//将r1-r5寄存器中的内容存到r0指向的地址的连续的20个字节空间中
stm r0, {r1-r4,r5}
//将r0指向的20个字节连续空间中的内容读到r6-r10寄存器中
ldm r0, {r6-r10}
@ 注:不管寄存器列表中的寄存器顺序如何永远都是低地址对应小编号寄存器,高地址对应高编号的寄存器
stm r0, {r5,r4,r3,r2,r1}
ldm r0, {r10,r9,r8,r7,r6}
3> 栈操作指令 stmfd ldmfd
增栈
压栈时,栈指针向高地址方向移动
减栈
压栈时,栈指针向低地址方向移动
满栈
当前栈指针指向的空间有有效的数据
空栈
当前栈指针指向的空间没有有效的数据
栈的操作方式 4种(满增栈,满减栈,空增栈,空减栈)
ARM处理器默认采用的是满减栈
// 满减栈指令:stmfd/ldmfd full descending
// 满增栈指令:stmfa/ldmfa full ascending
// 空减栈指令:stmed/ldmed empty descending
// 空增栈指令:stmea/ldmea empty ascending
@ 语法格式:
stmfd/ldmfd{cond} SP!, {寄存器列表}
!: 更新栈指针
ldr sp, =0x40000800 @ 初始化栈指针 //需要初始化栈指针的原因:默认为0,为满减栈,若不初始化给一个值,会往低地址存,为负数
mov r0, #3 @ r0, r1 赋初始值
mov r1, #4
bl add_func @ 调用加法函数
add r2, r0, r1 @ r2=r0+r1=0x7
b loop
add_func:
stmfd sp!, {r0-r1, lr} //压栈保存现场
mov r0, #5 @ 修改r0,r1值
mov r1, #6
bl sub_func
add r3, r0, r1 @ r3=r0+r1=0xB
ldmfd sp!, {r0-r1, pc} //出栈恢复现场
@ mov pc, lr @ 返回
sub_func:
stmfd sp!, {r0,r1,lr} @ 保存现场
mov r0, #9
mov r1, #4
sub r4, r0, r1
ldmfd sp!, {r0,r1,lr} @ 恢复现场
mov pc, lr
3.3.4 特殊功能寄存器操作指令 msr mrs
对于cpsr寄存器的读写只能用msr和mrs指令
@ 语法格式:
@ msr cpsr, Rn @ 写:cpsr = Rn
@ mrs Rn, cpsr @ 读:Rn = cpsr
@ 系统上的默认工作在svc模式
@ 从svc模式切换到用户模式
@ 10000 User mode;
@ 10011 SVC mode;
@ 方法1:
@ mov r0, #0xD0
@ msr cpsr, r0
@ 方法2:
@ 1. 先将cpsr中的值读到普通寄存器
mrs r0, cpsr
@ 2. 修改模式位,保证其他位不变
bic r0, r0, #0x1F
orr r0, r0, #0x10
@ 3. 将修改后的值写回到cpsr中
4、异常的处理过程
swi软中断指令
user(用户模式)
向下调用接口
----------------------(软中断(通过软中断号区分))---------
向上提供接口
kernel(svc模式)
------------------------------------------------------
hal
5、C和汇编的混合编程
C和汇编混合编程必须遵循ATPCS规范
1. arm-v7
参数的传递使用r0-r3,返回值通过R0返回
如果参数大于4个,使用压栈的方式进行传递。
函数封装时,形参的个数尽量不要超过4个。
2. arm-v8
参数的传递使用x0-x7,返回值通过x0返回,
如果参数大于8个,使用压栈的方式进行传递
5.1 汇编调用C
给r0-r3赋初始值,相当于给C的函数传递实参值
直接使用bl指令进行调用
5.2 C调用汇编
// 函数的声明
extern int add_func(int a, int b);
int add_func2(int a, int b, int c);
int sum;
int main()
{
// C调用汇编
sum = add_func(3,4);
sum = add_func2(4,5,6);
sum = add_func(3,4);
while(1);
return 0;
}
// 内联汇编
int add_func2(int a, int b, int c)
{
int sum;
asm volatile(
"add r0, r0, r1nt"
"add r0, r0, r2nt"
:"=r"(sum)
:"r"(a), "r"(b), "r"(c)
:"memory"
);
return sum;
}
.text
.global _start
_start:
@ 切换到用户模式
msr cpsr, #0xD0
@ 初始化User模式下的栈指针
ldr sp, =0x40000800
@ 汇编调用C代码 : 直接调用
b main
loop:
b loop
.end
int add_func(int a, int b)
{
return a+b;
}
void swap_func(int *a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
int add_func2(int a, int b, int c ,int d, int e)
{
return a+b+c+d+e;
}
5.3 内联汇编(内嵌汇编)
asm volatile(
"汇编指令"
:输出列表
:输入列表
:破坏列表
);
安装软件
- 安装Keil软件
FS6818(学生资料)工具软件汇编编程环境搭建
参考文档:汇编编程环境搭建.pdf
- 安装SecuretCRT串口工具
FS6818(学生资料)工具软件串口工具securetCRT
参考文档:SCRT804安装说明文档.docx
- 安装USB转串口的驱动
FS6818(学生资料)工具软件USB转串口驱动
双击直接安装即可。
- 安装ubuntu版本的交叉编译工具链
本地开发:(X86 GNU工具集:gcc)
PC端编写代码,PC端编译代码,
PC端运行代码。
交叉开发: (ARM GNU工具集:arm-none-linux-gnueabi-gcc)
PC端编写代码,PC端编译代码,
开发板(Target板)运行代码。
安装交叉编译工具链的过程:
1>. 在ubuntu的家目录下创建toolchain目录
2>. 拷贝交叉编译工具链到toolchain目录下,
并进行解压缩
FS6818(学生资料)工具软件ubuntu版本交叉编译器gcc-4.5.1.tar.bz2
tar -xvf gcc-4.5.1.tar.bz2
3>. 配置ubuntu系统的环境变量
对所有的用户有效:
/etc/environment
/etc/bash.bashrc
/etc/profile
只对当前用户有效
~/.bashrc
选择一个配置文件进行修改即可。
本次实验:
修改/etc/bash.bashrc,在文件最后边添加以下内容export PATH=$PATH:/home/linux/toolchain/gcc-4.5.1/bin修改为自己的路径
4>. 使环境变量立即生效
source /etc/bash.bashrc 或者重启ubuntu系统
5>. 测试交叉编译工具链是否安装成功
arm-none-linux-gnueabi-gcc -v
64位ubuntu系统兼容库 :
sudo apt-get install libncurses5-dev
sudo apt-get install lib32z1
# 内存地址的操作位运算符 : & | ^ ~ << >> 口诀:
& :与0清0,与1不变
| :或1置1,或0不变
^ : 异或1取反,异或0不变
1 ^ 1 -> 0
0 ^ 1 -> 1
1 ^ 0 -> 1
0 ^ 0 -> 0
内存 物理地址 地址的功能名
|----------|
| | 0xC001A000 reg1
|----------|
| | 0xC001A004 reg2
|----------|
| | 0xC001A008 reg3
|----------|
| | 0xC001A00C reg4
|----------|
[31:0]
``c
方法1 :
*(unsigned int *)0xC001A000 = *(unsigned int *)0xC001A000 | 0x8;
#define reg1 (*(unsigned int *)0xC001A000)
reg1 = reg1 | (0x1 << 3);
方法2:
// 声明一个结构体
typedef struct{
unsigned int reg1;
unsigned int reg2;
unsigned int reg3;
unsigned int reg4;
}reg_t;
#define REG (*(reg_t *)0xC001A000)
REG.reg1 = REG.reg1 | (0x1 << 3);
#define REG ((reg_t *)0xC001A000)
REG->reg1 = REG->reg1 | (0x1 << 3);
练习:
31 0
xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
1> 0xC001A000地址中的,第[3]位置1,保证其他位不变
2> 0xC001A000地址中的,第[10]位清0,保证其他位不变
3> 0xC001A004地址中的,第[7:4]位置1,保证其他位不变
4> 0xC001A004地址中的,第[23:20]位清0,保证其他位不变
5> 0xC001A008地址中的,第[15:10]位设置为0b101010,保证其他位不变
开发板介绍
3、汇编
3.1 汇编代码中的主要符号
- 指令 : 一条指令被编译生成32位机器码执行时,完成特定的功能
- 伪指令
本身不是一条指令,编译器会将其编译生成多条指令,完成一条伪指令的功能。
- 伪操作
不占用任何的内存空间(代码段),指导编译器对代码进行编译的。
.xxx
xxx:
.text //将定义符开始的代码编译到代码段(只读)
.data //将定义符开始的代码编译到数据段(可读写)
.end //文件结束
.equ //GPG3CON,0xE03001C0 定义宏(即用GPG3CON代替0xE03001C0)
.byte //定义变量1字节
.byte 0x11,'a',0定义字节数组
.word //定义word变量(4字节,32位机)
.word 0x12344578, 0x33445566
.string //定义字符串 .string "abcd "
.global _start //声明_start为全局符号
- 注释
单行注释: @
多行注释: /* */
.if 0 .else .endif
3.2 最基本的指令语法格式
(opcode){cond}{S} Rd, Rn, oprand2
opode : 指令的名字
{cond} : 条件码
{S} : 状态标志位
指令名后边加S,指令的执行结果影响CPSR寄存器的NZCV位,否则不影响CPSR的NZCV位
movs adds subs
Rd : 目标寄存器
Rn : 第一个操作寄存器
oprand2 : 第二个操作数
1> 可以是一个普通的寄存器
2> 可以是一个立即数或有效数
3> 可以是一个经过移位操作的寄存器
一条汇编指令占一行,指令名和目标寄存器之间使用空格隔开;寄存器之间使用逗号隔开;
(opcode){cond}{S}连到一起写。
汇编中,指令不区分大小写,举例如下
MOV R0, #0xFF ==== mov r0, #0xff
拓展:条件码
3.3 指令分类
3.3.1 数据操作指令
3.3.1.1 数据搬移指令 mov mvn ldr
@== mov/mvn{cond}{s} Rd, oprand2==
mov r0, #0xff @ r0 = 0xff
mov r1, r0 @ r1 = r0
mvn r2, #0xff @ r2 = ~0xff
mvn r3, r2 @ r3 = ~r2
@ mov pc, #0x3 错 //pc寄存器的0,1两位无效
mov r0, #0xFF000000
@ mov r1, #0xFFF 错
@ mov r2, #0xFFFF 错
mov r3, #0xFFFFFF
拓展:立即数和有效数
3.3.1.2 移位操作指令 lsl lsr asr ror
@ 语法格式:
@ opcode{cond}{S} Rd, Rn, Oprand2
mov r0, #0xFF
@ lsl 逻辑左移 :高位移出,低位补0
lsl r1, r0, #0x4 @ r1 = r0 << 4
@ lsr 逻辑右移 :低位移出,高位补0
lsr r2, r0, #0x4 @ r2 = r0 >> 4
@ asr 算数右移 :低位移出,高位符号位
@ ror 循环右移 :低位移出补到高位
mov r3, r0, lsl #4 √
mov r4, #(0xFF << 4) √
mov r4,0xFF,lsl #4 ×
3.3.1.3算数运算指令 add adc sub ***c mul
@ 语法格式:
@ opcode{cond}{S} Rd, Rn, Oprand2
@ add/sub: 普通的加减法指令
@ adc/***c: 待进位借位的加减法指令
@ 案例:
/*
实现两个64位数据的加法运算
第1个64位数,r0表示低32位,r1表示高32位
第2个64位数,r2表示低32位,r3表示高32位
结果,r4存低32位,r5存高32位
*/
mov r0, #0xFFFFFFFE
mov r1, #4
mov r2, #5
mov r3, #3
@ s:指令的执行结果影响cpsr的nzcv位
adds r4, r0, r2 @ r4 = r0 + r2 = 0x3
@ 判断C位的值
adc r5, r1, r3 @ r5 = r1 + r3 + C =0x8
@ 案例:
/*
实现两个64位数据的减法运算
第1个64位数,r0表示低32位,r1表示高32位
第2个64位数,r2表示低32位,r3表示高32位
结果,r4存低32位,r5存高32位
*/
mov r0, #4
mov r1, #8
mov r2, #5
mov r3, #3
subs r4, r0, r2 @ r4 = r0 - r2
***c r5, r1, r3 @ r5 = r1 - R3 - !C
@ mul
mov r7, #3
mov r8, #4
mul r6, r7, r8 r6=r7*r8 √
mul r6, r7,#4 ×
@ mul指令的第二个操作数只能是一个寄存器
3.3.1.4位运算指令 and(与) orr(或) eor(异或) bic(位清除)
@ 语法格式:
@ opcode{cond}{S} Rd, Rn, Oprand2
ldr r0, =0x87654321
@ 1> 将R0寄存器中的值,第[3]位置1,保证其他位不变
@ r0 = r0 | 0x8;
方法1:orr r0, r0, #0x8
方法2:@ orr r0, r0, #(0x1 << 3)
方法3:@ mov r1, #1
@ orr r0, r0, r1, lsl #3
@ 2> 将R0寄存器中的值,第[10]位清0,保证其他位不变
@ r0 = r0 & (~(0x1 << 10));
方法1:and r0, r0, #0xFFFFFBFF
方法2:and r0, r0, #(~0x400)
方法3:and r0, r0, #(~(0x1 << 10))
@ 3> 将R0寄存器中的值,第[7:4]位置1,保证其他位不变
@ r0 = r0 | (0xF << 4);
orr r0, r0, #(0xF << 4)
@ 4> 将R0寄存器中的值,第[23:20]位清0,保证其他位不变
@ r0 = r0 & (~(0xF << 20))
and r0, r0, #(~(0xF << 20))
@ 5> 将R0寄存器中的值,第[15:10]位设置为0b101010,保证其他位不变
1) 全部清0
r0 = r0 & (~(0x3F << 10))
and r0, r0, #(~(0x3F << 10))
2) 把相应的位置1
r0 = r0 | (0x3F << 10)
orr r0, r0, #(0x2A << 10)
@ 6> 将R0寄存器中的值,第[20]位取反,保证其他位不变
@ r0 = r0 ^ (0x1 << 20)
eor r0, r0, #(0x1 << 20)
@ bic 位清除指令
@ 将R0寄存器中的值,第[23:20]位清0,保证其他位不变
@ 本质: r0 = r0 & (~(0xF << 20))
bic r0, r0, #(0xF << 20)
3.3.1.5比较指令 cmp
@ 语法格式:
@ cmp{cond} Rn, oprand2
@ 注:1. 没有目标寄存器
@ 2. 执行的执行结果影响的是cpsr的nzcv位,不需要加s
@ 3. 本质:减法运算
@ 4. 比较指令和条件码结合使用
@ 案例:if (r0>r1) r0 = r0 - r1
@ else r1 = r1 - r0
mov r0, #15
mov r1, #9
cmp r0, r1
subhi r0, r0, r1
subcc r1, r1, r0 //无进位无符号小于
3.3.2 跳转指令 b/bl
@ 语法格式:
@ b/bl{cond} Label(函数名)
@ 汇编中的函数 Label: 指令
@ b : 不保存返回地址到lr寄存器,有去无回
@ bl : 保存返回地址到lr寄存器,有去有回,C语言函数的调用
@ 1. 调用汇编的函数
.if 0
mov r0, #1
mov r1, #2
b add_func
add_func:
add r2, r0, r1
.endif
@ 2. 实现一个0-100相加,结果保存到r0
mov r1, #0
func:
cmp r1, #100
bhi loop //无符号大于
add r0, r0, r1
add r1, r1, #1
b func
mov r6,#3
bl add_100 @ 保存返回地址到lr中
b loop
add_100:
mov r1, #0
stop:
cmp r1, #101
addcc r0, r0, r1
addcc r1, r1, #1
bcc stop
mov pc, lr @ 必须手动返回
loop:
b loop
@ 案例: 求两个数的最大公约数
mov r0, #25
mov r1, #10
mm:
cmp r0, r1
beq loop
subhi r0, r0, r1
subcc r1, r1, r0
bne mm
loop:
b loop
3.3.3 内存读写的操作指令 load/store
2> 多寄存器操作指令
3> 栈操作指令
load/store指令
单寄存器的操作指令 ldr str
@ r : Register
@ 语法格式:
ldr{cond} Rn, [Rm] //从Rm的地址中下载数据到Rn
str{cond} Rn, [Rm] //把Rn的值存到Rm地址里面
Rn : Rn寄存器中的数当成数据处理
[Rm] : Rm寄存器中的数当成内存的地址处理
== ldr : 将[Rm]执向的地址空间中的内容读到Rn寄存器中
str : 将Rn寄存器中的值,写到[Rm]指向的地址空间中。==
ldr r0, =0x40000800 //伪指令
ldr r1, =0x12345678 //伪指令
str r1, [r0]
ldr r2, [r0]
@ int *p = &a
@ p =r0 / *p = [r0]
@ (*p = a) = str
@ (a = *p) =ldr
@ ldrh/strh : 半字内存读写指令
@ h : half
@ strb/ldrb : 单字节内存读写指令
@ b : Byte
@ ldr/str的寻址方式
ldr r0, =0x40000800
ldr r1, =0x11111111
ldr r2, =0x22222222
ldr r3, =0x33333333
//将r1中的值写到r0+4指向的地址空间中,r0指向的地址空间不变
str r1, [r0, #4]
//将r2中的值写到r0执行的地址空间中,同时更新r0指向的地址空间:r0=r0+4
str r2, [r0], #4
//将r3中的值写到r0+4指向的地址空间中,同时更新r0指向的地址空间:r0=r0+4
str r3, [r0, #4]!
//同样ldr strh ldrh strb ldrb也支持以上三种不同的内存寻址方式
//注意:地址偏移量应该是load/store指令所操作内存的最小值的整数倍
2> 多寄存器操作指令 stm ldm
@ m : mutil
@ 语法格式:
stm/ldm{cond} Rm, {寄存器列表}
Rm : Rm中存放的是内存地址
{寄存器列表}:寄存器连续使用"-“隔开
寄存器不连续使用”,"隔开。
ldr r0, =0x40000800
ldr r1, =0x11111111
ldr r2, =0x22222222
ldr r3, =0x33333333
ldr r4, =0x44444444
ldr r5, =0x55555555
//将r1-r5寄存器中的内容存到r0指向的地址的连续的20个字节空间中
stm r0, {r1-r4,r5}
//将r0指向的20个字节连续空间中的内容读到r6-r10寄存器中
ldm r0, {r6-r10}
@ 注:不管寄存器列表中的寄存器顺序如何永远都是低地址对应小编号寄存器,高地址对应高编号的寄存器
stm r0, {r5,r4,r3,r2,r1}
ldm r0, {r10,r9,r8,r7,r6}
3> 栈操作指令 stmfd ldmfd
增栈
压栈时,栈指针向高地址方向移动
减栈
压栈时,栈指针向低地址方向移动
满栈
当前栈指针指向的空间有有效的数据
空栈
当前栈指针指向的空间没有有效的数据
栈的操作方式 4种(满增栈,满减栈,空增栈,空减栈)
ARM处理器默认采用的是满减栈
// 满减栈指令:stmfd/ldmfd full descending
// 满增栈指令:stmfa/ldmfa full ascending
// 空减栈指令:stmed/ldmed empty descending
// 空增栈指令:stmea/ldmea empty ascending
@ 语法格式:
stmfd/ldmfd{cond} SP!, {寄存器列表}
!: 更新栈指针
ldr sp, =0x40000800 @ 初始化栈指针 //需要初始化栈指针的原因:默认为0,为满减栈,若不初始化给一个值,会往低地址存,为负数
mov r0, #3 @ r0, r1 赋初始值
mov r1, #4
bl add_func @ 调用加法函数
add r2, r0, r1 @ r2=r0+r1=0x7
b loop
add_func:
stmfd sp!, {r0-r1, lr} //压栈保存现场
mov r0, #5 @ 修改r0,r1值
mov r1, #6
bl sub_func
add r3, r0, r1 @ r3=r0+r1=0xB
ldmfd sp!, {r0-r1, pc} //出栈恢复现场
@ mov pc, lr @ 返回
sub_func:
stmfd sp!, {r0,r1,lr} @ 保存现场
mov r0, #9
mov r1, #4
sub r4, r0, r1
ldmfd sp!, {r0,r1,lr} @ 恢复现场
mov pc, lr
3.3.4 特殊功能寄存器操作指令 msr mrs
对于cpsr寄存器的读写只能用msr和mrs指令
@ 语法格式:
@ msr cpsr, Rn @ 写:cpsr = Rn
@ mrs Rn, cpsr @ 读:Rn = cpsr
@ 系统上的默认工作在svc模式
@ 从svc模式切换到用户模式
@ 10000 User mode;
@ 10011 SVC mode;
@ 方法1:
@ mov r0, #0xD0
@ msr cpsr, r0
@ 方法2:
@ 1. 先将cpsr中的值读到普通寄存器
mrs r0, cpsr
@ 2. 修改模式位,保证其他位不变
bic r0, r0, #0x1F
orr r0, r0, #0x10
@ 3. 将修改后的值写回到cpsr中
4、异常的处理过程
swi软中断指令
user(用户模式)
向下调用接口
----------------------(软中断(通过软中断号区分))---------
向上提供接口
kernel(svc模式)
------------------------------------------------------
hal
5、C和汇编的混合编程
C和汇编混合编程必须遵循ATPCS规范
1. arm-v7
参数的传递使用r0-r3,返回值通过R0返回
如果参数大于4个,使用压栈的方式进行传递。
函数封装时,形参的个数尽量不要超过4个。
2. arm-v8
参数的传递使用x0-x7,返回值通过x0返回,
如果参数大于8个,使用压栈的方式进行传递
5.1 汇编调用C
给r0-r3赋初始值,相当于给C的函数传递实参值
直接使用bl指令进行调用
5.2 C调用汇编
// 函数的声明
extern int add_func(int a, int b);
int add_func2(int a, int b, int c);
int sum;
int main()
{
// C调用汇编
sum = add_func(3,4);
sum = add_func2(4,5,6);
sum = add_func(3,4);
while(1);
return 0;
}
// 内联汇编
int add_func2(int a, int b, int c)
{
int sum;
asm volatile(
"add r0, r0, r1nt"
"add r0, r0, r2nt"
:"=r"(sum)
:"r"(a), "r"(b), "r"(c)
:"memory"
);
return sum;
}
.text
.global _start
_start:
@ 切换到用户模式
msr cpsr, #0xD0
@ 初始化User模式下的栈指针
ldr sp, =0x40000800
@ 汇编调用C代码 : 直接调用
b main
loop:
b loop
.end
int add_func(int a, int b)
{
return a+b;
}
void swap_func(int *a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
int add_func2(int a, int b, int c ,int d, int e)
{
return a+b+c+d+e;
}
5.3 内联汇编(内嵌汇编)
asm volatile(
"汇编指令"
:输出列表
:输入列表
:破坏列表
);
安装软件
- 安装Keil软件
FS6818(学生资料)工具软件汇编编程环境搭建
参考文档:汇编编程环境搭建.pdf
- 安装SecuretCRT串口工具
FS6818(学生资料)工具软件串口工具securetCRT
参考文档:SCRT804安装说明文档.docx
- 安装USB转串口的驱动
FS6818(学生资料)工具软件USB转串口驱动
双击直接安装即可。
- 安装ubuntu版本的交叉编译工具链
本地开发:(X86 GNU工具集:gcc)
PC端编写代码,PC端编译代码,
PC端运行代码。
交叉开发: (ARM GNU工具集:arm-none-linux-gnueabi-gcc)
PC端编写代码,PC端编译代码,
开发板(Target板)运行代码。
安装交叉编译工具链的过程:
1>. 在ubuntu的家目录下创建toolchain目录
2>. 拷贝交叉编译工具链到toolchain目录下,
并进行解压缩
FS6818(学生资料)工具软件ubuntu版本交叉编译器gcc-4.5.1.tar.bz2
tar -xvf gcc-4.5.1.tar.bz2
3>. 配置ubuntu系统的环境变量
对所有的用户有效:
/etc/environment
/etc/bash.bashrc
/etc/profile
只对当前用户有效
~/.bashrc
选择一个配置文件进行修改即可。
本次实验:
修改/etc/bash.bashrc,在文件最后边添加以下内容export PATH=$PATH:/home/linux/toolchain/gcc-4.5.1/bin修改为自己的路径
4>. 使环境变量立即生效
source /etc/bash.bashrc 或者重启ubuntu系统
5>. 测试交叉编译工具链是否安装成功
arm-none-linux-gnueabi-gcc -v
64位ubuntu系统兼容库 :
sudo apt-get install libncurses5-dev
sudo apt-get install lib32z1
# 内存地址的操作位运算符 : & | ^ ~ << >> 口诀:
& :与0清0,与1不变
| :或1置1,或0不变
^ : 异或1取反,异或0不变
1 ^ 1 -> 0
0 ^ 1 -> 1
1 ^ 0 -> 1
0 ^ 0 -> 0
内存 物理地址 地址的功能名
|----------|
| | 0xC001A000 reg1
|----------|
| | 0xC001A004 reg2
|----------|
| | 0xC001A008 reg3
|----------|
| | 0xC001A00C reg4
|----------|
[31:0]
``c
方法1 :
*(unsigned int *)0xC001A000 = *(unsigned int *)0xC001A000 | 0x8;
#define reg1 (*(unsigned int *)0xC001A000)
reg1 = reg1 | (0x1 << 3);
方法2:
// 声明一个结构体
typedef struct{
unsigned int reg1;
unsigned int reg2;
unsigned int reg3;
unsigned int reg4;
}reg_t;
#define REG (*(reg_t *)0xC001A000)
REG.reg1 = REG.reg1 | (0x1 << 3);
#define REG ((reg_t *)0xC001A000)
REG->reg1 = REG->reg1 | (0x1 << 3);
练习:
31 0
xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
1> 0xC001A000地址中的,第[3]位置1,保证其他位不变
2> 0xC001A000地址中的,第[10]位清0,保证其他位不变
3> 0xC001A004地址中的,第[7:4]位置1,保证其他位不变
4> 0xC001A004地址中的,第[23:20]位清0,保证其他位不变
5> 0xC001A008地址中的,第[15:10]位设置为0b101010,保证其他位不变
开发板介绍
举报