1 必须文件
ST 提供的头文件支持 MDK-ARM, GCC, IAR 3种编译器, 下面采用 GCC
编译器 Arm GNU Toolchain Downloads – Arm Developer 或 安装包版
调试器服务端 OpenOCD
基础头文件仓库 STMicroelectronics/cmsis-core
F1头文件仓库 STMicroelectronics/cmsis-device-f1
F1头文件仓库 STMicroelectronics/stm32f1xx-hal-driver
外设 SVD 文件 stm32 svd
上述所有项目拉取/解压/解包后各自一个文件夹放在 stm32kits 中
2 工具链
新建 stm32kits/cmake/startup.bat, 内容如下
set CMAKE_TOOLCHAIN_FILE=%~dp0arm.toolchain.cmake
新建 stm32kits/cmake/arm.toolchain.cmake, 内容如下
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER_ID GNU)
set(CMAKE_CXX_COMPILER_ID GNU)
set(CMAKE_EXECUTABLE_SUFFIX_ASM ".elf")
set(CMAKE_EXECUTABLE_SUFFIX_C ".elf")
set(CMAKE_EXECUTABLE_SUFFIX_CXX ".elf")
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(STM32_KITS_DIR "${CMAKE_CURRENT_LIST_DIR}/..")
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_LIST_DIR}")
# MCU specific flags
set(TARGET_FLAGS "-mcpu=cortex-m3 ")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${TARGET_FLAGS}")
set(CMAKE_ASM_FLAGS "${CMAKE_C_FLAGS} -x assembler-with-cpp -MMD -MP")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic -fdata-sections -ffunction-sections -Wl,--no-warn-rwx-segments")
set(CMAKE_C_FLAGS_DEBUG "-O0 -g3")
set(CMAKE_C_FLAGS_RELEASE "-Os -g0")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3")
set(CMAKE_CXX_FLAGS_RELEASE "-Os -g0")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -fno-rtti -fno-exceptions -fno-threadsafe-statics")
set(CMAKE_C_LINK_FLAGS "${TARGET_FLAGS}")
set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} --specs=nano.specs")
set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,-Map=${CMAKE_PROJECT_NAME}.map -Wl,--gc-sections")
set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group -lc -lm -Wl,--end-group")
set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--print-memory-usage")
set(CMAKE_CXX_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group -lstdc++ -lsupc++ -Wl,--end-group")
新建 stm32kits/cmake/FindSTM32Driver.cmake, 内容如下
if(NOT DEFINED STM32Driver_FIND_COMPONENTS)
message(FATAL_ERROR "MCU name must be passed via COMPONENTS")
endif()
enable_language(C ASM)
foreach(mcu_name IN LISTS STM32Driver_FIND_COMPONENTS)
string(TOUPPER "${mcu_name}" MCU_NAME_UPPER)
if(NOT MCU_NAME_UPPER MATCHES "^STM32([C|F|G|H|L|M|N|U|W][A-Z0-9][A-Z0-9]?[A-Z0-9]?)([0-9][0-9])([A-Z])([0-9A-IZ])([BDG-KMPQTUVY]?)([6A7B3CD]?)$")
message(FATAL_ERROR "Invalid MCU name: ${mcu_name}")
endif()
string(TOLOWER "${CMAKE_MATCH_1}" MATCH_1_LOWER)
set(MCU_SERIES_VARIANT_LOWER "${MATCH_1_LOWER}")
set(MCU_SERIES_VARIANT "${CMAKE_MATCH_1}")
set(MCU_VARIANT "${CMAKE_MATCH_2}")
set(MCU_PIN_COUNT "${CMAKE_MATCH_3}")
string(TOLOWER "${CMAKE_MATCH_4}" CMAKE_MATCH_4_LOWER)
set(MCU_FLASH_LOWER "${CMAKE_MATCH_4_LOWER}")
set(MCU_FLASH "${CMAKE_MATCH_4}")
set(MCU_PACKAGE "${CMAKE_MATCH_5}")
set(MCU_TEMP_RANGE "${CMAKE_MATCH_6}")
set(mcu_flash_fit_ok) # 自适配头文件保证 SRAM 能完全访问, 但 Flash 可能会超出可访问区域, 此时需从模板文件做自定义
foreach(flash_fit IN ITEMS 0 1 2 3 4 5 6 7 8 9 A B Z C D E F G H I)
if(${flash_fit} STRGREATER_EQUAL ${MCU_FLASH})
string(TOLOWER "${flash_fit}" MCU_FLASH_FIT_LOWER)
if(EXISTS "${STM32_KITS_DIR}/cmsis-device-${MCU_SERIES_VARIANT_LOWER}/Source/Templates/gcc/startup_stm32${MCU_SERIES_VARIANT_LOWER}${MCU_VARIANT}x${MCU_FLASH_FIT_LOWER}.s")
set(mcu_flash_fit_ok TRUE)
set(MCU_FLASH_FIT "${flash_fit}")
break()
endif()
endif()
endforeach()
if(NOT mcu_flash_fit_ok)
message(FATAL_ERROR "Invalid MCU Flash: ${MCU_FLASH}")
endif()
if(NOT TARGET "${mcu_name}_CORE") # 直接从模板创建模板目标方便使用, 可自定义目标替换之
add_library("${mcu_name}_CORE" STATIC
"${STM32_KITS_DIR}/cmsis-device-${MCU_SERIES_VARIANT_LOWER}/Source/Templates/gcc/startup_stm32${MCU_SERIES_VARIANT_LOWER}${MCU_VARIANT}x${MCU_FLASH_FIT_LOWER}.s"
"${STM32_KITS_DIR}/cmsis-device-${MCU_SERIES_VARIANT_LOWER}/Source/Templates/system_stm32${MCU_SERIES_VARIANT_LOWER}xx.c"
)
target_include_directories("${mcu_name}_CORE" PUBLIC
"${STM32_KITS_DIR}/cmsis-core/Include"
"${STM32_KITS_DIR}/cmsis-device-${MCU_SERIES_VARIANT_LOWER}/Include"
"${STM32_KITS_DIR}/easyheader"
)
target_link_options("${mcu_name}_CORE" PUBLIC
"SHELL:-T\"${STM32_KITS_DIR}/cmsis-device-${MCU_SERIES_VARIANT_LOWER}/Source/Templates/gcc/linker/STM32${MCU_SERIES_VARIANT}${MCU_VARIANT}X${MCU_FLASH_FIT}_FLASH.ld\""
)
target_compile_definitions("${mcu_name}_CORE" PUBLIC
"STM32${MCU_SERIES_VARIANT}${MCU_VARIANT}x${MCU_FLASH_FIT}"
)
endif()
if(TARGET "${mcu_name}_HAL")
continue()
endif()
add_library("${mcu_name}_HAL" STATIC
"${STM32_KITS_DIR}/stm32${MCU_SERIES_VARIANT_LOWER}xx-hal-driver/Src/stm32${MCU_SERIES_VARIANT_LOWER}xx_hal.c"
"${STM32_KITS_DIR}/stm32${MCU_SERIES_VARIANT_LOWER}xx-hal-driver/Src/stm32${MCU_SERIES_VARIANT_LOWER}xx_hal_cortex.c"
)
target_link_libraries("${mcu_name}_HAL" PUBLIC "${mcu_name}_CORE")
target_include_directories("${mcu_name}_HAL" PUBLIC
"${STM32_KITS_DIR}/cmsis-core/Include"
"${STM32_KITS_DIR}/cmsis-device-${MCU_SERIES_VARIANT_LOWER}/Include"
"${STM32_KITS_DIR}/stm32${MCU_SERIES_VARIANT_LOWER}xx-hal-driver/Inc"
)
add_library("${mcu_name}_HAL_ALL" INTERFACE) # 编译器能自动裁剪未使用的代码
target_link_libraries("${mcu_name}_HAL_ALL" INTERFACE "${mcu_name}_HAL")
foreach(feature_lower IN ITEMS adc can cec crc dac dma eth exti flash fsmc gpio hcd i2c i2s irda iwdg mmc nand nor pccard pcd pwr rcc rtc sd sdmmc smartcard spi sram tim uart usart usb utils wwdg)
string(TOUPPER "${feature_lower}" feature_upper)
set(filename_base "${STM32_KITS_DIR}/stm32${MCU_SERIES_VARIANT_LOWER}xx-hal-driver/Src/stm32${MCU_SERIES_VARIANT_LOWER}xx")
if(EXISTS "${filename_base}_hal_${feature_lower}.c")
add_library("${mcu_name}_${feature_upper}" STATIC)
target_link_libraries("${mcu_name}_${feature_upper}" PUBLIC "${mcu_name}_HAL")
target_link_libraries("${mcu_name}_HAL_ALL" INTERFACE "${mcu_name}_${feature_upper}")
target_sources("${mcu_name}_${feature_upper}" PRIVATE "${filename_base}_hal_${feature_lower}.c")
if(EXISTS "${filename_base}_hal_${feature_lower}_ex.c")
target_sources("${mcu_name}_${feature_upper}" PRIVATE "${filename_base}_hal_${feature_lower}_ex.c")
endif()
target_compile_options("${mcu_name}_${feature_upper}" PRIVATE "-Wno-unused-parameter")
endif()
if(EXISTS "${filename_base}_ll_${feature_lower}.c")
add_library("${mcu_name}_LL_${feature_upper}" STATIC)
target_link_libraries("${mcu_name}_LL_${feature_upper}" PUBLIC "${mcu_name}_CORE")
target_include_directories("${mcu_name}_LL_${feature_upper}" PUBLIC "${STM32_KITS_DIR}/stm32${MCU_SERIES_VARIANT_LOWER}xx-hal-driver/Inc")
target_sources("${mcu_name}_LL_${feature_upper}" PRIVATE "${filename_base}_ll_${feature_lower}.c")
target_compile_definitions("${mcu_name}_LL_${feature_upper}" PUBLIC USE_FULL_LL_DRIVER register=)
target_compile_options("${mcu_name}_LL_${feature_upper}" PRIVATE "-Wno-unused-parameter")
endif()
endforeach()
target_link_libraries("${mcu_name}_UART" PUBLIC "${mcu_name}_RCC") # 打补丁
endforeach()
set(STM32Driver_FOUND TRUE)
新建 stm32kits/easyheader/stm32f1xx_hal_conf.h, 内容如下
#include <stm32f1xx_hal_conf_template.h>
至此 stm32kits 已手工创建完成
3 CLion 全局配置
设置→构建、执行、部署→工具链→"+"→系统
环境文件: stm32kits\cmake\startup.bat
C 编译器: arm-none-eabi-gcc.exe
C++ 编译器: arm-none-eabi-g++.exe
调试器: arm-none-eabi-gdb.exe
上述全局配置实际保存在 %APPDATA%\JetBrains\CLion2025.1\options\windows\toolchains.xml
中
<toolchain
name="ArmMinGW"
toolSetKind="SYSTEM_WINDOWS_TOOLSET"
customCCompilerPath="stm32kits\arm-gnu-toolchain-14.2.rel1-mingw-w64-x86_64-arm-none-eabi\bin\arm-none-eabi-gcc.exe"
customCXXCompilerPath="stm32kits\arm-gnu-toolchain-14.2.rel1-mingw-w64-x86_64-arm-none-eabi\bin\arm-none-eabi-g++.exe"
debuggerKind="CUSTOM_GDB"
customGDBPath="stm32kits\arm-gnu-toolchain-14.2.rel1-mingw-w64-x86_64-arm-none-eabi\bin\arm-none-eabi-gdb.exe"
environment="stm32kits\startup.bat" />
4 CLion 新项目配置
任意创建一个 C++ 项目后, 进行如下配置
设置→构建、执行、部署→CMake→配置文件
工具链: ArmMinGW
CMakeLists.txt 添加:
find_package(STM32Driver COMPONENTS STM32F103C8T6)
target_link_libraries(<项目名> PRIVATE STM32F103C8T6_GPIO)
源码模板
#include <stm32f1xx_hal.h>
extern "C" void SysTick_Handler() {
HAL_IncTick();
}
int main() {
HAL_Init();
while (true);
}
之后程序就能编译了
ST-Link 调试
下面配置嵌入式 GDB 调试服务端, 需安装 Embedded Development Support 插件
设置→构建、执行、部署→调试器→调试服务器→"+"→泛型→GDB 服务器
可执行文件: stm32kits/OpenOCD/bin/openocd.exe
实参: -f interface\stlink.cfg -f target\stm32f1x.cfg
下面配置调试客户端
设置→构建、执行、部署→调试器→调试服务器→调试器
调试器: stm32kits\arm-gnu-toolchain\bin\arm-none-eabi-gdb.exe
连接/实参: tcp:localhost:3333
(用于启动客户端后连接服务端)
上述配置将在 ${CMAKE_CURRENT_SOURCE_DIR}/.idea/debugServers
生成 OpenOCD.xml
, 该文件以后可直接复制使用, 其内容如下
<component name="DebugServers">
<generic-debug-target name="OpenOCD" uniqueID="c136ea77-b81f-463e-8e9b-ef6a84b56628" selected="true">
<debugger version="1">
<debugger toolchainName="ArmMinGW" />
<env />
</debugger>
<gdbserver exe="C:/program/stm32kits/OpenOCD-20240916-0.12.0/bin/openocd.exe" args="-f interface\stlink.cfg -f target\stm32f1x.cfg">
<env />
</gdbserver>
<console enabled="true" port="4444" />
<target reset-before="false" />
<connection remote-string="tcp:localhost:3333" custom-script="echo Connecting to target...\n $GDBTargetCommand$ echo Connected to target!\n" warmup-ms="500" />
</generic-debug-target>
</component>
最后再设置一下当前调试服务器, 之后可通过 ST-Link 走 SWD 接口 下载程序和调试
ST-Link 也要装好驱动, OpenOCD已内置了ST-Link的驱动, 位置在 stm32kits/OpenOCD/drivers/ST-Link/dpinst_amd64.exe
在运行被中断时, 可以用调试浮窗的内存视图直接查看 SRAM 内存
而功能寄存器需要在调试浮窗可找到外设页(需安装 Embedded Development Support 插件), 加载 svd 文件来读取
调试时会出现 info pretty-printer
执行失败的错误, 原因是 arm-none-eabi-gdb 编译时移除了 python 支持, 否则通常可以在 gdb 隔壁 ../share/gdb/python/gdb/command/pretty_printers.py
找到该命令
5 点亮 LED
以tb上常见的STM32F103C8T6最小系统板为例, 一个 LED 负极接在 PC13 的位置
下面闪烁该 LED
#include <stm32f1xx_hal.h>
#include <stm32f1xx_hal_rcc.h>
#include <stm32f1xx_hal_gpio.h>
extern "C" void SysTick_Handler() {
HAL_IncTick();
}
int main() {
HAL_Init();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin = GPIO_PIN_13;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOC, &GPIO_InitStructure);
while (true) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
HAL_Delay(100);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(100);
}
}
6 分析 elf 文件
IDA 加载 elf 文件后, Processor type 选 ARM Little-endian [ARM], Processor options→Edit ARM architecture options 选 ARMv7-M
可以看到08000000开始是向量表, 第2个是程序入口 Reset_Handler, 在0x0800021C
7 其他
项目模板
CLion 暂不支持将项目保存为项目模板 (详见 Save project as template in CLion. How? ), 只有 Idea 其他 IDE 支持, 新项目都要手动重新配置 工具链,调试服务器
串口调试
可尝试 CLion 插件 Serial Port Monitor