STM32F407VGT6从零建立一个标准库工程模板+VSCode或Keil5

一、前言

下载平台:STM32F407ZGT6
代码使用平台:VSCode
编译器:arm-none-aebi-gcc ---- 默认你已经安装
程序下载工具:STlink ---- 默认你拥有
批处理工具:make ---- 默认你已经安装
使用此方法可以不借助其它插件,例如:STM32EIDE。这个方法已经经过验证可以在STM32F103C8T6、STM32F103C6T6与STM32F407ZET6使用,因此个人认为此方法可以跨系列通用。
这里简要说明一下使用VSCode且不借助第三方插件的实现思路:
1.使用arm-none-aebi-gcc来编译工程
2.使用openocd通过STLink或者JLink来讲固件上传到STM32的Flash中
本文也会介绍Keil5创建标准库工程的方法。如果并不想了解VSCode创建工程,可以直接跳到Keil5创建工程部分。

二、开始新建工程

1、下载库文件

我们需要去ARM官网下载库文件,这个里面藏得有点深。不过我这里把流程也说一下吧!
我们最好把HAL库与标准库都下载下来吧,未来可以两个库一起学习!

1.1来到官网下载标准库文件

一步到位下载STM32F4的标准库:STSW-STM32065 | Product - STMicroelectronics
ST官网:https://www.st.com/
![[Pasted image 20250819195318.png]]在这里插入图片描述
![[Pasted image 20250819200007.png]]
![[Pasted image 20250819200101.png]]

![[Pasted image 20250819200200.png]]

![[Pasted image 20250819200446.png]]

![[Pasted image 20250819200522.png]]

![[Pasted image 20250819200714.png]]

1.2来到官网下载HAL库文件

一步到位下载STM32F4的HAL库文件:www.st.com
ST官网:https://www.st.com/
![[Pasted image 20250819195318.png]]

![[Pasted image 20250819200007.png]]

![[Pasted image 20250819200101.png]]

![[Pasted image 20250819201743.png]]

![[Pasted image 20250819201840.png]]

![[Pasted image 20250819202108.png]]

完成之后
![[Pasted image 20250819202543.png]]

2、开始配置VSCOde工程

我们这里配置的能在VSCode中,不借助Keil5工具的方法。当然我后面也会提供配置Keil5配置的方法。
我们先创建5个文件吧,精简工程
![[Pasted image 20250819203604.png]]

结构如下:
![[Pasted image 20250819203541.png]]
这里讲述一下各个文件的作用,然后我们开始迁移文件吧!

文件夹作用
CMSIS放置核心的文件与汇编文件
Library放置外设的头文件与源文件
Start放置启动的文件
User放置用户编写文件
ThirdPart放置第三方库,比如:FreeRtos、LVGL

2.1、配置CMSIS文件夹核心文件与汇编文件

2.1.1来到标准库

![[Pasted image 20250819210028.png]]

路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Include
全部复制到,我们的CMSIS文件夹中
![[Pasted image 20250819211125.png]]

2.1.2、来到HAL库

![[Pasted image 20250819212012.png]]

路径:
.\STM32Cube_FW_F4_V1.28.0\Drivers\CMSIS\Device\ST\STM32F4xx\Source\Templates\gcc
我们的芯片是STM32F407VGT6所以我们需要复制startup_stm32f407xx.s到CMSIS
![[Pasted image 20250819212215.png]]

3.CMSIS文件夹完成图

![[Pasted image 20250819212514.png]]

2.2、配置Library文件夹标准外设库文件

2.2.1、来到标准库

![[Pasted image 20250819210028.png]]

路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\STM32F4xx_StdPeriph_Driver
我们直接将inc与src直接复制到我们的Library文件夹中
![[Pasted image 20250819212840.png]]

不过我们需要在inc与src中把stm32f4xx_fmc.h和stm32f4xx_fmc.c文件移除,因为此文件与工程中的另外一个文件有重复定义部分,我们暂时移除。
![[Pasted image 20250819213140.png]]

2.2.2、Library文件夹完成图

![[Pasted image 20250819213439.png]]

2.3、配置Start文件夹启动文件

2.3.1、来到标准库

![[Pasted image 20250819210028.png]]

在标准库中的路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Device\ST\STM32F4xx\Include
我们把此文件夹下的文件复制到Start文件夹中
![[Pasted image 20250819213805.png]]

在标准库中的路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates
我们把此文件夹下的文件system_stm32f4xx.c复制到Start文件夹中
![[Pasted image 20250819214114.png]]

在标准库中的路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Project\STM32F4xx_StdPeriph_Templates
我们把此文件夹下的文件stm32f4xx_conf.h复制到Start文件夹中
![[Pasted image 20250819214545.png]]

2.3.2、Start启动文件夹完成图

![[Pasted image 20250819214710.png]]

2.4、配置User文件夹用户文件

2.4.1、来到标准库

![[Pasted image 20250819210028.png]]

在标准库中的路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Project\STM32F4xx_StdPeriph_Templates
我们把此文件夹下的文件mian.h、stmf4xx_it.h复制到User/inc文件夹中
我们把此文件夹下的文件mian.c、stmf4xx_it.c复制到User/src文件夹中
![[Pasted image 20250819215107.png]]

2.4.2、User用户文件夹的配置完成图

![[Pasted image 20250819215505.png]]

2.5、配置部分文件

此部分的前提是你安装过arm-none-aebi-gcc与openocd并且配置好了环境。如果不会或者其它,我会专门出一份教程来讲述arm-none-aebi-gcc与openocd。这里你只要知道,我会在这本部分的openocd的放置路径说明就行。

