第二部分 ARM指令系统

一、汇编指令的类别

1.ARM汇编指令(ARM公司定)

一条汇编指令唯一对应一条机器指令 RISC

  • 操作码:表示是何种操作 “指令助记符”
  • 操作数:
  • 结果操作数: 用来保存计算的结果 目的操作数 前置
  • 运算操作数:第一操作数 第二操作数

指令是用来“运算”:运算符操作数

2.伪指令

(由编译器厂商定的,keil环境下有keil的伪指令,GNU环境有gnu的伪指令)

  • keil环境下常用的伪指令:
data_a    //顶格写的 标号,标号表示一个地址
 DCD 5   //DCD X : 分配4bytes空间并且把X的值,填入此处

data_b
 SPACE 40     //SPACE X : 在此处开辟 X 个字节的空间,内容不定。

data_c
    DCD 2
    DCD 3
    SPACE 8*4
  • “段”:分区域
  • 代码段 这个区域都是代码—ROM 0x08000000—
  • 数据段 这个区域存放的是全局的数据
  • 堆栈段 stack

定义段:AREA 段名, 段的属性1, 段的属性2, …(段名自己定义,规定的定义)

AREA  mstack, DATA, READWRITE
  • 段的属性1:

CODE 代码段

CODE32 thumb-2代码

DATA 数据段

NOINIT 不初始化

  • 属性2:

READWRITE 只读可写

READONLY 只读

  • 属性3:

ALIGN=3 8字节对齐(2的3次幂对齐)

Cortex-M4要求代码必须是 8字节对齐,否则编译不会通过;

PRESERVE8 指令,表示后续如果没有特殊说明,都是采用 8字节对齐

当有C语言和汇编并存时,C语言的编译后的指令也需要8字节对齐。

3.宏指令(同上,由编译器厂商指定)

由编译器厂商写的,keil环境下有keil的宏指令,在keil中需要顶格

4.keil环境下的汇编语句格式

lable 指令 ;注释

lable:顶格写,标识这一行的地址
指令:ARM指令,伪指令
; 到行末,都是注释部分。

练习:一个启动.s文件

stack_size  EQU 0x200   ;宏指令,定义了两个宏
vector_size EQU 0x400
;define stack      ;名字为mystack,数据,可读可写
 AREA mystack,DATA,READWRITE
stack_S       ;顶格
 SPACE stack_size   ;设置空间大小
stack_end ;stack top--SP

;define vector table   ;定义向量表,只读
 AREA RESET,DATA,READONLY
vector_s
 DCD stack_end ;the first must be stack top 
 DCD code_start ;this must be start code
 SPACE vector_size
vector_end
;define code
 AREA mycode,CODE,READONLY,ALIGN=3
 PRESERVE8    ;8字节对齐
code_start
    MOVS R0,#-2    
    MOVS R1,#2  
    CMP R1,R0
    MOVGT R2,#2
    MOVLT R3,#3 
    MOVLT R4,#4  
    MOVLT R5,#5
 B .
 END

二、ARM指令的寻址方式

所谓寻址方式就是处理器根据指令中给出的地址信息来寻找物理地址的方式

1.立即数寻址

也叫立即寻址,是一种特殊的寻址方式,操作数本身包含在指令中,只要取出指令也就取到了操作数。这个操作数叫做立即数,对应的寻址方式叫做立即寻址。

例如:

MOV R0,#64     ;R0  ← 64
ADD R0, R0, #1  ;  R0   ← R0 + 1
SUB R0, R0, #0X3D  ;  R0   ← R0 – 0X3D

在ARM处理器中,立即数必须对应8位位图格式,即立即数是一个在16位或32位的寄存器中的8bit常数,经循环移动偶数位得到。合法的立即数必须能够找到得到它的那个常数,否则这个立即数就是非法的。

