3

嵌入式:ARM汇编语言程序设计基础教程

 1 year ago
source link: https://blog.51cto.com/u_15736437/5979338
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

汇编语言程序设计的步骤

① 合理地分配存储器资源,将前述的目标系统‘数据结构模型’表示到各存储器单元。

② CPU寄存器数量有限,在程序中,大多数操作都要使用寄存器;并且有的操作使用特定的寄存器(如堆栈操作使用SP/R13等),程序中要合理分配各寄存器的用途。

用计算机语言,对数据结构模型和流程图表示的算法进行准确地描述。

① 语法调试:排除程序中的语法错误。

② 功能调试:保证程序的逻辑功能正确性。

用文档形式记录说明程序的功能、使用方法、程序结构、算法流程等每一个阶段的工作。 把解题的方法、步骤用框图形式表示。如果问题比较复杂,那么可以逐步细化,直到每一框图可以容易编程为止。流程图不仅便于程序的编制,且对程序逻辑正确性也比较容易查找和修改。

顺序程序设计

顺序程序是一种最简单的程序结构,也称为直线程序,它的执行自始自终按照语句的先后顺序进行。这种结构的流程图,除了有一个起始框,一个终止框外,就是若干执行框。

例:试编制一程序,完成10+3的操作。

嵌入式:ARM汇编语言程序设计基础教程_ARM
AREA ARMex, CODE, READONLY ; 代码段名ARMex
ENTRY ; 程序的入口
CODE 32

start
MOV R0, #10 ; 将立即数10存入寄存器R0
MOV R1, #3 ; 将立即数3存入寄存器R1
ADD R0, R0, R1 ; R0 = R0 + R1
stop
MOV R0, #0x18 ; 这三条指令是ADS调试环境特约
LDR R1, =0x20026 ;程序运行结束返回编译器调试环境
SWI 0x123456

END ; 结束

分支程序设计

许多实际问题需要根据不同的情况作出不同的处理。在程序中,针对不同的情况把不同的处理方法编制成各自的处理程序段,运行时由机器根据当时的条件自动作出判断,选择执行相应的处理程序段。这样的程序结构中,计算机不再完全按指令存储的顺序执行,称之为分支。分支程序使用转移指令B、子程序调用指令BL或带状态转移指令BX来实现。

例:给定以下符号函数:

嵌入式:ARM汇编语言程序设计基础教程_ARM_02

任意给定值,假定为-25,存放在x单元,函数值存放在y单元;要求根据x中的值来确定y的值。

嵌入式:ARM汇编语言程序设计基础教程_分支程序_03
AREA symbol, CODE, READONLY ; 代码段的名字 symbol

ENTRY ; 程序的入口

CODE32

start

LDR R0, =x ; 加载数据段中的变量x地址,存入R0

LDR R1, =y ; 加载数据段中的变量y地址,存入R1

LDR R2, [R0] ; 加载变量x的值,存入R2

compare

CMP R2, #0 ; 将R2的值与0作比较

BEQ ZERO ; 如果R2等于0,那么转向标号ZERO处

BGT PLUS ; 如果R2大于0,那么转向标号PLUS处

MOV R3, #-1 ; 否则,R2小于0,将-1存入R3中

B stop

ZERO

MOV R3, #0 ; R2等于0,将0存入R3中

B stop

PLUS

MOV R3, #1 ; R2大于0,将1存入R3中

; 续上段代码

stop

STR R3, [R1]

MOV R0, #0x18

LDR R1, =0x20026

SWI 0x123456

AREA Data, DATA, READWRITE

x DCD -25

y DCD 0

END

循环程序设计

顺序程序和分支程序中的指令每次运行最多只执行一次。在实际应用中重复地做某些事的情况很多,这也是计算机最擅长的工作方式。重复地执行某些指令,最好用循环程序来实现。
循环程序的结构---重复地执行同一种运算,直到某种条件满足。

建立循环初始值。如设置地址指针、计数器、其他循环参数的起始值等。循环程序的主体业务代码,可以是一个顺序程序、分支程序或另一个循环程序。 为执行下一个循环而修改某些参数,尤其循环控制变量的修改等。

条件控制循环:通过判断循环终止条件是否已成立,控制循环。判断循环结束条件是否成立,决定是否继续循环。

例如:计数控制循环;通过计数循环次数,判断是否已达到预定次数,控制循环。对循环结束进行适当处理;有的循环程序可以没有这部分。