提前创建好Makefile文件与STM32F407VGTx_FLASH.ld文件
![[Pasted image 20250819223132.png]]

2.5.1、配置Makefile文件
1、懒得看那么多字,直接一步到位

不过还是建议,看一遍后文哦!

################################################################################################
# Generic Makefile for STM32F407VGT6 (GCC)
################################################################################################
TARGET = template
DEBUG = 1
OPT   = -Og
BUILD_DIR = build
######################################
# source
######################################
C_SOURCES = \
$(wildcard ./CMSIS/*.c) \
$(wildcard ./Library/src/*.c) \
$(wildcard ./Start/*.c) \
$(wildcard ./User/src/*.c)
  
ASM_SOURCES = \
./CMSIS/startup_stm32f407xx.s
######################################
# binaries
######################################
PREFIX  = arm-none-eabi-
CC      = $(PREFIX)gcc
CXX     = $(PREFIX)g++  # 添加 C++ 支持
AS      = $(PREFIX)gcc -x assembler-with-cpp
CP      = $(PREFIX)objcopy
SZ      = $(PREFIX)size
HEX     = $(CP) -O ihex
BIN     = $(CP) -O binary -S
######################################
# CFLAGS
######################################
CPU        = -mcpu=cortex-m4
FPU        = -mfpu=fpv4-sp-d16 -mfloat-abi=hard
MCU        = $(CPU) -mthumb $(FPU)

C_DEFS = \
-DSTM32F40_41xxx \
-DUSE_STDPERIPH_DRIVER \
-DHSE_VALUE=8000000  # 添加时钟定义

C_INCLUDES = \
-ICMSIS \
-ILibrary/inc \
-IStart \
-IUser/inc

# 添加 C++ 支持标志
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti -fno-threadsafe-statics -fno-use-cxa-atexit

ASFLAGS  = $(MCU) $(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
CFLAGS  += -MMD -MP -MF"$(@:%.o=%.d)"
# 添加链接时优化
#LTO_FLAGS = -flto -ffat-lto-objects
# 1. 关闭 LTO
LTO_FLAGS =
######################################
# LDFLAGS
######################################
LDSCRIPT = STM32F407VGTx_FLASH.ld
LIBS     = -lc -lm -lnosys -lstdc++  # 添加 C++ 库支持
LDFLAGS  = $(MCU) -specs=nano.specs -specs=nosys.specs -T$(LDSCRIPT) $(LIBS) \
           -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections \
           -Wl,--print-memory-usage  # 添加内存使用报告
# 2. 关闭 --gc-sections
#LDFLAGS  = $(MCU) -specs=nano.specs -specs=nosys.specs -T$(LDSCRIPT) $(LIBS) \
           -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref \
           -Wl,--print-memory-usage
######################################
# build rules
######################################
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
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) $(LTO_FLAGS) -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) $(LTO_FLAGS) -o $@
    $(SZ) $@
    @echo "Memory usage:"
    @arm-none-eabi-size $@
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
    $(HEX) $< $@
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
    $(BIN) $< $@
$(BUILD_DIR):
    mkdir $@
######################################
# clean
######################################
ifeq ($(OS),Windows_NT)
    RM = rmdir /S /Q
else
    RM = rm -rf
endif
clean:
    -$(RM) $(BUILD_DIR)
######################################
# download (openocd)
######################################
download:
    openocd -f "D:/SoftWare/Environment/openocd/scripts/interface/stlink-v2.cfg" 
            -f "D:/SoftWare/Environment/openocd/scripts/target/stm32f4x.cfg" \
            -c init -c halt \
            -c "program $(BUILD_DIR)/$(TARGET).elf verify reset exit"
######################################
# J-Link flash (auto generate script)
######################################
jlink:
    @echo starting...
    @echo r > temp_script.jlink
    @echo h >> temp_script.jlink
    @echo loadfile $(BUILD_DIR)/$(TARGET).hex >> temp_script.jlink
    @echo r >> temp_script.jlink
    @echo q >> temp_script.jlink
    @echo using J-Link...
    "C:/Progra~1/SEGGER/JLink_V856a/JLink.exe" -device STM32F407ZE -if SWD -speed 4000 -CommanderScript temp_script.jlink
    @del temp_script.jlink
    @echo === success ===
# 添加调试目标
debug:
    @echo r > temp_debug.jlink
    @echo h >> temp_debug.jlink
    @echo loadfile $(BUILD_DIR)/$(TARGET).elf >> temp_debug.jlink
    @echo r >> temp_debug.jlink
    @echo g >> temp_debug.jlink
    "C:/Progra~1/SEGGER/JLink_V856a/JLink.exe" -device STM32F407ZE -if SWD -speed 4000 -CommanderScript temp_debug.jlink
    @del temp_debug.jlink
-include $(wildcard $(BUILD_DIR)/*.d)
2、开始编写Makefile文件

我们在编写Makefile文件之前,一定要明确5件事
1.谁编译 ---- arm-none-aebi-xxx
2.源文件在哪
3.头文件在哪
4.汇编文件在哪
5.链接文件在哪
当然,这是看懂一个Makefile文件的前提,可以不会写,但是一定要能看懂大概在干什么。
下文会大致介绍,但是实际实现的Makefile不会那么简单,但是核心逻辑就是如此。
即便换成其它系列的芯片,核心改的地方大致也就是这几个地方!

2.1、谁编译

这里不仅添加了arm-none-eabi支持C还加入了C++支持,并且为了编译汇编文件支持。

######################################
# binaries
######################################
PREFIX  = arm-none-eabi-
CC      = $(PREFIX)gcc
CXX     = $(PREFIX)g++  # 添加 C++ 支持
AS      = $(PREFIX)gcc -x assembler-with-cpp
CP      = $(PREFIX)objcopy
SZ      = $(PREFIX)size
HEX     = $(CP) -O ihex
BIN     = $(CP) -O binary -S
2.2、源文件在哪
######################################
# source
######################################
C_SOURCES = \
$(wildcard ./CMSIS/*.c) \
$(wildcard ./Library/src/*.c) \
$(wildcard ./Start/*.c) \
$(wildcard ./User/src/*.c)
2.3、头文件在哪
C_INCLUDES = \
-ICMSIS \
-ILibrary/inc \
-IStart \
-IUser/inc
2.4、汇编文件在哪
ASM_SOURCES = \
./CMSIS/startup_stm32f407xx.s
2.5、链接文件在哪
######################################
# LDFLAGS
######################################
LDSCRIPT = STM32F407VGTx_FLASH.ld
2.6、这里提供一份现成的Makefile
################################################################################################
# Generic Makefile for STM32F407VGT6 (GCC)
################################################################################################
TARGET = template
DEBUG = 1
OPT   = -Og
BUILD_DIR = build
######################################
# source
######################################
C_SOURCES = \
$(wildcard ./CMSIS/*.c) \
$(wildcard ./Library/src/*.c) \
$(wildcard ./Start/*.c) \
$(wildcard ./User/src/*.c)
  
ASM_SOURCES = \
./CMSIS/startup_stm32f407xx.s
######################################
# binaries
######################################
PREFIX  = arm-none-eabi-
CC      = $(PREFIX)gcc
CXX     = $(PREFIX)g++  # 添加 C++ 支持
AS      = $(PREFIX)gcc -x assembler-with-cpp
CP      = $(PREFIX)objcopy
SZ      = $(PREFIX)size
HEX     = $(CP) -O ihex
BIN     = $(CP) -O binary -S
######################################
# CFLAGS
######################################
CPU        = -mcpu=cortex-m4
FPU        = -mfpu=fpv4-sp-d16 -mfloat-abi=hard
MCU        = $(CPU) -mthumb $(FPU)

C_DEFS = \
-DSTM32F40_41xxx \
-DUSE_STDPERIPH_DRIVER \
-DHSE_VALUE=8000000  # 添加时钟定义

C_INCLUDES = \
-ICMSIS \
-ILibrary/inc \
-IStart \
-IUser/inc

# 添加 C++ 支持标志
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti -fno-threadsafe-statics -fno-use-cxa-atexit

ASFLAGS  = $(MCU) $(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
CFLAGS  += -MMD -MP -MF"$(@:%.o=%.d)"
# 添加链接时优化
#LTO_FLAGS = -flto -ffat-lto-objects
# 1. 关闭 LTO
LTO_FLAGS =
######################################
# LDFLAGS
######################################
LDSCRIPT = STM32F407VGTx_FLASH.ld
LIBS     = -lc -lm -lnosys -lstdc++  # 添加 C++ 库支持
LDFLAGS  = $(MCU) -specs=nano.specs -specs=nosys.specs -T$(LDSCRIPT) $(LIBS) \
           -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections \
           -Wl,--print-memory-usage  # 添加内存使用报告
# 2. 关闭 --gc-sections
#LDFLAGS  = $(MCU) -specs=nano.specs -specs=nosys.specs -T$(LDSCRIPT) $(LIBS) \
           -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref \
           -Wl,--print-memory-usage
######################################
# build rules
######################################
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
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) $(LTO_FLAGS) -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) $(LTO_FLAGS) -o $@
    $(SZ) $@
    @echo "Memory usage:"
    @arm-none-eabi-size $@
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
    $(HEX) $< $@
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
    $(BIN) $< $@
$(BUILD_DIR):
    mkdir $@
######################################
# clean
######################################
ifeq ($(OS),Windows_NT)
    RM = rmdir /S /Q
else
    RM = rm -rf
endif
clean:
    -$(RM) $(BUILD_DIR)
######################################
# download (openocd)
######################################
download:
    openocd -f "D:/SoftWare/Environment/openocd/scripts/interface/stlink-v2.cfg" 
            -f "D:/SoftWare/Environment/openocd/scripts/target/stm32f4x.cfg" \
            -c init -c halt \
            -c "program $(BUILD_DIR)/$(TARGET).elf verify reset exit"
######################################
# J-Link flash (auto generate script)
######################################
jlink:
    @echo starting...
    @echo r > temp_script.jlink
    @echo h >> temp_script.jlink
    @echo loadfile $(BUILD_DIR)/$(TARGET).hex >> temp_script.jlink
    @echo r >> temp_script.jlink
    @echo q >> temp_script.jlink
    @echo using J-Link...
    "C:/Progra~1/SEGGER/JLink_V856a/JLink.exe" -device STM32F407ZE -if SWD -speed 4000 -CommanderScript temp_script.jlink
    @del temp_script.jlink
    @echo === success ===
# 添加调试目标
debug:
    @echo r > temp_debug.jlink
    @echo h >> temp_debug.jlink
    @echo loadfile $(BUILD_DIR)/$(TARGET).elf >> temp_debug.jlink
    @echo r >> temp_debug.jlink
    @echo g >> temp_debug.jlink
    "C:/Progra~1/SEGGER/JLink_V856a/JLink.exe" -device STM32F407ZE -if SWD -speed 4000 -CommanderScript temp_debug.jlink
    @del temp_debug.jlink
-include $(wildcard $(BUILD_DIR)/*.d)

那么肯定有人问我,这个Makefile是你写的吗?我的回答是,不完全是,这是STM32CubeMX自动生成并且我经过修改,获得而成的。这里有一些HAL库与标准库的一些兼容性问题,我把其修改成符合标准库的Makefile,这里主要的内容是LTO与链接文件的编写。有兴趣的同学可以去研究研究这里面的区别哦。
这里也提供一份AI帮忙优化的Makefile,验证过使用没问题:

################################################################################################
# Generic Makefile for STM32F407VGT6 (GCC)
################################################################################################
# 目标名称
TARGET = template
# 构建选项
DEBUG = 1
OPT   = -Og
# 构建目录
BUILD_DIR = build
# 工具链前缀
PREFIX  = arm-none-eabi-
# 工具定义
CC      = $(PREFIX)gcc
CXX     = $(PREFIX)g++
AS      = $(PREFIX)gcc -x assembler-with-cpp
CP      = $(PREFIX)objcopy
SZ      = $(PREFIX)size
HEX     = $(CP) -O ihex
BIN     = $(CP) -O binary -S
GDB     = $(PREFIX)gdb
######################################
# 源文件
######################################
# 使用更精确的文件查找方式,避免包含不需要的文件
C_SOURCES = \
$(wildcard ./CMSIS/*.c) \
$(wildcard ./Library/src/*.c) \
$(wildcard ./Start/*.c) \
$(wildcard ./User/src/*.c)

# 汇编源文件
ASM_SOURCES = ./CMSIS/startup_stm32f407xx.s

######################################
# 编译选项
######################################
# MCU 选项
CPU        = -mcpu=cortex-m4
FPU        = -mfpu=fpv4-sp-d16 -mfloat-abi=hard
MCU        = $(CPU) -mthumb $(FPU)

# C 定义
C_DEFS = \
-DSTM32F40_41xxx \
-DUSE_STDPERIPH_DRIVER \
-DHSE_VALUE=8000000 \
-D__weak="__attribute__((weak))" \
-D__packed="__attribute__((__packed__))"

# 包含路径
C_INCLUDES = \
-ICMSIS \
-ILibrary/inc \
-IStart \
-IUser/inc

# 汇编标志
ASFLAGS  = $(MCU) $(OPT) -Wall -fdata-sections -ffunction-sections

# C 标志
CFLAGS   = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

# 调试选项
ifeq ($(DEBUG), 1)
CFLAGS  += -g -gdwarf-2
endif

# 依赖生成
CFLAGS  += -MMD -MP -MF"$(@:%.o=%.d)"

# C++ 标志
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti -fno-threadsafe-statics -fno-use-cxa-atexit

# 链接时优化 (可选)
# LTO_FLAGS = -flto
######################################
# 链接选项
######################################
LDSCRIPT = STM32F407VGTx_FLASH.ld
LIBS     = -lc -lm -lnosys -lstdc++
LDFLAGS  = $(MCU) -specs=nano.specs -specs=nosys.specs -T$(LDSCRIPT) $(LIBS) \
           -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections \
           -Wl,--print-memory-usage

# 可选: 移除未使用段以减小代码大小
# LDFLAGS += -Wl,--gc-sections
######################################
# 构建目标
######################################
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin

# 对象文件列表
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))

# C 编译规则
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
    @echo "Compiling $<"
    @$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@

# 汇编编译规则
$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
    @echo "Assembling $<"
    @$(AS) -c $(CFLAGS) $< -o $@

# 链接规则
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
    @echo "Linking $@"
    @$(CC) $(OBJECTS) $(LDFLAGS) -o $@
    @echo "=== Build completed ==="
    @$(SZ) $@
    @echo "Memory usage:"
    @arm-none-eabi-size --format=sysv $@

# 生成 HEX 文件
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
    @$(HEX) $< $@
    
# 生成 BIN 文件
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
    @$(BIN) $< $@

# 创建构建目录
$(BUILD_DIR):
    @mkdir -p $@

######################################
# 清理规则
######################################
ifeq ($(OS),Windows_NT)
    RM = rmdir /S /Q
else
    RM = rm -rf
endif

clean:
    -$(RM) $(BUILD_DIR)

######################################
# 烧录和调试
######################################
# OpenOCD 烧录
OPENOCD = openocd
OPENOCD_SCRIPT_DIR = D:/SoftWare/Environment/openocd/scripts
OPENOCD_INTERFACE = stlink-v2.cfg
OPENOCD_TARGET = stm32f4x.cfg

  

download:
    $(OPENOCD) -f $(OPENOCD_SCRIPT_DIR)/interface/$(OPENOCD_INTERFACE) \
               -f $(OPENOCD_SCRIPT_DIR)/target/$(OPENOCD_TARGET) \
               -c init -c halt \
               -c "program $(BUILD_DIR)/$(TARGET).elf verify reset exit"

# J-Link 烧录
JLINK = "C:/Program Files/SEGGER/JLink_V856a/JLink.exe"
JLINK_DEVICE = STM32F407VG
JLINK_IF = SWD
JLINK_SPEED = 4000

jlink:
    @echo "Generating J-Link script..."
    @echo "r" > temp_script.jlink
    @echo "h" >> temp_script.jlink
    @echo "loadfile $(BUILD_DIR)/$(TARGET).hex" >> temp_script.jlink
    @echo "r" >> temp_script.jlink
    @echo "q" >> temp_script.jlink
    @echo "Flashing with J-Link..."
    @$(JLINK) -device $(JLINK_DEVICE) -if $(JLINK_IF) -speed $(JLINK_SPEED) -CommanderScript temp_script.jlink
    @rm -f temp_script.jlink
    @echo "=== Flash completed ==="

# 调试
debug:
    @echo "r" > temp_debug.jlink
    @echo "h" >> temp_debug.jlink
    @echo "loadfile $(BUILD_DIR)/$(TARGET).elf" >> temp_debug.jlink
    @echo "r" >> temp_debug.jlink
    @echo "g" >> temp_debug.jlink
    @$(JLINK) -device $(JLINK_DEVICE) -if $(JLINK_IF) -speed $(JLINK_SPEED) -CommanderScript temp_debug.jlink
    @rm -f temp_debug.jlink

# GDB 调试 (可选)
gdb:
    $(GDB) $(BUILD_DIR)/$(TARGET).elf -ex "target extended-remote :3333"
# 依赖包含
-include $(wildcard $(BUILD_DIR)/*.d)

# 显示帮助信息
help:
    @echo "=== Makefile 帮助 ==="
    @echo "make all       - 构建所有目标 (默认)"
    @echo "make clean     - 清理构建文件"
    @echo "make download  - 使用 OpenOCD 烧录"
    @echo "make jlink     - 使用 J-Link 烧录"
    @echo "make debug     - 使用 J-Link 调试"
    @echo "make gdb       - 启动 GDB 调试"
    @echo "make help      - 显示此帮助信息"

.PHONY: all clean download jlink debug gdb help
2.5.2.配置STM32F407VGTx_FLASH.ld(此文件自己编写或者AI生成)

这里我提供一份写好的链接文件:

/*
 * STM32F407VGT6 链接脚本
 * 硬件配置: 1024KB FLASH, 128KB SRAM, 64KB CCMRAM
 * 支持标准库、CCMRAM使用、调试信息
 */
/* 入口点定义 */
ENTRY(Reset_Handler)
/* 栈顶地址定义 */
_estack = ORIGIN(RAM) + LENGTH(RAM);
/* 堆和栈大小配置 */
_Min_Heap_Size  = 0x800;   /* 2KB堆 */
_Min_Stack_Size = 0x1000;  /* 4KB栈 */
/* 内存布局定义 */
MEMORY
{
  FLASH  (rx)  : ORIGIN = 0x08000000, LENGTH = 1024K  /* 主FLASH - STM32F407VGT6有1024KB */
  RAM    (xrw) : ORIGIN = 0x20000000, LENGTH = 128K   /* 主SRAM - STM32F407VGT6有128KB */
  CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K    /* CCMRAM - STM32F407VGT6有64KB */
}
/* 段配置 */
SECTIONS
{
  /* 1. 中断向量表 - 必须放在FLASH起始位置 */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector))                 /* 中断向量表必须保留 */
    . = ALIGN(4);
  } > FLASH
  /* 2. 代码段和只读数据 */
  .text :
  {
    . = ALIGN(4);
    *(.text)                             /* 程序代码 */
    *(.text*)                            /* 所有.text子段 */
    *(.rodata)                           /* 只读数据 */
    *(.rodata*)                          /* 所有.rodata子段 */
    *(.glue_7)                           /* ARM与Thumb指令交互 */
    *(.glue_7t)                          /* ARM与Thumb指令交互 */
    *(.gnu.linkonce.t*)
    /* 函数构造与析构 */
    KEEP(*(.init))
    KEEP(*(.fini))
    . = ALIGN(4);
    _etext = .;                          /* 代码段结束标记 */
  } > FLASH
  /* 3. ARM异常处理段 */
  .ARM.extab :
  {
    *(.ARM.extab* .gnu.linkonce.armextab.*)
  } > FLASH
  .ARM :
  {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } > FLASH
  /* 4. 初始化数组 */
  .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
  /* 5. 反初始化数组 */
  .fini_array :
  {
    PROVIDE_HIDDEN(__fini_array_start = .);
    KEEP(*(SORT(.fini_array.*)))
    KEEP(*(.fini_array*))
    PROVIDE_HIDDEN(__fini_array_end = .);
  } > FLASH
  /* 6. 已初始化数据段 (RAM中,从FLASH加载) */
  /* 使用与启动文件匹配的符号名称 */
  _sidata = LOADADDR(.data);
  .data :
  {
    . = ALIGN(4);
    _sdata = .;                          /* 数据段起始地址 - 与启动文件匹配 */
    *(.data)                             /* 数据段 */
    *(.data*)                            /* 所有.data子段 */
    *(.gnu.linkonce.d*)
    . = ALIGN(4);
    _edata = .;                          /* 数据段结束地址 - 与启动文件匹配 */
  } > RAM AT > FLASH
  /* 7. CCMRAM已初始化数据 (高速RAM) */
  _siccmram = LOADADDR(.ccmdata);
  .ccmdata :
  {
    . = ALIGN(4);
    _sccmdata = .;                       /* CCM数据起始 */
    *(.ccmdata)
    *(.ccmdata*)
    . = ALIGN(4);
    _eccmdata = .;                       /* CCM数据结束 */
  } > CCMRAM AT > FLASH
  /* 8. 未初始化数据段 (BSS) - 使用与启动文件匹配的符号名称 */
  .bss :
  {
    . = ALIGN(4);
    __bss_start__ = .;                   /* BSS起始 */
    _sbss = .;                           /* 与启动文件匹配 */
    *(.bss)
    *(.bss*)
    *(COMMON)                            /* 公共未初始化数据 */
    . = ALIGN(4);
    _ebss = .;                           /* 与启动文件匹配 */
    __bss_end__ = .;                     /* BSS结束 */
  } > RAM
  /* 9. CCMRAM未初始化数据 */
  .ccmbss (NOLOAD) :
  {
    . = ALIGN(4);
    _sccmbss = .;                        /* CCM BSS起始 */
    *(.ccmbss)
    *(.ccmbss*)
    . = ALIGN(4);
    _eccmbss = .;                        /* CCM BSS结束 */
  } > CCMRAM
  /* 10. 堆和栈 */
  ._user_heap_stack (NOLOAD) :
  {
    . = ALIGN(8);
    PROVIDE(end = .);
    PROVIDE(_end = .);
    . = . + _Min_Heap_Size;              /* 堆区域 */
    . = . + _Min_Stack_Size;             /* 栈区域 */
    . = ALIGN(8);
  } > RAM
  /* 11. 特殊段定义 */
  .ARM.attributes 0 : { *(.ARM.attributes) }  /* 架构属性 */
  .debug_info     0 : { *(.debug_info) }      /* 调试信息 */
  .debug_line     0 : { *(.debug_line) }      /* 调试行号 */
  .debug_frame    0 : { *(.debug_frame) }     /* 调试帧信息 */
  /* 12. 丢弃不需要的段 */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }
}
/* 提供给启动文件的堆/栈符号 */
PROVIDE(__heap_size = _Min_Heap_Size);
PROVIDE(__stack_size = _Min_Stack_Size);
PROVIDE(__heap_base = _ebss);
PROVIDE(__heap_limit = _ebss + _Min_Heap_Size);
2.5.3.删除部分可有可无的内容(集中在main.c与main.h文件)
1、删除main.h与main.c的部分代码

