
很多朋友一听说要在 Windows 10 下编译基于 RT-Thread 的 FMT 开源飞控代码第一反应就是头大。我一开始也一样明明文档里写的是 Linux 下一条scons命令搞定换到 Win10 就各种路径、工具链、环境变量问题冒出来。虽然 FMT 官方对 Linux/macOS 的支持更顺滑但现实是不少人主力机就是 Win10拿去实验室或者宿舍编译调试属实是刚需。这篇文章就是把我从零搭环境到最终编译出可烧录固件的完整过程写出来包括我遇到过的每个报错、每次排查思路以及最后沉淀下来的稳定方案。不管你是第一次接触 RT-Thread 生态还是已经在别的嵌入式项目里折腾过 scons这篇都值得你花十分钟看完再动手。1. FMT 和 RT-Thread 是什么关系先看懂构建体系再动手1.1 FMT 不是普通单片机工程它是一套基于 RT-Thread 的完整飞控固件FMT 的全称不需要我过多解释圈内都知道它是一套开源飞控/自动驾驶仪固件。它的特殊之处在于底层实时操作系统用的是 RT-Thread而不是大家更熟悉的 FreeRTOS 或者裸机调度。这就带来一个直接后果你在编译 FMT 的时候实际上是把 RT-Thread 内核、设备驱动框架、各类组件再加上 FMT 自己的飞行控制算法、状态机、日志系统等等全部编到一起。正因为底层是 RT-Thread所以编译这套代码就天然带上了 RT-Thread 社区的构建习惯scons GCC 工具链 menuconfig 配置。如果你之前只会用 Keil 点灯、用 IAR 跑外设例程第一次看到SConstruct、Kconfig、rtconfig.h这些文件大概率会愣一下。这不是 FMT 故意为难你而是为了让代码在多个编译器和多个硬件平台之间可移植、可配置RT-Thread 生态早就形成了这套玩法。1.2 为什么 FMT 的编译不能像普通工程那样打开IDE点编译很多嵌入式新手有一个惯性拿到一份代码先找.uvprojx或者.eww工程文件双击打开点编译完事。FMT 不行它没有提供现成的 Keil 工程给你点。原因很简单FMT 的代码体量、模块拆分、配置项数量已经远超一个 IDE 工程文件能舒服管理的规模。它的构建逻辑是由SConscript脚本驱动的哪些源文件参与编译、哪些宏定义要打开、链接脚本用哪个全部在脚本和Kconfig配置里动态生成。你只有跑一遍scons它才根据当前配置生成对应的编译任务。这意味着你在 Win10 下要做的不是打开工程而是搭建一套能让 scons 跑起来的命令行环境。这个观念转变过来后面所有步骤就顺理成章了。1.3 Win10 环境的特殊性路径分隔符、权限、杀软三个老熟人Win10 下编译开源项目绕不开三个麻烦路径分隔符反斜杠和正斜杠的混用、权限问题某些目录写入受限、以及 Windows Defender 对编译中间文件的干扰。FMT 的构建脚本是按跨平台思路写的在 Linux 下跑得很欢但到了 Windows 命令行里偶尔会遇到脚本拼接路径时用了/而某个 Windows 工具只认\于是报错。这类问题不是你代码写错了纯粹是平台差异。另外Win10 对 Program Files 这类目录有保护机制如果你把源码解压到 C 盘根目录或者 Program Files 下scons 在生成中间文件时可能因为权限不够而失败。最省心的做法是专门建一个纯英文路径的工作目录比如D:\FMT_Workspace。中文路径、空格路径在 Windows 下编译开源项目十个有九个会踩坑这不是玄学是工具链对路径解析的兼容性问题。2. Win10 工具链选型为什么绕开 Keil最终用 SCons 加 GCC2.1 我最终确定的工具链组合在我实际动手之前先把要准备的东西列了个清单。FMT 编译需要的东西不算多但每一样都得选对版本工具作用我用的版本Git拉取 FMT 和 RT-Thread 源码2.40 以上即可Pythonscons 依赖的运行环境3.8 或 3.10别用 3.12/3.13RT-Thread env 工具提供 scons、menuconfig 等命令最新 release 版本arm-none-eabi-gccARM 交叉编译器生成固件10.3-2021.10 或更新的 xPack 版本STM32CubeProgrammer / J-Flash烧录编译产物按你手头的调试器选Python 版本这个事我想单独强调一下。FMT 的构建脚本里用到了一些第三方 Python 库而较新版本的 Python 对某些库的兼容性并不好我一开始图新鲜装了 Python 3.12结果跑scons时直接报错后来换成 3.8 才正常。具体报错和排查过程放到后面第 4 节细说。2.2 为什么不用 Keil 或 IAR 来编译 FMT很多刚接触 FMT 的人会问既然 FMT 底层是 STM32那能不能用 Keil 直接编译答案是能但非常不推荐。Keil 的工程文件需要手动维护源文件列表FMT 这种级别的代码量手动添加几百个源文件本身就是个灾难而且 RT-Thread 的配置系统Kconfig menuconfig没法平滑接入 Keil 工程。就算你费劲把工程搭好了每次 FMT 上游代码更新、新增了模块或文件你又得手动去同步工程维护成本极高。IAR 的情况更麻烦FMT 官方并没有提供 IAR 的工程模板如果你自己折腾 IAR 移植要把启动文件、链接脚本、宏定义全部按 IAR 的语法重新搞一遍这个工作量不亚于自己写一个 BSP。所以我最后的选择很明确scons GCC。这套组合是 RT-Thread 生态的标准答案也是 FMT 官方推荐的构建方式。你在 RT-Thread 社区看到的 env 工具、scons 命令全部围绕这条路展开。2.3 工具链版本选择的具体建议关于编译器版本我多说一句。FMT 代码用到了部分较新的 C 语言特性如果编译器版本太老比如 GCC 7 甚至更早部分源码会报语法错误但如果版本太新又可能和构建脚本里的某些 flags 冲突。我实测下来arm-none-eabi-gcc 10.3 这个版本兼容性最好网上也最容易找到。如果你手上的环境已经装了其他版本建议优先尝试 10.3不行再换。可以用命令验证当前工具链版本arm-none-eabi-gcc --version如果输出arm-none-eabi-gcc (GNU Arm Embedded Toolchain 10.3-2021.10)说明版本没问题。另外提醒一下环境变量 PATH 里最好只保留一个 arm-none-eabi-gcc多个版本混在 PATH 里会导致 scons 在调用编译器时拿到一个意料之外的版本编译报错时你根本想不通是为什么。3. 从零搭环境env 配置、工具链路径与源码获取的完整命令3.1 第一步关闭 Win10 安全中心对编译目录的实时防护这一步很多人不知道但特别关键。Windows Defender 的实时防护会在你编译时反复扫描生成的.o中间文件和.elf产物导致编译速度大幅下降极端情况下还会把某些识别为可疑的工具链 exe 直接隔离。我在第一次编译时arm-none-eabi-gcc 的某个组件被 Defender 隔离导致编译到一半报找不到文件排查了半天才发现是被杀软吃了。建议在开始之前把整个工作目录加入 Defender 的排除列表或者至少在你编译期间暂时关闭实时防护。步骤如下Windows 安全中心 - 病毒和威胁防护 - 管理设置 - 排除项 - 添加排除项把D:\FMT_Workspace整个目录加进去。如果你用的是第三方杀软也建议同样处理。编译完固件之后再恢复实时防护不影响日常使用。3.2 第二步安装 RT-Thread env 工具并配置环境RT-Thread 官方提供了一款名为 env 的 Windows 命令行工具它内置了 scons、Python 环境以及 menuconfig 等常用命令相当于帮你把构建环境打包好了。你不需要自己手动安装 scons当然你想自己装也完全可以但 env 更省事。下载 env 工具后解压到一个纯英文路径比如D:\env。解压完成后打开 env 目录下的env.exe或者通过console.exe启动的终端它会自动加载 RT-Thread 生态需要的环境变量。然后在 env 终端里验证一下scons --version python --version如果都能正常输出版本号说明 env 环境已经可用。需要特别注意编译 FMT 时不要用普通的 CMD 窗口而是要用 env 自带的这个终端因为它会额外设置一些环境变量比如RTT_ROOT、RTT_CC等这些变量在 scons 构建过程中会被脚本读取。用普通 CMD 跑 scons 经常会出现一些莫名其妙的找不到配置错误根源就在环境变量缺失。3.3 第三步下载并配置 arm-none-eabi-gccenv 工具自带的 GCC 编译器版本可能不是 FMT 需要的版本。我更推荐去 ARM 官方或者 xPack 的发布页下载arm-none-eabi-gcc 10.3-2021.10下载完成后解压到类似D:\FMT_Workspace\tools\arm-gcc的路径。然后在 env 终端里手动指定编译器路径这一步很关键你要在 env 终端里执行set PATHD:\FMT_Workspace\tools\arm-gcc\bin;%PATH%再验证一下arm-none-eabi-gcc --version确认输出的是 10.3 版本。这里有一个非常容易踩的坑env 工具自带的 GCC 也在 PATH 里如果你不手动把新下载的 GCC 路径放在前面scons 调用的可能还是 env 内置的旧版本编译器导致编译选项不兼容。所以我上面的命令用set PATH...;%PATH%新路径在前优先被识别。3.4 第四步获取 FMT 源码并初始化相关子模块FMT 的源码托管在 GitHub/Gitee 上建议用 Git 克隆而不是直接下载 zip因为项目里有子模块直接下 zip 会缺失依赖。打开 env 终端进入工作目录cd D:\FMT_Workspace git clone https://github.com/YOUR_REPO_FMT.git fmt cd fmt git submodule update --init --recursive子模块更新这一步特别重要。FMT 依赖 RT-Thread 内核源码、各组件库、驱动库等这些依赖都是以 Git 子模块的形式挂载在项目里的如果不拉取子模块后面编译时会报一堆找不到头文件的错误。3.5 第五步配置编译目标并执行编译FMT 支持多种飞控硬件平台比如基于 STM32F427 的某类飞控板、基于 F103 的某些入门板等。你需要先通过 menuconfig 选择目标平台和功能模块。在 FMT 根目录下执行scons --menuconfig这个命令会打开一个图形化的配置界面类似 Linux 内核的 menuconfig。你需要在里面选择正确的板卡型号保存退出后配置会自动写入rtconfig.h。然后执行编译scons -j8-j8表示用 8 个线程并行编译能明显加快速度。如果你的电脑是 4 核用-j4即可并行任务数建议等于 CPU 核心数。第一次编译需要的时间比较长通常在几分钟到十几分钟不等取决于你电脑的配置。4. 第一次编译失败实录五个报错的完整排查链路这一节是我最想分享的内容。我自己第一次编译的时候前前后后踩了五个坑每一个单独拎出来都不难解决但串在一起就非常打击信心。我按实际的排查顺序记录如下你大概率也会遇到其中一个或几个。4.1 报错一Python 版本导致 scons 无法导入第三方模块现象在 env 终端执行scons -j8还没开始编译就直接报错提示某个 Python 模块找不到或者提示ModuleNotFoundError: No module named xxx。排查过程我先检查了 scons 是否能正常运行发现scons --version是好的那么问题出在项目脚本引入的第三方库上。看了报错堆栈定位到是 FMT 构建脚本里import了一个不常见的模块而当前 Python 环境没有安装这个模块。于是我用pip install安装缺失模块装完之后还是报错而且报错信息变了说某个函数不支持。这时候我意识到可能不是缺模块而是模块版本和 Python 版本不兼容。解决方案查看 env 工具内置的 Python 版本发现是 3.10而我自己在 PATH 中安装了 Python 3.12env 终端优先调用了系统的 Python 3.12。Python 3.12 对某些旧版第三方库的 C 扩展不兼容导致导入失败。解决方法是把 env 自带的 Python 路径移到 PATH 最前面或者在 env 终端中显式指定set PATHD:\env\tools\Python38;%PATH%重新执行python --version确认是 3.8 或 3.10 后再跑 scons这个报错就消失了。4.2 报错二编译器路径错误导致找不到 arm-none-eabi-gcc现象scons 能正常启动了编译进行到一半报错提示找不到arm-none-eabi-gcc.exe或者提示arm-none-eabi-gcc 不是内部或外部命令。排查过程这个报错很直白就是 PATH 里没有编译器路径。但我明明已经在 env 终端里set PATH了为什么还会找不到后来发现问题是我是在一个 env 终端里设置了 PATH然后开了另一个新的 env 终端窗口来编译新窗口的环境变量自然没有我之前手动设置的内容。所以每次打开新的 env 终端都需要重新执行一次路径设置命令否则编译器不在 PATH 里。解决方案不要每次都手动 set直接在D:\FMT_Workspace\fmt目录下新建一个set_env.bat批处理文件内容就是设置 PATH 的几行命令。以后每次编译先执行这个 bat再跑 sconsecho off set PATHD:\FMT_Workspace\tools\arm-gcc\bin;%PATH% set PATHD:\env\tools\Python38;%PATH% echo Environment ready.这样就能保证每个新终端都有一致的编译环境。这个习惯特别重要嵌入式项目里你不可能保证所有开发机都有人手动配环境写个一键配置脚本换电脑也能秒级恢复。4.3 报错三子模块未拉全导致头文件缺失现象编译继续往下跑开始编译 RT-Thread 内核文件时报错提示找不到rtthread.h或者找不到某个驱动库的头文件。排查过程这个报错的原因比较隐蔽。我明明在克隆后执行了git submodule update --init --recursive为什么还缺头文件我检查了项目目录下的子模块文件夹发现有些文件夹是空的有些只有一个.git文件。原因是 Git 子模块在克隆时可能因为网络原因没有完全下载或者子模块的依赖层级比较深--recursive没有完全拉取。解决方案重新执行一次子模块更新注意加上--recursive确保递归拉取所有嵌套子模块git submodule deinit -f . git submodule update --init --recursive也可以直接换用国内镜像源来代替部分子模块地址速度更快更稳定。拉完之后再去核对一下rt-thread目录下是否真的有include/rtthread.h文件确认无误再继续编译。4.4 报错四链接脚本路径或启动文件配置错误现象编译过程已经完成了 90%进入链接阶段时报错提示找不到.ld链接脚本或者找不到startup_stm32f427xx.S等启动文件再或者提示某段内存区域溢出。排查过程链接脚本和启动文件是跟具体 MCU 型号强相关的。我检查了 menuconfig 里选择的板卡型号发现自己选的是某一款飞控板但实际对应的 MCU 是另一个型号导致链接脚本不匹配。这个问题的本质是板卡型号选择错误配置生成的后缀、链接脚本路径全都跟着错了。解决方案回到 menuconfig仔细核对目标硬件的 MCU 型号和板卡定义。如果你用的是一块自制板或者某飞控板需要在 menuconfig 里选择对应的板卡预设。确认选择正确后删除编译中间文件重新来一次scons -c scons -j8scons -c会清理之前的编译产物很重要。因为在错误的板卡配置下编译出来的.o文件会被缓存就算你改了配置如果不清缓存scons 可能还会沿用旧的对象文件导致链接时新旧混合、各种诡异错误。4.5 报错五Windows Defender 隔离工具链文件现象编译在某个阶段突然报错提示找不到某个可执行文件甚至整个终端直接卡住查看任务管理器发现进程已经退出而在报错日志末尾出现类似访问被拒绝的提示。排查过程一开始我以为是自己误删了文件后来在 Defender 的威胁历史里看到了隔离记录被隔离的文件正是 arm-none-eabi-gcc 工具链里的某些组件。这是因为交叉编译器会生成临时可执行文件某些启发式扫描会误认为有风险。解决方案在编译期间把整个工作目录和工具链目录加入 Defender 排除列表具体操作我在前面 3.1 节已经写过了。这一条看起来是环境问题但很多人在 Win10 下编译失败都栽在这上面所以我把它放在报错排查的最后一环因为排查它的成本最低——你只需要看一眼 Defender 的隔离记录就能确认。4.6 一个值得注意的现象编译很慢不是电脑不行如果你编译时发现速度极慢CPU 占用率反而不高大概率是 Defender 在实时扫描生成的文件。我也遇到过类似困惑任务管理器显示编译进程没怎么吃 CPU但就是编译不动后来把工作目录加入排除列表之后速度立刻恢复正常同样的代码从十几分钟降到三分钟。如果你的编译也很慢先按这个思路检查杀毒软件。5. 编译产物分析怎么确认固件能烧、烧录方式怎么选5.1 编译成功后生成的产物长什么样当你看到scons: done building targets.说明编译成功。可以到build目录下查看生成的固件文件常见的包括.elf包含调试信息和符号表适合仿真器直接调试.bin纯二进制固件去掉了文件头和调试信息适合直接烧录到 Flash.hexIntel HEX 格式很多烧录工具只认这个格式.map链接地图文件记录了每个符号的地址、大小、占用情况排错必备在 FMT 项目里具体产物路径通常在build/fmt-xxx/下文件名可能和板卡型号相关。比如fmuv3.bin这样的命名。5.2 如何从编译日志和 map 文件确认固件没问题编译成功后不要急着烧录先看一眼编译日志末尾的固件内存占用信息。如果构建脚本打印了类似RAM: XYZ bytes, xx% used Flash: XYZ bytes, xx% used那就用这个数字粗略核对一下。你的目标板的 Flash 和 RAM 容量是已知的比如 STM32F427 有 2MB Flash 和 256KB RAM如果固件占用明显超出说明配置有问题烧进去必挂。如果编译日志没有打印内存占用信息可以打开.map文件搜索.text、.data、.bss等段的大小。.map文件是文本格式用任意文本编辑器就能查看里面每个函数的地址和大小都有是分析到底 flash 被谁吃掉了的一手资料。比如你怀疑某个日志库占的空间太大直接在 map 文件里搜这个库的符号就能看到它占了多少字节。5.3 烧录方式简要对比ST-Link、J-Link、串口 ISP固件已经生成接下来是烧录。在 FMT 开发中最常见的烧录方式有三种方式工具适用场景优劣势SWD 调试器STM32CubeProgrammer、J-Flash、OpenOCD日常开发调试推荐速度快支持仿真接线最少串口 ISP官方 Bootloader 串口工具无调试器时应急简单但依赖板子的 Bootloader速度慢DFU 方式通过 USB DFU 工具部分飞控板支持不需要独立调试器但进入 DFU 模式的方式因板而异我个人推荐 STM32CubeProgrammer 加 ST-Link原因很简单FMT 底层是 RT-Thread调试时你可能需要看线程运行状态CubeProgrammer 可以连接 SWD 接口支持擦除、烧录、校验一条龙也支持命令行自动化烧录。命令行烧录尤其适合经常编译的开发者写成个脚本一键编译、一键烧录。5.4 烧录后的第一项检查固件烧录完成后连接飞控板的 USB 串口打开串口终端波特率一般设置为 115200 或 460800具体看 FMT 的配置。如果系统成功启动你会看到 RT-Thread 的启动 logo、FMT 的版本信息和一系列初始化日志。这时你还能在终端里输入 RT-Thread 的ps命令查看当前系统线程列表——这对确认系统是否正常运行非常有用也是 RT-Thread 生态一个很有特色的调试手段。如果你在日志里看到类似main thread stack overflow、assert failed之类的信息不要急着怀疑固件有问题。先确认是不是烧录的固件和板卡型号不匹配再看看是否需要在 menuconfig 里调整线程栈大小。绝大多数烧完没反应的情况回头检查一下 menuconfig 里的配置都会有收获。写在最后一些给新人的额外建议如果你完全没接触过 RT-Thread 环境第一次在 Win10 下搭建 FMT 编译环境整个过程可能需要一到两个小时这很正常不要去怀疑自己的动手能力。我建议你把环境搭建和编译的过程当成一次熟悉 RT-Thread 构建体系的机会而不是单纯的下个命令、等个结果。scons 的日志输出其实非常有信息量编译到了哪个模块、链接了哪个库跟着日志多看几次你对 RT-Thread 的组件化构建方式理解会深很多。我自己就养成了一个习惯编译的时候不切窗口盯着日志顺序读一遍很多架构层面的疑问在这个过程中自然就解开了。最后再分享一个实际操作中的小心得把scons -c scons -j8做成一个批处理每次配置变更后先清理再编译能省下大量排查时间。很多人改完 menuconfig 后直接编译结果因为旧缓存文件还在遇到鬼畜报错我见证过太多次这种场景了。编译这件事整洁的环境比运气重要得多。