BLOG

【ARM Cortex

目录

隐藏

4.1 Keil新建工程与配置

4.1.1文件准备

4.1.2新建的MDK 工程

4.1.2.1新建工程

4.1.2.2配置 MDK 的配置选项

4.1.2.3硬件调试配置

4.2 GCC环境新建工程与配置

arm-none-eabi-gcc -v

小贴士

欢迎访问我的网站

资源获取方式

Cortex-M的开发环境有很多种,可以使用集成开发环境(IED),比如Keil,IAR等;也可使用GCC,使用GCC编译需要编写Makefile,比较麻烦,但是该方式能深入理解编译过程。另外ST开发了很多工具,也有自己的IDE,笔者不打算使用,但是笔者会使用STM32CubeMX软件开发STM32,这部分将在下一章讲解,本文将基于Keil和GCC开发,关于开发环境的搭建请参看上一章,笔者这里就不在赘述了。

笔者本文将以STM32为例,为了方便学习,笔者也会给出GD32和CH32的实例,请自行获取学习。

笔者使用的开发板是STM32F103ZET6。

4.1 Keil新建工程与配置

4.1.1文件准备

1.新建文件夹

新建STM32_Project目录,在STM32_Project文件夹下,我们新建7个文件夹,分别为CMSIS、FWLib、Listing、Output、Project、Readme、User。CMSIS 用来存放库为我们自带的启动文件和一些 Cortex-M系列的通用文件,CMSIS文件里存放的文件适合任何Cortex-M内核的单片机,CMSIS 的 缩写为:Cortex Microcontroller Software Interface Standard,是 ARM Cortex 微控制器软件接口标准,是 ARM 公司为芯片厂商提供的一套通用的且独立于芯片厂商 的处理器软件接口;FWlib 用来存放芯片厂家的库文件里面的 inc 和 src 这两个文件,这两个文件包含了芯片上的所有驱动。Listing 用来存放一些编译过程中产生的文件。Output用来保存软件编译后输出的文件。User用来存放工程文件和用户代码,包括主函数main.c。另外,再把文件名为keilkilll的文件放到STM32_Project文件夹用来清除不必要的文件。新建文件夹如下图所示:

2.添加文件

在STM32F10x_StdPeriph_Lib_V3.5.0目录下

 STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver 的 inc 跟 src 这两个文件夹拷贝到 STM32_Project\FWlib 文件夹中。

 STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm 的全部文件拷贝到 STM32_Project\CMSIS\startup(需先在 CMSIS 新建好 startup 文件夹)文件夹下。这些是用汇编写的启动文件。开发板用的 CPU 是 STM32F103ZE,V表示100脚,E = 512K字节的闪存存储器;其中 512KFlash,属于大容量的,所以等下我们把startup_stm32f10x_hd.s添加到我们的工程中。根据 ST 的官方资料:Flash 在 16 ~32 Kbytes 为小容量,64 ~128 Kbytes为 中容量,256 ~512 Kbytes为大容量,不同大小的 Flash 对应的启动文件不一 样,这点要注意。

STM32 各类产品对应的启动文件后缀名含义。

 STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport 的 core_cm3.c 和 core_cm3.h 也拷贝到 STM32_Project\CMSIS 文件夹下。

 STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x 的stm32f10x.h、system_stm32f10x.c、system_stm32f10x.h拷贝到STM32_Project\CMSIS 文件夹下。

 STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template下的 main.c、stm32f10x_conf.h、 stm32f10x_it.h、 stm32f10x_it.c 、system_stm32f10x.c 拷贝到STM32_Project\User目录下,再在STM32_Project\User目录下新建YS_STM32,其中YS_STM32用来存放用户自定义文件。

4.1.2新建的MDK 工程

4.1.2.1新建工程

1.新建工程

启动Keil 5软件,在工具栏 Project->New μVision Project…新建我们的工程文件,我们将新建的工程文件保存在STM32_Project\Project文件夹下,文件名取为YS_STM32,名字可以随便取,点击保存。

2.选择芯片