感觉有点麻烦,可以直接全部删除,区别只是想不想保留TimingDelay_Decrement函数
这里提供一下保留的mian.c代码,可以直接使用

#include "main.h"
#include <stm32f4xx_rcc.h>
#include <stm32f4xx_gpio.h>

static __IO uint32_t uwTimingDelay;
RCC_ClocksTypeDef RCC_Clocks;
static void Delay(__IO uint32_t nTime);
int main(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_GetClocksFreq(&RCC_Clocks);
  SysTick_Config(RCC_Clocks.HCLK_Frequency / 100);
  Delay(5);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  /* Infinite loop */
  while (1)
  {
    GPIO_ToggleBits(GPIOA,GPIO_Pin_0);
    //此处延时1秒
    Delay(100);
  }
}

void Delay(__IO uint32_t nTime)
{
  uwTimingDelay = nTime;
  while(uwTimingDelay != 0);
}

void TimingDelay_Decrement(void)
{
  if (uwTimingDelay != 0x00)
  {
    uwTimingDelay--;
  }
}

值得一提的是,如果全部删除main.c与main.h的内容,需要在stm32f4xx_it.c
注释函数TimingDelay_Decrement();
否则会报错哦!
![[Pasted image 20250819232836.png]]

2、官方文件system_stm32f4xx.c的一个很奇怪的while,我们只需要删除掉一个分号

