本篇简单介绍了关于汇编的一些基础指令的用法,但关于栈、寄存器等问题已经补充完整,同时加入了ARM 裸机开发环境搭建操作教程,汇编会用的语言基础环境依然是keil 4,但需要有相关支持,其他更多的指令建议大家多查手册来学习
学习核心目标
编写ARM 启动代码,为 C 语言运行搭建基础环境,核心任务包括:
- 初始化异常向量表(处理复位、中断等异常)
- 初始化各工作模式的栈指针寄存器(C 语言函数调用依赖栈)
- 开启 ARM 内核中断允许(配置 CPSR 寄存器)
- 将工作模式切换为User 模式(用户程序运行模式)
- 引导程序进入 C 语言
main
函数执行
一、ARM 汇编基础(格式与伪指令)
1. 汇编程序基本结构(汇编中注释用;)
ARM 汇编通过伪指令指导汇编器工作,非处理器指令,核心结构如下:(注意格式:最前面要加tab键)
AREA reset, CODE, READONLY ; 定义代码段,段名reset(复位向量段),属性只读
CODE32 ; 后续指令使用32位ARM指令集(Thumb为16位,需用THUMB伪操作)
ENTRY ; 标记程序入口(复位后第一个执行的指令位置)
; 具体内容
END ; 标记程序结束
2. 关键伪指令解析
伪操作 | 作用说明 |
---|---|
AREA |
定义段(代码段 / 数据段 / 栈段),格式:AREA 段名, 段属性, 访问权限 |
CODE32 /THUMB |
指定指令集类型:32 位 ARM 指令集 / 16 位 Thumb 指令集 |
ENTRY |
标记程序入口,一个工程仅一个 ENTRY(复位向量段需包含 ENTRY) |
END |
标记汇编文件结束,汇编器遇到 END 后停止处理 |
二、核心 ARM 指令
1. 数据传送指令(MOV/MVN)
(1)MOV 指令(数据移动 / 赋值)
功能:将立即数或寄存器值传送到目标寄存器(类似 C 语言的 = 赋值)
指令格式:
格式 说明 示例 MOV{S}<c> Rd, #const
立即数传送到 Rd MOV R0, #0x8
(R0=8)MOV{S}<c> Rd, Rm
Rm 值传送到 Rd MOV R1, R0
(R1=R0)MOV{S}<c> Rd, Rm, <shift>
Rm 移位后传送到 Rd(移位量 0-31) MOV R6, R0, LSL #31
(左移 31 位)
关键注意点:
- 移位操作支持
LSL
(逻辑左移)、LSR
(逻辑右移)、ASR
(算术右移)、ROR
(循环右移)、RRX
(带进位循环右移,无需移位量)- 立即数需满足12 位立即数规则<z在SUB指令部分所写>
(2)MVN 指令(按位取反)
- 功能:将立即数或寄存器值按位取反后传送到目标寄存器(类似 C 语言 ~)
- 指令格式:
MVN{S}<c> Rd, #const
/MVN{S}<c> Rd, Rm{, <shift>}
- eg:
MVN R0, #0x0
(R0=0xFFFFFFFF,因为 0x0 取反为全 1)
2. 算术运算指令(ADD/SUB/CMP)
(1)ADD 指令(加法)
功能:两个操作数相加,结果存入目标寄存器(类似 C 语言
+
)指令格式:
格式 说明 示例 ADD{S}<c> Rd, Rn, #const
Rn + 立即数 → Rd ADD R6, R0, #0xF0
ADD{S}<c> Rd, Rn, Rm{, <shift>}
Rn + (Rm 移位后) → Rd ADD R7, R0, R1, LSL #1
(R0+2*R1)-
注意:无
ADD Rd, #a, #b
格式(C 语言a+b
在编译阶段计算,无需机器指令)
(2)SUB 指令(减法)
功能:两个操作数相减,结果存入目标寄存器(类似 C 语言
-
)指令格式(与 ADD 类似):
SUB{S}<c> Rd, Rn, #const
(Rn - 立即数 → Rd)SUB{S}<c> Rd, Rn, Rm{, <shift>}
(Rn - 移位后的 Rm → Rd)
-
关键补充:
什么是立即数?
立即数是直接嵌入在指令中的常数,无需从内存、寄存器中读取,CPU 可直接用该常数参与运算,它的核心特点是 “指令自带数据”,能减少内存访问次数,提升指令执行效率。
12 位立即数规则
- 判断标准:一个数展开为 32 位二进制后,存在偶数位循环右移,使移位后高 24 位全 0,低 8 位为有效
imm8
(循环移位次数2N
,N=0~15 -> 2N = 0~30) - 原因:ARM 指令为 32 位,立即数由
4位循环移位量(N)+8位imm8
组成,共 12 位(即imm12
) - eg:
#0x80000000
(二进制1000...0000
)可通过imm8=0x80
、循环移位2*15=30
位得到,属于合法立即数;#0x101
无法通过该规则得到,为非法立即数
- 判断标准:一个数展开为 32 位二进制后,存在偶数位循环右移,使移位后高 24 位全 0,低 8 位为有效
(3)CMP 指令(比较)
- 功能:比较两个数(本质是
SUB
运算,但不保存结果,仅更新 CPSR 标志位)[通过做差得到的正负值来判断二者大小] - 指令格式:
CMP<c> Rn, #const
/CMP<c> Rn, Rm{, <shift>}
- 等价关系:
CMP R0, R1
≡SUBS R0, R1
(S
表示更新标志位) - 用途:配合条件跳转指令(如
BLE
、BGT
)实现分支逻辑
用subs实现指令比较(cmp作用)流程图
条件判断标志NZCV
CPSR寄存器中条件判断标志位
N: 符号标志位:上条指令执行结果最高位bit31为1,则 N = 1, 当结果作为有符号解释时为负值;
Z: 零值标志位:上条指令执行结果为0(即bit0 - bit31 均为0),则 Z = 1;
C: 进位标志位:进行无符号解读,如果在加法过程中进位或者减法时没有借位,则为 C = 1,否则 C = 0
V: 溢出标志位:进行有符号解读,是否发生溢出 -2^31 - 2^31-1(两个正数加得负数,两个负数加得正数)
条件码:eq ge gt le lt al(无条件执行)
equal:等于
not equal:不等于
3. 位操作指令(BIC/ORR)
(1)BIC 指令(指定位清0)
- 功能:将 Rn 的指定位清 0(按 Rm / 立即数的位掩码),结果存入 Rd(类似 C 语言
Rn & ~mask
) - 指令格式:
BIC{S}<c> Rd, Rn, #const
/BIC{S}<c> Rd, Rn, Rm{, <shift>}
- eg:
MOV R0, #0xFFFFFFFF ; R0=全1 MOV R1, #1 BIC R2, R0, R1, LSL #31 ; R0的bit31清0 → R2=0x7FFFFFFF
(2)ORR 指令(指定位置1)
- 功能:将 Rn 的指定位置 1(按 Rm / 立即数的位掩码),结果存入 Rd(类似 C 语言
Rn | mask
) - 指令格式:
ORR{S}<c> Rd, Rn, #const
/ORR{S}<c> Rd, Rn, Rm{, <shift>}
- eg:
MOV R0, #0x00 ; R0=全0 MOV R1, #1 ORR R8, R0, R1, LSL #31 ; R0的bit31置1 → R8=0x80000000
4. 内存访问指令(LDR)
LDR 指令(加载)
- 功能:从内存地址读取数据到寄存器
- 核心格式:
- 加载立即数地址:
LDR Rd, =const
(伪指令,将 32 位地址存入 Rd,解决MOV Rd, #const
无法处理大地址的问题) - 加载标签地址:
LDR Rd, <label>
(将标签对应的内存地址值存入 Rd)
- 加载立即数地址:
- eg:
LDR SP, =0x40001000
(初始化栈指针为0x40001000
,MOV SP, #0x40001000
会报错,因地址超 12 位立即数范围)
STR (存放指令)
功能: 将寄存器中的数据写入(存储)到指定的内存地址中。
核心格式:STR<c> <Rt>, <addressing_mode>
5. 跳转与函数调用指令(B/BL/BX)
指令 | 功能说明 | 示例 | 用途场景 |
---|---|---|---|
B |
无条件 / 条件跳转,不保存返回地址 | B loop (跳转到 loop 标签) |
循环、简单分支 |
BL |
跳转并保存返回地址到LR (Link Register) |
BL func (调用 func 函数) |
函数调用(需返回主调函数) |
BX |
跳转并切换指令集(ARM/Thumb) | BX LR (从 LR 恢复 PC,返回) |
函数返回(等价于MOV PC, LR ) |
- 等价关系:
B fun
≡LDR PC, =fun
(PC 为程序计数器,指向当前执行指令地址 + 8)
6. 寄存器组操作指令(STMFD/LDMFD)
用于栈操作(保护 / 恢复现场),核心是多寄存器的批量存储 / 加载,基于 ARM 的 “满减栈”
- 栈类型说明
满栈:栈指针(SP)指向最后一个已使用的栈元素
减栈:入栈时 SP 先减 4(32 位寄存器),再存储数据;出栈时先读取数据,再 SP 加 4
- STMFD(批量存储 / 入栈)
格式:STMFD SP!, {Rlist}(!表示入栈后自动更新 SP)
功能:将Rlist(如R0-R12, LR)中的寄存器值依次入栈,保护现场
eg:STMFD SP!, {R0-R12, LR}(函数调用前保护主调函数的寄存器和返回地址)
- LDMFD(批量加载 / 出栈)
格式:LDMFD SP!, {Rlist}(!表示出栈后自动更新 SP)
功能:将栈中数据依次加载到Rlist,恢复现场
eg:LDMFD SP!, {R0-R12, PC}(函数返回前恢复寄存器,并将 LR 值写入 PC,实现返回)
7. 系统寄存器指令(MRS/MSR)
用于读写特殊功能寄存器(如 CPSR),核心用途是修改处理器工作模式
- MRS(读取系统寄存器)
格式:MRS Rd, SpecReg(SpecReg 为特殊寄存器,如 CPSR)
功能:将特殊寄存器的值读取到通用寄存器 Rd
eg:MRS R0, CPSR(读取当前程序状态寄存器 CPSR 到 R0)
- MSR(写入系统寄存器)
格式:MSR SpecReg, Rd / MSR SpecReg, #const
功能:将 Rd 或立即数的值写入特殊寄存器
切换到 User 模式)
MRS R0, CPSR ; 读取CPSR
BIC R0, R0, #0x1FF ; 清除CPSR的M域(bit0-bit8,控制工作模式)
ORR R0, R0, #0x10 ; 设置M域为0x10(User模式)
MSR CPSR, R0 ; 写入CPSR,完成模式切换
核心指令总结
指令类型
指令
功能
示例
补充说明
数据传送
MOV/MVN
传送/取反数据
MOV R0, #8
/MVN R0, #0
MVN
实现按位取反(~)算术运算
ADD/SUB/CMP
加减法/比较
ADD R1, R0, #10
/CMP R0, R1
CMP
通过减法更新标志位(N/Z/C/V)位操作
BIC/ORR
位清除/置位
BIC R0, R0, #0xF
/ORR R0, R0, #0x1
BIC
等价于AND NOT
内存访问
LDR/STR
加载/存储数据
LDR R0, [R1]
/STR R0, [R1]
LDR
可加载立即数地址(伪指令)跳转调用
B/BL/BX
跳转/函数调用/返回
BL func
/BX LR
BL
自动保存返回地址到LR
寄存器组操作
STMFD/LDMFD
批量存储/加载(栈操作)
STMFD SP!, {R0-R12, LR}
满减栈:SP先减后存(入栈)
LDMFD SP!, {R0-R12, PC}
出栈时恢复
PC
实现返回系统寄存器操作
MRS/MSR
读写特殊寄存器(如CPSR)
MRS R0, CPSR
用于修改处理器模式或中断使能
MSR CPSR_c, #0x10
0x10
切换到 User模式
三、函数调用与参数传递
1. 循环实现(对应 C 语言循环结构)
循环三要素
循环结束条件
推动循环趋向终结的语句
循环的循环体
(1)do-while 循环(先执行循环体,再判断条件)
C 语言原码:
int i = 0;
int sum = 0;
do {
sum += i;
i++;
} while (i <= 100);
汇编实现:
MOV R0, #0 ; R0 = i = 0
MOV R1, #0 ; R1 = sum = 0
loop
ADD R1, R1, R0 ; sum += i
ADD R0, R0, #1 ; i++
CMP R0, #100 ; 比较i和100
BLE loop ; 若i<=100,跳回loop(BLE=Branch if Less than or Equal)
(2)while/for 循环(先判断条件,再执行循环体)
C 语言原码
int i = 0;
int sum = 0;
while (i <= 100) {
sum += i;
i++;
}
汇编实现:
MOV R0, #0 ; R0 = i = 0
MOV R1, #0 ; R1 = sum = 0
loop
CMP R0, #100 ; 先判断i<=100?
BGT finish ; 若i>100,跳至finish(BGT=Branch if Greater Than)
ADD R1, R1, R0 ; sum += i
ADD R0, R0, #1 ; i++
B loop ; 跳回loop继续判断
finish
B finish ; 死循环(防止程序跑飞)
2. 函数定义与调用(含现场保护)
(1)函数调用核心问题
- 问题 1:被调函数修改主调函数的寄存器,导致数据丢失
- 问题 2:函数嵌套时,
LR
被覆盖,无法正确返回 - 解决方案:栈保护现场(入栈保存寄存器,出栈恢复)
(2)汇编函数调用(嵌套调用)
PRESERVE8 ; 栈8字节对齐,解决C函数调用报错
AREA func_demo, CODE, READONLY
ENTRY
main:
LDR SP, =0x40001000 ; 初始化栈指针(关键:必须先初始化栈)
STMFD SP!, {R0-R12, LR} ; 保护main的现场(寄存器+返回地址)
BL func0 ; 调用func0,LR保存main的返回地址
LDMFD SP!, {R0-R12, LR} ; 恢复main的现场
MOV R3, #300 ;
3. 函数调用与参数传递
参数传递规则
前4个参数:通过
R0-R3
传递。更多参数:通过栈传递(从右向左压栈)。
返回值:32位通过
R0
,64位通过R0+R1
。
汇编调用C函数
MOV R0, #10 ; 参数1
MOV R1, #20 ; 参数2
BL c_func ; 调用C函数
C调用汇编函数
extern int asm_func(int a, int b); // 声明汇编函数
int result = asm_func(10, 20); // 调用
4. 栈保护与现场恢复
保护现场(压栈)
STMFD SP!, {R0-R12, LR} ; 保存寄存器及返回地址
恢复现场(弹栈)
LDMFD SP!, {R0-R12, PC} ; 恢复寄存器并返回(PC=LR)
5. 混合编程注意事项
栈对齐:使用 PRESERVE8
确保8字节对齐。
寄存器保护:被调函数需保护 R4-R11
(若修改)。
指令集切换:BX LR
可自动切换ARM/Thumb模式。
异常处理:软中断(SWI #7
)需在异常向量表中定义处理逻辑。
四、IMX6ULL开发环境搭建
1.开发板介绍
型号:正点原子 IMX6ULL-Mini开发板
核心配置
SOC:NXP i.MX6ULL Cortex-A7单核处理器
主频:528MHz(工业级)/800MHz(商业级)
封装:467引脚 GBA
内存:512MB DDR3L RAM
存储:8GB eMMC(支持SD卡/NAND/eMMC启动)
屏幕:4.3寸(800×480分辨率)
硬件结构
核心板(6层板):通过双列直插与底板连接
底板外设
红色LED:用户可编程控制
蓝色LED:电源指示灯(常亮)
510Ω限流电阻:保护LED灯珠
2.开发工具安装与配置
1. 代码编辑器(Windows端)
工具:Visual Studio Code
插件:安装 Arm Assembly
(支持汇编语法高亮)
工程创建:
创建目录:D:\IMX6ULL\led_asm
创建文件:start.S(注意大写S后缀,支持预处理)
2. 交叉编译器(Ubuntu端)
安装步骤
# 拷贝编译器到指定目录
sudo cp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm/
# 解压并清理
cd /usr/local/arm
sudo tar xvf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
sudo rm gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
配置环境变量
# 编辑.bashrc
vi ~/.bashrc
# 末尾添加:
export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/
# 生效配置
source ~/.bashrc
reboot # 重启虚拟机
验证安装
arm-linux-gnueabihf-gcc -v # 输出版本信息即成功
3. FTP文件传输服务
Ubuntu端配置:
# 安装vsftpd
sudo apt-get install vsftpd
# 修改配置
sudo vi /etc/vsftpd.conf
# 确保以下两项未注释:
local_enable=YES
write_enable=YES
# 重启服务
sudo /etc/init.d/vsftpd restart
sudo /etc/init.d/vsftpd status # 确认状态正常
Windows端安装FileZilla
运行安装包:FileZilla_3.39.0_win64-setup_bundled.exe
勾选“创建桌面快捷方式”
连接与文件上传
主机:Ubuntu的IP(如192.168.71.134
)
用户名:可设置为Ubuntu的用户名(或自行设置)
密码:可设置为Ubuntu的密码(或自行设置)
操作:右侧窗口(本地)拖拽文件至左侧(远程)即可上传
注意事项
- 编译器路径:务必确认PATH配置正确,否则编译命令无法识别
- FTP权限:确保Ubuntu用户目录有读写权限(通过
write_enable=YES
开启)- 网络连通:Windows与Ubuntu需在同一局域网,IP地址根据实际修改