“ CubeMX+cmake+ninja+arm-gcc ” STM32开发环境搭建 和 “printf重定向”。基于b站up主linkyourbin。
二、工具链下载报错
建议使用mysys2做工具包管理,也就是和博主一样。这样便于管理开发环境。
问题1:工具链已安装,但是终端找不到工具链

这个问题,是你的环境变量没有添加。mysys2的环境变量除了你使用的包管理文件的bin文件夹,还有usr文件夹需要添加。检查下这四个文件夹是不是都有。
这里环境变量中的地址要一直到bin为止,不然找不到你脚本中调用的.exe程序的。
(1)arm-gcc的存放文件夹arm_genu_toolchain/bin要有,编译工具的基础。
(2)注意因为我是用MSYS2 UCRT64下载的,所以我在环境变量中配置的urct64/bin。你可以根据你的下载包管理工具,比如MYSYS2 MINGW64...
(3)usr/bin文件夹是一定要有的,你下载的工具在这个里面。
(4)mingw64/bin文件夹是存放的gcc编译器,由于交叉编译他是armgcc和gcc混编,所以这个文件夹也要有。

三、编译报错
问题1:找不到c编译器

这一部分是CubeMX生成的,在cmake文件夹下的gcc-arm-none-eabi.cmake配置文件并没有被cmake命令正确识别。

将cmake命令改为:
#编译器编译成build_ninja,也可以改为生成Makefile,CubeMX设置的cmake默认编译器为ninja。
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/gcc-arm-none-eabi.cmake -DCMAKE_BUILD_TYPE=Debug -G "Ninja"
补充全流程命令行脚本,大家可以自己参考:
#可选,删除build
rm -rf build
#建立build文件夹
mkdir build && cd build
#生成build_ninja,作用于Makefile类似
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/gcc-arm-none-eabi.cmake -DCMAKE_BUILD_TYPE=Debug -G "Ninja"
#编译成elf
ninja
#烧录
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c "program F103_Cmake_Test.elf verify reset exit"
问题2:无法生成Makefile文件,无法使用make命令
这一个问题,建议切换为Ninja。因为如下图所示,CubeMX生成的配置文件CMakePresets.json中编译器默认为Ninja。
而我们为了工程的复用,尽量不修改cmake配置文件。

