基于Keil 5编写汇编程序
本文目的是通过keil 5 编写汇编程序来熟悉汇编语言的相关知识。这里分为两个部分:第一个部分在Keil上练习汇编的编写和调试,同时了解一下Hex文件的格式;第二个部分是使用汇编进行stm32F103的点灯实验,用实战的方式来加深理解。
目录
(一)汇编语言
1.简介
- 汇编语言(Assembly Language)是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。特定的汇编语言和特定的机器语言指令集是一一对应的,不同平台之间不可直接移植。
2.指令
- 传送指令
包括通用数据传送指令MOV、条件传送指令CMOVcc、堆栈操作指令PUSH/PUSHA/PUSHAD/POP/POPA/POPAD、交换指令XCHG/XLAT/BSWAP、地址或段描述符选择子传送指令LEA/LDS/LES/LFS/LGS/LSS等。 - 逻辑运算
用于执行算术和逻辑运算,包括加法指令ADD/ADC、减法指令SUB/SBB、加一指令INC、减一指令DEC、比较操作指令CMP、乘法指令MUL/IMUL、除法指令DIV/IDIV、符号扩展指令CBW/CWDE/CDQE、十进制调整指令DAA/DAS/AAA/AAS、逻辑运算指令NOT/AND/OR/XOR/TEST等。 - 移位指令
用于将寄存器或内存操作数移动指定的次数,包括逻辑左移指令SHL、逻辑右移指令SHR、算术左移指令SAL、算术右移指令SAR、循环左移指令ROL、循环右移指令ROR等。 - 位操作
包括位测试指令BT、位测试并置位指令BTS、位测试并复位指令BTR、位测试并取反指令BTC、位向前扫描指令BSF、位向后扫描指令BSR等。 - 控制转移
包括无条件转移指令JMP、条件转移指令JCC/JCXZ、循环指令LOOP/LOOPE/LOOPNE、过程调用指令CALL、子过程返回指令RET、中断指令INTn、INT3、INTO、IRET等。 - 串操作
用于对数据串进行操作,包括串传送指令MOVS、串比较指令CMPS、串扫描指令SCANS、串加载指令LODS、串保存指令STOS,这些指令可以有选择地使用REP/REPE/REPZ/REPNE和REPNZ的前缀以连续操作。 - 输入输出
用于同外围设备交换数据,包括端口输入指令IN/INS、端口输出指令OUT/OUTS。
3.优缺点
- 优点
- 可以轻松的读取存储器状态以及硬件I/O接口情况
- 编写的代码因为少了很多编译的环节,可以能够准确的被执行
- 作为一种低级语言,可扩展性很高
- 缺点
- 程序非常单调,特殊指令字符很少,造成了代码的冗长以及编写的困难
- 仍然需要自己去调用存储器存储数据,很容易出现BUG,而且调试起来也不容易;就算完成了一个程序,后期维护时候也需要耗费大量的时间,因为机器的特殊性造成了代码兼容性差的缺陷。
(二)使用Keil 5编写汇编程序
1.新建汇编工程
- 选择
Project
->New uVision Project...
- 给新建的工程命名并保存到到相应的位置,我这里命名为
test
- 由于后面会涉及到STM32的点灯实验,因此这里需要选择使用的硬件支持,这里我使用的是
STM32F103ZET6
所以选择STM32F103ZE
即可
- 勾选
CORE
和Startup
两个选项,点击OK
- 这时左边的
Project
选项中出现了我们刚才新建好的工程
2.新建汇编文件
- 在
Source Group 1
选项下右键选择Add New Item to Group Source Group 1...
- 选择
Asm File(.s)
,输入新建的汇编文件的名字(我这里命名为TEST
)
- 编写汇编程序
AREA MYDATA, DATA
AREA MYCODE, CODE
ENTRY
EXPORT __main
__main
MOV R0, #10
MOV R1, #11
MOV R2, #12
MOV R3, #13
;LDR R0, =func01
BL func01
;LDR R1, =func02
BL func02
BL func03
LDR LR, =func01
LDR PC, =func03
B .
func01
MOV R5, #05
BX LR
func02
MOV R6, #06
BX LR
func03
MOV R7, #07
MOV R8, #08
BX LR
END
3.调试汇编程序
- 编译
没有错误,没有警告。 - 配置Debug选项
- 右键工程,选择
Options for Target 'Target1'...
(或者直接点击图中的魔术棒的图标)
- 在
Debug
选项卡下选择使用仿真调试工具(我这里使用的ST-Link
进行程序调试因此选择ST-Link Dehugger
)
- 右键工程,选择
- 点击图中的调试按钮
Start/Stop Debug Session
开始调试
- 进入调试界面如下:
- 动态调试变量
- 在
MOV R0, #10
这里设置断点,运行程序(F5)
可以看到程序运行到这里时停了下来,此时MOV R0, #10
还未执行,我们可以看到R0、R1、R2、R3的值都为0还没有改变。
- 取消原来设置的断点并在
BL function1
这里设置断点,运行程序(F5)
我们可以看到此时R0、R1、R2、R3的值都相应的变为赋给它的值(R0变为0x0000000A
,R1变为0x0000000B
,R2变为0x0000000C
,R3变为0x0000000D
)
- 取消原来设置的断点并在fun03的
BX LR
这里设置断点,运行程序(F5)
通过上面的图片我们可以看到R5、R6、R7、R8的值从全零变为相应的值(R5变为0x00000005
,R6变为0x00000006
,R7变为0x00000007
,R8变为```0x00000008 ``)
- 在
4.hex文件简介
- 生成hex文件
工程选项中选择Output
,然后勾选Create HEX File
,再重新编译一下工程即可生成Hex文件
- Hex文件分析
- 打开生成的Hex文件如下:
Hex文件都是由记录(RECORD)组成的。在HEX文件里面,每一行代表一个记录,每个记录由冒号开始。
- Hex文件分析
- 打开生成的Hex文件如下:
第一个字节 0x02表示本行数据的长度;
第二、三个字节 0x00 0x00表示本行数据的起始地址;
第四个字节 0x00表示数据类型,数据类型有:0x00、0x01、0x02、0x03、0x04、0x05。
后面是数据字节
最后一个字节 0xD0为校验和。
数据类型如下:
00: Data Rrecord 用来记录数据,HEX文件的大部分记录都是数据记录
01: End of File Record 用来标识文件结束,放在文件的最后,标识HEX文件的结尾
02: Extended Segment Address Record 用来标识扩展段地址的记录
03: Start Segment Address Record 开始段地址记录
04: Extended Linear Address Record 用来标识扩展线性地址的记录
05: Start Linear Address Record 开始线性地址记录
(三)汇编点灯实验
1.实验程序
LED0 EQU 0x42218194 ;LED0的地址 (PB5)
RCC_APB2ENR EQU 0x40021018
;GPIOA_CRH EQU 0x40010804
GPIOB_CRL EQU 0x40010C00
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
AREA RESET, DATA, READONLY
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
AREA |.text|, CODE, READONLY
THUMB
REQUIRE8
PRESERVE8
ENTRY
Reset_Handler
BL LED_Init
MainLoop BL LED_ON
BL Delay
BL LED_OFF
BL Delay
B MainLoop
LED_Init
PUSH {R0,R1, LR}
LDR R0,=RCC_APB2ENR
ORR R0,R0,#0x08 ;使能GPIOB管脚时钟
LDR R1,=RCC_APB2ENR
STR R0,[R1]
LDR R0,=GPIOB_CRL
BIC R0,R0,#0XFF0FFFFF ;配置为模拟输入模式
LDR R1,=GPIOB_CRL
STR R0,[R1]
LDR R0,=GPIOB_CRL
ORR R0,R0,#0X00300000 ;配置为通用推挽输出模式
LDR R1,=GPIOB_CRL
STR R0,[R1]
MOV R0,#1
LDR R1,=LED0
STR R0,[R1]
POP {R0,R1,PC}
LED_ON
PUSH {R0,R1, LR}
MOV R0,#0
LDR R1,=LED0
STR R0,[R1]
POP {R0,R1,PC}
LED_OFF
PUSH {R0,R1, LR}
MOV R0,#1
LDR R1,=LED0
STR R0,[R1]
POP {R0,R1,PC}
Delay
PUSH {R0,R1, LR}
MOVS R0,#0
MOVS R1,#0
MOVS R2,#0
DelayLoop0
ADDS R0,R0,#1
CMP R0,#330
BCC DelayLoop0
MOVS R0,#0
ADDS R1,R1,#1
CMP R1,#330
BCC DelayLoop0
MOVS R0,#0
MOVS R1,#0
ADDS R2,R2,#1
CMP R2,#15
BCC DelayLoop0
POP {R0,R1,PC}
END
2.程序分析
- 预定义
LED0 EQU 0x42218194
RCC_APB2ENR EQU 0x40021018
GPIOB_CRL EQU 0x40010C00
这里主要是进行LED管脚位段(bit-band)地址、RCC_APB2ENR外设时钟使能寄存器和GPIOB_CRL端口配置低寄存器地址的设置。其中RCC_APB2ENR外设时钟使能寄存器地址基本上是固定的,不需要改动;而LED管脚和端口配置寄存器的地址需要根据实际情况查阅相关手册进行相应的修改。
这里根据下面红色方框中的公式进行LED管脚位带地址的计算(参照库函数工程版本中sys.h头文件的宏定义)
Cortex™-M3存储器映像包括两个位段(bit-band)区。这两个位段区将别名存储器区中的每个字映射到位段存储器区的一个位,在别名存储区写入一个字具有对位段区的目标位执行读-改-写操作的相同效果。在STM32F10xxx里,外设寄存器和SRAM都被映射到一个位段区里,这允许执行单一的位段的写和读操作。
关于位段的详细信息请参考《Cortex™-M3技术参考手册》
GPIOB_CRL端口配置低寄存器地址则是在GPIOB的基地址上参照手册上的偏移地址计算得出。
-
启动初始化
- 分配栈空间
Stack_Size EQU 0x00000400 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size __initial_sp
AREA命令:AREA 命令指示汇编器汇编一个新的代码段或数据段。段是独立的、指定的、不可见的代码或数据块,它们由链接器处理。格式如下:
AREA 段名,段属性1,段属性2,段属性3。。。
AREA STACK, NOINIT, READWRITE, ALIGN=3
NOINIT: = NO Init,不初始化。
READWRITE : 可读,可写。
ALIGN =3 : 2^3 对齐,即8字节对齐。SPACE命令:SPACE 命令保留一个用零填充的存储器块。
所以这段代码意思是:分配一个STACK段,该段不初始化,可读写,按8字节对齐。分配一个大小为Stack_Size的存储空间,并使栈顶的地址为__initial_sp。- 分配向量表
AREA RESET, DATA, READONLY __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler
-
开始代码段
AREA |.text|, CODE, READONLY ;通知汇编器,开始代码段,设置为只读
THUMB
REQUIRE8
PRESERVE8
;这段的意思是,汇编器支持THUMB指令,代码段按8字节对齐
ENTRY
ENTRY命令:声明整个程序的入口点,有且仅有一个。
- 程序正式开始
相关指令:- BL:带链接的跳转指令。当使用该指令跳转时,当前地址(PC)会自动送入LR寄存器。
- B:无条件跳转。
- PUSH/POP:将值存入堆栈/从堆栈取出
- LDR和STR:寄存器的装载和存储指令。
- ORR 按位或操作。
- BIC 先把立即数取反,再按位与。
- CMP:CMP是比较两个数,相等或大于则将标志位C置位,否则将C清零。
- BCC是个组合指令,实际为B+CC,意思是如果C=0则跳转。
3.实验结果
实验说明
我这里采用STM32F103ZET6
开发板进行LED的点灯实验,并且完全采用纯汇编的方式实现间隔一定时间点亮LED灯,因此我这里将之前添加的启动文件给去掉,避免后续编译时产生类似于error: L6235E: More than one section matches selector - cannot all be FIRST/LAST.
的错误,这因为我们这里使用的纯汇编语言编写stm32点灯实验,所以在编写的.s文件中添加了堆栈指针的初始化,而之前仿真调试时没有添加堆栈指针的初始化;如果没有去掉之前添加启动文件,则会因为有两个启动文件导致编译失败。
(四)总结
这次在Keil 5上使用汇编语言进行调试和stm32的点灯实验,让我有了一番不一样的体验;感觉虽然相比于C语言来说,汇编确实要更加难以理解和编程实现并且难以移植到其他硬件上去,但是它的执行心率确实要快了不少;与库函数、寄存器编写的点灯实验相比,汇编语言编写的LED点灯其Hex文件只有477字节,而寄存器和库函数编写的点灯其Hex文件大小分别为3.81KB和5.32KB。由此可见,汇编语言执行的高效和简洁。而且在学习的过程中,也让我更加了解程序是如何被机器识别和执行的,感觉收获很大。
参考文章:
1.汇编语言 (面向机器的程序设计语言)–百度百科
2.ARM汇编基础之基于MDK创建纯汇编语言的STM32工程
3.hex文件说明
4.简单的STM32 汇编程序—闪烁LED
更多推荐
所有评论(0)