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真正在干什么。

相关推荐
youcans_13 小时前
【嵌入式软件AI编程】22. STM32 基础实验:GPIO外部中断
stm32·单片机·嵌入式硬件·ai编程·claude
未来_攻城狮13 小时前
STM32F407VGT6 芯片详解:高性能 Cortex-M4 MCU 入门指南
stm32·单片机·嵌入式硬件
xcLeigh13 小时前
FreeRTOS实战:用STM32+SEGGER打造工业级实时嵌入式系统
人工智能·stm32·单片机·嵌入式硬件·ai
路上有只喵14 小时前
认识元器件 · 单片机(MCU/MPU/SOC) · 实战篇1
单片机·嵌入式硬件
芯岭技术郦14 小时前
XL4460 FSK 无线透传模组
stm32·单片机·嵌入式硬件
半导体憨包14 小时前
共源共栅(Cascode)功率器件:让 SiC / GaN “兼容“你的硅栅驱动
嵌入式硬件·生成对抗网络·硬件工程·半导体·dc-dc·半导体芯片
天空'之城14 小时前
单片机基础核心知识点汇总(五十)
单片机·嵌入式硬件
新晨单片机设计17 小时前
S033A-基于STM32单片机拼图游戏机【Proteus仿真+Keil程序+原理图】
stm32·单片机·嵌入式硬件·拼图游戏机
Silicore_Emma17 小时前
D2596HV:高压异步DC-DC降压芯片简介
嵌入式硬件·消费电子·dc-dc系列电路·电源管理电路·降压(buck)开关稳压器
2501_9226395818 小时前
率能半导体 SS6811H双通道H桥驱动芯片:38V/1.6A,覆盖舞台灯光、安防相机、办公自动化、游戏机、机器人五大应用场景
单片机·嵌入式硬件·硬件工程