![[Pasted image 20250819232156.png]]

3、在stm32f4xx.h中注释掉如下宏

如果不注释会出现解决40多个警告!
![[Pasted image 20250819231711.png]]

2.5.4、配置系统正确时钟

正确的把时钟配置,需要修改两个地方,并且要明确知道你的板子的外部高速晶振的频率,这里我的板子晶振频率是8MHz。

1、在文件stm32f4xx.h中修改HSE_VALUE的值

![[Pasted image 20250819234650.png]]

2、在文件system_stm32f4xx.c中修改PLL_M的值

![[Pasted image 20250819234909.png]]

3、简述为何需要如此设置

STM32 通常被认为是 有 4 个独立时钟源

  • HSI(高速内部时钟):内部 RC 振荡器,8 MHz,精度较低,无需外部元件。
  • HSE(高速外部时钟):可接外部晶振或时钟源,频率范围 4 MHz~16 MHz。
  • LSI(低速内部时钟):内部 RC 振荡器,约 40 kHz,用于独立看门狗或 RTC。
  • LSE(低速外部时钟):外部 32.768 kHz 晶振,主要用于 RTC

众所周知,STM32F407VGT6能支持的最大时钟是168MHz,但是这4个独立时钟源却没有一个频率能达到168MHz,那么这个168MHz的频率是哪里来的呢?
这部分的知识涉及到stm32里面的时钟的一个最为特殊的时钟源----倍频锁相环时钟(PLLCLK)。它常常被系统选择为时钟源,它可以做到将低频输入时钟通过锁相环(PLL)倍频,输出高频稳定时钟,供系统、外设或CPU使用。
这里的知识可以去stm32f4xx参考手册中的时钟树学习,这里文章篇幅问题就不深入探索,如果感兴趣,我会专门出文章讲解。你只要知道,经过上面的配置之后,你的MCU就达到168MHz的频率运行。这里贴出时钟树的图片:
![[Pasted image 20250820000253.png]]