判断一个立即数是否合法可以用以下的办法:即对于这个立即数进行循左移或右移操作,看看经过移动偶数位后,是否可以得到一个不大于0XFF的立即数(即不超过8位的立即数),如果可以得到,这个立即数就是合法的,否则就是非法的。

2.寄存器寻址

寄存器寻址就是利用寄存器中的数值作为操作数,也称为寄存器直接寻址

例如:

ADD R0,R1, R2  ;R0  ← R1 + R2

这种寻址方式是各类微处理器经常采用的一种方式,也是执行效率较高的寻址方式。

3.寄存器间接寻址

寄存器间接寻址就是把寄存器中的值作为地址,再通过这个地址去取得操作数,操作数本身存放在存储器中

;R0 ←[R1],以寄存器R1的值作为操作数的地址,把取得操作数传送到R0中
LDR R0,[R1]
;R0 ←R1 + [R2],以寄存器R2的值作为操作数的地址,取得操作数后与R1相加,结果存入寄存器R0中。
ADD R0,R1,[R2]

4.寄存器偏移寻址

这是ARM指令集特有的寻址方式,它是在寄存器寻址得到操作数后再进行移位操作,得到最终的操作数。

例如:

MOV R0,R2,LSL  #3   ;R0 ← R2 * 8 ,R2的值左移3位,结果赋给R0。
MOV R0,R2,LSL  R1  ;R2的值左移R1位,结果放入R0。

5.寄存器基址变址寻址

它将寄存器(该寄存器一般称作基址寄存器)中的值与指令中给出的地址偏移量相加,从而得到一个地址,通过这个地址取得操作数。

例如:

