系列文章目录
一、Linux嵌入式学习之Ubuntu入门(一)基本命令、软件安装及文件结构
二、Linux嵌入式学习之Ubuntu入门(二)磁盘文件介绍及分区、格式化等
三、Linux嵌入式学习之Ubuntu入门(三)用户、用户组及文件权限
四、Linux嵌入式学习之Ubuntu入门(四)Makefile
文章目录
- 系列文章目录
- 前言
- GNU汇编语法
- 基本格式
- 段定义
- 伪操作
- 函数
- 常用的汇编指令
- 内部数据传输
- 存储器访问
- LDR
- STR
- 压栈和出栈指令
- 跳转指令
- B指令
- BL指令
- 算术运算指令
- 逻辑运算指令
前言
嵌入式的汇编涉及到堆栈,SP指针,以及DDR初始化。
GNU汇编语法
学stm32的时候有一个startup_stm32f10x_hd.s的启动文件,在keil MDK和IAR两个软件下,语法有不同。
ARM汇编编译使用的是GCC编译器,要符合GNU语法。
基本格式
格式:
label: instruction @ comment
- label 即标号,表示地址位置,可以通过这个标号得到指令的地址。任何以“:”结尾的标识符都会被识别为一个标号。
- instruction 即指令,也就是汇编指令或伪指令。
- @符号,表示后面的是注释,也可以使用“/ * ”和 “ * / ”来注释。
- comment 是注释内容。
指令、伪指令、伪操作、寄存器名可以大写或小写,但不能混用
段定义
系统预定义了一些段名:
- .text 表示代码段。
- .data 初始化的数据段。
- .bss 未初始化的数据段。
- .rodata 只读数据段。
自己使用.section伪操作定义:
.section .testsection @定义一个 testsetcion 段
伪操作
- .byte 定义单字节数据,比如.byte 0x12。
- .short 定义双字节数据,比如.short 0x1234。
- .long 定义一个 4 字节数据,比如.long 0x12345678。
- .equ 赋值语句,比如.equ num, 0x12,表示 num=0x12。
- .align 数据字节对齐,比如: align 4 表示 4 字节对齐。
- .end 表示源文件结束。
- .global 定义一个全局符号,比如: .global _start。
例如:
.global _start
_start:
ldr r0, =0x12 @r0=0x12
.global 是伪操作,表示_start 是一个全局标号,类似 C 语言里面的全局变量
函数
GNU 汇编同样也支持函数,其中返回语句不是必须的。
函数名:
函数体
返回语句
例如:
/* 未定义中断 */
Undefined_Handler:
ldr r0, =Undefined_Handler
bx r0 @返回指令
/* SVC 中断 */
SVC_Handler:
ldr r0, =SVC_Handler
bx r0
/* 预取终止中断 */
PrefAbort_Handler:
ldr r0, =PrefAbort_Handler
bx r0
常用的汇编指令
内部数据传输
指令 | 含义 |
---|---|
MOV R0, R1 | 将寄存器 R1 中的数据传递给 R0 |
MOV R0, #0X12 | 将立即数 0X12 传递给 R0 寄存器 |
MRS R0, CPSR | 将特殊寄存器 CPSR 里面的数据传递给 R0(MRS 指令用于将特殊寄存器(如 CPSR 和 SPSR)中的数据传递)(读特殊寄存器) |
MSR CPSR, R0 | MSR 指令用来将普通寄存器的数据传递给特殊寄存器(写特殊寄存器) |
存储器访问
I.MX6UL 中的寄存器就是 RAM 类型的,用汇编来配置 I.MX6UL 寄存器的时需先将要配置的值写入到 Rx(x=0~12)寄存器中,然后将 Rx 中的数据写入到 I.MX6UL 寄存器中。读取寄存器过程相反。
LDR
从存储加载数据到寄存器 Rx ,或者将一个立即数加载,加载立即数时用“=”
如 I.MX6UL 有个寄存器 GPIO1_GDIR,其地址为 0X0209C004,
1 LDR R0, =0X0209C004 @ R0=0X0209C004
2 LDR R1, [R0] @ 读取地址 0X0209C004 中的数据到 R1 寄存器中
STR
STR 就是将数据写入到存储器中
例如:
配置寄存器 GPIO1_GDIR 的值为 0X20000002
1 LDR R0, =0X0209C004 @ R0=0X0209C004
2 LDR R1, =0X20000002 @ R1=0X20000002
3 STR R1, [R0] @将 R1 的值写入到 R0 所保存的地址中
LDR 和 STR 都是按照字进行读取和写入的,在指令“LDR”后面加上 B 或 H,比如按字节操作的指令就是 LDRB 和STRB,按半字操作的指令就是 LDRH 和 STRH。
指令 | 含义 |
---|---|
LDR Rd, [Rn , #offset] | 从存储器 Rn+offset 的位置读取数据存放到 Rd 中 |
STR Rd, [Rn, #offset] | 将 Rd 写入到存储器中的 Rn+offset 位置 |
压栈和出栈指令
中断和跳转时要保存当前状态:现场保护。与之对应还有恢复现场。(就是保存 R0~R15 这些寄存器值)
压栈的指令为 PUSH,出栈的指令为 POP, 可一次操作多个寄存器数据,利用当前的栈指针 SP 来生成地址。
如:压栈前SP地址指向 0X80000000
PUSH {R0~R3, R12} @将 R0~R3 和 R12 压栈
PUSH {LR} @将 LR 进行压栈
(0x80000000指向R12,0x7FFFFFFC指向R0)
出栈:
POP {LR} @先恢复 LR
POP {R0~R3,R12} @在恢复 R0~R3,R12
PUSH 和 POP 的另外一种写法是“STMFD SP!”和“LDMFD SP!”
STMFD SP!,{R0~R3, R12} @R0~R3,R12 入栈
STMFD SP!,{LR} @LR 入栈
LDMFD SP!, {LR} @先恢复 LR
LDMFD SP!, {R0~R3, R12} @再恢复 R0~R3, R12
STM和LDM是多存储和多加载,前面 LDR 和 STR每次只能读写存储器中的一个数据。
FD 是 Full Descending 的缩写,即满递减的意思:SP 指向最后一个入栈的数值,堆栈是由高地址向下增长的,编号小的对应低地址,编号高的对应高地址。
跳转指令
可以用指令或者直接向PC寄存器中写入数据。
指令 | 描述 |
---|---|
B < label> | 跳转到 label,如果超过了+/-2KB,可以指定 B.W< label>使用 32 位的跳转指令 |
BX < Rm> | 间接跳转,跳转到存放于 Rm 中的地址处,并且切换指令集 |
BL < label> | 跳转到标号地址,并将返回地址保存在 LR 中。 |
BLX < Rm> | 结合 BX 和 BL 的特点,跳转到 Rm 指定的地址,并将返回地址保存在 LR 中,切换指令集。 |
B指令
调用的函数不会再返回到原来的执行处
_start:
ldr sp,=0X80200000 @设置栈指针
b main @跳转到 main 函数
BL指令
跳转之前会在寄存器 LR(R14)中保存当前 PC 寄存器值,所以可以通过将 LR 寄存器中的值重新加载到 PC 中来继续从跳转之前的代码处运行。
push {r0, r1} @保存 r0,r1
cps #0x13 @进入 SVC 模式,允许其他中断再次进去
bl system_irqhandler @加载 C 语言中断处理函数到 r2 寄存器中
cps #0x12 @进入 IRQ 模式
pop {r0, r1}
str r0, [r1, #0X10] @中断执行完成,写 EOIR
第 5 行就是执行 C 语言版的中断处理函数,当处理完成以后返回来继续执行下面的程序,所以使用了 BL 指令。
算术运算指令
(详细见正点原子的驱动开发指南)