3、开始配置Keil5工程

配置Keil5工程总共分为两个大类,创建工程目录与Keil5软件里面的配置,我们在进行的时候,务必要先去KEIL官方下载,对应的芯片包,否则在创建工程时无法选择芯片型号。
![[Pasted image 20250820003943.png]]

去官网,下载安装运行即可Arm Keil | Keil STM32F4xx_DFP
![[Pasted image 20250820004801.png]]

3.1、工程目录配置

我们先创建5个文件吧,精简工程
![[Pasted image 20250819203604.png]]

结构如下:
![[Pasted image 20250819203541.png]]

这里讲述一下各个文件的作用,然后我们开始迁移文件吧!

文件夹作用
CMSIS放置核心的文件与汇编文件
Library放置外设的头文件与源文件
Start放置启动的文件
User放置用户编写文件
ThirdPart放置第三方库,比如:FreeRtos、LVGL(这个可以先空着)
3.1.1、配置CMSIS文件夹核心文件与汇编文件
1、来到标准库

![[Pasted image 20250819210028.png]]

路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Include
全部复制到,我们的CMSIS文件夹中
![[Pasted image 20250820083024.png]]

在标准库中的路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm
我们把此文件夹下的startup_stm32f40_41xxx.s文件复制到CMSIS文件夹中
![[Pasted image 20250820082953.png]]