用计数控制循环适用于已知循环次数的循环程序设计

例:从x单元开始的30个连续字单元中存放有30个无符号数,从中找出最大者送入y单元中。

嵌入式:ARM汇编语言程序设计基础教程_循环程序_04

根据题意,把第一个数先送入Rx寄存器,将Rx中的数与后面的29个数逐个进行比较,如果Rx中的数较小,则将该较大的数送入Rx ;继续与余下的数据逐个比较。在比较过程中, Rx中始终保持较大的数,共计比较29次,则最终Rx中保留了最大数,最后把Rx中的数(最大者)送入y单元。

AREA max, CODE, READONLY ; 代码段的名字 max
ENTRY ; 程序的入口
CODE32
num EQU 29 ; 比较的次数
start
LDR R0, =x ; R0指向源数据块x
LDR R1, =y ; R1指向单元y
LDR R2, =num ; R2作为计数器
LDR R3, [R0] ; 将源数据块x中第一个数加载到R3中
compare
ADD R0, R0, #4 ; 每进行一次比较,将R0指针地址加4
LDR R4, [R0] ; 依次将源数据块x中下一个数加载到R4中
CMP R3, R4 ; 比较R3和R4中数的大小
MOVCC R3, R4 ; 如果R3小于R4,则将较大的数送入R3中
SUBS R2, R2, #1 ; 计数器值减1
BNE compare ; 如果不为0,那么继续跳到compare执行
STREQ R3, [R1] ; 如果为0,那么循环比较结束,R3是最大的数
; 并且将R3中的数加载到R1指向的单元(即y)中


stop
MOV r0, #0x18
LDR r1, =0x20026
SWI 0x123456

AREA Data, DATA, READWRITE
x DCD 73,59,61,34,81,107,225,231,54,43
DCD 100,35,1,42,222,254,34,71,100,31
DCD 33,119,13,44,18,147,55,244,97,3
y DCD 0
END

② 适用于已知循环条件的循环程序设计

适用于无法确定循环次数,但知道循环结束的条件

例:从自然数1开始累加,直到累加和大于1000为止,统计被累加的自然数的个数,并把统计的个数送入n单元,把累加和送入sum单元。

根据题意,被累加的自然数的个数事先未知,因此不能用计数方法控制循环。但题目中给定一个条件,即累加和大于1000则停止累加,因此,可以根据这一条件控制循环。我们用R3寄存器放累加和,用R4寄存器放每次取得的自然数,其中它的值也是统计自然数的个数。

嵌入式:ARM汇编语言程序设计基础教程_子程序_05
AREA SUM, CODE, READONLY ; 代码段的名字 SUM
ENTRY ; 程序的入口
CODE32
start
LDR R0, =n ; 将数据段中自然数的个数n的地址加载到R0寄存器
LDR R1, =sum ; 将数据段中自然数的累加和sum的地址加载到R1寄存器
LDR R3, =0 ; R3存放自然数的累加和
LDR R4, =0 ; R4用于循环个数的统计/每次取得的自然数
LDR R5, =1000 ; R5用于循环结束的界限值
continue
ADD R4, R4, #1 ; 取下一个自然数
ADD R3, R3, R4 ; 累加自然数
CMP R3, R5 ; 比较累加和是否超过了1000
BCC continue ; 如果小于1000,那么跳到compare执行
STRCS R3, [R1] ; 如果大于1000,那么将累加和存储到R1所指向的单元中
STRCS R4, [R0] ; 如果大于1000,那么将已累加的自然数个数值存储
; 到R0所指向的单元中


stop
MOV r0, #0x18
LDR r1, =0x20026
SWI 0x123456

AREA Data, DATA, READWRITE
n DCD 0 ; 定义累加的自然数的个数
sum DCD 0 ; 定义自然数的累加和

END

子程序设计

子程序概念

如果在一个程序中的多处用到同一段程序代码,那么可以把这段共同的程序代码抽取出来,写成一个相对独立的程序段,每当需要执行这段代码时,就调用这个程序段,执行完这个程序段后,再返回原来调用它的程序。这样编写程序时,就不必重复写这段代码了,而这样的程序段称为子程序或子过程。

子程序的调用与返回

主程序中使用BL指令实现子程序的调用

BL 子程序名

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

MOV PC, LR

主程序与子程序之间的参数传递

主程序调用子程序时,可以向子程序传递一些参数;同样,子程序运行后也可把一些结果参数传回给主程序。主程序与子程序之间的这种信息传递称为参数传递。

