当前位置:   article > 正文

第四章 基于ARM7系列的汇编语言程序设计【嵌入式系统】_汇编语言程序设计 ——基于arm体系结构 (第4版) pdf

汇编语言程序设计 ——基于arm体系结构 (第4版) pdf

前言

2023-7-3 18:34:20

以下内容源自《【嵌入式系统】》
仅供学习交流使用

推荐

第三章 ARM7指令系统【嵌入式系统】

第四章 基于ARM7系列的汇编语言程序设计

4.1 ARM7 汇编语言程序结构格式

4.1.1 汇编语言的基本概念

按照抽象层次划分可将计算机语言分为三类:机器语言、汇编语言和高级语言。

4.1.2 ARM7 汇编语言程序框架

在这里插入图片描述

4.2 ARM7 汇编语言程序中的符号

4.2.1 符号命名规则

在汇编语言程序设计中,经常使用各种符号表示变量、常量和地址等,以增加程序的可
读性。在编制符号名时,必须遵循以下的约定:

  • 符号由大小写字母、数字以及下划线组成;
  • 符号区分大小写,同名的大、小写符号会被汇编器认为是两个不同的符号;
  • 符号在其作用范围内必须惟一,即在其作用范围内不可有同名的符号;且符号名不得与系统中保留字相同;
  • 符号名不应与指令或伪指令同名。

4.2.2 常量

常量就是程序中出现的那些固定值,也就是说其值在程序的运行过程中不能被改变,除
了自身的值以外,没有其他属性的数值。ARM7 汇编程序所支持的常量可以分为数值常量、
逻辑常量、字符串常量和和字符常量。例如,立即数寻址时所有的立即数、直接寻址时所有
的地址、ASCII 字符等都是常量。在汇编语言源程序中,数值常量按其基数的不同,可有二
进制数、八进制数、十进制数、十六进制数等几种不同表示形式,分别用不同的前缀加以区
别。

  1. 数值常量一般为 32 位的整数。
  2. 逻辑常量只有两种取值情况:{TRUE}和{FALSE},使用时需要带大括号。
  3. 字符串常量是一个固定的字符串,一般用于程序运行时的信息提示,使用时需要带
    双引号,如“Hello ARM!”和“123456”。 *补充:如果需要使用双引号或字符$,则必须使用
    ""或$$代替
  4. 字符常量是一个用单引号括起来的单个字符。

4.2.3 变量

变量就是在程序运行过程中可以改变的量。通过定义变量可以简化程序的表达,增加程
序的可读性,方便对程序进行修改,便于交流与记忆。ARM7 汇编语言程序所支持的变量有
数值变量、逻辑变量和字符串变量。

  1. 数值变量用于保存程序运行时的数字值,但注意数字值的大小不应该超过数字变量
    所能表示的范围。
  2. 逻辑变量用于保存程序运行时的逻辑值,逻辑值只有两种取值情况:真(TRUE)
    或假(FALSE)。
  3. 字符串变量用于保存程序运行时的一个字符串,但注意字符串的长度不应该超过
    512 个字节,最小长度为 0。

4.3 ARM7 汇编器伪指令

在 ARM7 汇编程序中,汇编器伪指令主要有符号定义伪指令、数据定义伪指令、汇编
控制伪指令、宏指令等。

4.3.1 符号定义伪指令

符号定义伪指令用于定义程序中的变量、对变量赋值以及定义寄存器的别名等操作。常
用的符号定义伪指令有以下 4 种:

  1. 全局变量定义伪指令 GBLA、GBLL、GBLS
    格式:GBLA(GBLL 或 GBLS) 全局变量名
    功能:用于定义一个全局变量并将其初始化。其中 GBLA 伪指令用于定义一个全局的
    数值变量并初始化为 0;GBLL 伪指令用于定义一个全局的逻辑变量并初始化为{False};
    GBLS 伪指令用于定义一个全局的字符串变量并初始化为空。