新建工程后,会弹出窗口,这让我们选择片型号,我们用的芯片是 ST 公司的STM32F103ZE,有64K SRAM,512K Flash,属于高集成度的芯片。按如下选择即可。

接下来会弹出工程管理器,这里可以选择启动文件和CMSIS,这里就不选择了,我们这里用的是 ST 的库,库文件里面也自带了这一份启动代码,所以为了保持库的完整性,我们就不需要开发环境为我们自带的启动代码了,稍后我们自己手动添加。

3.工程管理

点击Keil的文件管理器,将 Target 改为 YS_STM32(不改也行),新建五个组,分别命名为STARTUP、FWlib、CMSIS、USER、Readme。STARTUP 从名字就可以看得出我们是用它来放我们的启动代码的,FWlib 用来存放库文件,CMSIS 用来存放 M系列单片机通用的文件,USER用来存放用户自定义的应用程序,Readme存放工程文档。然后添加相应文件。

修改main.c的代码。

/**

******************************************************************************

* @file main.c

* @author BruceOu

* @lib version V3.5.0

* @version V1.0

* @date 2021-02-06

* @blog https://blog.bruceou.cn/

* @Official Accounts 嵌入式实验楼

* @brief STM32F103ZET6工程模板

******************************************************************************

*/

/* Includes*********************************************************************/

#include "./LED/stm32f103_led.h"

/*延时函数*/

void Delay(u32 xms);

/**

* @brief 主函数

* @param None

* @retval int

*/

int main(void)

{

/* LED 初始化 */

LED_GPIO_Config();

while(1)

{

// ODR GPIOB

// 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 (复位值)

// 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0

GPIOB->ODR = 0XFFFE; //低电平,GPIOB0(LED1)灯灭

Delay(0x4FFFFF);

GPIOB->ODR = 0XFFFF; //高电平,GPIOB0(LED1)灯亮

Delay(0x4FFFFF);

}

}

/**

* @brief 延时函数

* @param

xms 延时长度

* @retval None

*/

void Delay( u32 xms)

{

//for(; nCount != 0; nCount--);(方法一)

while(xms--);//(方法二)

}

/**********************************END OF FILE*******************************/

为了方便观察现象,这里写了个控制LED的实例,具体如何配置将在后文详细,当然啦,如果有一定基础,直接根据自己的板子修改GPIO的即可。

4.1.2.2配置 MDK 的配置选项

点击工具栏中的魔术棒按钮

在弹出来的窗口中选中Output点击 Select Folder for Objects… 设置编译后输出文件保存的位置(放在STM32_Project\Output文件夹下)。同时把 Create HEX File 和 Browse information 这两个选项框也选上。

同样在 Listing 这个选项卡中,我们也点击 Select Folder listings…定位到模板中的 Listing 文件夹。

在C/C++选项卡,在 Define 里面输入添加 USE_STDPERIPH_DRIVER,STM32F10X_HD。添加 USE_STDPERIPH_DRIVER 是为了屏蔽编译器的默认搜索路径,转而使用我们添加到工程中的 ST 的库,添加 STM32F10X_HD 是因为我们用的芯片是大容量的,添加了STM32F10X_HD 这个宏之后,库文件里面为大容量定义的寄存器我们就可以用了。芯片是小或中容量的时候宏要换成STM32F10X_LD 或者 STM32F10X_MD。其实不管是什么容量的,我们只要添加上STM32F10X_HD 这个宏即可,当你用小或者中容量的芯片时,那些为大容量定义的寄存器我不去访问就是了,反正也访问不了,关于这两个宏的详细解释参看后文的小贴士。在 Include Paths 栏点击,在这里添加库文件的搜索路径,这样就可以屏蔽掉默认的搜索路径。

4.1.2.3硬件调试配置

接着以上操作,这个工程默认的是软件仿真,如果开发板要用 J-LINK (ST-LINK同理)调试的话,还需要在 开发环境中做如下修改。实际上,我们开发程序的时候 80%都是在硬件上调试的。

然后设置下Port。

一般都是SW接口,到此,常用的配置就完成了。

最后再来编译下。

出现一下信息表示编译通过。