2、配置CMSIS文件夹核心文件与汇编文件完成图

![[Pasted image 20250820083117.png]]

3.1.2、配置Library文件夹标准外设库文件
1、来到标准库

![[Pasted image 20250819210028.png]]

在标准库路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\STM32F4xx_StdPeriph_Driver
我们把此路径下的inc与src文件夹直接复制到我们的Library文件夹中
![[Pasted image 20250820083352.png]]

2、配置Library文件夹标准外设库文件完成图

![[Pasted image 20250820083446.png]]

3.1.3、配置Start文件夹启动文件
1、来到标准库

![[Pasted image 20250819210028.png]]

在标准库路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Device\ST\STM32F4xx\Include
我们复制此路径下的stm32f4xx.h与system_stm32f4xx.h文件到Start文件夹下
![[Pasted image 20250820083756.png]]

在标准库路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates
我们复制此路径下的system_stm32f4xx.c文件到Start文件夹下
![[Pasted image 20250820083908.png]]

2、配置Start文件夹启动文件完成图

![[Pasted image 20250820084012.png]]

3.1.4、配置User文件夹用户文件
1、来到标准库

![[Pasted image 20250819210028.png]]

在标准库路径:
.\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Project\STM32F4xx_StdPeriph_Templates
复制main.c、main.h、stm32f4xx_it.c、stm32f4xx_it.h、stm32f4xx_conf.h到我们的User文件夹下,把头文件放置到inc中,源文件放置到src中
![[Pasted image 20250820084137.png]]