三种参数传递方式

  1. 寄存器传递参数方式
  2. 存储区域传递参数方式
  3. 堆栈传递参数方式

寄存器传递参数方式

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

技术思想:主程序将待传递的数据直接写入约定的通用寄存器,在子程序中直接使用;或子程序返回后,主程序直接从约定的通用寄存器中获得子程序的结果数据。

应用特点:这种方式适合于传递较少参数的应用场合。

解题思路:

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

AREA StrCopy, CODE, READONLY
ENTRY
CODE32
start
LDR R1, =srcstr ; R1指向数据区的源字符串
LDR R0, =dststr ; R0指向数据区的目的字符串
BL strcopy ; 调用子程序strcopy,完成字符串拷贝
stop
MOV R0, #0x18 ; 程序结束返回编译器调试环境
LDR R1, =0x20026
SWI 0x123456
strcopy
LDRB R2, [R1], #1 ; 将R1指向的单元内容加载到R2中
STRB R2, [R0], #1 ; 将R2中的数存储到R0指向的单元中
CMP R2, #0 ; 检查R0的值是否等于0
BNE strcopy ; 如果不等于0,那么转到strcopy处执行
MOV PC, LR ; 子程序返回


AREA Strings, DATA, READWRITE
srcstr DCB "First string - source",0 ; 源字符串
dststr DCB "Second string - destination",0 ; 目的字符串
END

存储区域传递参数方式

例:通过设置的入口参数查找函数地址表,实现选择不同的函数功能。

说明:本题中通过事先将函数地址存放在存储单元中,通过查找地址表的方法,实现根据“选择项(choice)”进入不同的函数体功能。

技术思想:主程序和子程序约定了某一共享内存块用于参数传递,主程序在BL调用子程序前,先将要传递的参数写入到约定的存储单元,子程序可从约定的内存读取这些参数;子程序返回时,也可以使用该方式将数据传给主程序。

应用特点:这种方式可以传递大批量数据。

实现方法:当主程序与子程序有较多的数据需要传递时,可以通过共享内存区或传内存数据块地址方式来传递批量数据。

通过伪指令ADR直接装载近距离数据块地址;

通过伪指令ADRL直接装载中距离数据块地址;

通过语句LDR Rd, =Label转载远距离的数据块地址;

AREA Jump, CODE, READONLY
num EQU 4 ; 函数地址表内容的个数
ENTRY
CODE32
start
LDR R0, =choice ; R0指向存储区的choice单元
LDR R0, [R0] ; 设置第一个参数:选择执行哪一个函数
MOV R1, #16 ; 设置第1个操作数
MOV R2, #2 ; 设置第2个操作数
BL arithfunc ; 调用子程序arithfunc
stop
MOV R0, #0x18 ; 程序结束返回编译器调试环境
LDR R1, =0x20026
SWI 0x123456
arithfunc
CMP R0, #num ; 比较R0的值是否超过函数地址表的个数
MOVHS PC, LR ; 如果大于,那么就返回到标号stop处
ADR R3, JumpTable ; 将函数地址表的地址作为基地址
LDR PC, [R3, R0, LSL #2] ; 根据R0参数进入对应的子程序


JumpTable ; 函数地址表的入口基地址
DCD DoAdd ; 加法子程序
DCD DoSub ; 减法子程序
DCD DoMul ; 乘法子程序
DCD DoDiv ; 除法子程序
DoAdd
ADD R0, R1, R2 ; R0 = R1 + R2
MOV PC, LR ; 返回
DoSub
SUB R0, R1, R2 ; R0 = R1 - R2
MOV PC, LR ; 返回
DoMul
MOV R0, R1, LSL R2 ; R0 = R1 << R2
MOV PC, LR ; 返回
DoDiv
MOV R0, R1, LSR R2 ; R0 = R1 >> R2
MOV PC, LR ; 返回
AREA NUM, DATA, READWRITE
choice DCD 3 ; 0:表示选择加法子程序 1:表示选择减法子程序
; 2:表示选择乘法子程序 3:表示选择除法子程序
END

堆栈传递参数方式

主程序和子程序使用同一个堆栈,主程序在BL调用子程序前,先将要传递的参数压入到堆栈中,子程序可从堆栈中读取传过来的数据;子程序返回需要向主程序传递参数时,也可使用此方法。

参考文献:

孟祥莲.嵌入式系统原理及应用教程(第2版)[M].北京:清华大学出版社,2017.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK