ARM嵌入式 --- 汇编

张开发
2026/4/3 11:07:15 15 分钟阅读
ARM嵌入式 --- 汇编
一、ARM汇编语法与核心概念1.1 伪操作汇编的 “预处理指令”伪操作Pseudo Operation不是真正的 ARM 机器指令不会生成机器码仅在汇编阶段由汇编器如 GNU as、ARM armasm处理作用是指导汇编过程、定义数据、分配内存、控制编译流程等相当于汇编语言的 “语法糖” 和 “编译器指令”。指令生成机器码CPU执行伪操作不生成机器码仅控制汇编器行为。常用核心伪操作分类伪操作作用示例.text代码段.text.data数据段.data.global声明全局符号.global main.word分配4字节并初始化val: .word 0x1234.equ定义常量.equ LED_ADDR, 0x40021000.align对齐.align 4示例area RESET,code,readonly;声明了一个名为 RESET 的代码段 code32 ;代表了后面的指令是ARM指令 entry ;代表该段是程序的入口点 start ;标号 mov r0,#1 ;r0 1 mov r1,#2 ;r1 2 add r0,r0,r1 ;r0 r0 r1 nop ;伪指令 b start ;跳转回start1.2 立即数与移位操作1.2.1 立即数ARM指令中的立即数并非可以是任意32位值它必须是8位常数循环右移偶数位得到的结果。这种编码方式节省指令空间且有严格限制核心规则立即数 8位常数循环右移2n位n0~15即右移 0/2/4/.../30 位简单理解不是所有 32 位常量都能作为立即数只有能通过 “8 位基础数 偶数次右移” 得到的数才能直接写在指令中反例0x12345678无法用 8 位 移位表示不能直接作为立即数需通过LDR加载到寄存器若需要大范围立即数可使用ldr伪指令ldr r0, 0x12345678 汇编器自动转成LDR 文字池合法 / 非法立即数速查立即数是否合法原因0xFF合法8 位常数右移 0 位0xFF00合法0xFF右移 8 位2*40x1234非法无法用 8 位 偶数移位得到0x80000000合法0x80右移 30 位2*15合法立即数直接用MOV R0, #0xFF非法立即数用LDR加载LDR R0, 0x12345678汇编器自动处理为文字池加载1.2.2 移位操作5 种移位指令移位类型指令后缀作用说明示例逻辑左移LSL高位丢弃低位补 0等价于 ×2ⁿMOV R0, R1, LSL #2→ R0 R1 2逻辑右移LSR低位丢弃高位补 0等价于 ÷2ⁿ无符号MOV R0, R1, LSR #2→ R0 R1 2无符号算术右移ASR低位丢弃高位补符号位等价于 ÷2ⁿ有符号MOV R0, R1, ASR #2→ R0 R1 2有符号循环右移ROR低位循环移到高位无补位MOV R0, R1, ROR #2→ R1 循环右移 2 位到 R0带进位循环右移RRX1 位循环右移CPSR 进位位参与循环MOV R0, R1, RRX→ R1 进位位循环右移 1 位实战示例; 计算 R0 R1 * 4左移2位比乘法指令更高效 MOV R0, R1, LSL #2 ; 计算 R0 R1 / 2无符号数右移1位 MOV R0, R1, LSR #1 ; 计算 R0 R1 / 2有符号数算术右移1位 MOV R0, R1, ASR #1二、数据操作与运算2.1 数据传送与逻辑指令核心数据传送指令指令作用示例MOV寄存器间数据传送或立即数加载到寄存器MOV R0, R1→ R0 R1MOV R0, #0x10→ R0 0x10MVN按位取反后传送NOT 操作MVN R0, #0xFF→ R0 ~0xFF 0xFFFFFF00LDR从内存加载数据到寄存器32 位字LDR R0, [R1]→ R0 *R1R1 指向的内存值STR将寄存器数据存储到内存32 位字STR R0, [R1]→ *R1 R0LDRB/STRB字节加载 / 存储8 位LDRB R0, [R1]→ 加载 1 字节到 R0 低 8 位LDRH/STRH半字加载 / 存储16 位LDRH R0, [R1]→ 加载 2 字节到 R0 低 16 位核心逻辑指令指令作用示例AND按位与常用于位清零、掩码操作AND R0, R0, #0x0F→ 保留 R0 低 4 位其余清零ORR按位或常用于位置 1ORR R0, R0, #0x0F→ 将 R0 低 4 位置 1EOR按位异或常用于位翻转EOR R0, R0, #0x0F→ 翻转 R0 低 4 位BIC位清除按位与反等价于 AND ~ 掩码BIC R0, R0, #0x0F→ 清除 R0 低 4 位等价于 AND R0, R0, #0xFFFFFFF0实战示例GPIO 位操作; 配置GPIOA第5位为输出假设GPIOA_MODER寄存器地址为0x40020000 LDR R0, 0x40020000 ; 加载GPIOA_MODER地址 LDR R1, [R0] ; 读取当前寄存器值 BIC R1, R1, #(3 10) ; 清除第10、11位对应PA5的模式位 ORR R1, R1, #(1 10) ; 将第10、11位设为01输出模式 STR R1, [R0] ; 写回寄存器完成配置2.2 算术运算指令核心算术指令指令作用示例ADD加法ADD R0, R1, R2→ R0 R1 R2ADD R0, R1, #10→ R0 R1 10SUB减法SUB R0, R1, R2→ R0 R1 - R2ADC带进位加法用于大数运算ADCS R0, R1, R2→ R0 R1 R2 C进位位并更新 CPSRSBC带进位减法用于大数运算SBCS R0, R1, R2→ R0 R1 - R2 - !CMUL32 位乘法结果低 32 位MUL R0, R1, R2→ R0 R1 * R2仅保留低 32 位UMULL/SMULL64 位乘法无符号 / 有符号UMULL R0, R1, R2, R3→ R1 (高 32 位) R0 (低 32 位) R2 * R3UDIV/SDIV无符号 / 有符号除法ARMv7 及以上支持UDIV R0, R1, R2→ R0 R1 / R2无符号实战示例64 位加法运算; 计算 R1R0 R3R2 R5R4R1R0为64位结果高32位R1低32位R0 ADDS R0, R2, R4 ; 低32位相加S后缀更新CPSR进位位 ADC R1, R3, R5 ; 高32位相加加上低32位的进位2.3 条件判断指令ARM 指令集最大的特色之一是所有指令都支持条件执行无需额外跳转指令即可实现条件判断极大提升代码效率。条件判断的核心是CPSR寄存器的标志位以及CMP等比较指令。2.3.1 CPSR 标志位条件判断的 “状态机”CPSRCurrent Program Status Register当前程序状态寄存器的低 4 位是条件标志位是所有条件指令的判断依据标志位全称置 1 条件作用N(Negative)负标志运算结果为负数最高位为 1判断有符号数正负Z(Zero)零标志运算结果为 0判断是否相等、是否为 0C(Carry)进位标志加法产生进位、减法无借位无符号数溢出判断、大数运算V(Overflow)溢出标志有符号数运算溢出有符号数溢出判断常用条件码指令后缀ARM 指令通过添加条件码后缀实现条件执行核心条件码如下后缀含义对应标志位适用场景EQ相等EqualZ1if(a b)NE不相等Not EqualZ0if(a ! b)CS/HS进位 / 无符号大于等于Carry Set/High or SameC1if(a b)无符号CC/LO无进位 / 无符号小于Carry Clear/LowC0if(a b)无符号MI负数MinusN1if(a 0)有符号PL正数 / 零PlusN0if(a 0)有符号GT有符号大于Greater ThanZ0, NVif(a b)有符号GE有符号大于等于Greater or EqualNVif(a b)有符号LT有符号小于Less ThanN≠Vif(a b)有符号LE有符号小于等于Less or EqualZ1 或 N≠Vif(a b)有符号AL总是执行Always无默认可省略2.3.2 CMP 指令条件判断的 “触发者”CMPCompare指令本质是减法运算仅更新 CPSR 标志位不保存运算结果是条件判断的核心指令常配合条件码使用。核心用法CMP R0, R1 ; 计算 R0 - R1仅更新CPSR不修改R0/R1实战示例条件分支; if(R0 R1) { R2 1; } else { R2 0; } CMP R0, R1 MOVEQ R2, #1 ; 相等时执行R21 MOVNE R2, #0 ; 不相等时执行R20三、流程控制循环与分支的实现3.1 do-while 循环先执行后判断do-while 循环的特点是先执行循环体再判断条件因此循环体至少执行 1 次ARM 汇编中通过B跳转指令 条件判断实现。C 语言原型int i 0; do { i; } while(i 10);ARM 汇编实现MOV R0, #0 ; R0 i 0 loop: ADD R0, R0, #1 ; i循环体 CMP R0, #10 ; 比较i和10 BCC loop ; 无符号小于C0时跳回loop即i 10时继续循环 ; 循环结束R0 103.2 while/for 循环先判断后执行while/for 循环的特点是先判断条件再执行循环体条件不满足则不执行循环体是最常用的循环结构。C 语言原型whileint i 0; while(i 10) { i; }ARM 汇编实现MOV R0, #0 ; R0 i 0 B check ; 先跳转到条件判断 loop: ADD R0, R0, #1 ; i循环体 check: CMP R0, #10 ; 比较i和10 BCC loop ; i 10时跳回loop执行循环体 ; 循环结束R0 10C 语言原型forfor(int i 0; i 10; i) { // 循环体 }ARM 汇编实现MOV R0, #0 ; 初始化i0 B check loop: ; 循环体此处省略 ADD R0, R0, #1 ; i check: CMP R0, #10 BCC loop四、函数调用ARM 过程调用标准AAPCS函数调用是汇编编程的核心ARM 架构遵循AAPCSARM Architecture Procedure Call Standard标准规定了寄存器使用、栈结构、参数传递等规则确保 C 语言与汇编、汇编与汇编之间的函数调用兼容。4.1 跳转指令函数调用的 “桥梁”ARM 汇编通过跳转指令实现函数调用核心指令如下指令作用区别B无条件跳转Branch仅修改 PC不保存返回地址用于简单分支BL带链接跳转Branch with Link跳转前将返回地址当前 PC4保存到 LRR14用于函数调用BX跳转并切换指令集Branch and Exchange可切换 ARM/Thumb 指令集用于跨指令集调用BLX带链接跳转并切换指令集结合 BL 和 BX 的功能用于跨指令集函数调用核心用法BL func ; 跳转到func函数LR 当前PC4返回地址 ; 函数执行完后通过 MOV PC, LR 或 BX LR 返回4.2 ARM 栈类型与初始化函数调用的 “内存栈”栈是函数调用中用于保存局部变量、参数、返回地址的内存区域ARM 架构支持满递减栈FDFull Descending这是 AAPCS 标准的默认栈类型满Full栈指针 SPR13指向最后一个入栈的数据递减Descending栈向内存地址减小的方向生长入栈时 SP 减小出栈时 SP 增大栈初始化将SP指向一块内存的高地址; 假设栈顶在8MB处 ldr sp, 0x8000000压栈Push/出栈Poppush {r0, r1, lr} ; SP - 12依次存入r0,r1,lr pop {r0, r1, pc} ; 依次恢复并弹出到PC返回4.3 现场保护与恢复函数调用的 “安全保障”ARM 寄存器分为调用者保存寄存器R0-R3, R12和被调用者保存寄存器R4-R11调用者保存寄存器函数调用时调用者需自行保存这些寄存器被调用者可随意修改被调用者保存寄存器被调用者必须保存这些寄存器返回前恢复原值否则调用者数据会被破坏R0-R3参数传递、临时使用调用者保存调用前若需要保留由调用者压栈R4-R11局部变量被调用者保存函数内使用必须恢复原值R12IP内部过程调用临时寄存器R13SPR14LR若函数内会调用其他函数需保存LRR15PC函数现场保护与恢复标准写法; 函数int add(int a, int b) → 返回ab add_func ; 现场保护保存被调用者需要使用的R4-R11此处仅用R4仅保存R4 STMFD SP!, {R4, LR} ; FD栈满递减入栈!表示SP自增同时保存LR返回地址 ; 函数体R0a, R1bAAPCS参数传递规则前4个参数用R0-R3 ADD R4, R0, R1 ; R4 a b MOV R0, R4 ; 返回值存入R0AAPCS返回值规则32位用R0 ; 现场恢复恢复寄存器和LR LDMFD SP!, {R4, PC} ; 出栈时将LR的值加载到PC实现函数返回STMFD SP! , {regs}满递减栈入栈将寄存器列表压入栈SP 自动更新LDMFD SP! , {regs}满递减栈出栈从栈中恢复寄存器SP 自动更新函数返回通过LDMFD将 LR 加载到 PC或BX LR实现五、汇编与 C 语言互调在嵌入式开发中C 语言负责业务逻辑汇编负责底层硬件操作两者互调是必备技能完全遵循 AAPCS 标准。5.1 汇编调用 C 函数汇编中调用 C 逻辑汇编调用 C 函数时需遵循 AAPCS 参数传递规则前 4 个参数依次存入R0-R3超过 4 个参数压入栈从右往左入栈返回值32 位存入R064 位存入R0-R1实战示例C 语言函数// 定义一个加法函数供汇编调用 int add(int a, int b) { return a b; }ARM 汇编调用代码AREA CODE, CODE, READONLY ENTRY ; 导入C函数add IMPORT add START MOV R0, #10 ; 第一个参数a10存入R0 MOV R1, #20 ; 第二个参数b20存入R1 BL add ; 调用C函数add返回值存入R0 ; 调用后R0 30 END5.2 C 语言调用汇编函数C 中调用底层汇编C 语言调用汇编函数时汇编函数需遵循 AAPCS 标准导出全局符号参数 / 返回值规则与上述一致。实战示例ARM 汇编函数AREA CODE, CODE, READONLY ; 导出汇编函数add供C语言调用 EXPORT add add ; 现场保护可选根据寄存器使用情况 STMFD SP!, {R4, LR} ; 函数体R0a, R1b ADD R0, R0, R1 ; R0 a b直接作为返回值 ; 现场恢复 LDMFD SP!, {R4, PC} ENDC 语言调用代码// 声明汇编函数相当于函数原型 int add(int a, int b); int main() { int res add(10, 20); // 调用汇编函数res30 return 0; }六、工作模式与异常处理ARM 架构支持7 种工作模式用于不同的运行场景异常处理是嵌入式系统响应硬件中断、软件异常的核心机制。6.1 工作模式切换系统权限的 “开关”ARM 的 7 种工作模式及说明工作模式缩写权限触发场景用户模式USR非特权普通应用程序运行系统模式SYS特权运行特权级操作系统任务快中断模式FIQ特权响应快速中断请求中断模式IRQ特权响应普通中断请求管理模式SVC特权复位、软中断SWI进入操作系统内核模式中止模式ABT特权数据 / 指令预取中止内存访问错误未定义模式UND特权执行未定义指令模式切换核心CPSR 寄存器工作模式由 CPSR 寄存器的M[4:0]位低 5 位控制不同模式对应不同的 M 位值模式切换需在特权模式下修改 CPSR。常用模式 M 位值工作模式M 位值二进制M 位值十六进制用户模式100000x10系统模式111110x1F管理模式100110x13中断模式100100x12快中断模式100010x11实战示例切换到用户模式; 从管理模式SVC切换到用户模式USR MRS R0, CPSR ; 读取当前CPSR到R0 BIC R0, R0, #0x1F ; 清除M位低5位 ORR R0, R0, #0x10 ; 设置M位为0x10用户模式 MSR CPSR_c, R0 ; 写回CPSR仅修改控制位c位域完成模式切换6.2 异常向量表异常处理的 “入口地址表”异常向量表是 ARM 架构中固定位置的内存区域默认从 0x00000000 开始可通过协处理器配置重映射存储了每种异常的处理函数入口地址当异常发生时CPU 自动跳转到对应向量地址执行。异常向量表结构按地址顺序异常类型向量地址优先级说明复位0x000000001最高系统上电 / 复位触发初始化系统未定义指令0x000000046执行未定义指令触发软中断SWI0x000000086执行 SWI 指令触发用于系统调用预取中止0x0000000C5指令预取错误触发数据中止0x000000102数据访问错误触发保留0x00000014-保留未使用普通中断IRQ0x000000184外部中断请求触发快中断FIQ0x0000001C3次高快速中断请求触发示范 : SWI进入 SWI 异常后提取 SWI 编号 7 → 交给 C 函数处理 → 返回area reset, code, readonly code32 entry ; 异常向量表 b reset_handler ; 0x00 复位 b undef_handler ; 0x04 未定义 b swi_handler ; 0x08 SWI 重点 b abort_handler b abort_handler b . b irq_handler b fiq_handler ; 其他异常占位 undef_handler: b . abort_handler: b . irq_handler: b . fiq_handler: b . ; 复位入口初始化栈并进入C reset_handler: ldr sp, 0x80000000 ; 设栈根据硬件改 bl main ; 进入C语言main b . ; SWI 处理函数核心 : 提取SWI编号并调用C处理 swi_handler: stmfd sp!, {r4-r12, lr} ; 保存现场 sub r0, lr, #4 ; 计算 SWI 指令地址 LR - 4 ldr r1, [r0] ; 读取 SWI 指令内容 bic r0, r1, #(0xff 24) ; 提取低24位 SWI号例如#7 import c_deal_swi ; 导入C处理函数 bl c_deal_swi ; 调用C函数R0 SWI号 ldmfd sp!, {r4-r12, pc}^ ; 恢复现场并返回切换模式 end关键要点总结1. SWI 异常入口向量表偏移0x08指令SWI #imm立即数低 24 位2. LR SWI 地址 4所以 SWI 指令地址 LR - 43. 提取 SWI 号步骤sub r0, lr, #4得到指令地址ldr r1, [r0]读取指令bic r0, r1, #0xFF000000屏蔽高 8 位 → 得到低 24 位 SWI 号4. 现场保存与恢复保存stmfd sp!, {r4-r12, lr}恢复ldmfd sp!, {r4-r12, pc}^^必须加异常返回时恢复 CPSR 模式

更多文章