2、配置User文件夹用户文件完成图

![[Pasted image 20250820084512.png]]

以上工程文件配置完成!

3.2、Keil5工程配置

我们在来到Keil5之前,给我们的工程创建一个文件夹Link保存中间文件,防止工程变成混乱!等下直接Keil5的工程文件放置到Link文件夹中。
![[Pasted image 20250820084903.png]]

3.2.1、开始创建工程

创建工程
![[Pasted image 20250820085031.png]]

选择创建到Link文件夹中
![[Pasted image 20250820085131.png]]

![[Pasted image 20250820085158.png]]

选择好自己的芯片
![[Pasted image 20250820085322.png]]

3.2.2、开始配置工程源文件路径

![[Pasted image 20250820085513.png]]

创建与工程目录匹配的工程结构
![[Pasted image 20250820090427.png]]

开始加入源文件与汇编文件
![[Pasted image 20250820085843.png]]

![[Pasted image 20250820090012.png]]

![[Pasted image 20250820090340.png]]

![[Pasted image 20250820090544.png]]

配置完成
![[Pasted image 20250820090628.png]]

![[Pasted image 20250820090656.png]]

3.2.3、开始配置工程头文件路径与宏配置

配置魔术棒
![[Pasted image 20250820090820.png]]

![[Pasted image 20250820091015.png]]

![[Pasted image 20250820091426.png]]

来到C/C++加入宏

STM32F40_41xxx,USE_STDPERIPH_DRIVER

![[Pasted image 20250820091712.png]]

增加头文件路径
![[Pasted image 20250820091856.png]]

![[Pasted image 20250820091957.png]]

![[Pasted image 20250820092036.png]]

对了记得正确配置好STlink下载哦!
![[Pasted image 20250820095750.png]]

![[Pasted image 20250820095858.png]]

![[Pasted image 20250820095945.png]]

以上魔法棒全部配置完成!

3.2.4、解决报警告问题

![[Pasted image 20250820092241.png]]

1、在stm32f4xx.h中注释掉如下宏

![[Pasted image 20250819231711.png]]

2、官方文件system_stm32f4xx.c的一个很奇怪的while,我们只需要删除掉一个分号

![[Pasted image 20250819232156.png]]

报警告已解决!
![[Pasted image 20250820095032.png]]

3.2.5、删除部分可有可无的内容(集中在main.c与main.h文件)
1、删除main.h与main.c的部分代码

感觉有点麻烦,可以直接全部删除,区别只是想不想保留TimingDelay_Decrement函数
这里提供一下保留的mian.c代码,可以直接使用

#include "main.h"
#include <stm32f4xx_rcc.h>
#include <stm32f4xx_gpio.h>

static __IO uint32_t uwTimingDelay;
RCC_ClocksTypeDef RCC_Clocks;
static void Delay(__IO uint32_t nTime);
int main(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_GetClocksFreq(&RCC_Clocks);
  SysTick_Config(RCC_Clocks.HCLK_Frequency / 100);
  Delay(5);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  /* Infinite loop */
  while (1)
  {
    GPIO_ToggleBits(GPIOA,GPIO_Pin_0);
    //此处延时1秒
    Delay(100);
  }
}

