# 1.当前教程所需工具和掌握程度
(1.vscode使用了几周,只是作为程序代码编辑器使用,只装了C/C++代码编辑插件,和一个ReUI主题插件,感觉完成vscode+arm-gcc组合编译工作,得了解vscode的外部工具调用方式,即.vscode/launch.json、tasks.json命令配置,(c_cpp_properties.json只是C/C++编辑插件相关)。它俩就是完成自动化的脚本执行重点了。
(2.gcc-arm-none-eabi是被调用的外部工具,是ARM官方提供的ARM架构编译工具链,暂时只用到了其中一个工具arm-none-eabi-gcc.exe,用以编译链接程序源码。实际编译有复杂层次的工程程序代码时,是通过Makefile进行组织的,make工具通过Makefile文件来识别和调用arm-none-eabi-gcc.exe对整个工程进行精细编译链接和管理。
(3.make工具是另一个被调用的外部工具,由GNU提供,有两种方式使用,一是通过mgwin32跨平台的linux工具集软件(好像仍然需要单独下载make工具,只提供操作环境),另一种是直接使用make for windows的make工具(我是用的这的,因为我只需要make不需要其他一堆工具)。由于make基本是直接调用Makefile文件执行的,所以本身不难,难点在于Makefile理解和使用。
# 2.三个工具下载网址
(1.[vscode](Visual Studio Code - Code Editing. Redefined)
(2.[gcc-arm-none-eabi](GNU Toolchain | GNU Arm Embedded Toolchain – Arm Developer)
(3.[make](GNU make for Windows (equation.com))
(4.vscode直接解压就可以使用,我把启动程序添加到开始磁贴栏里用的。gcc-arm-none-eabi和make也是直接解压到软件盘目录下(我都下载的压缩包文件),不过这两个需要添加启动程序文件所在路径的环境变量(用户环境变量即可),这样才可以全局启动。
可以在win终端窗口里用命令启动测试下是否全局可以启动使用,用 arm-none-eabi-gcc -v 和 make -v测试下,能正常找到和输出软件版本信息说明可以使用。
# 3.编译工程需要的文件
(1.tasks.json
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"dependsOn": "clean", // 依赖项,表示在执行 build 前会首先自动执行 clean ,然后才执行build。
"label": "build", // build任务标签,将出现在vscode的task栏里
"type": "shell",
"command": "make",
"args": [
//"--file=STM32F4xx_StdPeriph_Templates/GNU-ARM/Makefile",
"-j4" // make 工具参数,作用暂时未知,另外还有 -j -j8等。
],
"group": "build", // 作用暂时未知。
"problemMatcher": "$gcc"
},
{
"label": "clean", // clean任务标签,将出现在vscode的task栏里
"type": "shell", // 默认类型
"command": "make", // 使用工程编译工具是make,它通过Makefile调用arm-gcc进行底层代码编译链接。
"args": [
//"--file=STM32F4xx_StdPeriph_Templates/GNU-ARM/Makefile", // 这是一开始想作为一个独立工程文件夹来使用的,后续会尝试使用
"clean" // 自定义的clean标签,make工具会在Makefile内查找这个自定义标签并执行其代表的实际make指令或者powershell指令。
],
"problemMatcher": "$gcc" // 无影响参数
},
]
}
a.这里表示有坑。vscode貌似提供两种任务文件,task.json 和 tasks.json 文件,我理解的是两者分别可以创建单任务和多任务,我按照一篇教程写了单任务的,但是实际是创建了多个任务,貌似因为这个原因导致不能正常打开执行,后续我改为tasks.json使用的。
b.这里表示也有坑。vscode新建*.json文件有两种,一种普通json文件,另一种是json with comments命令脚本文件,这里需要设为json with comments文件作为命令脚本使用,否则不会被识别和执行(我不保证确实有这个区别,记得有一篇博文也提到了这个问题,我按照那个要求修改的)
c.还有个坑。整个工程编译过程需要先清理掉旧的编译中间文件,才能进行新的编译,否则一直卡在开头的几个源文件处报错,提示无法找到XXXXX.c文件,依赖于XXXX.elf文件。由于是在Makefile中,后面Makefile详谈。
(2.launch.json
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ARM Debug",
"type": "cppdbg",
"request": "launch", // 这个名称需要和前面tasks.json文件的参数保持一致,其他参数在本教程暂时未使用,或者编译过程不受影响。
"miDebuggerPath": "d:/gcc-arm-none-eabi-10-2020-q4-major/bin/arm-none-eabi-gdb.exe",
"targetArchitecture": "arm",
//"program": "./build/${workspaceRootFolderName}.elf",
"args": [],
"stopAtEntry": false,
"MIMode": "gdb",
"setupCommands": [
{
"text": "file './build/${workspaceRootFolderName}.elf'"
},
{
"text": "target remote localhost:3333"
},
{
"text": "monitor reset"
},
{
"text": "monitor halt"
},
{
"text": "load"
}
],
"preLaunchTask": "build",
"cwd": "${workspaceRoot}"
},
{
"name": "Debug (OpenOCD)",
"cwd": "${workspaceRoot}",
//"executable": "./build/0119test.elf",
"request": "launch",
"type": "cortex-debug",
"servertype": "openocd",
"device": "STM32F4",
"runToMain": true,
"configFiles": [
"cmsis-dap.cfg",
"stm32f4x.cfg"
]
}
]
}
a.这里需要提示有坑。原因同上。
(3.Makefile
##########################################################################################################################
# File automatically-generated by tool: [projectgenerator] version: [3.0.0] date: [Fri Jul 10 15:04:01 CST 2020]
##########################################################################################################################
# ------------------------------------------------
# Generic Makefile (based on gcc)
#
# ChangeLog :
# 2017-02-10 - Several enhancements + project update mode
# 2015-07-22 - first version
# ------------------------------------------------
######################################
# target
######################################
## 生成的目标文件名 *.elf, *.hex 等
TARGET = GCC_F407
######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -Og
#######################################
# paths
#######################################
# Build path ## 编译中间文件和目标文件存放文件夹名称
BUILD_DIR = build
######################################
# source
######################################
# C sources ## 程序工程的C源文件,同样需要逐个添加,可以精确指定需要编译的C源文件
C_SOURCES = \
Libraries/STM32F4xx_StdPeriph_Driver/src/misc.c \
Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_gpio.c \
Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_rcc.c \
Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_syscfg.c \
user/system_stm32f4xx.c \
user/stm32f4xx_it.c \
user/main.c \
# ASM sources ## 程序工程的ASM汇编源文件,注意ST标准外设库的启动文件是gcc_ride7/文件夹下的
ASM_SOURCES = \
Libraries/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc_ride7/startup_stm32f40_41xxx.s
#######################################
# binaries
#######################################
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
#######################################
# CFLAGS
#######################################
# cpu ## 程序工程的芯片架构指定,本工程使用STM32F407是cortex-m4
CPU = -mcpu=cortex-m4
# fpu
# NONE for Cortex-M0/M0+/M3
# float-abi
# mcu
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)
# macros for gcc
# AS defines ## 程序工程的ASM汇编源文件全局宏定义,未使用不管
AS_DEFS =
# AS includes ## 程序工程的ASM汇编源文件全局包含文件,未使用不管
AS_INCLUDES =
# C defines ## 程序工程的C源文件全局宏定义,注意前缀 -D (define)
C_DEFS = \
-DUSE_STDPERIPH_DRIVER \
-DSTM32F40_41xxx
# C includes ## 程序工程的C源文件全局包含文件,注意前缀 -I (include)
C_INCLUDES = \
-ILibraries\CMSIS\Include \
-ILibraries\CMSIS\Device\ST\STM32F4xx\Include \
-ILibraries\STM32F4xx_StdPeriph_Driver\inc \
-Iuser \
# 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 = Libraries/CMSIS/linker/stm32_flash.ld
# libraries
#LIBS = -lc -lm -lnosys
LIBS = -lc
LIBDIR =
#LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections
LDFLAGS = $(MCU) -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: ## 程序工程的旧文件清理指令,通过make在windows终端powershell上执行,不能使用rm等unix shell指令,rd指令说可以自行查找
# -rm -fR $(BUILD_DIR)
# -rd /s /q \STM32F4xx_StdPeriph_Templates\MAKE-ARM\$(BUILD_DIR)
-rd /s /q $(BUILD_DIR)
#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)
# *** EOF ***
a.有坑提示。由于一部分教程使用的是mgwin32这个unix工具集,本身支持unix终端指令,所以在Makefile中的旧文件删除使用了 rm 指令,这在powershell上无法识别执行,所以会出现旧的文件不能清理,新的文件不能编译产生的烦人问题,卡了我半天,偶尔一次手动删除了 build文件夹,就可以正常进行编译了,原因是虽然clean 任务仍然未正常执行,但是后续的 build编译指令却可以正常进行下去了。至于 rd 指令的后面俩参数 /s /q 大家自己查找学习加即可。
b.新坑提示。Makefile命令后面不能添加注释,否则会无法正常被执行,导致报错找不到目标而终止编译,原因就是出现了错误指令。
例如下面写法,将提示无法找到目标文件。
TARGET = GCC_F407 ## 生成的目标文件名 *.elf, *.hex 等
(4.stm32_flash.ld
链接文件
/* Entry Point */
ENTRY(Reset_Handler)
/* 1、设置了用户ram的最高地址 */
/* Highest address of the user mode stack */
_estack = 0x20020000; /* end of RAM */
/* 2、设置用户栈空间以及堆空间的大小 */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x4000; /* required amount of heap */
_Min_Stack_Size = 0x1000; /* required amount of stack */
/* 3、指定了RAM与ROM的起始地址,以及大小 */
/* Specify the memory areas */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K
}
/* 4、将相关内容放到对应段中 */
/* Define output sections */
SECTIONS
{
/* 中断向量表放在.isr_vector段中 */
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* 程序代码和其他部分数据存放在.text段中 */
/* 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
/* .rodata 存放常量(只读数据) */
/* 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 和 .init_array 保存程序或共享对象加载时的初始化函数指针 */
.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 保存程序或共享对象退出时的退出函数地址 */
.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);
/* .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
_siccmram = LOADADDR(.ccmram);
/* CCM-RAM section
*
* IMPORTANT NOTE!
* If initialized variables will be placed in this section,
* the startup code needs to be modified to copy the init-values.
*/
/* .ccmram 是STM32中只允许内核访问的空间,可以将一些内核放在这里使用 */
.ccmram :
{
. = ALIGN(4);
_sccmram = .; /* create a global symbol at ccmram start */
*(.ccmram)
*(.ccmram*)
. = ALIGN(4);
_eccmram = .; /* create a global symbol at ccmram end */
} >CCMRAM AT> FLASH
/* .bss 保存了那些用到但未被初始化的数据 */
/* 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 用户的堆栈段 */
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(4);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(4);
} >RAM
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}
a.好了,应该是最后一个坑了。这个文件在ST标准外设库里没找到,作用是划分指定芯片flash存储地址和程序、数据等界限的,arm-gcc链接编译生成的*.o目标文件生成最终程序文件需要这个文件。我是在网上另一篇博文找的这段代码,里面的中文注释应该是博主在写博文时好心添加的,但是我没注意注释是否合法,在其他问题都解决后,最后链接一直出问题,找来找去发现是*.ld文件不支持双斜杠 // 注释,所以把全部的 // 注释 改为 /**/ 块注释就可以了。
感觉是C的标准注释,好像新的 // 注释是C++引进的。
网友大佬说 # 注释也可以在*.ld文件使用,我试了下果然可以,虽然内容显示不是注释,而且会编译提示告警,但是确实能正常编译链接了!
# 4.程序示例工程目录结构
这个感觉还是挺重要的,可以直观的让大家了解前面一堆东西怎么组合起来的。
a.说明。我用的是STM32F4xx标准外设库,库目录结构就不多展示了,main程序也是标准库官方示例未作改动。
b.说明。linker这个文件夹是我自己添加进Libraries库的,可以放工程的任意位置,只要在Makefile中指明路径就可以。
c.更新说明。新的测试结果表明不适用mgwin32这个unix工具集,在win的cmd窗口下操作DOS指令,不想bashshell那样灵活,当前主要遇到问题就是tasks.json调用了不在当前目录下的Makefile后不能正常执行make指令,所以Makefile文件和build文件夹还是暂时老实放在工程根目录下吧。
# 5.这是编译结果截图
真是太难了……激动地写这个博文留个念吧。
# 其他注意
1. c_cpp_properties.json 软件工程的C语言智能识别配置
这个是编辑代码时vscode进行自动识别C/C++代码的工具配置文件
{
"configurations": [
{
"name": "ARM",
// 1. 工程的头文件路径
"includePath": [
// JSON的工作空间文件路径
"${workspaceFolder}/**",
"libraries/CMSIS/CM3/CoreSupport/",
"libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x/",
"libraries/STM32F10x_StdPeriph_Driver_V3.5.0/inc/",
"libraries/TCPIP_ENC28J60_lib/",
"user/"
],
// 2. ST库工程配置全局宏
"defines": [
"USE_STDPERIPH_DRIVER",
"STM32F10X_LD"
//"VECT_TAB_FLASH"
],
// 为编译工具arm-gcc提供依赖环境文件的路径
// 例如等基础整型类型定义文件
"compilerPath": "d:/gcc-arm-none-eabi-10-2020-q4-major/bin/arm-none-eabi-gcc.exe",
"cStandard": "c11",
"cppStandard": "c++17",
// 编译器为windows下的arm-gcc
"intelliSenseMode": "windows-gcc-arm"
}
],
"version": 4
}
需要按照工程配置的一般只有头文件路径和全局宏定义两项。
但是"compilerPath"这个标签必须指定,否则会出现程序中的基础依赖文件找不到或者宏控制异常,导致全部文件的基础数据类型(例如int8_t/uint8_t等)找不到定义,如果只是简单地将编译器的依赖文件夹“include/”路径添加到普通的工程头文件路径"includePath"下,仍然会出现问题(应该是编译器的自身控制宏导致的)。……这个忘了添加导致我搞了半天路径包含还是解决不掉类型未定义的问题,尴尬。