观前提醒,以下内容基于Cortex M4的STM32F407进行
代码使用keil进行仿真实现
任务:寻找1000以内的水仙花数,保存在存储器中
首先我们需要知道水仙花数是怎样计算出来的,先写出高级代码,然后转换成相应的汇编。
水仙花数是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身。例如:1^3 + 5^3+ 3^3 = 153。
那么c语言层面如何进行实现,以下为实现代码:
void is_Narcissistic(int a){
int sum = 0;
//依次获取各位数
int b = a;
while(b%10!=0||b/a!=0){
b = b%10;
sum+=b*b*b;
b = a/10;
}
}
实现起来不是很难,重要的是将其转换成汇编。
下面我们一步步来实现这个任务
汇编启动代码
编程第一步,就是永恒的hello world,所以现在让我们来看看怎样书写启动代码,执行汇编语句。
首先我们要知道当我们用高级代码进行编程的时候,去执行程序,都会经过
- 预处理
- 汇编:将高级语言转换成汇编代码
- 编译
- 链接
而程序所在的内存空间从高地址到低地址被分成堆栈,空洞,数据,代码段
所以我们首先要定义各个段来存放相应的数据。
首先是定义段的格式(不能顶格)
AREA [段名],[段类型],[权限] ;定义一个数据段,可读写
标号 ;这个就是段的起始地址
SPACE 段大小 ;开辟多大的空间
标号(表示结尾)
其中段类型,可以为CODE,DATA,STACK
另外,我们需要一个向量表,在linux中向量中用于记录发生中断时,处理中断的向量号,以及相应的地址,这里我们需要把定义的堆栈段,代码段记录在向量表中。
那么我们就根据程序段的为中,从高地址到低地址定义段
;定义宏,方便后期数值的修改
stack_size EQU 0x200
vertor_size EQU 0x400
AREA mystatck,DATA,READWRITE ;定义一个数据段,可读写
stack_s
SPACE stack_size ;开辟一个大小为stack_size大小的空间
stack_end ;stack top--SP(required)
AREA RESET,DATA,READONLY ;name must be RESET
vector_s
DCD stack_end ;the first must be SP(test)
DCD code_start ;code start
SPACE vertor_size ;开辟一个大小为vector_size大小的空间
vector_end
AREA mycode,CODE,READONLY,ALIGN=3 ;ALIGN=3(required)
PRESERVE8 ;指定当前文件保持堆栈八字节对齐
code_start
B . ;表示跳转到code_start的位置继续执行,若不写这一句,
;代码可能执行到我们不知道什么位置的位置
END
所以,接下来的任务就是在code_start标号开始的位置来写汇编代码了。
任务1:保存数到存储器
由于我们的任务事先申明了要将找到的数保存在寄存器中,所以,我们需要再去定义一个数据段存放数据,这个段我们就写在代码段的前面,给定100字节的空间。
;define data
AREA mydata,DATA,READWRITE
data_i
SPACE 100
然后就是保存数到存储器。
这里我们回顾一下冯·诺依曼结构,有输入输出设备,存储器,运算器,控制器五个部分组成,例如我们想实现3+5的操作,首先控制器需要发送信号,从存储器找到3的地址,取出3,然后通过总线,将3放在寄存器中,5也是如此,然后运算器进行2个数的假发运算,计算的结果则通过总线写回存储器中。
看到了嘛,这个过程都是寄存器到存储器,或者存储器到寄存器的单向过程。
那么我们在汇编中,也是如此,如果要进行运算,肯定要把数存放在寄存器中,然后从寄存器写回存储器。
让我们来做一个实验,将寄存器中的数据写入存储器
MOV R0,#1
LDR R1,=data_i ;在R1寄存器中存放一个地址
STR R0,[R1] ;从寄存器加载数据到存储器
那么我们就完成了这个基本任务,还需要学会一个知识点,就是在汇编里面写函数,因为code_start标号开始的部分,我们可以看作c里面的主函数,我们不会把所有的数据处理都放在主函数里面,同理,汇编里面也是。
在汇编写一个函数
首先我们要知道当我们调用一个函数时,我们需要保护现场,即将调用前的寄存器数据,下一条执行的指令压栈,等到函数返回的时候出栈,恢复现场,这里就要用到POP和PUSH指令
让我们看到下面的指令,将通用寄存器的数值压栈,那么LR是什么,他记录的是函数调用的返回地址,当我们调用完之后,将LR的数值弹栈到PC中,那么下一条执行的语句就是函数调用后的下一条需要执行的语句。
这这个保护现场,恢复现场是我们需要在函数中写的。
PUSH {R1-R12,LR}
POP {R1-R12,PC}
定义一个函数使用以下格式,你可能会疑惑,之前不是说R0也是通用寄存器,为什么他不用保护。
这里就要说到参数的使用了,我们都知道,一个函数的定义需要指定入口参数(多个),返回参数(只能1个),函数名,函数体。那么入口参数放在哪里呢,答案是,放在R0-R3中,依次存放函数的第1,2,3,4个参数,那么如果我们的参数大于4个怎么办 ?答案是放在栈中,不过现在我们不需要这么多参数,所以就不演示了
那么返回参数呢,我们规定放在R0中,这也解释了为什么不保护R0,因为如果R0被保护,这个函数的返回参数就会变成传过来的第一个参数。
函数名 PROC
PUSH {R1-R12,LR}
POP {R1-R12,PC}
ENDP
下面,我们写一个数模10的函数,由于数在机器中是用二进制表示的,所以我们转换思路,取最后4位,如果数>9,那么模就是9,否则就是取到的结果。
其中的BIC指令的功能是让指定位变0。
因为我们要让结果与9比较,那么用到CMP指令,如果大于,则赋值R0为9,大于是Greater than,所以在MOV的基础上加上GT,表示大于的时候才执行这条指令。
cal_mod_ten PROC
PUSH {R1-R12,LR}
BIC R0,R0,#0xf<<28
CMP R0,#9
MOVGT R0,#9
POP {R1-R12,PC}
ENDP
循环语句怎么实现
由于我们需要找出1000以内的水仙花数,那么需要进行循环,从1到1000取数,分别调用判断函数。还记得GOTO语句嘛,直接粗暴的跳转表示法,汇编里面也是这个思路,循环体运行完,就跳转到循环体最开始,如果不满足条件,则跑到循环体最后,跳出循环
loop
B loop_end ;出循环条件,一定要写,否则不能出循环
B loop
loop_end
例如,做一个小实验,计算1+……100
MOV R0,#1 ;1
MOV R1,#0 ;sum
loop
CMP R0,#100
BGT loop_end ;表示大于i>100就出循环
ADD R1,R0,R1 ;sum = sum+i
ADD R0,R0,#1 ;i=i+1
B loop ;跑到循环开始
loop_end
水仙花数判断实现
首先,通用寄存器R0-R12是我们都可以使用的
其次,我们写汇编的时候,除非是大佬,不要一定要先写一个c找到思路,对照这写出汇编。
下面就是我写的水仙花数判断过程了
;define data
AREA mydata,DATA,READWRITE
data_i
SPACE 100
IMPORT cal_thr ;由于三次方难以用汇编代码实现,故调用c函数
AREA mycode,CODE,READONLY,ALIGN=3
PRESERVE8
code_start PROC
MOV R0,#0
MOV R1,#1 ;i
LDR R2,=data_i
loop_p ;从2开始计算,1不算水仙花数?
ADD R1,#1 ;i++
CMP R1,1000 ;i<1000
BGT loop_p_end
MOV R0,R1
BL is_Narcissistic
CMP R0,R1 ;i==is_Narcissistic(i)
BNE loop_p
STR R1,[R2] ;保存到存储器
ADD R2,#4 ;地址左移4
B loop_p
loop_p_end
B .
ENDP
is_Narcissistic PROC ;返回各位数三次方和计算结果
PUSH {R1-R12,LR}
MOV R3,#0 ;sum = 0
MOV R2,R0 ;b = a
loop_n
BL cal_mod_ten ;i%10
CMP R0,#0 ;i%10==0
BEQ OUT_FLAG
BL cal_thr ;(i%10)^3
ADD R3,R0 ;sum+=(i%10)^3
OUT_FLAG
MOV R0,R2
BL cal_div_ten i/10==0
CMP R0,#0
BEQ loop_n_end
MOV R2,R0
B loop_n
loop_n_end
MOV R0,R3
POP {R1-R12,PC}
ENDP
cal_div_ten PROC ;计算一个数除以10的商
PUSH {R1-R12,LR}
MOV R1,R0
MOV R0,#0 ;sum
loop_d
CMP R1,#10 ;i<10
BLT loop_d_end
SUB R1,#10 ;i-=10
ADD R0,#1 ;sum+=1
B loop_d
loop_d_end
POP {R1-R12,PC}
ENDP
cal_mod_ten PROC ;计算一个数模10的结果
PUSH {R1-R12,LR}
loop_m
CMP R0,#10 ;i<10
BLT loop_m_end
SUB R0,#10 ;i-=10
B loop_m
loop_m_end
POP {R1-R12,PC}
ENDP
END
extern int cal_thr(int);
int cal_thr(int a){
return a*a*a;
}