ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SCons构建STM32F103嵌入式工程:从Keil迁移的声明式实践

SCons构建STM32F103嵌入式工程:从Keil迁移的声明式实践 1. 为什么是 SCons而不是 Keil、CubeMX 或 CMake“用 SCons 编译 STM32F103 工程”——这句话乍看像一句操作指令实则藏着一个嵌入式开发者在项目规模化、团队协作化、持续集成化过程中必然遭遇的转折点。我第一次在真实产线项目里把 Keil MDK 的 .uvprojx 文件彻底删掉改用纯文本 SConstruct 脚本构建整个 F103 电机控制固件时团队里两位十年 Keil 老手盯着终端里scons -Q输出的绿色[CC]和[LD]行沉默了三分钟。不是因为看不懂而是突然意识到原来我们过去十年反复点击“Rebuild”所掩盖的是编译逻辑的黑盒化、依赖关系的手动维护、跨平台移植的脆弱性以及——最要命的——当新同事接手项目时连“为什么 main.c 必须放在 Src 目录下”都得靠口耳相传。SCons 不是另一个 IDE它是一套声明式构建系统核心哲学就一条源码即构建逻辑。你写下的每一行 Python 脚本都在显式定义“哪些文件参与编译”、“用什么工具链”、“头文件路径怎么展开”、“链接脚本如何注入”、“生成的 .hex/.bin 如何校验”。这和 Keil 的图形化配置点选 Device、勾选 Use MicroLIB、手动拖拽 Group本质不同也和 CubeMX 生成的 Makefile一堆自动生成、不敢轻易修改的 shell 片段形成鲜明对比甚至和 CMake语法抽象层厚、新手易陷在 target_link_libraries 和 include_directories 的嵌套里相比SCons 的 Python 基底让逻辑更直白——比如你要给所有 .c 文件加-O2 -mthumb -mcpucortex-m3直接写env.Append(CCFLAGS[-O2, -mthumb, -mcpucortex-m3])没有宏、没有变量作用域陷阱就是 Python 列表追加。为什么偏偏是 STM32F103这个芯片太典型了ARM Cortex-M3 内核、标准 CMSIS 库结构、ST 提供的固件库非 HAL、常见外设USART1/3、TIM2/3、ADC1、DAC、SPI1全都有但官方工具链支持却卡在“够用但不开放”层面。Keil 编译慢本质是每次 rebuild 都扫描整个工程目录做增量判断而 SCons 的依赖数据库.sconsign.dblite基于文件内容哈希改一行代码只重编它和所有依赖它的 .o连带重链接——实测某 3 万行电机 PIDFOC 固件Keil 全量编译 48 秒SCons 增量编译稳定在 3.2 秒内。至于“stm32f103 串口1和串口3使用差异”这类问题SCons 更是天然解法你在SConscript里为 USART1 定义一组宏-DUSE_USART1为 USART3 单独建个子环境env_usart3 env.Clone()再分别指定不同初始化函数入口编译产物自动隔离根本不会出现“改了串口3配置串口1莫名失效”的玄学问题。它适合谁不是初学者——刚学 GPIO 点灯的新人用 Keil 新建工程点五下鼠标就能跑起来这时候硬推 SCons 是给自己找麻烦。它适合已经用过 Keil/CubeMX、开始写多模块驱动、需要做自动化测试、准备上 CI/CD 流水线、或团队里有 Linux/Python 背景成员的进阶用户。尤其当你遇到“keil5 编译很慢”“vs2010 编译报 error msb6006 cmd.exe 已退出代码为3”这类 Windows 下构建环境混乱导致的错误时SCons 的跨平台一致性同一份脚本在 Windows WSL、Ubuntu Docker、macOS 上运行结果完全一致就成了救命稻草。我经手的三个量产项目从 F103 到 L432KC再到 G071RB构建脚本主体逻辑没变过只是替换了芯片定义、启动文件、链接脚本——这才是工程可维护性的起点。2. SCons 构建体系的核心骨架与关键设计决策SCons 构建 STM32F103 工程绝不是简单替换 Makefile。它是一套分层明确、职责清晰的 Python 脚本体系核心由三类文件构成顶层SConstruct总控、模块级SConscript功能划分、工具链封装tools/可复用能力。这种结构不是为了炫技而是为了解决嵌入式开发中三个致命痛点芯片差异、外设组合爆炸、调试与发布双模式切换。2.1 顶层 SConstruct全局策略中枢SConstruct是整个构建系统的“宪法”它不负责具体编译只做三件事加载工具链、定义全局环境、调度子构建脚本。我坚持把它控制在 80 行以内超过就拆。典型结构如下# SConstruct import os import sys # 1. 工具链路径统一管理避免硬编码 TOOLCHAIN_ROOT os.environ.get(ARMGCC_PATH, /opt/gcc-arm-none-eabi) if not os.path.exists(TOOLCHAIN_ROOT): raise EnvironmentError(fARM GCC toolchain not found at {TOOLCHAIN_ROOT}) # 2. 创建基础构建环境关键 env Environment( tools[gcc, g, ar, as, ld, objcopy, objdump], toolpath[tools], # 指向自定义工具定义 ) # 3. 全局编译选项所有模块继承 env.Append( CCFLAGS[ -mcpucortex-m3, -mthumb, -mfpuvfp, -mfloat-abisoft, -O2, -Wall, -Wextra, -ffunction-sections, -fdata-sections, -stdgnu99, -DSTM32F103xB, # 核心芯片定义 -DUSE_FULL_LL_DRIVER, # 使用标准库而非 HAL ], CPPPATH[ #Inc, # 项目头文件根目录 #Libraries/CMSIS/Include, #Libraries/STM32F1xx_HAL_Driver/Inc, #Libraries/STM32F1xx_HAL_Driver/Inc/Legacy, ], LIBPATH[#Libraries/STM32F1xx_HAL_Driver/Lib], LIBS[stm32f1xx_hal], ) # 4. 关键按芯片型号动态加载链接脚本 chip_variant ARGUMENTS.get(chip, stm32f103c8t6) link_script fLinker/{chip_variant}.ld if not os.path.exists(link_script): raise ValueError(fLinker script {link_script} not found) env.Replace(LINKFLAGS[f-T{link_script}, --specsnosys.specs]) # 5. 调度子构建这才是业务逻辑所在 SConscript(Src/SConscript, exportsenv) SConscript(Drivers/SConscript, exportsenv) SConscript(Middlewares/SConscript, exportsenv)这里每个设计都有深意toolpath[tools]让你能覆盖 SCons 默认的 gcc 工具定义比如强制arm-none-eabi-gcc路径CPPPATH用#开头表示相对于项目根目录避免相对路径错误LINKFLAGS中的--specsnosys.specs是嵌入式关键——它剥离了 libc 中对系统调用如_sbrk,_write的依赖否则链接会失败而ARGUMENTS.get(chip, stm32f103c8t6)支持命令行传参scons chipstm32f103zet6瞬间切换不同 Flash/RAM 规格的板子比 Keil 里手动改 Device 设置快十倍。2.2 模块化 SConscript应对外设组合爆炸STM32F103 的外设不是孤立的。USART1 依赖 RCC 使能、GPIOA 初始化TIM2 捕获需要 AFIO 重映射配置DAC 输出正弦波得配 TIM6 触发。如果所有代码堆在main.c里改一个外设就得通读三千行。SCons 的SConscript就是为解耦而生。以Drivers/SConscript为例# Drivers/SConscript Import(env) # 创建独立环境避免污染全局 driver_env env.Clone() # 为特定外设添加专属编译选项 usart1_env driver_env.Clone() usart1_env.Append(CPPDEFINES[USE_USART1]) usart1_env.Append(CPPPATH[#Drivers/USART1]) tim2_env driver_env.Clone() tim2_env.Append(CPPDEFINES[USE_TIM2_CAPTURE]) tim2_env.Append(CPPPATH[#Drivers/TIM2]) # 分别编译生成独立 .o usart1_obj usart1_env.Object(#Drivers/USART1/usart1_driver.c) tim2_obj tim2_env.Object(#Drivers/TIM2/tim2_capture.c) # 返回对象供主环境链接 Return(usart1_obj tim2_obj)看到没usart1_env和tim2_env是两个克隆环境它们的CPPDEFINES互不影响。当你在main.c里写#ifdef USE_USART1时只有usart1_obj被编译进去同理#ifdef USE_TIM2_CAPTURE只影响tim2_obj。这直接解决了“stm32f103 多路捕获”场景下不同通道CH1/CH2/CH3配置冲突的问题——你完全可以为 CH1 写tim2_ch1.c为 CH2 写tim2_ch2.c各自在SConscript里定义不同宏编译时按需链接零耦合。而 Keil 里实现同样效果得建多个 Target每个 Target 复制一整套配置维护成本指数级上升。2.3 自定义 Tools掌控底层构建细节SCons 默认的gcc工具链对嵌入式不够友好。比如objcopy生成.bin时默认从0x08000000开始但 F103 的实际 Flash 起始地址是0x08000000而某些 Bootloader 要求.bin从0x08002000开始跳过 8KB 用户区。这时就得自己写tools/arm_gcc.py# tools/arm_gcc.py from SCons.Tool import gcc from SCons.Builder import Builder def generate(env, **kw): # 继承默认 gcc 工具 gcc.generate(env) # 替换 objcopy 构建器支持 offset 参数 env[OBJCOPY] arm-none-eabi-objcopy env[OBJCOPYFLAGS] [-O, binary] env[OBJCOPYCOM] $OBJCOPY $OBJCOPYFLAGS $SOURCE $TARGET # 新增 bin_with_offset 构建器 env.Append(BUILDERS{ BinWithOffset: Builder( action$OBJCOPY $OBJCOPYFLAGS --adjust-section-vma .text$OFFSET $SOURCE $TARGET, suffix.bin, src_suffix.elf ) }) def exists(env): return True然后在SConstruct里调用env.Tool(arm_gcc) # 加载自定义工具 firmware_bin env.BinWithOffset(firmware.bin, firmware_elf, OFFSET0x08002000)这样生成的firmware.bin就自动偏移了 8KB烧录到 Bootloader 后能精准跳转。这种细粒度控制是 Keil 的“Output - Create HEX File”选项框永远做不到的。3. 实操全流程从零搭建可运行的 F103 SCons 工程现在我们动手搭一个最小可行工程。目标编译出能在 STM32F103C8T6Blue Pill 板上跑的 LED 闪烁固件支持scons编译、scons flash烧录、scons clean清理。全程不依赖 Keil、CubeMX 或任何 GUI 工具所有文件用 VS Code 手动创建。3.1 项目目录结构与文件准备先建立清晰目录树这是 SCons 可维护性的基石stm32f103-scons/ ├── SConstruct # 总控脚本 ├── SConscript # 可选根目录构建入口 ├── tools/ │ └── arm_gcc.py # 自定义工具链 ├── Inc/ │ ├── main.h │ ├── stm32f103xb.h # CMSIS 标准头文件 │ └── stm32f1xx_hal_conf.h # HAL 库配置 ├── Src/ │ ├── main.c # 主程序 │ ├── system_stm32f1xx.c # 系统时钟初始化 │ └── startup_stm32f103xb.s # 启动文件汇编 ├── Libraries/ │ ├── CMSIS/ # 下载自 ST 官网 │ └── STM32F1xx_HAL_Driver/ # 同上 ├── Linker/ │ └── stm32f103c8t6.ld # 链接脚本关键 └── scripts/ └── openocd_flash.tcl # OpenOCD 烧录脚本提示startup_stm32f103xb.s和system_stm32f1xx.c必须从 ST 官方固件库STM32CubeF1中提取不能自己写。CMSIS 和 HAL Driver 库建议用 1.8.4 版本兼容性最好。链接脚本stm32f103c8t6.ld是核心它定义了 Flash128KB、RAM20KB的起始地址和内存布局。我提供一个精简版/* Linker Script for STM32F103C8T6 */ MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 128K RAM (rwx) : ORIGIN 0x20000000, LENGTH 20K } SECTIONS { .isr_vector : { *(.isr_vector) } FLASH .text : { *(.text) *(.rodata) } FLASH .data : { *(.data) } RAM AT FLASH .bss : { *(.bss) *(COMMON) } RAM }3.2 编写核心源码让 LED 闪起来Src/main.c是灵魂必须包含标准 HAL 初始化流程#include main.h #include stm32f1xx_hal.h // 全局 HAL 句柄 UART_HandleTypeDef huart1; TIM_HandleTypeDef htim2; void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_USART1_UART_Init(void); static void MX_TIM2_Init(void); int main(void) { HAL_Init(); // HAL 库初始化 SystemClock_Config(); // 系统时钟配置72MHz MX_GPIO_Init(); // PA0 输出接 LED MX_USART1_UART_Init(); // 串口1 初始化 MX_TIM2_Init(); // TIM2 初始化用于延时 while (1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); // 翻转 PA0 HAL_Delay(500); // 500ms 延时 } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; __HAL_RCC_HSE_CONFIG(RCC_HSE_ON); while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) RESET) {} RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL RCC_PLL_MUL9; // HSE*9 72MHz if (HAL_RCC_OscConfig(RCC_OscInitStruct) ! HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider RCC_HCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_2) ! HAL_OK) { Error_Handler(); } } static void MX_GPIO_Init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); } static void MX_USART1_UART_Init(void) { huart1.Instance USART1; huart1.Init.BaudRate 115200; huart1.Init.WordLength UART_WORDLENGTH_8B; huart1.Init.StopBits UART_STOPBITS_1; huart1.Init.Parity UART_PARITY_NONE; huart1.Init.Mode UART_MODE_TX_RX; huart1.Init.HwFlowCtl UART_HWCONTROL_NONE; huart1.Init.OverSampling UART_OVERSAMPLING_16; if (HAL_UART_Init(huart1) ! HAL_OK) { Error_Handler(); } } static void MX_TIM2_Init(void) { htim2.Instance TIM2; htim2.Init.Prescaler 7199; // 72MHz / (71991) 10kHz htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 9999; // 10kHz / 10000 1Hz htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; if (HAL_TIM_Base_Init(htim2) ! HAL_OK) { Error_Handler(); } } void Error_Handler(void) { while(1) {} }注意HAL_Delay()依赖 SysTick 中断所以system_stm32f1xx.c里的SystemCoreClock必须正确设置为 72000000且HAL_Init()会自动配置 SysTick。这段代码在 Keil 里能跑在 SCons 里一样能跑——因为 SCons 只管编译链接不管代码逻辑。3.3 SConstruct 与 SConscript 的完整实现SConstruct已精简专注可运行import os import sys # 工具链路径根据你的安装调整 ARMGCC_PATH /opt/gcc-arm-none-eabi-10-2020-q4-major/bin if not os.path.exists(ARMGCC_PATH): ARMGCC_PATH C:/Program Files (x86)/GNU Arm Embedded Toolchain/10 2020-q4-major/bin env Environment( tools[gcc, g, ar, as, ld, objcopy, objdump], toolpath[tools], ENV{PATH: ARMGCC_PATH} ) # 全局编译选项 env.Append( CCFLAGS[ -mcpucortex-m3, -mthumb, -O2, -Wall, -Wextra, -ffunction-sections, -fdata-sections, -stdgnu99, -DSTM32F103xB, -DUSE_FULL_LL_DRIVER, -IInc, -ILibraries/CMSIS/Include, -ILibraries/STM32F1xx_HAL_Driver/Inc ], LINKFLAGS[ -TLinker/stm32f103c8t6.ld, --specsnosys.specs, -Wl,--gc-sections ], LIBS[stm32f1xx_hal], LIBPATH[Libraries/STM32F1xx_HAL_Driver/Lib] ) # 定义构建目标 src_files Glob(Src/*.c) Glob(Src/*.s) obj_files env.Object(src_files) # 链接生成 .elf firmware_elf env.Program(firmware.elf, obj_files) # 生成 .hex 和 .bin firmware_hex env.Hex(firmware.hex, firmware_elf) firmware_bin env.Bin(firmware.bin, firmware_elf) # 自定义烧录命令依赖 OpenOCD env.Command(flash, firmware_bin, openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c program $SOURCE verify reset exit) # 清理命令 env.Clean(flash, [firmware.elf, firmware.hex, firmware.bin]) # 默认目标 Default(firmware_hex)tools/arm_gcc.py简化版仅处理 hex/bin 生成from SCons.Tool import gcc from SCons.Builder import Builder def generate(env, **kw): gcc.generate(env) env[HEXCOM] $OBJCOPY -O ihex $SOURCE $TARGET env[BINCOM] $OBJCOPY -O binary $SOURCE $TARGET env.Append(BUILDERS{ Hex: Builder(action$HEXCOM, suffix.hex, src_suffix.elf), Bin: Builder(action$BINCOM, suffix.bin, src_suffix.elf) }) def exists(env): return True3.4 编译、烧录与验证一切就绪打开终端Windows 推荐 Git Bash 或 WSLLinux/macOS 直接 terminal# 第一步安装 SConsPython 3.7 pip install scons # 第二步确保 ARM GCC 工具链在 PATH 中 export PATH/opt/gcc-arm-none-eabi-10-2020-q4-major/bin:$PATH # Linux/macOS # 或 Windows在系统环境变量中添加路径 # 第三步编译-Q 参数静默输出只显示关键步骤 scons -Q # 成功输出应类似 # [CC] Src/main.c # [CC] Src/system_stm32f1xx.c # [AS] Src/startup_stm32f103xb.s # [AR] Libraries/STM32F1xx_HAL_Driver/Lib/libstm32f1xx_hal.a # [LD] firmware.elf # [HEX] firmware.hex # [BIN] firmware.bin # 第四步烧录需连接 ST-Link/V2 scons flash # OpenOCD 输出应显示 # Info : STLINK V2J37S7 (API v2) VID:PID 0483:3748 # Info : Target voltage: 3.221442 # Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints # Info : starting download # Info : flash programming... # verified 12345 bytes in 0.234s (51.52 KiB/s) # Info : Resetting target # Info : halted: PC: 0x080001a0此时 Blue Pill 板上的 LED通常接 PA0应以 1Hz 频率稳定闪烁。用串口助手如 XCOM连接 USART1PA9/PA10波特率 115200能看到 HAL 库初始化日志如果加了printf。整个过程没有点击、没有弹窗、没有许可证提示——只有命令行和结果。4. 常见问题排查与实战避坑指南用 SCons 编译 STM32F103最大的陷阱不是语法错误而是环境隐含假设被打破。我踩过的坑基本都围绕“路径”“符号”“链接”三大雷区。下面按发生频率排序附真实日志和解决方案。4.1 “undefined reference to__libc_init_array” —— libc 与裸机的战争现象scons编译通过链接时报错arm-none-eabi-gcc: error: unrecognized command line option -nostdlib /usr/lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld: firmware.elf: in function _start: (.text0x10): undefined reference to __libc_init_array collect2: error: ld returned 1 exit status原因SCons 默认链接时会尝试链接标准 C 库libc但嵌入式裸机程序不需要它。-nostdlib参数没生效或者--specsnosys.specs被忽略。排查步骤运行scons -n -Q-n表示只打印命令不执行查看实际链接命令arm-none-eabi-gcc -o firmware.elf ... -TLinker/stm32f103c8t6.ld --specsnosys.specs ...如果命令里没有--specsnosys.specs检查SConstruct中LINKFLAGS是否被后续代码覆盖。如果存在但依然报错说明nosys.specs文件缺失。该文件位于 GCC 工具链的arm-none-eabi/lib/gcc/arm-none-eabi/version/目录下。终极方案在SConstruct中显式指定 specs 路径gcc_path /opt/gcc-arm-none-eabi-10-2020-q4-major env.Replace(LINKFLAGS[ f-TLinker/stm32f103c8t6.ld, f--specs{gcc_path}/arm-none-eabi/lib/gcc/arm-none-eabi/10.2.1/specs, -Wl,--gc-sections ])实操心得我曾因 Ubuntu 系统自带的gcc-arm-none-eabi包版本太老4.9nosys.specs不存在死磕三天。最终卸载系统包从 ARM 官网下载gcc-arm-none-eabi-10-2020-q4-major解压使用问题消失。记住嵌入式工具链宁可手动管理不要信包管理器。4.2 “fatal error: stm32f1xx_hal.h: No such file or directory” —— 头文件路径的幻影现象编译main.c时找不到 HAL 头文件但ls Libraries/STM32F1xx_HAL_Driver/Inc/确实存在stm32f1xx_hal.h。原因CPPPATH路径错误。常见错误有三用了相对路径./Libraries/...而 SCons 的#表示项目根目录CPPPATH中路径拼写错误比如STM32F1xx_HAL_Driver/Incl少了个 uGlob(Src/*.c)匹配到了Src/subdir/main.c但CPPPATH没包含#Src/subdir。快速验证法在main.c顶部加一行#pragma message HAL include path test #include stm32f1xx_hal.h如果编译时看到HAL include path test说明头文件找到了否则路径必错。解决方案统一用#前缀并用os.path.join拼接防 Windows 路径斜杠问题import os env.Append(CPPPATH[ #Inc, os.path.join(#Libraries, CMSIS, Include), os.path.join(#Libraries, STM32F1xx_HAL_Driver, Inc), ])4.3 “section.isr_vectorwill not fit in regionFLASH” —— 链接脚本的容量陷阱现象链接时报错提示.isr_vector段放不下arm-none-eabi-gcc: error: ld returned 1 exit status /opt/gcc-arm-none-eabi-10-2020-q4-major/arm-none-eabi/bin/ld: firmware.elf section .isr_vector will not fit in region FLASH原因.isr_vector是中断向量表固定占 256 字节64 个 4 字节指针。但链接脚本里FLASH区域长度设小了或者startup_stm32f103xb.s里向量表定义超长。排查检查stm32f103c8t6.ld中LENGTH 128K是否正确C8T6 是 128KB Flash用arm-none-eabi-objdump -h firmware.elf查看各段大小Sections: Idx Name Size VMA LMA File off Algn 0 .isr_vector 00000100 08000000 08000000 00010000 2**0 1 .text 00002a3c 08000100 08000100 00010100 2**0如果.isr_vector的VMA虚拟地址不是0x08000000说明链接脚本没生效。修复确保链接脚本第一行是MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 128K }且SECTIONS中.isr_vector : { *(.isr_vector) } FLASH在最前面。4.4 “scons: *** [firmware.elf] SourceSrc/startup_stm32f103xb.snot found” —— 汇编文件的后缀玄学现象SCons 报错找不到启动文件但文件明明存在且ls Src/能看到。原因SCons 默认不识别.s后缀为汇编源文件。它只认.S大写或.asm。解决方案两种方法任选其一把startup_stm32f103xb.s重命名为startup_stm32f103xb.SLinux/macOS 区分大小写Windows 不区分但 SCons 统一按 Unix 规则在SConstruct中显式告诉 SCons.s是汇编env.Append(ASFLAGS[-x, assembler-with-cpp]) # 允许 .s 文件预处理 env[ASCOM] $CC $ASFLAGS -c -o $TARGET $SOURCE # 用 gcc 处理 .s4.5 “OpenOCD: JTAG scan chain interrogation failed” —— 烧录失败的物理层真相现象scons flash执行后OpenOCD 报错无法连接芯片Error: JTAG scan chain interrogation failed: all zeroes Error: Check JTAG interface, cables and power这不是 SCons 的问题但常被误认为构建失败。真实原因有ST-Link 驱动未安装Windows 需装 STSW-LINK009板子未上电Blue Pill 的 3.3V 灯没亮SWD 引脚SWCLK/SWDIO接触不良检查杜邦线是否松动BOOT0 引脚被拉高F103 进入系统存储器启动模式无法烧录。速查清单用万用表测BOOT0对地电压应为 0V低电平拔掉 USB 线短接BOOT0和3.3V再插 USBLED 应快闪进入 Bootloader 模式此时BOOT01正常工作时BOOT00换一根质量好的 USB 线劣质线供电不足ST-Link 无法识别。注意事项我见过最诡异的一次是 USB 扩展坞供电不足导致 ST-Link 电压跌到 2.8VOpenOCD 一直报错。直接插主板 USB 口秒好。硬件问题永远先查供电和物理连接。5. 进阶技巧让 SCons 工程真正工业级可用做到“能编译、能烧录”只是 SCons 的入门。真正的价值在于它能把嵌入式开发从“手工作坊”推向“现代工程”。以下是我在线上项目中验证过的五个进阶技巧
返回列表