接下来就是可进行程序烧写和验证了。

当然啦,能下载的前提是需要安装J-link等设备,也是可以使用其他下载方式,后面的章节会详细描述。

4.2 GCC环境新建工程与配置

笔者已经在《STM32开发环境搭建》系列文章详细介绍了该方式,但是笔者使用STM32CubeMX生成的工程,Makefile也是STM32CubeMX生成的,笔者接下来要介绍如何在标准库的基础上使用GCC开发。

在Keil工程的基础上删除Listing、Output、Project目录,在顶层目录添加Makefile和链接文件STM32F103ZETx_FLASH.ld。

Makefile

##########################################################################################################################

# File automatically-generated by tool: [projectgenerator] version: [3.15.2] date: [Fri Apr 08 20:05:41 CST 2022]

##########################################################################################################################

# ------------------------------------------------

# Generic Makefile (based on gcc)

#

# ChangeLog :

# 2022-04-08 - first version

# ------------------------------------------------

######################################

# target

######################################

TARGET = YS_STM32

######################################

# building variables

######################################

# debug build?

DEBUG = 1

# optimization

OPT = -O0

#######################################

# paths

#######################################

# Build path

BUILD_DIR = build

######################################

# source

######################################

# C sources

C_SOURCES = \

CMSIS/system_stm32f10x.c \

User/main.c \

User/stm32f10x_it.c \

FWLib/src/stm32f10x_rcc.c \

FWLib/src/stm32f10x_gpio.c \

User/YS_STM32/LED/stm32f103_led.c

# ASM sources

ASM_SOURCES = \

CMSIS/startup/startup_stm32f10x_hd.s

#######################################

# binaries

#######################################

PREFIX = arm-none-eabi-

# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)

# either it can be added to the PATH environment variable.

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

#######################################

# CFLAGS

#######################################

# cpu

CPU = -mcpu=cortex-m3

# fpu

# NONE for Cortex-M0/M0+/M3

# float-abi

# mcu

MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)

# macros for gcc

# AS defines

AS_DEFS =

# C defines

C_DEFS = \

-DUSE_STDPERIPH_DRIVER \

-DSTM32F10X_HD

# AS includes

AS_INCLUDES =

# C includes

C_INCLUDES = \

-ICMSIS \

-IFWLib/inc \

-IUser \

-IUser/YS_STM32

# compile gcc flags

ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

ifeq ($(DEBUG), 1)

CFLAGS += -g -gdwarf-2

endif

# Generate dependency information

CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"

#######################################

# LDFLAGS

#######################################

# link script

LDSCRIPT = STM32F103ZETx_FLASH.ld

# libraries

LIBS = -lc -lm -lnosys

LIBDIR =

LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections

# default action: build all

all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin

#######################################

# build the application

#######################################

# list of objects

OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))

vpath %.c $(sort $(dir $(C_SOURCES)))

# list of ASM program objects

OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))

vpath %.s $(sort $(dir $(ASM_SOURCES)))

$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)

$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@

$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)

$(AS) -c $(CFLAGS) $< -o $@

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile

$(CC) $(OBJECTS) $(LDFLAGS) -o $@

$(SZ) $@

$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)

$(HEX) $< $@

$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)

$(BIN) $< $@

$(BUILD_DIR):

mkdir $@

#######################################

# clean up

#######################################

clean:

-rm -fR $(BUILD_DIR)

#######################################

# dependencies

#######################################

