一、启动流程
对于STM32F103从flash的启动流程如下:
首先设置栈:CPU会从0x08000000读取值,用来设置SP(不使用C语言可以不设置,或者在程序里设置SP)
然后跳转:CPU从0x08000004得到地址值,根据它的BIT0切换为ARM状态或Thumb状态,然后跳转
对于cortex M3/M4,它只支持Thumb状态,所以0x08000004上的值bit0必定是1
0x08000004上的值 = Reset_Handler + 1
接着就从Reset_Handler继续执行
二、在keil-MDK下编写程序
打开keil,新建工程,选择STM32F103ZE
新建start.s文件,编写如下代码;
- PRESERVE8 ;指示编译器8字节对齐
- THUMB ;指示编译器以后的指令为THUMB指令
- ; Vector Table Mapped to Address 0 at Reset
- AREA RESET, CODE, READONLY ;定义只读数据段,标记为RESET,其实放在CODE区,位于0地址
- EXPORT __Vectors ;在程序中声明一个全局的标号__Vectors,该标号可在其他的文件中引用
-
- __Vectors DCD 0 ;当前地址写入一个字(32bit)数据,值为0x00000000,实际是应该填入栈顶地址
- DCD Reset_Handler ;当前地址写入一个字(32bit)数据,值为Reset_Handler的值,即程序入口地址
- AREA |.text|, CODE, READONLY ;定义代码段,标记为.text
- ; Reset handler ;利用PROC、ENDP这一对伪指令把程序段分为若干个过程,使程序的结构加清晰
- Reset_Handler PROC ;过程的开始
- EXPORT Reset_Handler [WEAK] ;[WEAK] 弱定义,意思是如果在别处也定义该标号(函数),在链接时用别处的地址。
- ; 1、使能 GPIOB
- LDR R0, =(0x40021000 + 0x18)
- LDR R1, [R0]
- ORR R1, R1, #(1<<3)
- STR R1, [R0]
-
- ; 2、把GPIOB5设置为输出引脚
- LDR R0, =(0x40010C00 + 0x00)
- LDR R1, [R0]
- ORR R1, R1, #(1<<20)
- STR R1, [R0]
-
- ; 3、设置GPIOB5的输出寄存器
- LDR R2, =(0x40010C00 + 0x0C)
-
- ;4、loop循环
- Loop
- ; 5、设置GPIOB5输出高
- LDR R1, [R2]
- ORR R1, R1, #(1<<5)
- STR R1, [R2]
-
- LDR R0, =1000000
- BL delay
-
- ; 6、设置GPIOB5输出低
- LDR R1, [R2]
- BIC R1, R1, #(1<<5)
- STR R1, [R2]
-
- LDR R0, =1000000
- BL delay
-
- B Loop
- ENDP ;过程的结束
- delay
- SUBS R0, R0, #1
- BNE delay
- BX LR
- ALIGN ;填充字节使地址对齐
- END ;整个汇编文件结束
复制代码
然后添加如下两行在编译中执行的命令:- fromelf --bin --output=led.bin Objects\led.axf
- fromelf --text -a -c --output=led.dis Objects\led.axf
复制代码
或者如下通用命令,和上面的命令是等效的- fromelf --bin -o "[ DISCUZ_CODE_2 ]lt;a href="mailto:L@L.bin">L@L.bin</a>" "#L"
- fromelf --text -a -c --output="[ DISCUZ_CODE_2 ]lt;a href="mailto:L@L.dis">L@L.dis</a>" "#L"
复制代码
第一行生成.bin文件,第二行生成反汇编.dis文件
然后点击构建,可以看到,已经生成了led.bin文件和反汇编led.dis文件
打开led.dis和led.bin文件,可以看到,因为stm32f103的cotex-m3内核使用的是Thumb指令集,所以其指令长度既有16位又有32位;按stm32在flash模式下的启动顺序,先在0x0800000地址下获取栈地址,在0x08000000地址下获取程序入口地址+1的值,其中最后一位1表示thumb指令。
将其烧写到开发板,可以看到其LED灯闪烁
三、使用gcc编译程序
编写在gcc下使用的start.s汇编代码
- .syntax unified /* 指明当前汇编文件的指令是ARM和THUMB通用格式 */
- .cpu cortex-m3 /* 指明cpu核为cortex-m3 */
- .fpu softvfp /* 软浮点 */
- .thumb /* thumb指令 */
- .global _reset /* .global表示_start是一个全局符号 */
- .word 0x00000000 /* 当前地址写入一个字(32bit)数据,值为0x00000000,实际上应为栈顶地址 */
- .word _reset+1 /* 当前地址写入一个字(32bit)数据, 值为_reset标号代表的地址+1,即程序入口地址*/
- _reset: /* 标签_start,汇编程序的默认入口是_start */
- /* 1、使能 GPIOB */
- LDR R0, = (0x40021000 + 0x18) /* 将APB2外设时钟使能寄存器的地址值写入R0 */
- LDR R1, [R0] /* 读取该寄存器的值 */
- ORR.W R1, R1, #(1<<20) /* 修改读出的值 */
- STR R1, [R0] /* 写入修改后的值到该寄存器 */
- /* 2、把GPIOB5设置为输出引脚 */
- LDR R0, = (0x40010c00 + 0x00)
- LDR R1, [R0]
- ORR.W R1, R1, #(1<<20)
- STR R1, [R0]
- /* 3、设置GPIOB5的输出寄存器 */
- LDR R2, = (0x40010c00 + 0x0c)
- /* 4、loop循环 */
- loop:
- /* 5、设置GPIOB5输出高 */
- LDR R1, [R2]
- ORR.W R1, R1, #(1<<5)
- STR R1, [R2]
- LDR R0, =1000000
- BL delay
- /* 6、设置GPIOB5输出低 */
- LDR R1, [R2]
- BIC.W R1, R1, #(1<<5)
- STR R1, [R2]
- LDR R0, =1000000
- BL delay
- b loop
- delay:
- SUBS R0,R0,#1
- BNE delay
- BX LR
复制代码
以及Makefile文件如下
- led.bin:start.S
- arm-none-eabi-gcc -c start.s -o led.o
- arm-none-eabi-ld led.o -Ttext 0X80000000 -o led.elf
- arm-none-eabi-objcopy led.elf -O ihex led.hex
- arm-none-eabi-objcopy led.elf -O binary -S led.bin
- arm-none-eabi-objdump -D -m cortex-m3 led.elf > led.dis
- clean:
- rm -rf *.o led.elf led.hex led.bin led.dis
复制代码
然后执行make命令,如下所示,编译成功,
将其烧写到开发板,可以看到其LED灯闪烁
四、比较MDK和GCC编译差别
分别比较两个.dis文件,如下所示,按stm32在flash模式下的启动顺序,先在0x0800000地址下获取栈地址,在0x08000000地址下获取程序入口地址+1的值,其中最后一位1表示thumb指令。
另外发现其中有一个位置不同,但在汇编文件中使用的指令是相同的,这是因为gcc编译器会强制将SUBS有两个相同寄存器指令转换为单个寄存器操作。
————————————————
转载:Willliam_william
|