实验目的
-
熟悉并掌握常用ARM汇编指令
-
熟悉并掌握"C+汇编"混合编程技术
-
熟练使用ARM软件开发调试工具Keil
实验内容
-
学习使用Keil开发工具;
-
实现累加运算功能;
-
实现字符串拷贝功能;
-
实现求和运算功能;
-
实现冒泡排序算法。
实验步骤
学习使用Keil开发工具
运行Keil,建立工程文件,单步运行调试演示示例程序,深刻理解每一条指令,观察寄存器,内存空间的变化。并且了解软件的使用和调试方法。
-
点击"Keil uVision5"打开软件主窗口,点击Project->New uVision
Project建立新工程,为新工程命名。 -
为工程选择目标器件:三星S3C2440A。
-
选择是否添加S3C2440.s启动文件:C源程序工程选"是",汇编源程序工程选"否"。
-
在工程Source
Group上点右键选择添加新工程文件,选择源文件类型,录入源代码并保存。 -
对工程进行build或rebuild。
-
点击debug按钮进入/退出调试,忽略代码大小限制。使用单步调试,仔细观察过程中关键寄存器值的变化。
实现累加运算功能
将实验1.2文件夹中的sum.s文件添加至工程中,根据代码参考流程图补全程序中的内容,然后debug调试,观察结果。
实现字符串拷贝功能
-
将实验1.3文件夹中的testfile.s和main.c文件添加至工程中。
-
在汇编文件testfile.s中添加两行汇编代码,分别实现:
a. 拷贝源字符串的一个字节到R2中;
b. 将拷贝的字节复制到目标空间。
-
运行Debug进行调试。
实现求和运算功能
-
将实验1.4文件夹中的sum.c和testfile.s文件添加至工程中。
-
在汇编文件testfile.s中相应位置添加汇编代码,通过调用c函数g()实现a+b+c+d+e,结果存在R8中。
-
运行Debug进行调试,观察实验现象。
实现冒泡排序算法
-
将实验1.5文件夹中的maopao.s文件添加至工程中。
-
在汇编文件maopao.s中相应位置添加汇编代码,实现冒泡排序。
-
运行Debug进行调试。
-
在debug界面,点击Debug →Memory Map,修改地址分段属性。
-
观察实验现象
实验结果
学习使用Keil开发工具
ARM数据处理指令寻址方式
在ARM中数据处理指令的寻址方式可以大致的分为三类,分别是立即数寻址,寄存器寻址和寄存器移位寻址,在这段示例代码中用到了上述的三种方式,每条代码的作用已经在下图中给出:
我们生成解决方案,然后进行单步调试,最终运行的结果如下所示。
ARM内存访问指令寻址方式
同样的,每段程序的作用已经在注释中标明:
我们生成解决方案,然后进行单步调试,对于出现的无法读取或者写入,可以在memory
map中用如下的方式进行改变,得到部分的运行结果如下所示
指令 LDR R6,[R2],由此也可以看到ARM采用的是小段存储的方式。
指令 STR R3,[R7] 将一个值写入内存
其他的结果都是类似的,我们不再赘述。
ARM堆栈指针SP的变化
同样的,每段程序的作用已经在注释中标明:
我们具体的看两个指令的效果:
指令 STMFA R13!,{R2-R5}
指令 STMEA R13!,{R1-R5}
从这里就可以看出满递增和空递增的区别,其他的不再赘述。
ARM程序计数器PC的变化
关键代码的作用如注释所示,由于前边的都是数据的存取指令,我们这部分探究的是PC的变化,所以前边的指令我们不再分析,直接从LOOP部分开始,关键部分代码的作用已经在注释中标明。
ARM程序状态寄存器PSR的变化
CPSR中的各个字段的含义如下所示:
-
N:设为指令结果的第31位,如果结果当作二进制补码表示的有符号整型,N=1表示结果是负的;N=0表示结果是非负的。
-
Z:指令结果是0的情况下设置为1,反之设为0。
-
C:如果加法运算产生无符号溢出,C设为1,反之设为0;如果减法运算产生一个借位,C就设置为1,反之设为0;对于包含移位操作的非加减运算,C设为移位器移出的最后一位。
-
V:如果发生符号溢出,设置为1。
-
A:abort中断禁止位
-
I:被置位后禁止IRQ中断
-
F:被置位后禁止FIQ中断
-
M的五位是工作模式,共有七种
代码的含义已经在注释中给出,如下所示:
部分指令运行产生的变化如下所示:
指令 SUBS R3,R2,R1
即指令的运算结果为0,并且产生了一个借位
指令 ADDS R7,R5,R2,LSL #0x4
即没有产生任何的异常
指令 CMP R1,R2
-
对于加法,包括比较指令CMN,如果加法运算产生无符号溢出,C设为1,反之设为0。
-
对于减法,包括比较指令CMP,如果减法运算产生一个借位,C就设置为1,反之设为0。
ARM工作模式的切换
ARM共有如下的其中工作模式,是由CPSR中的5位M字段来指定的,对应的值和模式对应的功能如下所示:
进入每个模式的方式都是类似的,每种模式能访问的寄存器也已经在表中给出,我们只说明其中的一种模式:
指令 MSR CPSR_cxsf,R0 进入system模式 M变为0x1F 如下所示
实现累加运算功能
在原先的程序上,我们补全一部分内容,每段指令的作用已在注释中给出。
最终运行结果如下所示,0x13BA对应十进制的5050,结果正确:
实现字符串拷贝功能
将给出的c代码和汇编代码导入到程序中,然后补全代码如下所示,关键语句的作用已在注释中标明。
下边给出的是汇编代码,同样的关键语句的作用已在注释中标明。
然后我们在监视中添加dststr和srcstr,观察运行前后值的变化,下两图分别是运行前和运行后dststr的变化。
实现求和运算功能
将给出的c代码和汇编代码导入到程序中,然后更正示例程序中的代码。
下边给出的是汇编代码,同样的关键语句的作用已在注释中标明。
然后我们调试程序,同样的设置监视在c代码中观察传入的参数。
随后调用结束,返回汇编程序,寄存器中的值如下所示,返回值存在R0中,其值为0x1E,即对应十进制的30,计算结果正确。
实现冒泡排序算法
冒泡排序是一种简单的排序方法,每次循环能将最大的元素"下沉"到数组的尾部,就避免了对后边元素的再次排列,其程序框图可用下图表示。
在这样的思想基础上,我们补充汇编程序如下所示,同样的关键代码的作用已经在注释中标明。
随后我们点击Debug调试程序,在地址为0x40000000的地址中手动填入数据,如下所示。
随后单步调试,观察程序运行中的变化,如下所示是程序在结束第一轮外循环后,数组的变化,可以看到,最大的0x14已经被放在了数组的末尾。
最终程序运行的结果如下图所示:
实验总结
在实验1中,我们探究了ARM的基本工作方式,了解了他们的数据处理指令寻址方式,内存访问指令寻址方式,堆栈指针SP的使用,程序计数器PC的工作方式和子程序的返回和调用,程序状态寄存器PSR的使用和工作模式的切换。
在实验2中,我们利用纯汇编的方式完成了累加的操作,掌握了如何控制循环的方式。
在实验3中,我们用C调用汇编,要在在C中声明函数原型,并加extern关键字;在汇编中用EXPORT导出函数名,并用该函数名作为汇编代码段的标识。其中EXPORT伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。在汇编的最后用MOV
PC,LR返回主程序。
在实验4中,我们在汇编中调用C的函数,需要在汇编中
IMPORT对应的C函数名。在调用该C函数之前还需要通过汇编语言传递该函数的参数。C和汇编之间的参数传递是通过ATPCS规定行的。如果函数有不多于四个参数,对应的用ARM寄存器RO-R3来进行传递,多于4个时借助栈。函数的返回值通过R0来返回。
在实验5中,我们实现了基于汇编的冒泡排序,设计了相比之前更加复杂的算法,对汇编语言的设计程序有了更好的理解和掌握。
实验思考题
- ADD替换成ADDS ,SUB替换成SUBS有什么影响?
运算结果不影响CPSR中相应标志位的值,跳转指令因为上一步的CPSR
的值没有改变而无法正确执行。
- MOV替换成MOVNE有什么影响?
只有在上一步计算结果为不相等时才执行。
- STMIA换成STMIB ,STMIA换成STMDA有什么区别?
第一个是将每次传送后地址加4改每次传送前地址加4,第二个是将每次传送后地址加4改为每次传送后地址减4
- 思考用ARM汇编实现1+3+5+…+(2n+1)或者2+4+6+…+2n。
- 实验3中如果去除汇编代码中的"EXPORT
strcopy"会有什么现象,为什么?
C语言无法调用用strcopy函数。因为EXPORT伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。
- 实验4中如果去除汇编代码中的"IMPORT …" 会有什么现象,为什么?
无法调用c语言的main函数。因为IMPORT伪指令用于通知编译器要使用的标号或变量在其他的源文件中定义。