GBLS GlobalStr ; 定义一个全局的字符串变量 GlobalStr,并初始化为空
GlobalStr SETS “Hello World!” ; 对全局的字符串变量赋值为“Hello World!”
  • 1
  • 2
  1. 局部变量定义伪指令 LCLA、LCLL、LCLS
    格式:LCLA(LCLL 或 LCLS) 局部变量名
    功能:用于定义一个局部变量并将其初始化。其中 LCLA 伪指令用于定义一个局部的
    数值变量并初始化为 0;LCLL 伪指令用于定义一个局部的逻辑变量并初始化为{False};
    LCLS 伪指令用于定义一个局部的字符串变量并初始化为空。
LCLL LocalLog ; 定义一个局部的逻辑变量 LocalLog,并初始化为 False
LocalLog SETL {TRUE} ; 对局部的逻辑变量赋值为 TRUE
  • 1
  • 2
  1. 变量赋值伪指令 SETA、SETL、SETS
    格式:SETA(SETL 或 SETS) 表达式
    功能:用于对一个已经定义的全局变量或者局部变量赋值。其中 SETA 伪指令用于对数
    值变量赋值;SETL 伪指令用于对逻辑变量赋值;SETS 伪指令用于对字符串变量赋值。
  2. 寄存器列表定义伪指令 RLIST
    格式:名称 RLIST {通用寄存器列表}
    功能:用于对一个通用寄存器列表定义名称,这样在 ARM7 汇编语言程序中使用
    LDM/STM 指令,对列表中的通用寄存器按寄存器编号由小到大的顺序进行访问,与列表中
    的寄存器排列次序无关。
reglist RLIST {R0-R3, R8, R12}
…
STMFD SP!, reglist ; 将列表 reglist 存储到堆栈中
…
LDMIA R4, reglist ; 将列表 reglist 加载到 R4 中
  • 1
  • 2
  • 3
  • 4
  • 5

4.3.2 数据定义伪指令

数据定义的目的是为目标系统的数学模型中的各种抽象数据类型分配存储单元并进行
初始化。数据定义伪指令就是为定义变量并给变量赋初值,或者仅仅定义变量(分配存储单
元),而不赋予特定的值。
常用的数据定义伪指令有 DCB、DCW、DCD、SPACE、MAP、LTORG 等。

4.3.3 ARM7 汇编控制伪指令

汇编控制伪指令用于控制汇编程序的执行流程,常用于编写 ARM 芯片的初始化代码。
常用的汇编控制伪指令包括以下几条:
1. IF、ELSE、ENDIF
格式:

IF 逻辑表达式
	指令序列
ELSE
	指令序列
ENDIF
  • 1
  • 2
  • 3
  • 4
  • 5

功能:根据逻辑表达式是否成立决定执行哪个指令序列。当 IF 的逻辑表达式为真时,
执行 IF 后的指令序列;否则执行 ELSE 后的指令序列。其中,ELSE 及其后的指令序列
可以没有。IF、ELSE、ENDIF 伪指令也可以嵌套使用。示例代码如程序清单 4-4 所示。

在这里插入图片描述

2. WHILE、WEND
格式:

WHILE 逻辑表达式
	指令序列
WEND
  • 1
  • 2
  • 3

功能:根据逻辑表达式是否成立决定是否循环执行指令序列。当 WHILE 的逻辑表达式
为真,则执行指令序列,当指令序列执行完后,再次判断逻辑表达式的值,若为真则继
续执行指令序列,直至逻辑表达式的值为假。示例代码如程序清单 4-4 所示。
3. MACRO、MEND
格式:

MACRO {$标号} 宏名 {$参数 1 {,$参数 2}…}
	指令序列
MEND
  • 1
  • 2
  • 3

功能:如果在汇编语言源程序中需要多次使用同一个程序段,可以将这个程序段定义为
一个宏指令,然后在每次需要时,即可简单地使用宏指令名来代替(称为宏调用),从
而避免了重复书写,使源程序更加简洁、易读。

4.3.4 杂项汇编器伪指令

