STM32F401RE 使用汇编点灯 - 理解其工作流程

1. 资料与环境的准备

1.1 资料

这里我使用的开发板是 NUCLEO-F401RE,涉及到的资料:

《PM0214 - STM32 Cortex®-M4 MCUs and MPUs programming manual》
《RM0368 - STM32F401xB/C and STM32F401xD/E advanced Arm®-based 32-bit MCUs》
《MB1136 - DEFAULT-C03_Schematic》
《Using as The gnu Assembler Version 2.19.51》
《The GNU linker ld Version 2.19.51》

对以上资料解释一下:
PM0214:STM32 Cortex-M4 架构的编程手册,可以看到内存的映射以及支持的指令
RM0368:F401RE 的芯片手册,查看各个外设相关的寄存器设置方法
MB1136:开发板的原理图
Assembler:GNU as 汇编器的说明,接下来将使用 arm-linux-gnueabi-as 对汇编代码进行编译
linker:GNU ld 链接器的说明,因为 as 只输出目标文件,最终的程序需要进行链接。

1.2 环境

交叉编译工具

sudo apt-get install gcc-arm-linux-gnueabihf

烧写工具
STM32 ST-LINK Utility

2. 程序启动流程

F401RE 支持三种启动方式,最常用的是从 flash 启动。

一旦选择这个模式,指令将从 flash 中读取的,根据内存映射,程序从 0x0800 0000 这个位置启动。

1

如上图可知,对于 F401RE 这款芯片,在 flash 启动模式下,0x0000 0000 ~ 0x0007 FFFF 这个内存范围和 0x0800 0000 ~ 0x0807 FFFF 这个内存范围是映射的同一块物理内存。

接下来,了解一下 STM32 Cortex-m4 的启动流程。

打开 PM0214 手册,有如下部分可以看到,复位异常的向量地址是 0x0000 0004

M 系列和 A 系列异常向量存放的内容不一样,M 系列的异常向量存放的是地址,而 A 系列的异常向量存放的是指令

注释:这句话不敢确定是否完全准确,个人经验总结的,如下黄色背景的那句话说的是从 Reset 向量提供的地址开始执行。

1
1

当然在 RM0368 中可以看到 F401RE 这款芯片的中断异常向量表。

在这个中断异常向量表中,本实验只需关注 Reset 就 ok 了。

1

关于时钟源,在 STM32 标准库提供的启动文件中,显示初始化时钟源。这里使用芯片默认提供的 HSI 时钟源。

F401RE 这款芯片比较简单,点亮一个 LED 只需使能 AHB1 上的 GPIOA,然后就是设置管脚的功能了。

3. 编写代码并获得可执行二进制文件

建立两个文件 map.lds、start.S

map.lds 是链接脚本

start.S 是汇编代码,这里使用 GNU as,所以要参考相关的说明文档。

map.lds

脚本的目的是指定输出的格式,输出的架构,及程序的入口,程序段的起始位置。

.text 段起始位置应该是 0x0800 0000,F401RE 这款芯片在 flash 模式下,0x0000 00000x0800 0000 映射的是同一物理地址。

OUTPUT_FORMAT 在 linker 文档中有详细的介绍。第一个参数标识默认格式,第二个是大端格式,第三个是小端格式。这里选择的都是 arm 架构。

链接器可以使用 -EL 和 -EB 命令行参数指定小端和大端格式。

OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
	. = 0x00000000;
	.text : { *(.text) }
	.data : { *(.data) }
	.bss  : { *(.bss)  }
}

start.S

@ cortex-m4 只能使用 thumb 和 thumb-2 指令集
@ unified 指示使用统一语法格式,具体见 Assembler 文档中 9.3.2.1 这一节
.syntax unified    
.cpu cortex-m4     @ 指定 CPU 架构
.thumb             @ 使用 thumb 指令集

.text
.global _start 
_start : 
	.word 0        @ 保留,通常设置为栈顶
	.word _reset   @ 复位中断程序的起始位置

_reset:
	@ RCC_AHB1 使能 GPIOA
	ldr r0, =0x40023830
	ldr r1, [r0]
	orr r1, r1, #0x1
	str r1, [r0]

	@ GPIOA_MODER PA_5 设置为输出模式,其它配置保持默认(推挽输出,无上下拉,低速)
	ldr r0, =0x40020000
	ldr r1, [r0]
	mov r2, #0x1
	orr r1, r1, r2, LSL #10
	str r1, [r0]

loop:
	@ 灯亮
	ldr r1, [r0, #0x14]
	orr r1, r1, r2, LSL #5
	str r1, [r0, #0x14]

	@ 延时
	ldr r3, =10000000
d0:
	subs r3, r3, #1
	bne d0

	@ 灯灭
	ldr r1, [r0, #0x14]
	bic r1, r1, r2, LSL #5
	str r1, [r0, #0x14]

	@ 延时
	ldr r3, =10000000
d1:
	subs r3, r3, #1
	bne d1

	b loop
.end

编译

arm-linux-gnueabi-as -c start.S -o led.o
arm-linux-gnueabi-ld led.o -Tmap.lds -o led.elf
arm-linux-gnueabi-objcopy led.elf -O binary led.bin

4. 烧写

4.1 连接设备

打开 STM32 ST-LINK Utility,选择 Target -> Connect

1

4.2 写入文件

选择 Target -> Program

1

注意起始地址输入 0x0800 0000,选择文件 led.bin,勾选 Reset after programming

点击 Start,查看板子灯开始闪烁

1

遗留问题

灯只有在 STLINK 烧写之后开始闪烁,如果按复位键或重新上电灯不会闪烁 ?

posted on 2023-09-22 19:37  kahle  阅读(456)  评论(0)    收藏  举报

导航