本文目的是通过keil 5 编写汇编程序来熟悉汇编语言的相关知识。这里分为两个部分:第一个部分在Keil上练习汇编的编写和调试,同时了解一下Hex文件的格式;第二个部分是使用汇编进行stm32F103的点灯实验,用实战的方式来加深理解。
Project
->New uVision Project...
test
STM32F103ZET6
所以选择STM32F103ZE
即可CORE
和Startup
两个选项,点击OK
Project
选项中出现了我们刚才新建好的工程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
Start/Stop Debug Session
开始调试MOV R0, #10
这里设置断点,运行程序(F5)MOV R0, #10
还未执行,我们可以看到R0、R1、R2、R3的值都为0还没有改变。BL function1
这里设置断点,运行程序(F5)0x0000000A
,R1变为0x0000000B
,R2变为0x0000000C
,R3变为0x0000000D
)BX LR
这里设置断点,运行程序(F5)0x00000005
,R6变为0x00000006
,R7变为0x00000007
,R8变为```0x00000008 ``)第一个字节 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 开始线性地址记录
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
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命令:声明整个程序的入口点,有且仅有一个。
实验说明
我这里采用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