一、汇编语言简介
汇编语言, 即第二代计算机语言,用一些容易理解和记忆的字母,单词来代替一个特定的指令,比如:用“ADD”代表数字逻辑上的加减,“MOV”代表数据传递等等,通过这种方法,人们很容易去阅读已经完成的程序或者理解程序正在执行的功能,对现有程序的bug修复以及运营维护都变得更加简单方便。但计算机的硬件不认识字母符号,这时候就需要一个专门的程序把这些字符变成计算机能够识别的二进制数。因为汇编语言只是将机器语言做了简单编译,所以并没有根本上解决机器语言的特定性,所以汇编语言和机器自身的编程环境息息相关,推广和移植很难,但是还是保持了机器语言优秀的执行效率,因为他的可阅读性和简便性,汇编语言到现在依然是常用的编程语言之一。 汇编语言不像其他大多数的程序设计语言一样被广泛用于程序设计。在今天的实际应用中,它通常被应用在底层,硬件操作和高要求的程序优化的场合。驱动程序、嵌入式操作系统和实时运行程序都需要汇编语言。
二、在Keil下完成一个汇编程序的编写
- 1、新建工程,点击上方的的Project->New μVision Project… 选择工程存放位置及工程名。
- 选择芯片
-
添加启动文件
-
在左侧Source Group 1文件上右键,选择Add new item…,选择Asm File,然后点击Add
-
添加代码
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
- 为了能在MDK中进行仿真,需要进行一些设置,点击魔法棒,选择Debug,Use Simulator,将下方的Dialog.DLL修改成DARMSTM.DLL,Parameter修改成-pSTM32F103C8(根据你选的芯片来写),如果这里不进行修改,仿真调试的时候就会一直循环在
这是由于我们进行的仿真,没有外部晶振进行起振。
- 开始仿真,点击上方的Start/Stop Debug(或者直接ctrl+F5),然后点击②处的RUN,再点击Stop
- 运行结果
- 最终生成 hex文件的各段的大小
Code:指程序中代码的字节数
RO-data:指程序中定义的常量字节数
RW-data :程序中已初始化的变量字节数
ZI-Data:程序中未初始化的变量字节数
可计算出flash和RAM的占用情况:
flash = Code + RO-data + RW-data
ram = RW-data + ZI-dat
- 打开hex文件
如图,其中第一行020000040800F2中,可以看做是0x02 0x00 0x00 0x04 0x08 0x00 0xf2,其前四个字节和最后一个字节有特殊含义。中间为数据
第一个0×02表示该行数据中有两个数据
第二个,第三个0x00 0x00表示本行数据的起始地址位
第四个字节有0x00 0x01 0x02 0x03 0x04 0x05,分别有以下含义
'00’Data Rrecord:用来记录数据,HEX文件的大部分记录都是数据记录
'01’文件结束记录:用来标识文件结束,放在文件的最后,标识HEX文件的结尾**
'02’扩展段地址记录:用来标识扩展段地址的记录
'03’开始段地址记录:开始段地址记录
'04’扩展线性地址记录:用来标识扩展线性地址的记录
'05’开始线性地址记录:开始线性地址记录
最后一个字节0xf2为校验和。
校验和的算法为:
计算0xf2前所有16进制码的累加和(不计进位),检验和 = 0x100 - 累加和
三、 用汇编程序完成每间隔1秒钟闪烁一次LED的程序。
- 按上面的步骤再建立一个新工程,不用加启动文件,加启动文件的步骤可省略。
- 主要代码
LED0 EQU 0x422201b4
RCC_APB2ENR EQU 0x40021018
GPIOC_CRH EQU 0x40011004 ;预定义
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指令,代码段按8字节对齐
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} ;将R0,R1,LR入栈
LDR R0,=RCC_APB2ENR
ORR R0,R0,#0x04
LDR R1,=RCC_APB2ENR
STR R0,[R1]
LDR R0,=GPIOC_CRH
BIC R0,R0,#0XFF0FFFFF ;配置为模拟输入模式
LDR R1,=GPIOC_CRH
STR R0,[R1]
LDR R0,=GPIOC_CRH
ORR R0,R0,#0X00300000 ;配置为通用推挽输出模式,最大速度为50MHz
LDR R1,=GPIOC_CRH
STR R0,[R1]
MOV R0,#1 ;将立即数1送入R0.
LDR R1,=LED0 ;将PC13 bit-bond的地址送入R1.
STR R0,[R1] ;将R0的值,也就是1,送给R1中的值所指向的地址中
POP {R0,R1,PC} ;将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 ;加法,R0=R0+1
CMP R0,#330 ;计算R0-330的值,R0<330,则C=0;否则C=1。
BCC DelayLoop0 ;若是C=0,则跳到DelayLoop0,若是c=1,则不跳转
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}
NOP
END
- 代码解析
1、预定义
LED0 EQU 0x422201b4 //PC13的Bit-Bond地址
RCC_APB2ENR EQU 0x40021018 //时钟总线APB2地址
GPIOC_CRH EQU 0x40011004 //GPIO端口C高位寄存器CRH起始地址
为方便操作,给每个需要用到的寄存器地址定义一个名字。博主的stm32F103核心板的灯在PC13,计算公式:
((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
经过查询stm32中文手册可知,端口C地址为0x4001100C,bitnum=D(16进制),计算过程:
最终算出PC13端口地址为0x422201b4,后两个地址是固定的,查询stm32手册即可。
2,分配栈空间
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
这一段摘自启动文件。
AREA命令: AREA 命令指示汇编器汇编一个新的代码段或数据段。段是独立的、指定的、不可见的代码或数据块,它们由链接器处理。
格式如下:
AREA 段名,段属性1,段属性2,段属性3。。。
SPACE命令: SPACE 命令保留一个用零填充的存储器块。
所以整段的意思为:分配一个STACK段,该段不初始化,可读写,按8字节对齐。分配一个大小为Stack_Size的存储空间,并使栈顶的地址为__initial_sp。
3,其他命令
ENTRY命令: 声明整个程式的入口点,入口点有且仅有一个。
LDR和STR: 寄存器的装载和存储指令。
LDR是把地址装载到寄存器中(比如R0)。
STR是把值存储到寄存器所指的地址中。
ORR和BIC:
ORR 按位或操作。ORR R0,R0,#0x04意思即将R0中的数或上0x04,再将结果送往R0中。实际意思就是将R0的第二位置1,其他位不变。
BIC 先把立即数取反,再按位与。
CMP和BCC:
CMP是比较两个数,相等或大于则将标志位C置位,否则将C清零。BCC是个组合指令,实际为B+CC,意思是如果C=0则跳转。
CMP R2,#15; 计算R2-15的值,若是R2<15,则C=0;若是R2>=15,则C=1。
BCC DelayLoop0;若是C=0,则跳到DelayLoop0,若是c=1,则不跳转。
4、PC13端口
LDR R0,=GPIOC_CRH
BIC R0,R0,#0XFF0FFFFF ;配置为模拟输入模式
LDR R1,=GPIOC_CRH
STR R0,[R1]
LDR R0,=GPIOC_CRH
ORR R0,R0,#0X00300000 ;配置为通用推挽输出模式,最大速率为50MHz
- 将代码下载到核心板上,效果展示:
四、参考资料
1、HEX文件格式详解:https://blog.csdn.net/weixin_43836778/article/details/108598294
2、简单的STM32 汇编程序—闪烁LED:https://www.cnblogs.com/WeyneChen/p/4860764.html
3、ARM汇编基础之基于MDK创建纯汇编语言的STM32工程:https://blog.csdn.net/u010632165/article/details/106481146
4、STM32中文参考手册_V10