引言
前段时间学习了Makefile,了解到其批量编译的功能。所以去尝试复刻到keil编译文件。这里实现了window下实现makefile对标准库来编译。之后也会在Linux环境下继续探索。
这里的核心软件还是gcc-arm-none-eabi的arm环境,与MinGW64提供的make软件。
核心修改点为makefile文件的修改与标准库函数里面的两个函数的修改。个人认为这种方法应该也可以用到别的芯片上面,不过目前没有去验证。我会提供此次使用的全部工程文件。每次上传文件到csdn,别人下载都会要收费或者收积分,我真的:)-
一、软件与环境准备
我们需要准备三个环境包与一个软件(提供一个Make file模板)
MInGW64 ---- 提供make软件(全名:mingw32-make.exe)
openocd ---- 提供烧录的环境(支持stlink,jlink等多种方式)
gcc-arm-none-eabi ---- 为ARM的板子如STM32编译必须的编译器
STM32CubeMx ---- 提供一个Makefile模板和一点必须的文件
下载地址:
MInGW64
mingw-w64 Releases · niXman/mingw-builds-binaries · GitHub
openocd
https://sourceforge.net/projects/openocd/ Releases · openocd-org/openocd · GitHub
gcc-arm-none-eabi
Downloads | GNU Arm Embedded Toolchain Downloads – Arm Developer
STM32CubeMx
https://www.st.com/en/development-tools/stm32cubemx.html
为避免文章拖沓,这里默认已经安装或者下载好这些必备的软件或者压缩包。
然后需要分别将MINGW64、openocd、gcc-arm-none-eabi 的bin目录配置到系统环境中
MInGW64
openocd
gcc-arm-none-eabi
STM32CubeMx
安装完STM32CubeMX后面,我们需要它去下载库文件。这个库文件一般会保存在以下路径内
让其去生成一个工程,不过工程选择Makefile工程。下面简单配置一下:
到达这一步,cubemx的使命已经完成。
二、修改Makefiel文件
1.我们把上面的这三个文件单独拿出来
2.我们把标准库从零搭建一个STM32的工程模板_stm32固件库下载及安装-优快云博客,或者你的或者其它人的,只要是一个标准模板。全部拿过来就可以,这里我把我的拿过来
完成图:
那么开始吧,修改makefile文件
四个需要修改的makefile文件地方
makefile文件总共有四个地方需要修改
step1
第一处
下面替换上面
C_SOURCES = \
$(wildcard ./CMSIS/*.c) \
$(wildcard ./Library/src/*.c) \
$(wildcard ./Library/inc/*.c) \
$(wildcard ./Start/*.c) \
$(wildcard ./User/*.c)
改完后:
总而言之,这是我的工程文件,如果你的,也要根据自己的C文件在哪,全部加进去。比如我,为了省事,干脆把全部文件路径都加进去了,即便里面可能没有C文件
step2
第二处
下面替换上面 :
C_DEFS = \
-DSTM32F10X_MD\
-DUSE_STDPERIPH_DRIVER
改完之后:
其实这里也是我们在keil里面添加的宏,这样更容易理解我们为什么要这样去做。
makefile文件中
-D为添加宏定义,这里添加的宏为
STM32F10X_MD
USE_STDPERIPH_DRIVER
step3
第三处
下面替换上面:
C_INCLUDES = \
-ICMSIS\
-ILibrary/inc\
-ILibrary/src\
-IStart\
-IUser
改完之后
这里是添加头文件路径,不需要.*h,它会自己去找,这里只要提供路径即可,为了省事,我干脆把全部文件路径都加进去了,即便里面可能没有和h文件
makefile文件中
-I为添加头文件路径。
step4
第四处,这里主要是要让make clean起作用。因为window环境下,不认识rm -f命令,这是linux的bash命令
下面替换上面:
ifeq ($(OS),Windows_NT)
RM = rmdir /S /Q
else
RM = rm -rf
endif
clean:
-$(RM) $(BUILD_DIR)
改完之后:
makefile文件修改完成。这里贴出,我的完整文件
##########################################################################################################################
# File automatically-generated by tool: [projectgenerator] version: [4.3.0-B58] date: [Fri Mar 14 19:48:43 CST 2025]
##########################################################################################################################
# ------------------------------------------------
# Generic Makefile (based on gcc)
#
# ChangeLog :
# 2017-02-10 - Several enhancements + project update mode
# 2015-07-22 - first version
# ------------------------------------------------
######################################
# target
######################################
TARGET = make_again
######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -Og
#######################################
# paths
#######################################
# Build path
BUILD_DIR = build
######################################
# source
######################################
#第一处需要修改的地方
# C sources
C_SOURCES = \
$(wildcard ./CMSIS/*.c) \
$(wildcard ./Library/src/*.c) \
$(wildcard ./Library/inc/*.c) \
$(wildcard ./Start/*.c) \
$(wildcard ./User/*.c)
# ASM sources
ASM_SOURCES = \
startup_stm32f103xb.s
# ASM sources
ASMM_SOURCES =
#######################################
# 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 = \
-DSTM32F10X_MD\
-DUSE_STDPERIPH_DRIVER
# AS includes
AS_INCLUDES =
# C includes
#第三处需要修改的地方
C_INCLUDES = \
-ICMSIS\
-ILibrary/inc\
-ILibrary/src\
-IStart\
-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 = STM32F103C8Tx_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)))
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASMM_SOURCES:.S=.o)))
vpath %.S $(sort $(dir $(ASMM_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)/%.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
#######################################
#第四处需要修改的地方
ifeq ($(OS),Windows_NT)
RM = rmdir /S /Q
else
RM = rm -rf
endif
clean:
-$(RM) $(BUILD_DIR)
#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)
# *** EOF ***
三、稍微修改标准库函数文件中的两个函数
如果我们直接编译会出现报错,执行make32(如果你把mingw32-make.exe复制一份改为make32.exe)
我们需要回到模板里面的标准库函数里面的core_cm3.c文件。我目前只能通过改函数的手段来解决这个问题。这个问题我目前的能力还无法解释。
在core_cm3.c大概700多行的地方
下面替换上面:
uint32_t __STREXB(uint8_t value, uint8_t *addr)
{
uint32_t result;
register uint8_t *tmp_addr asm("r1") = addr;
register uint8_t tmp_value asm("r2") = value;
__ASM volatile (
"strexb %0, %2, [%1]"
: "=r" (result)
: "r" (tmp_addr), "r" (tmp_value)
: // No clobbered registers
);
return result;
}
uint32_t __STREXH(uint16_t value, uint16_t *addr)
{
uint32_t result;
register uint16_t *tmp_addr asm("r1") = addr;
register uint16_t tmp_value asm("r2") = value;
__ASM volatile (
"strexh %0, %2, [%1]"
: "=r" (result)
: "r" (tmp_addr), "r" (tmp_value)
: // No clobbered registers
);
return result;
}
完成,不管你采用什么方式,覆盖还是其它。这里的函数必须要去修改。不然编译无法通过。
此时再次编译就成功了!
四、烧录
我们可以在build文件夹中获得.hex文件。其实如果串口烧录。就没必要干其它的了。
但是这里我们打算详细讲解stlink的烧录。
stlink烧录
这里我们前面配置的openocd就起作用了。我们要用到的openocd主要有两个地方
./openocd/scripts/interface/stlink-v2.cfg
./openocd/scripts/target/stm32f1x.cfg
第一个是选择stlink的版本,但是openocd并不是只支持stlink
第二个是stm32的版本,这里是stm32f103c8t6,所有选择stm32f1x.cfg
路径需要绝对路径,但是你如果精通脚本或者其它什么,也可以去改。
openocd -f "openocd的路径/openocd/scripts/interface/stlink-v2.cfg" -f "openocd的路径/openocd/scripts/target/stm32f1x.cfg" -c init -c halt -c "program ./build/hex的程序名字.hex verify reset" -c shutdown
不能直接用,要去改!要去改!
连接stlink,确保连上了,然后执行改过的指令
写入成功! 脱离Keil,使用Makefile来编译标准库STM32工程阶段任务已经完成!
这里我执行的命令是:
openocd -f "D:/SoftWare/Environment/openocd/scripts/interface/stlink-v2.cfg" -f "D:/SoftWare/Environment/openocd/scripts/target/stm32f1x.cfg" -c init -c halt -c "program ./build/make_again.hex verify reset" -c shutdown
openocd -f "D:/SoftWare/Environment/openocd/scripts/interface/stlink-v2.cfg" -f "D:/SoftWare/Environment/openocd/scripts/target/stm32f1x.cfg" -c init -c halt -c "program ./build/make_again.hex verify reset" -c shutdown
五、配置VSCode(非必须)
老地方,launch.json与tasks.json文件,
老问题,路径要改,两个文件的路径都必须改。改成自己的openocd路径
"D:/SoftWare/Environment/openocd/scripts/interface/stlink-v2.cfg",
"D:/SoftWare/Environment/openocd/scripts/target/stm32f1x.cfg"
launch.json文件
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Microcontroller",
"type": "cortex-debug",
"request": "launch",
"cwd": "${workspaceFolder}",
"executable": "${workspaceFolder}/build/*.elf",
"servertype": "openocd",
"configFiles": [
"D:/SoftWare/Environment/openocd/scripts/interface/stlink-v2.cfg",
"D:/SoftWare/Environment/openocd/scripts/target/stm32f1x.cfg"
],
"preLaunchTask": "build",
"runToEntryPoint": "main", // 替换为 runToEntryPoint
"svdFile": "${workspaceFolder}/STM32F103xx.svd"
}
]
}
tasks.json文件
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "build",
"command": "make",
"args": [
"-C", "${workspaceFolder}",
"all"
],
"problemMatcher": [
"$gcc"
],
"group": "build"
},
{
"type": "shell",
"label": "download",
"command": "openocd",
"args": [
"-f", "D:/SoftWare/Environment/openocd/scripts/interface/stlink-v2.cfg",
"-f", "D:/SoftWare/Environment/openocd/scripts/target/stm32f1x.cfg",
"-c", "program ${workspaceFolder}/build/*.elf verify reset exit"
],
"problemMatcher": [
"$gcc"
],
"group": "build"
},
{
"type": "shell",
"label": "clean",
"command": "make",
"args": [
"-C", "${workspaceFolder}",
"clean"
],
"problemMatcher": [
"$gcc"
],
"group": "build"
},
{
"type": "shell",
"label": "rebuild",
"command": "make",
"args": [
"-C", "${workspaceFolder}",
"clean", "all"
],
"problemMatcher": [
"$gcc"
],
"group": "build"
}
]
}
ok,改完就可以了。不过这是不能在线调试的哦。
下个关于这个的文章应该就是在linux环境下实现了!
六、注意事项
回顾整个过程,你应该会对stm32CubeMX生成另外两个文件感兴趣,它们分别是
要搞清楚它们,回要库本身。这里一个是启动文件的汇编.s文件,一个是 链接脚本.ld文件。
这两个文件在使用gcc-arm-none-eabi 是必须拥有的。那么这两个文件在哪里用上去了呢?
我们重新回到makefile文件中
注意观察哦,这里都是提供了型号芯片系列的名字f103,这也是我认为可以复现在其它芯片的原因。而且这里都没有加路径,那是因为这里我在根目录处,与User、Library等文件在同一位置。如果把这两个文件放在其它地方,这里也需要修改路径。说来也是有点好玩,用HAL里面的文件驱动标准库里面的东西,也就是说,它们在本质上面的一些东西是互通的。
这里是在HAL提供的库里面找到的,但是遗憾的是标准库也有类似的东西,不过即便带有gcc的标注,标准库提供的也不能通过gcc-arm-none-eabi 来编译。这里的原因我尚且没有找到。我个人推测,应该需要对应的编译器才能使用对应的启动文件,这里gcc-arm-none-eabi可能目前没有支持到这里。