四、一键下载编译脚本run.sh
这个脚本由AI生成,编译和下载每一步都会询问你。
默认下载的文件格式为hex,也可以指定为bin。
移植到别的项目只需要修改前面三行。
点击查看run.sh脚本
#!/bin/bash
#===============================================================================
# STM32 一键编译下载脚本 (Ninja + OpenOCD)
# 支持生成 .elf、.hex、.bin 文件,默认烧录 .hex
#===============================================================================
set -e # 任何命令失败立即退出
#-------------------------------------------------------------------------------
# 配置区域(根据实际情况修改)
#-------------------------------------------------------------------------------
PROJECT_NAME="F103_Cmake_Test"
BUILD_DIR="build/Debug"
# 输出文件路径
ELF_FILE="${BUILD_DIR}/${PROJECT_NAME}.elf"
HEX_FILE="${BUILD_DIR}/${PROJECT_NAME}.hex"
BIN_FILE="${BUILD_DIR}/${PROJECT_NAME}.bin"
# OpenOCD 配置
OPENOCD_CFG="-f interface/stlink.cfg -f target/stm32f1x.cfg"
# 烧录文件类型: "hex" 或 "bin"(默认 hex)
# bin文件体积更小,但是需要手动指定地址。是一种纯二进制文件,没有调试信息。
FLASH_TYPE="hex"
#-------------------------------------------------------------------------------
# 颜色输出函数
#-------------------------------------------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1"
}
#-------------------------------------------------------------------------------
# 步骤 1:检查依赖工具
#-------------------------------------------------------------------------------
check_dependencies() {
info "检查依赖工具..."
local missing_tools=()
if ! command -v cmake &> /dev/null; then
missing_tools+=("cmake")
fi
if ! command -v ninja &> /dev/null; then
missing_tools+=("ninja")
fi
if ! command -v openocd &> /dev/null; then
missing_tools+=("openocd")
fi
if ! command -v arm-none-eabi-objcopy &> /dev/null; then
missing_tools+=("arm-none-eabi-objcopy (arm-none-eabi-gcc 工具链)")
fi
if [ ${#missing_tools[@]} -ne 0 ]; then
error "缺少以下工具: ${missing_tools[*]}"
error "请先安装: pacman -S cmake ninja openocd arm-none-eabi-gcc"
exit 1
fi
success "所有依赖工具已就绪"
}
#-------------------------------------------------------------------------------
# 步骤 2:检查文件是否存在
#-------------------------------------------------------------------------------
check_files() {
local need_rebuild=0
if [ ! -f "$ELF_FILE" ]; then
need_rebuild=1
fi
if [ $need_rebuild -eq 0 ]; then
return 0
else
return 1
fi
}
#-------------------------------------------------------------------------------
# 步骤 3:从 ELF 生成 HEX 和 BIN 文件
#-------------------------------------------------------------------------------
generate_output_files() {
info "从 ELF 生成 HEX 和 BIN 文件..."
# 生成 HEX 文件
if arm-none-eabi-objcopy -O ihex "$ELF_FILE" "$HEX_FILE"; then
local hex_size=$(stat -c%s "$HEX_FILE")
success "HEX 文件生成成功: $HEX_FILE (${hex_size} bytes)"
else
error "HEX 文件生成失败!"
exit 1
fi
# 生成 BIN 文件
if arm-none-eabi-objcopy -O binary "$ELF_FILE" "$BIN_FILE"; then
local bin_size=$(stat -c%s "$BIN_FILE")
success "BIN 文件生成成功: $BIN_FILE (${bin_size} bytes)"
else
error "BIN 文件生成失败!"
exit 1
fi
}
#-------------------------------------------------------------------------------
# 步骤 4:清理并重新构建
#-------------------------------------------------------------------------------
build_project() {
info "开始构建项目..."
# 删除旧构建目录
if [ -d "build" ]; then
info "清理旧构建目录..."
rm -rf build
fi
# 使用 CMake Preset 配置
info "运行 cmake --preset=Debug..."
if ! cmake --preset=Debug; then
error "CMake 配置失败!"
exit 1
fi
# 使用 Ninja 编译
info "运行 ninja 编译..."
cd "$BUILD_DIR"
if ! ninja; then
error "编译失败!"
cd ../..
exit 1
fi
cd ../..
# 检查 ELF 是否生成
if [ ! -f "$ELF_FILE" ]; then
error "ELF 文件未生成: $ELF_FILE"
exit 1
fi
local elf_size=$(stat -c%s "$ELF_FILE")
success "构建完成!ELF 大小: ${elf_size} bytes"
# 生成 HEX 和 BIN
generate_output_files
}
#-------------------------------------------------------------------------------
# 步骤 5:烧录到 STM32
#-------------------------------------------------------------------------------
flash_device() {
# 确定要烧录的文件
local flash_file=""
if [ "$FLASH_TYPE" = "hex" ]; then
flash_file="$HEX_FILE"
elif [ "$FLASH_TYPE" = "bin" ]; then
flash_file="$BIN_FILE"
else
error "未知的 FLASH_TYPE: $FLASH_TYPE"
exit 1
fi
# 检查烧录文件是否存在
if [ ! -f "$flash_file" ]; then
error "烧录文件不存在: $flash_file"
exit 1
fi
info "开始烧录到 STM32..."
info "烧录文件类型: $FLASH_TYPE"
info "烧录文件路径: $flash_file"
# 检查 ST-Link 连接
info "检查 ST-Link 连接..."
if ! openocd $OPENOCD_CFG -c "init" -c "exit" 2>/dev/null; then
warning "ST-Link 连接可能有问题,但仍尝试烧录..."
fi
# 烧录文件
info "烧录 $flash_file ..."
# 如果是 BIN 文件,需要指定加载地址(STM32F103 通常是 0x08000000)
if [ "$FLASH_TYPE" = "bin" ]; then
if openocd $OPENOCD_CFG -c "program $flash_file 0x08000000 verify reset exit"; then
success "烧录成功!"
else
error "烧录失败!"
error "可能原因:"
error " 1. ST-Link 未连接或驱动未安装"
error " 2. 接线错误(SWCLK/SWDIO/GND)"
error " 3. 目标芯片被锁死"
exit 1
fi
else
# HEX 文件自带加载地址信息
if openocd $OPENOCD_CFG -c "program $flash_file verify reset exit"; then
success "烧录成功!"
else
error "烧录失败!"
error "可能原因:"
error " 1. ST-Link 未连接或驱动未安装"
error " 2. 接线错误(SWCLK/SWDIO/GND)"
error " 3. 目标芯片被锁死"
exit 1
fi
fi
}
#-------------------------------------------------------------------------------
# 步骤 6:显示固件信息
#-------------------------------------------------------------------------------
show_firmware_info() {
info "固件信息:"
echo "----------------------------------------"
echo "ELF 文件:"
arm-none-eabi-size "$ELF_FILE"
echo ""
echo "HEX 文件大小: $(stat -c%s "$HEX_FILE") bytes"
echo "BIN 文件大小: $(stat -c%s "$BIN_FILE") bytes"
echo "----------------------------------------"
echo "ELF 段信息:"
arm-none-eabi-objdump -h "$ELF_FILE" | head -20
echo "----------------------------------------"
}
#-------------------------------------------------------------------------------
# 步骤 7:显示帮助信息
#-------------------------------------------------------------------------------
show_help() {
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " -h, --help 显示此帮助信息"
echo " -b, --bin 使用 BIN 文件进行烧录(默认使用 HEX)"
echo " -r, --rebuild 强制重新编译"
echo " -f, --flash 只烧录,不编译"
echo " -s, --size 只显示固件大小,不编译不烧录"
echo ""
echo "示例:"
echo " $0 编译并烧录 HEX 文件"
echo " $0 -b 编译并烧录 BIN 文件"
echo " $0 -r 强制重新编译并烧录"
echo " $0 -f 只烧录现有 HEX 文件"
echo " $0 -s 只显示固件大小"
}
#-------------------------------------------------------------------------------
# 解析命令行参数
#-------------------------------------------------------------------------------
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-b|--bin)
FLASH_TYPE="bin"
shift
;;
-r|--rebuild)
REBUILD_FORCE=1
shift
;;
-f|--flash)
FLASH_ONLY=1
shift
;;
-s|--size)
SIZE_ONLY=1
shift
;;
*)
error "未知选项: $1"
show_help
exit 1
;;
esac
done
}
#-------------------------------------------------------------------------------
# 主流程
#-------------------------------------------------------------------------------
main() {
parse_args "$@"
echo ""
echo "=============================================="
echo " STM32 一键编译下载工具 (增强版)"
echo " Project: $PROJECT_NAME"
echo " Date: $(date)"
echo " 烧录类型: $FLASH_TYPE"
echo "=============================================="
echo ""
# 检查依赖
check_dependencies
# 只显示大小
if [ "$SIZE_ONLY" = "1" ]; then
if check_files; then
generate_output_files
show_firmware_info
success "✅ 完成"
else
error "ELF 文件不存在,请先编译"
exit 1
fi
exit 0
fi
# 只烧录
if [ "$FLASH_ONLY" = "1" ]; then
if check_files; then
generate_output_files
flash_device
success "🎉 烧录完成!"
else
error "ELF 文件不存在,无法生成烧录文件"
exit 1
fi
exit 0
fi
# 检查是否需要重建
if check_files && [ "$REBUILD_FORCE" != "1" ]; then
warning "发现已有 ELF 文件"
read -p "是否重新编译?(y/N): " rebuild_choice
if [[ $rebuild_choice =~ ^[Yy]$ ]]; then
build_project
else
info "跳过编译,使用现有 ELF 文件"
generate_output_files
fi
else
build_project
fi
# 显示固件信息
show_firmware_info
# 确认烧录
echo ""
read -p "是否烧录到 STM32?(y/N): " flash_choice
if [[ $flash_choice =~ ^[Yy]$ ]]; then
flash_device
success "🎉 一键编译下载完成!"
else
info "跳过烧录"
success "✅ 编译完成"
echo " ELF: $ELF_FILE"
echo " HEX: $HEX_FILE"
echo " BIN: $BIN_FILE"
fi
}
#-------------------------------------------------------------------------------
# 运行主函数
#-------------------------------------------------------------------------------
main "$@"
使用方法:将run.sh拷贝到你的工程根目录。
命令行执行 ./run.sh
五、Printf重定向报错
重定向程序:
int _write(int fd, char *pBuffer, int size)
{
// 避免串口发送过程中的死循环,加入超时机制
const uint32_t timeout = 100000; // 超时周期,假设最多等待 100,000 次
uint32_t timeout_counter = 0;
for (int i = 0; i < size; i++)
{
// 等待直到串口的数据寄存器空
while ((USART1->SR & 0x40) == 0)
{
timeout_counter++;
if (timeout_counter >= timeout)
{
// 如果超过超时限制,可以跳出并返回错误,或者做其他处理
return -1; // 返回错误
}
}
USART1->DR = (uint8_t) pBuffer[i]; // 写入数据寄存器,发送字符
}
return size; // 返回成功发送的字符数量
}
/* USER CODE END 1 */
在Up主的基础上,改动了添加的Cmake配置文件。内容依旧加到最后。
## 有中文注释的部分,是cubemx不会自动生成的,需要手动添加
set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -u _printf_float") # 支持printf函数打印浮点数
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lm") # 链接数学库libm。这一部分与博主有些不同,不是使用的C_Link。
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections,--no-warn-rwx-segments") # 取消 rwx 段的警告
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-parameter") # 忽略C代码中未使用参数的警告
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter") # 忽略C++代码中未使用参数的警告
补充:
以下由元宝AI生成



六、番外:在keil中实现printf重定向
依旧在usart.c增加代码:
#ifdef _GNUC_
#define PUTCHAR_PROTOTYPE int _io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch,FILE *f)
#endif
PUTCHAR_PROTOTYPE{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,sizeof(ch),0xFFF);
return ch;
}
勾选MicroLib,不然程序会卡死。


浙公网安备 33010602011771号