ARM汇编

最近学习了ARM汇编,汇编和C语言思维完全不一样。C语言我们写while、for循环,写函数调用,编译器默默帮我们处理跳转、保存寄存器、栈操作。到了汇编这一层,所有这些工作都要我们亲手写出来,每一步CPU干了什么,都得心里有数。

一、汇编里实现循环

不管是C语言的do‑while、while还是for循环,底层翻译成汇编,本质都离不开循环三要素:循环结束条件、让循环走向结束的语句、循环体。

do‑while:先执行循环体,再判断条件。对应汇编就是先做运算,再比较,条件满足就跳回继续循环。

举个例子求0~100累加:先做相加、变量自增,再用cmp做比较,ble小于等于就跳回标签继续跑。

mov r0, #0 ;i

mov r1, #0 ;sum

loop

add r1, r1, r0

add r0, r0, #1

cmp r0, #100

ble loop

while / for:先判断条件,满足才执行循环体。汇编里就要把cmp比较放在最前面,如果条件不满足直接跳出循环。

同样是0‑100求和,上来先判断,如果i大于100直接跳到结束标签,否则执行循环内容。

mov r0, #0

mov r1, #0

loop

cmp r0, #100

bgt finish

add r1, r1, r0

add r0, r0, #1

b loop

finish

b finish

总的来说:do‑while是先干活,后判断;while/for是先判断,后干活。C语言只是语法外壳,底层都是标签+比较+跳转。

二、ARM汇编函数调用

C语言写函数很轻松,写好函数,直接调用就完事。汇编里面调用函数,要面对两个很现实的问题:

  1. 被调用的函数会修改通用寄存器,把主函数正在用的数据冲掉;

  2. 函数嵌套调用的时候,返回地址容易乱掉,不知道调用完回到哪里。

解决办法就是栈,保护现场和恢复现场。

  1. ARM栈类型

栈分四种:满增、满减、空增、空减。

复制代码
满栈:sp指针指向最后一个入栈的有效数据
空栈:sp指向下一个可以存放数据的空位
增栈:地址变大;
减栈:地址变小

ARM默认使用满减栈(FD),也就是STMFD、LDMFD这一组指令。

注意栈指针初始化不能直接写mov sp,#0x40001000,这个数不属于合法立即数,会直接报错,要用ldr sp,=0x40001000。同时Keil魔术棒里面要配置IRAM内存地址和大小,不然栈空间都没有。

stmfd sp!,{寄存器列表}:保护现场,把寄存器压入栈,!代表执行完指令自动更新sp指针

ldmfd sp!,{寄存器列表}:恢复现场,把栈里的数据弹回寄存器。特别注意,如果写ldmfd ...,{...,pc},弹栈的同时直接完成函数返回,就不用再写bx lr。

函数调用流程简单梳理:

  1. 设置栈指针sp;

  2. 主调函数先stmfd压栈,保护现场;

  3. bl 函数名跳转,bl会自动把返回地址存到lr寄存器;

  4. 执行被调用函数;

  5. 返回时ldmfd弹栈恢复寄存器,回到主函数继续往下跑。

这里要记住:pc存当前执行地址,lr存放函数调用的返回地址,bx lr就是跳回返回地址。

  1. b、bl、bx简单区分

b 标签:单纯跳转,不会保存返回地址,跳过去就回不来;

bl 标签:跳转+把下一条指令地址保存到lr,用于函数调用,执行完可以回来;

bx 寄存器:跳转到寄存器保存的地址,还会切换ARM/Thumb指令集,函数返回常用bx lr。

三、汇编和C语言相互调用

实际开发很少全部手写汇编,大多是C为主,少量汇编。分为两种场景:汇编调用C函数、C调用汇编函数。

汇编调用C函数

  1. 在汇编文件里import c_add声明外部C函数;C文件用extern声明。

  2. 调用前做好保护现场,寄存器传参。

    ARM规则:前4个参数放在r0‑r3,多于4个的参数,直接压到栈上。返回值放在r0。

示例:

stmfd sp!, {r0-r12, lr}

mov r0, #1

mov r1, #2

mov r2, #3

mov r3, #4

mov r4, #5

stmfd sp!, {r4} ;第5个参数压栈

bl c_add

ldmfd sp!, {r4}

ldmfd sp!, {r0-r12, lr}

C语言调用汇编函数

  1. 汇编里面用export func1把函数导出,对外可见;

  2. 在C文件写extern int func1(int a,int b);声明,之后C就可以直接像普通函数一样调用。

    混编统一规则:参数前4个r0~r3,多余参数压栈;函数返回值放到r0寄存器,不管汇编调C还是C调汇编都遵守这个约定。

四、CPSR程序状态寄存器,修改CPU工作模式

CPSR寄存器里面存着条件标志位还有CPU工作模式M位域。

两个关键指令:

MRS Rd, cpsr:把CPSR内容读到通用寄存器,读状态寄存器;

MSR cpsr, Rn:把通用寄存器写回CPSR,修改状态寄存器。

修改模式套路:先读CPSR到普通寄存器,把M域清零,或上新的模式码,再写回CPSR。

mrs r0, cpsr

bic r0, r0, #(0x1FF << 0)

orr r0, r0, #(0x10 << 0)

msr cpsr, r0

同时CPSR的NZCV条件标志位,cmp比较指令会改变它们,之后bgt、ble这类带后缀的条件跳转就依靠这些标志位判断。

还有软中断异常swi #7,会触发异常,跳转到异常向量表,也是后面学习中断的基础。

学ARM汇编最大感受就是,C语言把很多底层细节全部封装起来了。以前写循环写函数,只管业务逻辑,现在汇编每一次跳转,每一次压栈弹栈都要自己操心。

刚开始写很容易栈没配置好、忘记保护lr、立即数报错,各种各样的编译坑。多动手敲代码,对着仿真看寄存器、sp指针的变化,一点点就能理解CPU真正在干什么。

相关推荐
深圳市恒锐丰科技杨生2 小时前
EG2134 300V 三相独立半桥栅极驱动芯片|屹晶 EGmicro
嵌入式硬件·硬件工程
LCG元2 小时前
STM32F103 SPI 驱动 W5500 以太网模块实战:硬件协议栈寄存器体系、Socket 状态机避坑与 TCP 吞吐实测
stm32·单片机·嵌入式硬件
Zhang.Yi3 小时前
STM32-SPI
stm32·单片机·嵌入式硬件
en.en..3 小时前
网络分层封装 与 TCP粘包的因果关系
网络·单片机·嵌入式硬件
天空'之城3 小时前
单片机基础核心知识点汇总(三十九)
单片机·嵌入式硬件
爱吃汽的小橘5 小时前
移位运算hao
嵌入式硬件
单片机仿真设计6 小时前
【proteus仿真】基于 STM32F103 的汽车温度监测系统(仿真图+程序)
stm32·单片机·嵌入式硬件·proteus·课设
anew___6 小时前
舵机——让 Arduino 动起来
c++·stm32·单片机·嵌入式硬件·c
周洲08307 小时前
STM32 SPI 通信实战|高速通信、片选、数据收发
stm32·嵌入式硬件