实验目标
学会使用汇编语言写函数
背景补充
ABI 应用程序二进制接口
- 标准规定寄存器r0,r1,r2和r3应该按顺序使用,如果函数中没有输入,那么就不必使用任何寄存器;如果只需要1个输入,那么就使用r0,需要2个输入,就使用r0和r1,以此类推;函数的输出总使用r0,同理如果没有输出就不需要使用r0输出。
- 对于r4~r12寄存器,在每次调用函数之后都需要还原现场,保证使用前后寄存器的值不变。
- 函数运行结束之后需要返回,那么此时我们必须知道返回的地址,该地址用lr寄存器保存。
- sp寄存器保存的是堆栈中第一个参数的地址,并且配合push{r4,r5},pop{r4,r5}这样的指令进行使用。
各个寄存器的使用功能如下表所示,可供查阅
代码分析
GetGpioAddress
.globl GetGpioAddress |
我们可以通过这种方式定义在文件外部的函数代码,既可以本文件调用也可以让其他文件的函数引用。然后设置GPIO的基地址,并把lr寄存器的值保存到pc寄存器中,实现功能与之前实验基本相同。
SetGpioFunction
.globl SetGpioFunction |
cmp指令中,如果r0>53则跳过cmpls指令运行movhi,函数结束;cmpls指令中,如果r1>7则运行movhi,函数结束。正确情况下是r0<=53,r1<=7,指令继续执行。
push {lr} |
调用GetGpioAddress函数,首先将返回地址压栈,然后保存现场避免丢失寄存器的值(假如我们不知道调用的函数是如何工作的,那么就需要保存所有的寄存器,此处我们只保存r0)。
调用结束之后我们可以知道r0中的值就是GPIO的地址,r1保存function code,r2保存GPIO端口(r1和r2在main函数中定义)
functionLoop$: |
循环以保证r2<9,地址r0 = r0+(GPIO%10)* 4
add r2, r2,lsl #1 |
为了加快运行速度,(r2 * 3) 变换成 (r2 左移1位 + r2),然后剩余代码与之前实验功能一致,将对应位置1,同时使用pop指令出栈lr寄存器的值送到pc中。
SetGpio
.globl SetGpio |
这里使用到了register aliases,类似于c语言中的宏,优点就是避免只用符号代替的寄存器混淆掉。
cmp pinNum,#53 |
首先检测给出的pinNum是否在允许范围,更改aliases所用的寄存器并调用函数,最后r2保存pinNum,r0保存的是gpioAddr。
pinBank r3 |
利用r3把54个GPIO划分成32和22两组,分别对应GPIO 0~31的地址0x20200000和GPIO 32~53的地址0x20200004
and pinNum,#31 |
生成对应GPIO口的置位数值,注意其中31在二进制中表示5个1,
teq pinVal,#0 |
注意teq(test equal),并保存在相应地址处,pop出栈结束函数。
最后注意堆栈结构,并写入main函数中
结果展示
本实验结果与ok02结果相同