还有一些汇编器伪指令,在汇编语言源程序中经常使用,称为杂项汇编器伪指令,专门
主要用于定义汇编程序中段代码属性、定义符号属性、引用/包含其它文件等作用。

  1. AREA
  2. ALIGN
  3. CODE16 和 CODE32
  4. ENTRY
  5. END
  6. EQU
  7. EXPORT 和 GLOBAL
  8. IMPORT
  9. EXTERN
  10. GET 和 INCLUDE
  11. INCBIN
  12. RN
  13. ROUT

4.4 ARM7 汇编语言程序的上机过程

4.5 ARM7 汇编语言程序设计

推荐:
ARM简单程序设计【嵌入式系统】

4.5.1 汇编语言程序设计的步骤

一般来说,编写一个汇编语言源程序的基本步骤如下:

  1. 分析目标系统,建立数学模型,确定算法
  2. 根据算法画出流程图
  3. 合理分配寄存器、存储空间和外设资源
  4. 根据流程图编写源程序
  5. 上机调试程序
  6. 形成文档

4.5.2 顺序程序设计

在这里插入图片描述

4.5.3 分支程序设计

在这里插入图片描述
1. 简单分支结构

在这里插入图片描述

在这里插入图片描述
2. 复杂分支结构
复杂分支结构就是多个简单分支流程的嵌套,对于这类结构程序的设计掌握的重要原则
就是:分支流程的判读依据是 ARM 处理器中 CPSR 寄存器,而 CPSR 寄存器在各模式中是
共享的,且个数是 1 个(CPSR 寄存器中 NZCV 各位对应的不是‘1’就是‘0’),所以对于
分支程序的判断,只要条件满足进入其中一个分支,那么暗含着其他分支肯定不满足条件(也
意味着在当前上下文环境中程序员不用去关心其他分支)。

3. 多分支结构
多分支结构类似 C 语言中 switch-case 类型分支语句。

