参考代码:Embedded/stm32/assembly-led · guorong/study - 码云 - 开源中国 (gitee.com)
在main函数中添加如下代码:
AREA |.text|, CODE, READONLY
GPIOB_CRL EQU 0X40010C00
GPIOB_CRL_OUT EQU 1<<21 ;1<<21
GPIOB_ODR EQU 0X40010C0C
GPIOB_ODR_OUT EQU 1<<5 ;1<<5
GPIOB_BSRR EQU 0X40010C10
GPIOB_BSRR_OFF EQU 1<<5 ;1<<5
GPIOB_BSRR_ON EQU 1<<21 ;1<<21
RCC_APB2ENR EQU 0x40021018
RCC_APB2ENR_DATA EQU 1<<3 ;1<<3
main PROC
EXPORT main
LDR R1, =RCC_APB2ENR ; 将寄存器基地址载入R1
LDR R2, [R1] ; 将R1的数据读取到R2
ORR R2,R2,#RCC_APB2ENR_DATA ; 将R2的值改为目标值
STR R2, [R1] ; 将R0的值写入目标寄存器
LDR R1, =GPIOB_CRL ; 将寄存器基地址载入R1
LDR R2, [R1] ; 将R1的数据读取到R2
ORR R2,R2,#GPIOB_CRL_OUT ; 将R2的值改为目标值
STR R2, [R1] ; 将R0的值写入目标寄存器
label
LDR R1, =GPIOB_BSRR ; 将寄存器基地址载入R1
MOV R2,#GPIOB_BSRR_ON
STR R2, [R1] ; 将R0的值写入目标寄存器
LDR R4, =1000000 ; R4被加载为延时计数的初值,这个值依据时钟频率和需要的延时进行调整
Delay_Loop
SUBS R4, R4, #1 ; 每个循环递减计数器
BNE Delay_Loop ; 如果R4不为零则继续循环
LDR R1, =GPIOB_BSRR ; 将寄存器基地址载入R1
MOV R2,#GPIOB_BSRR_OFF
STR R2, [R1] ; 将R0的值写入目标寄存器
LDR R4, =1000000 ; R4被加载为延时计数的初值,这个值依据时钟频率和需要的延时进行调整
Delay_Loop2
SUBS R4, R4, #1 ; 每个循环递减计数器
BNE Delay_Loop2 ; 如果R4不为零则继续循环
B label
ENDP
ALIGN
END
即可编译下载实现PB5不断翻转电平,可以看到LED灯在闪烁
注意,编译的时候,需要带上use microlib,否则可能会有导致代码卡住。原因是在reset handler中,首先调到了__main函数中,这个函数不是main函数,而是在C库中(默认不带micro lib编译的时候),这个函数主要的作用就是内存初始化,包括将初始化过的可读写的全局变量和静态变量拷贝到内存对应的区域,初始化那些初始化为0的静态变量和全局变量,然后将栈顶地址和堆地址设置好,之后跳转到main函数,在这个过程中可能会碰到一个BKPT 0xAB的地方,是内核调试的一个相关指令,导致程序出问题,编译为microlib就可以避免这个问题。microlib是keil专门为 嵌入式实现了的一个更加精简的库函数,用来取代C标准库。在代码编译的时候,勾选和不勾选use microlib的时候,可以明显看到编译出来的代码的大小有变化。
其实对于汇编语言来说,可以不进行__main的那些内存初始化操作,在reset handler中,直接跳转到main函数中执行,可以理解为__main是为了C语言运行准备了环境。即如下所示
可以不带入microlib编译,正常运行。但是问题就是内存区域没有初始化,数据为上电后乱七八糟的数据,可以看到右下角的内存数据是乱的。所以不建议这么做。
另一个有趣的现象是不use microlib的时候,sp指向了0x20000660,即内存空间用了0x660的大小,其中在启动文件的最开始的位置,定义了栈的大小为0x400,堆空间的大小为0x200,剩余的0x60的大小是初始化为bss段的,即初始化为0的区域,双击项目文件里的Target1目录,可以打开map文件,里边有解释内存的分布情况,可以看到是.bss和HEAP和STACK
而在勾选了use microlib之后,内存空间只保留了栈大小,为0x20000400,没有bss段和堆空间(堆空间只有在使用malloc申请内存的时候,才会使用)