-include $(wildcard $(BUILD_DIR)/*.d)

# *** EOF ***

STM32F103ZETx_FLASH.ld

/*

******************************************************************************

**

** File : LinkerScript.ld

**

**

** Abstract : Linker script for STM32F103ZETx series

** 512Kbytes FLASH and 64Kbytes RAM

**

** Set heap size, stack size and stack location according

** to application requirements.

**

** Set memory bank area and size if external memory is used.

**

** Target : STMicroelectronics STM32

**

** Distribution: The file is distributed “as is,” without any warranty

** of any kind.

**

*****************************************************************************

*/

/* Entry Point */

ENTRY(Reset_Handler)

/* Highest address of the user mode stack */

_estack = 0x20010000; /* 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

{

RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K

FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 512K

}

/* Define output sections */

SECTIONS

{

/* The startup code goes first into FLASH */

.isr_vector :

{

. = ALIGN(4);

KEEP(*(.isr_vector)) /* Startup code */

. = ALIGN(4);

} >FLASH

/* The program code and other data goes into FLASH */

.text :

{

. = ALIGN(4);

*(.text) /* .text sections (code) */

*(.text*) /* .text* sections (code) */

*(.glue_7) /* glue arm to thumb code */

*(.glue_7t) /* glue thumb to arm code */

*(.eh_frame)

KEEP (*(.init))

KEEP (*(.fini))

. = ALIGN(4);

_etext = .; /* define a global symbols at end of code */

} >FLASH

/* Constant data goes into FLASH */

.rodata :

{

. = ALIGN(4);

*(.rodata) /* .rodata sections (constants, strings, etc.) */

*(.rodata*) /* .rodata* sections (constants, strings, etc.) */

. = ALIGN(4);

} >FLASH

.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH

.ARM : {

__exidx_start = .;

*(.ARM.exidx*)

__exidx_end = .;

} >FLASH

.preinit_array :

{

PROVIDE_HIDDEN (__preinit_array_start = .);

KEEP (*(.preinit_array*))

PROVIDE_HIDDEN (__preinit_array_end = .);

} >FLASH

.init_array :

{

PROVIDE_HIDDEN (__init_array_start = .);

KEEP (*(SORT(.init_array.*)))

KEEP (*(.init_array*))

PROVIDE_HIDDEN (__init_array_end = .);

} >FLASH

.fini_array :

{

PROVIDE_HIDDEN (__fini_array_start = .);

KEEP (*(SORT(.fini_array.*)))

KEEP (*(.fini_array*))

PROVIDE_HIDDEN (__fini_array_end = .);

} >FLASH

/* used by the startup to initialize data */

_sidata = LOADADDR(.data);

/* Initialized data sections goes into RAM, load LMA copy after code */

.data :

{

. = ALIGN(4);

_sdata = .; /* create a global symbol at data start */

*(.data) /* .data sections */

*(.data*) /* .data* sections */

. = ALIGN(4);

_edata = .; /* define a global symbol at data end */

} >RAM AT> FLASH

/* Uninitialized data section */

. = ALIGN(4);

.bss :

{

/* This is used by the startup in order to initialize the .bss secion */

_sbss = .; /* define a global symbol at bss start */

__bss_start__ = _sbss;

*(.bss)

*(.bss*)

*(COMMON)

. = ALIGN(4);

_ebss = .; /* define a global symbol at bss end */

__bss_end__ = _ebss;

} >RAM

/* User_heap_stack section, used to check that there is enough RAM left */

._user_heap_stack :

{

. = ALIGN(8);

PROVIDE ( end = . );

PROVIDE ( _end = . );

. = . + _Min_Heap_Size;

. = . + _Min_Stack_Size;

. = ALIGN(8);

} >RAM

/* Remove information from the standard libraries */

/DISCARD/ :

{

libc.a ( * )

libm.a ( * )

libgcc.a ( * )

}

.ARM.attributes 0 : { *(.ARM.attributes) }

}

然后修改启动文件,将STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ ST\STM32F10x\startup\gcc_ride7 的全部文件拷贝到 STM32_Project\CMSIS\startup目录下,当前的启动文件就使用startup_stm32f10x_hd.s。

接下来就可以编译了。

关于GCC环境的搭建参看笔者前面的文章就可以了,Linux和Windows板本的都有。

笔者是在Windows开发,这里贴出GCC编译的环境搭建,为了方便查看。

下载地址

【注】这里要选择GUN-RM下的工具,GUN-A是Cortex-A系列的交叉编译工具。

下载后解压,并把安装目录下的bin文件夹添加到环境变量:

然后在命令窗口中输入下面的命令验证安装是否成功:

arm-none-eabi-gcc -v

如果有信息输出,那就是装好了。

GCC编译工程是需要编写Makefile,这里笔者已经编写好了,直接用就行。那么就需要一个工具来识别Makefile文件,也就是make工具,在Linux中已经自带make了,这一步就可以省了。

本文的make工具是依赖Git工具的,我相信很多朋友都用过Git,但是很少使用Git的make等功能。

Git的bash实际上也就是一个mingw,是可以支持部分Linux指令的,但是只有少部分。在编译代码的时候经常会使用make命令反而在bash下默认是不支持的。

Make工具下载地址

下载make-4.1-2-without-guile-w32-bin.zip 文件。

把该文件进行解压,把解压出来的文件全部拷贝的git的安装目录下:

C:\Program Files\Git\mingw64

把文件夹进行合并,如果跳出来需要替换的文件要选择不替换。

这样在git bash窗口下就可以执行make了。

没有安装Git先安装Git工具。

Git下载地址:https://git-scm.com/download/win

最后我们使用make编译下前文新建的工程,编译通过显示如下:

编译成功就会在build目录下生成固件。

值得注意的是,国产的很多厂家都没有提供GCC版本的启动文件,需要自行修改,笔者在后面会介绍如何编写启动文件。

在后面的章节笔者讲解程序的下载与调试,尽请期待!

小贴士

关于STM32 MDK中USE_STDPERIPH_DRIVER, STM32F10X_HD问题的解释

初学STM32,在MDK 环境中使用STM32固件库建立工程时,初学者可能会遇到编译不通过的问题。出现如下警告或错误提示:

warning: #223-D: function "assert_param" declared implicitly;assert_param(IS_GPIO_ALL_PERIPH(GPIOx));

这时候我们需要在“Target Options”中的“C/C++”选项卡中如下图所示红框中添加USE_STDPERIPH_DRIVER、STM32F10X_HD。这样才能使编顺利通过。

知其然了,我们还得知其所以然。下面就笔者给大家一一道来。我们知道,程序的执行是从“main.c”文件开始的,其中必须包含有头文件“stm32f10x.h”。我们打开“stm32f10x.h”,按下“Ctrl+F”键,查找USE_STDPERIPH_DRIVER,在“Find What”栏中输入“USE_STDPERIPH_DRIVER”。值得注意的是在查找之前工程必须是编译过了的。如下图所示。

点击“Find Next”,出现“USE_STDPERIPH_DRIVER”对应的代码行,我们能在第8296-8298行找到如下图所示代码段。

这段代码的意思是,只有用预编译指令预定义了“USE_STDPERIPH_DRIVER”,才会将"stm32f10x_conf.h"包含进“stm32f10x.h”中,从而被"main.c"用到。这就解释了,为什么我们没有在“main.c”中包含"stm32f10x_conf.h",而在编译之后却被包含进了"main.c"中,出现如图-5所示的情况。"stm32f10x_conf.h"文件相当于一个开关文件,如果要用到STM32固件库驱动标准外设,则外设驱动头文件是必不可少的,如“stm32f10x_gpio.h”。在"stm32f10x_conf.h"中我们通过代码#include "stm32f10x_gpio.h"来实现这个操作。

说到这儿估计大家已经对“USE_STDPERIPH_DRIVER”的来龙去脉有个清晰的认识了吧?其实单从字面意思理解就是“使用标准外设驱动”,在C/C++预定义中加“USE_STDPERIPH_DRIVER”就是允许“使用标准外设驱动”了。至于加入的“STM32F10X_HD“同样能在文件”stm32f10x.h“通过如上所述的方法通过功能查找来进行解释,其实它也是对于对应硬件的某些定义起开关作用罢了。其实写在这里,他的名字叫define。也就跟你在工程里面写define XXX是一样的。只不过写在这里的话,是全局的而已。

欢迎访问我的网站

BruceOu的哔哩哔哩

BruceOu的主页

BruceOu的博客

BruceOu的CSDN博客

BruceOu的简书

BruceOu的知乎

资源获取方式

1.关注公众号[嵌入式实验楼]

2.在公众号回复关键词[Cortex-M]获取资料提取码