第一次学习使用STM32的芯片,有太多要学习的知识,有更多的各种指南和手册,这给我的感觉就是无从下手。现在终于走过了一个完整的开发流程,做一个记录,给诸位提供一些思路。
我的软硬件环境
硬件
选板子我主要有几个关注点:TYPE-C接口,可以通过USB下载固件,简洁。所有我没有看那些开发板,最终找到了下面这个设计和做工看起来都不错的板子。
这块板子的芯片是STM32F401CEU6,有复位和BOOT0按键,一个用户按键,一个用户灯,并且预留了FLASH芯片的焊盘。
编译工具
使用gcc-arm-none-eabi工具链,配合makefile管理整个工程的编译工作。
ARM GNU 工具链
GNU make
库
使用ST公司提供的STM32标准外设软件库,其中包括CMSIS的实现(微控制器软件接口标准,Cortex Microcontroller Software Interface Standard)。
STM32标准外设软件库
参考资料
- 板子的原理图。
- STM32F401CE的数据手册和参考手册。产品页面
- AN2606:STM32微控制器系统存储器自举模式。PDF
- STM32标准外设软件库文档,包含在库文件夹中。
- GCC文档。
关于芯片
- ARM是一家成立于英国剑桥的公司,本身不生产芯片,而是出售芯片设计技术的授权。
- ARM公司推出了多个系列的产品,从ARM1到ARM11,ARM11之后的产品改用Cortex命名,分为M、R、A三类。随着系列的演进,芯片的体系架构也在进化。
- 半导体生成厂商们从ARM公司购买ARM处理器核,根据各自应用领域,加入外围电路,形成自己的ARM微处理器芯片。
- STM32F401CEU6是由ST公司生产的ARM Cortex-M4 MCU,包含512KB Flash和96KB RAM、DSP、FPU、ART加速器,最高主频为84MHz,核心架构为ARMv7E-M。
工程建立
首先看下工程的目录结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
TOP
|-- makefile
|-- 000-libraries
| `-- STM32F4xx_DSP_StdPeriph_Lib_V1.8.0
| `-- Libraries
| |-- CMSIS
| `-- STM32F4xx_StdPeriph_Driver
`-- 001-GPIOToggle
|-- makefile
|-- src
| `-- main.c
|-- lib
| |-- startup_stm32f401xx.s
| |-- stm32f4xx.h
| |-- system_stm32f4xx.c
| `-- system_stm32f4xx.h
|-- scripts
| `-- STM32F401VCTx_FLASH.ld
`-- build
|
顶层目录下的makefile文件为一些基本的变量设置。000-libraries文件夹下存放库文件,其中就包含STM32F4xx的标准外设软件库和CMSIS实现。001-GPIOToggle是我的第一个工程,使用按键控制LED的亮灭,其中lib文件夹下是从CMSIS中拷贝来的可能根据工程而修改的代码。
代码编写
作为一个例子,我想要实现按键按下灯点亮,按键松开灯熄灭的效果,本质一点就是芯片端口的输入和输出设置。
首先,查看板子原理图,LED连接到PC13口和3.3V,KEY连接到PA0口和GND。
接着翻看器件的参考手册,在第8章,General-purpose I/Os (GPIO)中讲到:
Each general-purpose I/O port has four 32-bit configuration registers (GPIOx_MODER,
GPIOx_OTYPER, GPIOx_OSPEEDR and GPIOx_PUPDR), two 32-bit data registers
(GPIOx_IDR and GPIOx_ODR), a 32-bit set/reset register (GPIOx_BSRR), a 32-bit locking
register (GPIOx_LCKR) and two 32-bit alternate function selection register (GPIOx_AFRH
and GPIOx_AFRL).
大意就是可以通过GPIOx的寄存器来配置端口和控制输入输出。所谓GPIOx,就是PC13口由GPIOC控制,PA0口由GPIOA控制。
每个寄存器的具体内容,在8.4节,GPIO registers中都有说明,包括每个寄存器的复位值、对应端口的bit范围、是否可读写、以及设定值的意义。例如GPIOC_MODER[27:26]用来控制PC13的功能,00表示输入,01表示输出。除了输入输出,还可以设置输出方式(推挽和开漏)、端口速度、上拉下拉。这里我将PC13设置为推挽低速输出,PA0设置为上拉低速输入。
那么该如何在C代码中设置这些寄存器呢?根据CMSIS文档中CMSIS-CORE/Peripheral Access一节,可以使用GPIOC->MODER
的形式控制寄存器,或者查看CMSIS头文件stm32f4xx.h
的源代码,得知GPIO和GPIO_TypeDef的定义:
1
|
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint16_t BSRRL; /*!< GPIO port bit set/reset low register, Address offset: 0x18 */
__IO uint16_t BSRRH; /*!< GPIO port bit set/reset high register, Address offset: 0x1A */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
|
那么,main.c的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
// main.c
#include "stm32f4xx.h"
#include <stdint.h>
#define BTN_PIN ((uint16_t)0x0001)
#define LED_PIN ((uint16_t)0x2000)
int main() {
GPIOA->MODER = 0x0C000000;
GPIOA->OTYPER = 0x00000000;
GPIOA->OSPEEDR = 0x0C000000;
GPIOA->PUPDR = 0x64000001;
GPIOC->MODER = 0x04000000;
GPIOC->OTYPER = 0x00000000;
GPIOC->OSPEEDR = 0x00000000;
GPIOC->PUPDR = 0x00000000;
while(1) {
if(GPIOA->IDR & BTN_PIN) { // BTN not pushed
GPIOC->BSRRL = LED_PIN; // set bit, LED off
} else {
GPIOC->BSRRH = LED_PIN; // reset bit, LED on
}
}
}
|
编译流程
顶层目录下的makefile中是工具设置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
PREFIX = arm-none-eabi-
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
|
编译的基本流程是,首先将所有的源文件包括.c和.s文件编译为目标文件.obj,然后将所有的.obj链接成为.elf文件,再通过.elf文件转换得到.hex和.bin文件。编译和链接选项为C_FLAGS
、AS_FLAGS
、LD_FLAGS
,具体涵义可以参考GCC文档。其中一部分选项:
- -mcpu=cortex-m4,设置ARM处理器
- -mthumb,设置指令集
- -W -Wall,打开警告
- -fdata-sections -ffunction-sections,将每个函数或符号创建为一个sections,配合-Wl,–gc-sections指示链接器去掉不用的section,能够减少最终的可执行程序的大小。
链接脚本STM32F401VCTx_FLASH.ld是从官方的例子中拷贝来的,主要需要修改的地方如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = 0x20018000; /* end of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200; /* required amount of heap */
_Min_Stack_Size = 0x400; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K
}
|
工程makefile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
######################################
# build tools
######################################
TOP_DIR = $(abspath ..)
include $(TOP_DIR)/makefile
######################################
# build settings
######################################
TARGET = GPIOToggle
DEBUG = 0
OPT = -Og
######################################
# pahts
######################################
BUILD_DIR = ./build
LIBRARY_DIR = $(TOP_DIR)/000-libraries/STM32F4xx_DSP_StdPeriph_Lib_V1.8.0/Libraries
######################################
# sources
######################################
C_SOURCES = ./src/main.c \
./lib/system_stm32f4xx.c
ASM_SOURCES = ./lib/startup_stm32f401xx.s
######################################
# C_FLAGS
######################################
CPU = -mcpu=cortex-m4
FPU = -mfpu=fpv4-sp-d16
FLOAT-ABI = -mfloat-abi=hard
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)
C_DEFS = -DSTM32F401xx
C_INCLUDES = -I./lib \
-I$(LIBRARY_DIR)/CMSIS/Include
C_FLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) \
-std=gnu11 -W -Wall -fdata-sections -ffunction-sections
ifeq ($(DEBUG), 1)
C_FLAGS += -g -gdwarf-2
endif
C_FLAGS += -MMD -MP -MF"$(addprefix $(BUILD_DIR)/, $(notdir $(@:%.o=%.d)))"
######################################
# AS_FLAGS
######################################
AS_DEFS =
AS_INCLUDES =
AS_FLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) \
-W -Wall -fdata-sections -ffunction-sections
######################################
# LD_FLAGS
######################################
LD_SCRIPT = ./scripts/STM32F401VCTx_FLASH.ld
LIBS = -lc -lm -lnosys
LIBDIR =
LD_FLAGS = $(MCU) -specs=nano.specs -specs=nosys.specs \
-T$(LD_SCRIPT) $(LIBDIR) $(LIBS) \
-Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref \
-Wl,--no-wchar-size-warning -Wl,--gc-sections
######################################
# build the application
######################################
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))
.PHONY: all hex bin elf obj clean
all: $(BUILD_DIR) obj elf hex bin
hex: $(BUILD_DIR)/$(TARGET).hex
bin: $(BUILD_DIR)/$(TARGET).bin
elf: $(BUILD_DIR)/$(TARGET).elf
obj: $(OBJECTS)
$(BUILD_DIR)/$(TARGET).hex: $(BUILD_DIR)/$(TARGET).elf
@ echo "HEX $<"
@ $(HEX) $< $@
$(BUILD_DIR)/$(TARGET).bin: $(BUILD_DIR)/$(TARGET).elf
@ echo "BIN $<"
@ $(BIN) $< $@
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) makefile
@ echo "LD $(OBJECTS)"
@ $(CC) $(OBJECTS) $(LD_FLAGS) -o $@
@ $(SZ) $@
$(BUILD_DIR)/%.o: %.c makefile | $(BUILD_DIR)
@ echo "CC $<"
@ $(CC) -c $(C_FLAGS) $< -o $@
$(BUILD_DIR)/%.o: %.s makefile | $(BUILD_DIR)
@ echo "AS $<"
@ $(AS) -c $(AS_FLAGS) $< -o $@
$(BUILD_DIR):
@ mkdir $@
clean:
@ echo "CLEAN"
@ rm -rf $(BUILD_DIR)
|
烧录
根据资料AN2606:
自举程序存储在 STM32 器件的内部自举 ROM 存储器(系统存储器)中。在芯片生产期间由 ST 编程。其主要任务是通过一种可用的串行外设(USART、CAN、USB、I2C 等)将应用程序下载到内部 Flash 中。每种串行接口都定义了相应的通信协议,其中包含兼容的命令集和序列。
以及27节,STM32F401xD(E) 器件自举程序的说明,这个芯片支持通过USB的DFU下载,只需要在器件上电时设置BOOT1=0,BOOT0=1,就可以进入自举程序模式。
首先通过官方提供的DFU File Manager,将HEX文件转换为DFU文件,再通过官方提供的DfuSe工具,就可以将程序下载程序到硬件中了。
出于方便的考虑,可以在板子上烧一个bootloader,比如说STM32_HID_Bootloader,这样就不用每次进DFU模式和转换文件了。