Raspberry Pi OK03

实验目标

学会使用汇编语言写函数

背景补充

ABI 应用程序二进制接口

  1. 标准规定寄存器r0,r1,r2和r3应该按顺序使用,如果函数中没有输入,那么就不必使用任何寄存器;如果只需要1个输入,那么就使用r0,需要2个输入,就使用r0和r1,以此类推;函数的输出总使用r0,同理如果没有输出就不需要使用r0输出。
  2. 对于r4~r12寄存器,在每次调用函数之后都需要还原现场,保证使用前后寄存器的值不变。
  3. 函数运行结束之后需要返回,那么此时我们必须知道返回的地址,该地址用lr寄存器保存。
  4. sp寄存器保存的是堆栈中第一个参数的地址,并且配合push{r4,r5},pop{r4,r5}这样的指令进行使用。

各个寄存器的使用功能如下表所示,可供查阅


代码分析

GetGpioAddress

.globl GetGpioAddress
GetGpioAddress:
ldr r0,=0x20200000
mov pc,lr

我们可以通过这种方式定义在文件外部的函数代码,既可以本文件调用也可以让其他文件的函数引用。然后设置GPIO的基地址,并把lr寄存器的值保存到pc寄存器中,实现功能与之前实验基本相同。

SetGpioFunction

.globl SetGpioFunction
SetGpioFunction:
cmp r0,#53
cmpls r1,#7
movhi pc,lr

cmp指令中,如果r0>53则跳过cmpls指令运行movhi,函数结束;cmpls指令中,如果r1>7则运行movhi,函数结束。正确情况下是r0<=53,r1<=7,指令继续执行。

push {lr}
mov r2,r0
bl GetGpioAddress

调用GetGpioAddress函数,首先将返回地址压栈,然后保存现场避免丢失寄存器的值(假如我们不知道调用的函数是如何工作的,那么就需要保存所有的寄存器,此处我们只保存r0)。

调用结束之后我们可以知道r0中的值就是GPIO的地址,r1保存function code,r2保存GPIO端口(r1和r2在main函数中定义)

functionLoop$:
cmp r2,#9
subhi r2,#10
addhi r0,#4
bhi functionLoop$

循环以保证r2<9,地址r0 = r0+(GPIO%10)* 4

add r2, r2,lsl #1
lsl r1,r2
str r1,[r0]
pop {pc}

为了加快运行速度,(r2 * 3) 变换成 (r2 左移1位 + r2),然后剩余代码与之前实验功能一致,将对应位置1,同时使用pop指令出栈lr寄存器的值送到pc中。

SetGpio

.globl SetGpio
SetGpio:
pinNum .req r0
pinVal .req r1

这里使用到了register aliases,类似于c语言中的宏,优点就是避免只用符号代替的寄存器混淆掉。

cmp pinNum,#53
movhi pc,lr
push {lr}
mov r2,pinNum
.unreq pinNum
pinNum .req r2
bl GetGpioAddress
gpioAddr .req r0

首先检测给出的pinNum是否在允许范围,更改aliases所用的寄存器并调用函数,最后r2保存pinNum,r0保存的是gpioAddr。

pinBank .req r3
lsr pinBank,pinNum,#5
lsl pinBank,#2
add gpioAddr,pinBank
.unreq pinBank

利用r3把54个GPIO划分成32和22两组,分别对应GPIO 0~31的地址0x20200000和GPIO 32~53的地址0x20200004

and pinNum,#31
setBit .req r3
mov setBit,#1
lsl setBit,pinNum
.unreq pinNum

生成对应GPIO口的置位数值,注意其中31在二进制中表示5个1,

teq pinVal,#0
.unreq pinVal
streq setBit,[gpioAddr,#40]
strne setBit,[gpioAddr,#28]
.unreq setBit
.unreq gpioAddr
pop {pc}

注意teq(test equal),并保存在相应地址处,pop出栈结束函数。


最后注意堆栈结构,并写入main函数中


结果展示

本实验结果与ok02结果相同