;R0 ←[R1 + 4],将R1的内容加上4形成操作数的地址,取得的操作数存入寄存器R0中。
LDR R0,[R1,#4]

;R0 ←[R1 + 4]、R1 ←R1 + 4,将R1的内容加上4形成操作数的地址,取得的操作数存入寄存器R0中,然后,R1的内容自增4个字节。其中!表示指令执行完毕把最后的数据地址写到R1。
LDR R0,[R1,#4]!

;R0 ←[R1 + R2],将寄存器R1的内容加上寄存器R2的内容形成操作数的地址,取得的操作数存入寄存器R0中。
LDR R0,[R1,R2]

;R0→[R1 -4],将R1中的数值减4作为地址,把R0中的数据存放到这个地址中。
STR R0, [R1,#-4]

LDR R0,[R1],#4    ;R0 ←[R1]、R1 ←R1+4

6.多寄存器寻址

一次完成多个寄存器值的传送。

例如:

;R1←[R0],R2←[R0+4],R3←[R0+8],R4←[R0+12]
LDMIA  R0,{R1,R2,R3,R4}  ;LDM:Load Data from Memory to Register.

7.相对寻址

相对寻址是一种特殊的基址寻址,特殊性是它把程序计数器PC中的当前值作为基地址,语句中的地址标号作为偏移量,将两者相加之后得到操作数的地址。

例:

BL   NEXT ;相对寻址,跳转到NEXT处执行。

8.堆栈寻址

堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用堆栈指针(Stack Pointer, SP)指示当前的操作位置,堆栈指针总是指向栈顶。

根据堆栈的生成方式不同,可以把堆栈分为递增堆栈和递减堆栈两种类型:

  • 递增堆栈:向堆栈写入数据时,堆栈由低地址向高地址生长。
  • 递减堆栈:向堆栈写入数据时,堆栈由高地址向低地址生长。

同时,根据堆栈指针(SP)指向的位置,又可以把堆栈分为满堆栈(Full Stack)和空堆栈(Empty Stack)两种类型。

  • 满堆栈(Full Stack):堆栈指针指向最后压入堆栈的数据。满堆栈在向堆栈存放数据时的操作是先移动SP指针,然后存放数据。在从堆栈取数据时,先取出数据,随后移动SP指针。这样保证了SP一直指向有效的数据。
  • 空堆栈(Empty Stack):堆栈指针SP指向下一个将要放入数据的空位置。空堆栈在向堆栈存放数据时的操作是先放数据,然后移动SP指针。在从堆栈取数据时,是先移动指针,再取数据。这种操作方式保证了堆栈指针一直指向一个空地址(没有有效数据的地址)。

9.块拷贝寻址

块拷贝寻址用于寄存器数据的批量复制,它实现从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器传送数据。块拷贝寻址与堆栈寻址有所类似。两者的区别在于:堆栈寻址中数据的存取是面向堆栈的,块拷贝寻址中数据的存取是面向寄存器指向的存储单元的

三、ARM指令的格式

ARM指令的基本格式

< opcode > {< cond >}{S} < Rd >, < operand1 > {, < operand2 >}
<>内的必须要的 {} 可选的

  • opcode: operator code 操作码,指令助记符,表示哪条指令
  • cond: condition 条件。该指令执行的条件。如果省略不写,则表示该条件指令无条件执行(必须执行)

条件码

含义

判断方式

EQ

Equal(相等)

Z == 1

NE

Not Equal(不相等)

Z == 0

CS/HS

Carry Set (C == 1)

unsigned Higher or Same

a>=b

C == 1

CC/LO

Carry Clear(C == 0)

a<b

C == 0

MI

MInous(负数)

N == 1

PL

Positive or Zero(非负数)

N == 0

VS

V Set(溢出)

V == 1

VC

V Clear(没溢出)

V == 0

HI

unsigned Higher

(C == 1) && (Z == 0)

LS

unsigned Lower or Same

(C == 0) || (Z == 1)

GE

signed Greater or Equal

N == V

LT

Less Than

N != V

GT

Greater Than

(N == V) && (Z == 0)

LE

Less than or Equal

(Z==1) || ((N != V))

  • S: Status 表示该指令执行结果是否影响xPSR(程序状态寄存器)的标志位
    例如:
MOV R0, #0  ;-> 不会影响任何状态标志
MOVS R0, #0  ;->会影响状态标志位

有一些指令如: CMP, CMN, TEQ … 这些不加S也影响状态标志位,因为这些指令,不保存运算结果,只影响状态标志位

  • Rd: Register Desatiation 目标寄存器(用来保存运算的结果)
  • operand1: 第一个操作数
  • operand2: 第2个操作数(有些指令没有第二个操作数)

操作数形式:

  • #immer_8r 立即数(常量表达式))
ADD R0, R1, #250  
ADD R0, R1, #0x10
ADD R0, R1, #(1 << 3) | (1 <<4)

为了避免立即数不合规,建议使用LDR R0, =666666

  • Rm 寄存器
ADD R0, R1, R2
  • Rm,shift 寄存器移位方式
LSL #n  
    Logic Shift Left 逻辑左移n位,
    Logic逻辑移位,无论是左移还是右移,空出的位,都补0
LSR #n
 Logic Shift Right 逻辑右移n位,

在移出的位为0的情况下, 
LSL #n 就相当于在原寄存器值 乘以 2的n次方
LSR #n 就相当于在原寄存器值 除以 2的n次方

ASR #n 算术右移n位
    算术移位,不应该改变它的符号。
    最高n位补符号位的值.

ASL #n  没有 => LSL     

ROR #n  Rotate Right 循环右移
 把右边移的n位,补到最左边。

RRX 带扩展的循环右移1位
 带C(xPSR.C) 那个位

代码测试

(1)

MOV R1,#3 ;r1==3
MOV R2,#1 ;r2==1
LSL R2,R1 ;r2==8  1<<3

(2)

ADD R0, R1, R2, LSR #2 ;(2)和(1)组合测试

(3) R0<—2 R0*12—>R2

MOV R0,#2
MOV R2,R0
LSL R0,2
ADD R2,R0,R2,LSL #3

四、ARM指令(UAL) v7 32bit

1.ARM存储器访问指令

用来在存储器(Memory) 《-》寄存器之间传递数据

把数据从存储器 -> 寄存器 加载 Loader LDR

把数据从寄存器 -> 存储器 存储 Store STR

  • LDR{cond} {S} {B/H} Rd, <地址>
    STR{cond} {B/H} Rd, <地址>
    任务:char a=0x41 char b=a
MOV R0,0x41
LDR R1,=0X20001000
STRB R0,[R1]
LDRB R2,[R1]
STRB R2,[R1,#4]
  • B: Byte一个字节, H: Half word半字(两个字节),如果省略,默认为4个字节,B/H决定加载/存储多少个字节。
    S: Signed把地址的那个变量,当作是一个有符号的。如果没有S就把地址的那个变量,当作是一个无符号的。
  • 地址确定方式: 基址寄存器 + 偏移量

[Rn]

Rn

地址值

[Rn, 偏移量]

Rn + 偏移量

Rn值不变

[Rn, 偏移量]!

Rn + 偏移量

Rn值+偏移量

[Rn], 偏移量

Rn

Rn值+偏移量

[ ]内表示存储器的地址值,如果有**!,偏移量**在[]外边,则表示做完后,基址值自动增加偏移量。

偏移量有以下3种方式:

  • 立即数:LDR R1, [R0, #0x12]
  • 寄存器:LDR R1, [R0, R2]
  • 寄存器及移位常数:LDR R1, [R0, R2, LSL #2]
  • 任务1: 给存储器0x20001000单元写入一个整数(-2)
MOV R0, #-2
LDR R1, =0x20001000
STR R0, [R1]
  • 任务2: 将0x20001000单元的数值转到0x20001008
LDR R1,=0X20001000
LDR R2,[R1]
STR R2,[R1,#8]
  • 练习1.1
    char ch =0x80; //编译时刻或运行时刻,为ch分配一个存储器空间 0x2000 1000
    int a; //编译时刻或运行时刻,为a分配一个存储器空间 0x2000 1004
MOV R0,#0x80
LDR R1,=0x20001000
STRB R0,[R1]
LDRSB R2,[R1]
STR R2,[R1,#4]
  • 练习1.2
    unsigned char ch =0x80; //编译时刻或运行时刻,为ch分配一个存储器空间 0x2000 1000
    int a; //编译时刻或运行时刻,为a分配一个存储器空间 0x2000 1004
MOV R0,#0x80
LDR R1,=0x20001000
STRB R0,[R1]
LDRB R2,[R1]
STR R2,[R1,#4]
  • 多寄存器存取

在一个连续(地址递增/地址递减)的存储器地址上,进行多个(用列表的形式)寄存器的存取。

  • 多寄存器加载 LDM Loader Multi
    多寄存器存储 STM Store Multi
    LDM{cond}<模式> Rn{!}, reglist
    STM{cond}<模式> Rn{!}, reglist
    由<模式>来指定存储器的多个地址,是连续增加,还是连续递减:
    无论是哪种模式,低地址都是对应编号低的寄存器
  • IA: Incrememt After 每次传送后地址自动增加(+4) <-----先传数据再加地址
  • DB: Decrement Before 每次传送前地址自动减少(-4) <-----先减地址再传数据
  • IB: Increment Before 每次传送前地址先自动增加(+4) <-----先加地址再传数据
  • DA: Decrement After 每次传送后地址自动减少(-4) <-----先传数据再减地址

ARM Cortex M4只使用IA, DB

例子:

MOV R0,#12
MOV R1,#34
MOV R2,#56
MOV R8,#78
;R-->M
LDR R3,=0x20001000
① STMIA R3!,{R0-R2,R8} ;R3=0x20001010 //空递增,先放R0
② STMDB R3!,{R0-R2,R8} ;R3=0x20000FF0 //满递减,先减再传输
MOV R0,#0x12
MOV R1,#0x34
MOV R2,#0x56
MOV R8,#0x78
;M-->R
① LDMDB R3!,{R0-R2,R7} ;//满递减,出栈,先出R8
② LDMIA R3!,{R0-R2,R7}
  • reglist: 表示寄存器列表,可以包含多个寄存器,使用","隔开,寄存器由小到大排列(编译系统会自动按序号排)
    如: {R1,R3,R4,R5} {R1,R3-R5}
  • ! 可加可不加
    加: 表示最后存储器的地址写入到Rn中去
    不加: 最后Rn的值不变
  • 任务: 将R0-R3放到存储器单元0x20000200开始的递减连续单元存放,然后再恢复。
MOV R1,#1
MOV R2,#2
MOV R3,#3
LDR R0,=0x20000200
STMDB R0!,{R1-R3}
MOV R1,#12
MOV R2,#34
MOV R3,#56
LDMIA R0!,{R1-R3}
  • 堆栈操作: 堆栈的低地址对应编号低的寄存器
    压栈:PUSH < reglist >
    出栈:POP < reglist >
    "栈"就是一块内存,上面那两条指令,并没有指定内存地址,PUSH, POP用的地址寄存器是SP
    堆栈有四种类型:
  • A: add 递增堆栈
  • D: Dec递减堆栈
  • 栈顶指针可以保存元素 -> 满堆栈 Full
  • 也可以指向空(不保存元素) ->空堆栈 Empty
  • EA: 空递增 STMIA–LDMDB
  • FA: 满递增 STMIB–LDMDA
  • ED: 空递减 STMDA–LDMIB
  • FD: 满递减 STMDB–LDMIA <----- ARM采用的堆栈形式
STMDB SP!,{R0-R2,R8} ;PUSH {R0-R2,R8}
LDMIA SP!,{R7,R0-R2} ;POP {R0-R2,R7}

2.ARM数据处理指令 -> 对寄存器内容操作

(1)数据传送指令

MOV{cond}{S} Rd, operand2 Rd <— operand2

MVN{cond}{S} Rd, operand2 Rd <— ~operand2(取反)

例如:MOV R0,#-1 <==> MVN R0,#0

(2)算术运算: 加减

ADD{cond}{S} Rd, Rn, operand2 Rd <— Rn + operand2

ADC{cond}{S} Rd, Rn, operand2 Rd <— Rn + operand2 + xPSR.C

SBC{cond}{S} Rd, Rn, operand2 Rd <— Rn - operand2 - !xPSR.C 带借位的减法

RSB{cond}{S} Rd, Rn, operand2 operand2 - Rn —> Rd 逆向减法指令

RSC{cond}{S} Rd, Rn, operand2 operand2 - Rn - !xPSR.C —> Rd 带借位的逆向减法

(3)逻辑运算指令 (按位)

AND{cond}{S} Rd, Rn, operand2 AND 与, Rn & operand2 —> Rd 按位与

ORR{cond}{S} Rd, Rn, operand2 OR 或, Rn | operand2 —> Rd 按位或

EOR{cond}{S} Rd, Rn, operand2 EOR 异或 Rn ^ operand2 —> Rd 按位异或

BIC{cond}{S} Rd, Rn, operand2 Rn & (~operand2) —> Rd 把Rn中 operand2中的为1的哪些位置上的bit位清零

  • 练习:
  • R0 低4位清零
MOV R0,#0x7fffffff
AND R0,R0,#~0xf
BIC R0,R0,#0xf
  • R0 清零
MOV R0,#0x7fffffff
AND R0,R0,#0
EOR R0,R0
  • 取寄存器R0中的b7-b10,赋值给R1
MOV R0,#0xffffffff
MOV R1,R0,LSR #7
AND R1,R1,#0xf
  • 高4位清零
MOV R0,#0x7fffffff
BIC R0,R0,#0xf<<28
  • bit15 bit13 清零
MOV R0,#0xffffffff
BIC R0,R0,#1<<3
BIC R0,R0,#1<<15
  • 取出bit10到bit7放到R1
MOV R1,R0,LSR #7
AND R1,R1,#0xf

(4) 比较指令: 不需要加S,直接影响xPSR中的标志位。运算结果不保存。

CMP Rn, operand2 比较Rn与operand2的大小,Rn - operand2

xPSR.Z == 1 => EQ

CMN Rn, operand2 比较Rn与operand2的大小,Rn + operand2(负数比较)

TST Rn, operand2 Rn & operand2,用来测试Rn中特定的bit位是否为1

Rn & operand2 => xPSR.Z == 1
=> 说明operand2中为1的哪些bit的,在Rn都为0

  • 例:测试R2中的第2和第3bit位,是否为1(需要分开测,不能一起
MOV R2,#0xb
TST R2,#1<<2
TST R2,#1<<3

TEQRn, operand2 Rn ^ operand2,测试是否相等

Rn == operand2 => Rn ^ operand2 == 0 => xPSR.Z ==

3. 乘法指令

4.分支指令:用来实现代码的跳转

(1)分支指令

B lable lable -> PC, 不带返回的跳转

BL lable 过程调用,函数调用 带返回的

把下一条指令的地址 —> LR lable -> PC

(2)直接向PC寄存器赋值

MOV PC, LR
MOV PC, #0x80000000
;第二种
LDR R2,=code_start
MOV PC, R2

5.杂项指令

MRS Rd, xPSR

  • xPSR:APSR, IPSR, EPSR
  • 程序状态寄存器的值 赋值给 Rd
MOVS R1,#-1
MRS R0,APSR ;R0==0x80000000

MSR xPSR, Rd 将通用寄存器Rd的值,赋值给程序状态寄存器

6.伪指令

伪指令,机器不识别,但是可以表示程序员或编译器的某种想要的操作。
编译器会把伪指令变成一条或多条合适的机器指令。

(1)NOP 空操作,不产生任何实际的效果,但是占用机器周期。

(2)LDR{cond} Rd, =expr

  • 如果expr是一个合法的立即数:
    LDR Rd, =expr <=> MOV Rd, #expr
  • 解决立即数不合规的问题,可以直接给出存储器的地址
    LDR R0,=0x12345678
  • 标号(地址)
;先定义数据段
 AREA mydata,DATA,READWRITE
data_i
 SPACE 4
 
;标号指向数据空间的地址
MOV R0,#4
LDR R1,=data_i
STR R0,[R1]

练习: 2*24 的值放到数据段存储

MOV R0,#2
LSL R1,R0,#4
ADD R0,R1,R0,LSL #3
LDR R2,=data_i
STR R0,[R2]
B .

五、ARM程序设计

1.if/else 如何实现的

if (a > b){
    c = 5;
}else{
    c = 6;
}
CMP R0, R1
MOVGT R2, #5  ;
MOVLE R2, #6
;或者
CMP R0, R1
MOVLE R2, #6
MOVGT R2, #5

编译器如何选择?if ( likely(a > b) )

  • likely(x): 编译器的修饰词,告诉编译器,后面的这个表达式x很有可能成立
  • unlikely(x): 编译器的修饰词,告诉编译器,后面的这个表达式x不太可能成立

2.循环是如何实现的

for (i = 1, sum = 0; i <= 10;i++){
    sum = sum + i;
}
loop_sum 
    CMP R0, #10   ;设置循环条件
    BGT loop_sum_end ;不满足跳出循环
    ADD R1,R1,R0
    ADD R0,R0,#1
    B loop_sum   ;继续循环
loop_sum_end

练习: 计算100以内所有的奇数之和。

code_start
;1+3+..+99
 MOV R0,#1
 MOV R1,#0
loop_sum
 CMP R0,#100   ;R0<=100
 BGT loop_sum_end ;R0>100
 ADD R1,R1,R0  ;sum=sum+i
 ADD R0,R0,#2  ;i=i+2
 B loop_sum
loop_sum_end

B .
END

3.过程调用(函数调用)如何实现

格式:

label PROC
    PUSH
    (看你的)
    POP
    ENDP

难点:

  • 入口参数传送用 R0, R1,R2, R3,如果超过4个参数,后面的参数需要放到栈空间
    R0:对应第一个参数 R1:第二个参数……
  • 函数的返回值用R0,如果是64bits,用R1(高32位)

提醒: R0是返回值,所以在实现过程中,你要记住R0不要乱用。
“单值类型” (所以在一进入过程,就把R0的值存到另一个寄存器里)

  • 函数实现
    ”现场保护” PUSH {R2-R12,LR} LR也要保存,否则,在过程中,就不能调用其他过程啦。

保护除了传参外的所有寄存器

  • “现场恢复” POP {R2-R12, PC}

练习: 写一个过程调用 计算两个数之和

code_start PROC
;3+5=8
    MOV R0,#3
    MOV R1,#5
    BL sum_two
    B .
    ENDP
sum_two PROC
    PUSH {R2-R12, LR}
    ADD R0,R0,R1
    POP {R2-R12, PC}
    ENDP

 END

4. 汇编文件 与 C文件共存

第一点:汇编中如何调用C代码

  • 需要在汇编文件,“进口”:引入相应的全局变量名或全局函数, “符号”
    IMPORT 函数名or全局变量名
  • 直接调用就好
    BL sum_three

第二点:C文件中如何调用汇编

  • 申明是外部函数 例如:extern int sum_two(int a, int b);
  • 直接调用汇编过程(写对应的过程名就好)
  • 回到汇编代码中还需要说明该函数是“出口”函数 EXPORT sum_two

练习: 汇编过程sum_two,编写一个c语言函数实现三个数之和的计算

extern int sum_two(int a, int b);
int sum(int a, int b, int c){
 return  c+sum_two(a, b);
}
IMPORT sum_three
 EXPORT sum_two

 AREA mycode,CODE,READONLY,ALIGN=3
 PRESERVE8 
  
code_start PROC
 MOV R0,#2
 MOV R1,#3
 MOV R2,#4
 BL sum_three
 B .
 ENDP
 
sum_two PROC
 PUSH {R2-R12, LR}
 ADD R0,R0,R1
 POP {R2-R12, PC}
 ENDP
 
 END

六、作业

  1. 用汇编语言实现一个函数,判断a是否为b的倍数
    输入:a,b
    输出:1是倍数 0不是倍数
/*
 return 1: a是b的倍数
 return 0: a不是b的倍数
*/
int Is_Multi(int a, int b) 3,2{
    int i = b; 2
        while (b <= a){
            if (a == b){
                return 1;
            }
            b = b + i;
        }
 return 0;
}
code_start PROC
    MOV R0,#4
    MOV R1,#2
    BL Is_Multi
    B .
    ENDP
Is_Multi PROC
    PUSH {R2-R12, LR}
    MOV R3,R0
    MOV R4,R1
    MOV R0,#0
loop
    CMP R4,R3
    BGT loop_end
    CMP R4,R3
    MOVEQ R0,#1
    BEQ loop_end
    ADD R4,R4,R1
    B loop
loop_end
    POP {R2-R12, PC}
 ENDP

 END
  1. 用汇编语言实现一个函数,判断一个数x是否为质数/完全数
int Is_Prime(int x){
    int i ;
    for (i = 2; i < x; i++){
        if (Is_Multi(x, i)){
            return 0;
        }
    }
    return 1;
}
code_start PROC
    MOV R0,#28
    BL Is_Perfect
    B .
    ENDP

Is_Perfect PROC
    PUSH {R1-R12,LR}
    ;R0=x, R1=i
    MOV R1,#2 ;loop i
    MOV R2,#1 ;sum
    MOV R3,R0 ;R3=x

loop_get_sum
    MOV R0,R3  ;the first param = x
    CMP R1,R3  ;R1<R3 i<x
    BEQ loop_get_sum_end
    BL Is_Multi
    CMP R0,#1  ;R0==1
    ADDEQ R2,R2,R1 ;get sum
    ADD R1,R1,#1 ;i++
    B loop_get_sum 
loop_get_sum_end
    CMP R2,R3
    MOVEQ R0,#1
    MOVNE R0,#0
    POP {R1-R12,PC} 

    ENDP

Is_Multi PROC
    PUSH {R2-R12, LR}
    MOV R3,R0
    MOV R4,R1
    MOV R0,#0
loop
    CMP R4,R3
    BGT loop_end
    MOVEQ R0,#1
    BEQ loop_end
    ADD R4,R4,R1
 B loop
loop_end
    POP {R2-R12, PC}
    ENDP 

 END
  1. 用汇编语言实现100以内所有素数之和

提示:
把100以内所有的质数,都保存在一个数组中。
x是不是质数 拿 < x的所有素数去整除x,即可。
2—data—{2}
3—3:2—{2,3}
4—4:2
5—5:2—5:3—{2,3,5}

  1. 求1000以内的完数,把个数保存到R0,R1-R12保存求出来的完数
data_size EQU 0x100

data_i
 SPACE data_size  
data_end
;define code
 AREA mycode,CODE,READONLY,ALIGN=3
 PRESERVE8 
  
code_start PROC
 MOV R1,#1000 
 MOV R2,#2 ;i=2
 MOV R3,#0 ;num=0
 LDR R4,=data_i
loop_is_perfect
 MOV R0,R2
 CMP R2,R1
 BGT loop_is_perfect_end
 BL Is_Perfect
 CMP R0,#0
 STRNE R2,[R4]
 ADDNE R4,R4,#4 ;R4+4
 ADDNE R3,R3,#1 ;num++
 ADD R2,R2,#1 ;i++
 B loop_is_perfect
loop_is_perfect_end
 MOV R0,R3
 LDMDB R4,{R1-R12}
 
 B .
 ENDP
  
Is_Perfect PROC
 PUSH {R1-R12,LR}
 ;R0=x, R1=i
 MOV R1,#2 ;loop i
 MOV R2,#1 ;sum
 MOV R3,R0 ;R3=x
 
loop_get_sum
 MOV R0,R3 ;the first param = x
 CMP R1,R3 ;R1<R3 i<x
 BEQ loop_get_sum_end
 BL Is_Multi
 CMP R0,#1 ;R0==1
 ADDEQ R2,R2,R1 ;get sum
 ADD R1,R1,#1 ;i++
 B loop_get_sum 
loop_get_sum_end
 CMP R2,R3
 MOVEQ R0,#1
 MOVNE R0,#0
 POP {R1-R12,PC} 
 
 ENDP
  
Is_Multi PROC
 PUSH {R2-R12, LR}
 MOV R3,R0
 MOV R4,R1
 MOV R0,#0
loop
 CMP R4,R3
 BGT loop_end
 CMP R4,R3
 MOVEQ R0,#1
 BEQ loop_end
 ADD R4,R4,R1
 B loop
loop_end
 POP {R2-R12, PC}
 ENDP 

 END
  1. 判断闰年函数
  2. 求最近质数函数
    求一个正整数n的最近质数,如果n本身就是质数,则返回本身,如果有两个最近质数,返回较小的哪个。
  3. 水仙花数 100-1000的水仙花数,并保存 0x20000000查看

水仙花数(Narcissistic number)也被称为超完全数字不变数(pluperfect digital invariant, PPDI)、自恋数、自幂数、阿姆斯壮数或阿姆斯特朗数(Armstrong number),水仙花数是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身。例如:1^3 + 5^3+ 3^3 = 153。