switch(varA)
 {
	 case ‘+’: R0 = R1 + R2; 
	 case ‘-’: R0 = R1 - R2;
	 case ‘*’: R0 = R1 * R2; 
	 case ‘\’: R0 = R1 \ R2;
	 default: break;
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

解决方法:通过事先将将与各 case 语句对应的汇编语言功能函数地址存放在存储单元
中,通过查找地址表的方法,实现根据“选择项(choice)”进入不同的函数体,实现选择
不同的函数功能。通常将这块存储区域称为“跳转表”,其存储单元内容就是与各 case 语句
对应的汇编语言功能函数的入口地址。

然后通过将跳转表的入口地址加载到某个寄存器中,再使用相对 PC 的寻址(将跳转表
和相对偏移量之和加载到 PC 寄存器中),可实现跳转到对应功能段的操作。

跳转表示例程序如程序清单 4-8 所示。
在这里插入图片描述

4.5.4 循环程序设计

1. 循环程序的基本结构
将重复地执行某些指令的程序称为循环程序。
循环程序通常有以下五个部分组成,如图 4-20 所示。

在这里插入图片描述
2. 循环控制方法
循环程序设计中一个重要环节就是如何控制循环次数。下面分别介绍常见的两种情况:
(1) 用计数控制循环
这种情况适用于循环次数已知的时候。一般处理的方法有两种,具体由程序员根据个人
情况具体选择。
① 先将已知的循环次数送入计数器 Rn,然后每循环一次,计数器 Rn 减 1,直至 Rn
为 0 时循环结束;

MOV Rn, #N ; 循环次数初始化
……
LOOP: ; 循环体开始
……
SUBS Rn, Rn, #1 ; 修改计数器
BGT LOOP ; 判断计数器是否等于 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

② 先将计数器 Rn 初始化为 0,然后每循环一次,计数器 Rn 加 1,直至 Rn 等于已知
的循环次数时循环结束;

MOV Rn, #0 ; 循环次数初始化
……
LOOP: ; 循环体开始
……
ADD Rn, Rn, #1 ; 修改计数器
CMP Rn, #N ; 
BNE LOOP ; 判断计数器是否等于 N
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述
在这里插入图片描述
(2) 用条件控制循环
有些情况无法确定循环次数,但循环结束的条件是已知的,这时可通过循环测试结束条
件是否满足的方法,条件满足结束循环,否则继续循环。

在这里插入图片描述
在这里插入图片描述
3. 单重循环程序设计
从流程图 4-21 和图 4-22 中可以看到,其循环体内不再包含循环结构,也就是说只包
含一个循环体,将具有这种结构特点的程序称为单重循环程序。
4. 多重循环程序设计
多重循环,顾名思义就是循环体内嵌套有循环体。
在这里插入图片描述
在这里插入图片描述

4.5.5 子程序设计

1. 子程序概念
在一个程序中,如果其中有些代码内容完全相同或相似,为了简化程序,可以把这些重
复的程序代码抽取出来,并按一定的格式编写成相对独立的程序段。每当主程序在执行过程
中需要执行这段程序,就通过调用指令来调用该段程序,执行完后再返回到主程序继续执行。
这样当编写程序时,就不必重复书写这段代码了,将这样的程序段称为子程序或子过程,而
调用子程序的程序称为主程序或调用程序

2. 子程序的调用与返回
主程序中使用 BL 指令可以实现子程序的调用,语法如下所示:

BL 子程序名
  • 1

在子程序的结束处,可以使用如下指令返回到主程序中。

MOV PC, LR
  • 1

3. 调用程序与子程序之间的参数传递
调用程序在调用子程序时,可以向子程序传递一些参数;同样,子程序运行后也可以把
一些结果参数传回给调用程序。调用程序与子程序之间的这种信息传递称为参数传递。参数
传递有以下三种方式。
(1) 寄存器传递参数方式
技术思想:主程序将待传递的数据直接写入约定的通用寄存器,再进行 BL 子程序调用;
或子程序返回后,主程序直接从约定的通用寄存器中获得子程序的结果数据。
应用特点:这种方式适合于传递较少参数的应用场合。

例 4-1 用子程序实现内存区里的字符串拷贝功能,即将存储单元中源字符串对应拷贝到目的
字符串中。

解题思路:通过设定两个地址指针,分别指向存储区中的源字符串和目的字符串;然后
通过加载和存储指令(LDR 和 STR)的寄存器间接寻址方式,依次从源字符串读取一个字
符数据,写入到目的字符串的对应字符位置中,直到遇到源字符串的结束标志’0’为止。

在这里插入图片描述
(2) 存储区域传递参数方式
技术思想:主程序和子程序约定了某一共享内存块用于参数传递,主程序在 BL 调用子
程序前,先将要传递的参数写入到约定的存储单元,子程序可从约定的内存读取这些参数;
子程序返回时,也可以使用该方式将数据传给主程序。
应用特点:这种方式可以传递大批量数据。
实现方法:当主程序与子程序有较多的数据需要传递时,可以通过共享内存区或传内存
数据块地址方式来传递批量数据。通常有三种方法装载地址:
① 通过 ADR 伪指令直接装载近距离数据块地址;
② 通过 ADRL 伪指令直接装载较近距离数据块地址;
③ 通过伪指令 LDR Rd, =Label 转载远距离的数据块地址;

4-2 通过设置的入口参数查找函数地址表,实现选择不同的函数功能。
示例代码见程序清单 4-8 所示。

(3) 堆栈传递参数方式
主程序和子程序使用同一个堆栈,主程序在 BL 调用子程序前,先将要传递的参数压入
到堆栈中,子程序可从堆栈中读取传过来的数据;子程序返回需要向主程序传递参数时,也
可使用此方法。这种参数传递方式多见于高级语言的函数调用时,编译系统采用的参数传递
方法,汇编程序初级应用中不常见,在此不再冗述。

4.6 C 语言和 ARM7 汇编语言的混合/交叉编程

推荐:
ARM与C语言的混合编程【嵌入式系统】

最后

2023-7-3 19:01:37

祝大家逢考必过
点赞收藏关注哦

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/420739
推荐阅读
相关标签
  

闽ICP备14008679号