void Delay(__IO uint32_t nTime)
{
  uwTimingDelay = nTime;
  while(uwTimingDelay != 0);
}

void TimingDelay_Decrement(void)
{
  if (uwTimingDelay != 0x00)
  {
    uwTimingDelay--;
  }
}

值得一提的是,如果全部删除main.c与main.h的内容,需要在stm32f4xx_it.c
注释函数TimingDelay_Decrement();
否则会报错哦!
![[Pasted image 20250819232836.png]]

3.2.6、配置系统正确时钟

正确的把时钟配置,需要修改两个地方,并且要明确知道你的板子的外部高速晶振的频率,这里我的板子晶振频率是8MHz。

1、在文件stm32f4xx.h中修改HSE_VALUE的值

![[Pasted image 20250819234650.png]]

2、在文件system_stm32f4xx.c中修改PLL_M的值

![[Pasted image 20250819234909.png]]

3、简述为何需要如此设置

STM32 通常被认为是 有 4 个独立时钟源

  • HSI(高速内部时钟):内部 RC 振荡器,8 MHz,精度较低,无需外部元件。
  • HSE(高速外部时钟):可接外部晶振或时钟源,频率范围 4 MHz~16 MHz。
  • LSI(低速内部时钟):内部 RC 振荡器,约 40 kHz,用于独立看门狗或 RTC。
  • LSE(低速外部时钟):外部 32.768 kHz 晶振,主要用于 RTC

众所周知,STM32F407VGT6能支持的最大时钟是168MHz,但是这4个独立时钟源却没有一个频率能达到168MHz,那么这个168MHz的频率是哪里来的呢?
这部分的知识涉及到stm32里面的时钟的一个最为特殊的时钟源----倍频锁相环时钟(PLLCLK)。它常常被系统选择为时钟源,它可以做到将低频输入时钟通过锁相环(PLL)倍频,输出高频稳定时钟,供系统、外设或CPU使用。
这里的知识可以去stm32f4xx参考手册中的时钟树学习,这里文章篇幅问题就不深入探索,如果感兴趣,我会专门出文章讲解。你只要知道,经过上面的配置之后,你的MCU就达到168MHz的频率运行。这里贴出时钟树的图片:
![[Pasted image 20250820000253.png]]

以上全部完成!

三、后记

如果完全按照本文完成工程配置,你可以获得一个完全脱离Keil与VSCode第三方插件的STM32VGT6工程,与一个常规的Keil5工程模板。
本文初衷也是希望能够让更多人了解STM32工程的配置过程与提供其它思路。当然本文也有很多的纰漏,比如没有说明arm-none-aebi-gcc、make、openocd等何处下载与配置,这些都是基于篇幅做的取舍,可以看我往期的文章,里面其实有介绍。
https://blog.csdn.net/weixin_65437579/article/details/145797256
https://blog.csdn.net/weixin_65437579/article/details/146296055
那么这个工程有用吗?我的回答是有用!
这是main.c文件
PA0为LED
PA1为按键

#include "main.h"
#include <stm32f4xx_rcc.h>
#include <stm32f4xx_gpio.h>
#include "stm32f4xx_exti.h"
#include "misc.h"
#include <stm32f4xx_syscfg.h>
void EXTI1_IRQHandler(void)
{
  if (EXTI_GetITStatus(EXTI_Line1) != RESET)
  {
    GPIO_ToggleBits(GPIOA, GPIO_Pin_0);
    EXTI_ClearITPendingBit(EXTI_Line1); // 清中断标志
  }
}
static void GPIO_EXTI_Config(void)
{
  GPIO_InitTypeDef gpio;
  EXTI_InitTypeDef exti;
  NVIC_InitTypeDef nvic;
  /* 1. 使能时钟 */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
  /* 2. 配置 PA1 为上拉输入 */
  gpio.GPIO_Pin = GPIO_Pin_1;
  gpio.GPIO_Mode = GPIO_Mode_IN;
  gpio.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOA, &gpio);
  /* 3. 将 PA1 映射到 EXTI1 */
  SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource1);
  /* 4. EXTI1 下降沿触发 */
  exti.EXTI_Line = EXTI_Line1;
  exti.EXTI_Mode = EXTI_Mode_Interrupt;
  exti.EXTI_Trigger = EXTI_Trigger_Falling;
  exti.EXTI_LineCmd = ENABLE;
  EXTI_Init(&exti);
  /* 5. NVIC 配置 */
  nvic.NVIC_IRQChannel = EXTI1_IRQn;
  nvic.NVIC_IRQChannelPreemptionPriority = 0x0F;
  nvic.NVIC_IRQChannelSubPriority = 0x0F;
  nvic.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&nvic);
}
static __IO uint32_t uwTimingDelay;
RCC_ClocksTypeDef RCC_Clocks;
static void Delay(__IO uint32_t nTime);

int main(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_GetClocksFreq(&RCC_Clocks);
  SysTick_Config(RCC_Clocks.HCLK_Frequency / 100);
  Delay(5);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  GPIO_EXTI_Config();
  while (1)
  {
    Delay(100);
  }
}



void Delay(__IO uint32_t nTime)
{
  uwTimingDelay = nTime;
  while(uwTimingDelay != 0);
}


void TimingDelay_Decrement(void)
{
  if (uwTimingDelay != 0x00)
  {
    uwTimingDelay--;
  }
}

实物图:
随便找的板子,无广!
![[IMG_20250820_101735.jpg]]

![[IMG_20250820_101737.jpg]]

达到程序预期效果!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值