ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Ubuntu下用VSCode编译Betaflight AT32F437DEV固件指南

Ubuntu下用VSCode编译Betaflight AT32F437DEV固件指南 做嵌入式开发这几年我一直有个习惯能用源码跑的东西绝不只拿现成固件。尤其是飞控这种跟安全和调参体验强相关的玩意儿光是刷别人编译好的 hex 终究不踏实。你要是玩 Betaflight 又正好拿到一块 AT32F437DEV 开发板想在 Ubuntu 上用 VSCode 写代码、看配置、自己编固件这篇文章基本就是照着抄就能跑通的路线图。我会带你从零搭好整个编译环境装好工具链拉下源码把 AT32F437DEV 的固件在本地编出来最后再聊几个我实际编译中踩过的坑省得你在终端里对着报错干瞪眼。1. 动手之前先理清思路为什么要在 Ubuntu 上自己编固件1.1 传统 Betaflight 固件下载方式的局限大多数玩家刷 Betaflight 固件第一反应是打开 Betaflight Configurator从固件列表里挑一个目标板然后点“烧写”。日常起飞确实够用但你要真想在开发板上调自己的功能、改默认 PID、加自定义滤波、或者测试还没合并的新补丁这种黑盒流程完全撑不住。AT32F437DEV 这种板子和常见的 STM32F4/F7 目标板不同它是基于雅特力ArteryAT32F437 芯片的配置和资源分布在特定 target 目录下官方预编译固件不一定覆盖所有板卡版本和外围组合最好的方式就是本地编译。本地编译飞控固件带来的好处非常直接能直接改动 Betaflight 源码里的算法和配置宏重新生成固件。对新版本 release 可以自行提前适配不用等 Configurator 推送。编译过程能看见详细报错和依赖关系统遇到问题可以定位到具体代码。配合 VSCode 做代码阅读、搜索和重构效率比看网页源码高太多。我一直觉得飞控玩家如果跨过了“编译固件”这道坎基本就进入了第二个阶段不再是单纯的使用者而是能从代码层面理解飞控行为的爱好者。所以这篇文章不教你用 Configurator 一键刷写而是把 VSCode Ubuntu Betaflight 源码这一套真正串起来。1.2 Ubuntu 与 VSCode 的组合为什么顺手做嵌入式开源项目Ubuntu 几乎是默认主战场原因倒不是 Windows 不行而是大量构建脚本、交叉编译工具链和依赖库在 Linux 生态下更干净少了很多环境变量、路径分隔符、防火墙拦截之类的破事。Betaflight 的 Makefile 和工具链都是围绕 Linux 设计调试过的在 Ubuntu 上跑 make 指令基本不会遇到莫名其妙的权限和格式问题。VSCode 这边呢它本身不参与编译但它承担了“阅读源码 触发构建 快速定位错误”的角色。正因为 Betaflight 源码量大结构也绕单靠 vim 或者记事本根本没法快速跳转。VSCode 的 C/C 插件能解析整个工程索引配合 tasks.json 可以把编译命令集成到 IDE 里改完代码一键 build报错还能直接跳转到对应行体验非常顺。这个组合我用了很久稳定省心。2. 环境准备Ubuntu 和 VSCode 的具体安装路子2.1 Ubuntu 系统选型和安装细节如果你手头没有现成的 Linux 机器建议装 Ubuntu 22.04 LTS 或 24.04 LTS长期支持版本软件源里的 GCC 库比较新网上资料也最多踩坑时好搜。安装方式主要有三种我分别说下适用场景实体机独立安装性能最好编译速度最快适合长期搞开发的人。因为飞控编译涉及大量文件CPU 核心多就是王道。把 BIOS 里的 Secure Boot 关掉可以避免后续驱动和系统启动的各种兼容问题。VMware/VirtualBox 里装 Ubuntu适合电脑上已有 Windows 环境、不想折腾双系统的人。记得给虚拟机分配至少 4 核 CPU 和 8GB 内存磁盘建议 60GB 以上因为 Betaflight 源码加工具链加编译中间文件会占不少空间。WSL2Windows 下最轻量的选择和 VSCode 的集成度极高。如果你主要用 Windows我其实最推荐 WSL2因为它不用切系统直接在 Windows 桌面上处理所有 Linux 命令性能损失也比虚拟机小很多。装完 Ubuntu 之后先做两件基本动作换软件源国内网络环境下能显著提速、执行一次系统更新。sudo apt update sudo apt upgrade -y这一步很重要很多初次编译失败其实就是因为编译器的某些依赖库版本太旧。更新完系统再装基础构建工具。sudo apt install -y build-essential git pkg-configbuild-essential 会带来 gcc、g、make 等一堆基础编译工具。git 是拉源码必备。这些是后面一切操作的前提别偷懒跳过。2.2 VSCode 安装及常用插件配置VSCode 在 Ubuntu 上的安装非常简单可以直接上官网上传 deb 包然后用 dpkg 安装也可以启用微软的 apt 仓库。最无脑的方式是去 VSCode 官网下载 .deb 安装包然后终端执行sudo dpkg -i code_*.deb sudo apt-get install -f -y装好后打开建议第一时间装 4 个插件C/Cms-vscode.cpptools提供代码补全、跳转、语法高亮编译报错也可以直接在编辑器里显示。Remote - WSL如果你用 WSL2这个插件让你直接在 Windows 的 VSCode 里操作 Ubuntu 环境路径和终端都不用手动切换。GitLens查看源码历史和 blame 时很好用排查自己改没改过错很有帮助。Task Explorer配合 tasks.json 可视化执行构建任务编译命令一目了然。装完后建议在 VSCode 里按CtrlShiftP打开命令面板运行C/C: Edit Configurations (UI)把编译器路径指到后面会安装的 arm-none-eabi-gcc就能获得完整的飞控源码代码索引。2.3 交叉编译工具链安装与版本选择Betaflight 编译不是用普通 x86 的 gcc而是需要一个面向 ARM 嵌入式平台的交叉编译器常用的是 arm-none-eabi-gcc。这个工具链的作用是把我们在 x86 机器上写好的 C 代码编译成 ARM Cortex-M 内核能跑的机器码。AT32F437DEV 用的 AT32F437 芯片是 ARM Cortex-M4F 内核和 STM32F4 是同一类架构所以工具链是通用的。Ubuntu 的软件源里有 gcc-arm-none-eabi 包直接 apt 安装最快sudo apt install -y gcc-arm-none-eabi但我个人比较推荐去 ARM 官网下载官方工具链安装因为 Betaflight 对 GCC 版本有一定兼容性要求而从 Ubuntu 源装的版本可能因为版本过新或过旧出现奇怪的编译警告甚至错误。比如在部分 Ubuntu 版本中apt 默认的 gcc-arm-none-eabi 是老版本 10.3 或者 9.x而新版 Betaflight 某些模块在 GCC 11 下才能编译通过。如果遇到编译后期报错建议直接装官方 12.3 版本。安装完工具链后务必验证版本arm-none-eabi-gcc --version能正常打印版本号说明工具链已经生效。另外记得确认一下 make 版本make --versionmake 是 Betaflight 构建系统最核心的驱动版本太低会在解析部分 Makefile 时出现怪问题装完 build-essential 后通常不会低于 4.2够用。3. 拉取 Betaflight 源码并理解工程结构3.1 克隆源码仓库Betaflight 的源码托管在 GitHub 上克隆时记得用--recursive参数因为它里面包含了一些子模块比如飞控驱动、CMSIS 库等。如果漏掉这个参数后面编译时会报找不到头文件的错误。cd ~ git clone --recursive https://github.com/betaflight/betaflight.git cd betaflight克隆完成后可以查看当前分支git branch -a git tag | tail -20如果你并不想追最新的主分支master 往往处于持续开发状态稳定性一般我建议切到一个稳定的 release 分支例如 4.5.x 或 4.6.x。切 tag 的命令git checkout 4.5.1这里要特别注意切完分支之后子模块也要同步更新否则源码树不完整编译必挂。所以建议在切换 tag 后执行git submodule update --init --recursive3.2 源码目录结构解析target 藏在哪Betaflight 的源码根目录看起来会让人稍微懵一下但核心目录其实非常清楚src/main所有飞控核心逻辑包括任务调度、PID、滤波、传感器驱动。src/main/target存放各个目标板的配置目录这也是我们找 AT32F437DEV 的地方。src/main/target/AT32F437DEV特定于 AT32F437DEV 的 board config硬件初始化、引脚定义、外设配置都在这里。makeBetaflight 构建辅助目录存放各种编译辅助脚本。Makefile整个工程的构建入口Betaflight 几乎所有编译命令都从这里触发。obj编译时生成的中间文件和最终 hex 固件都在这里。说到 AT32F437DEV这里多讲一句。很多刚开始玩 Betaflight 的人以为 target 名称和具体硬件板子名称是一一对应的实际上现代 Betaflight 已经大量采用 unified target 的方式一块板卡既要指定 target也要在 CLI 里设置 BOARD_NAME。AT32F437DEV 在源码里实际上是一个开发者版统称目标对应了 JHE 等厂商的一批 AT32F437 飞控。编译的时候指定TARGETAT32F437DEV最后生成的固件就能通过 Configurator 或者命令直接刷入。打开src/main/target/AT32F437DEV/target.h你会看到这个板卡核心定义包括时钟频率、UART 映射、电机引脚、LED 状态灯等。如果你想按自己的接线改资源这个文件就是最直接的第一站。我建议编译前先打开这个文件看一眼确认板的资源定义和手里实物一致尤其是电机输出通道和接收机 UART。3.3 构建产物与 flash 地址理解编译的最终产物是一份 hex 文件位于obj/main/betaflight_4.5.1_AT32F437DEV.hex这种编号格式的文件。这个 hex 文件可以直接用 Betaflight Configurator 的“从本地固件刷写”功能选择也可以通过 ST-Link 等工具用命令行写入。AT32F437DEV 型号的芯片虽然内核是 Cortex-M4但它的 Flash 地址映射和 STM32F4 并不完全一样。编译时链接脚本会处理好地址所以我们不需要手动设置只要保证编译用的 target 名称与板卡对应链接文件就不会出错。一旦混用 target编译生成的入口地址不匹配刷进去大概率变砖这一点一定要记牢。4. 编译 AT32F437DEV 固件的核心流程4.1 Betaflight 构建系统Makefile 在做什么Betaflight 的编译系统和 Linux 内核风格极为相似最外层是通过 make 命令解析Makefile。当你执行make TARGETxxx时它实际上做了四件事解析 Makefile 中的目标平台配置加载对应的 target 目录。检查工具链确认 arm-none-eabi-gcc 等命令可用。遍历所有.c源码文件逐个编译成.o目标文件。把所有目标文件链接成最终的 ELF 文件然后转成 bin/hex 格式。整个过程的输入输出参数几乎都靠 make 变量传递而 AT32F437DEV 这个 target 则指定了对应的硬件描述。构建系统的好处是只要你把命令写对后面所有琐碎的编译步骤都不用管。需要说明的是Betaflight 早期版本还有 manual target 和 unified target 之分但 AT32F437DEV 属于较新的开发板型号直接使用默认构建规则即可。不需要额外指定UNIFIED参数直接一条 make 命令就行。4.2 编译命令与首次构建实录在源码根目录执行make TARGETAT32F437DEV注意首次编译的时间会比较长因为要编译整个工程包括所有驱动模块。我的机器是 8 核 16 线程大概用了 3 分钟如果是在虚拟机里可能平均要 6-10 分钟。这个阶段终端里会哗哗滚动大量CC src/main/...的输出这代表每个 C 文件正在被编译是正常现象。如果没报错最终终端会显示固件生成路径和 hex 文件大小类似这张印象中的输出arm-none-eabi-size obj/main/betaflight_4.5.1_AT32F437DEV.elf text data bss dec hex filename ... Build succeeded - betaflight_4.5.1_AT32F437DEV.hex看到 “Build succeeded”说明整个 AT32F437DEV 固件已经在本地环境构建成功。你可以去obj/main/目录下找 hex 文件路径里带日期或版本号的那份就是。如果你在编译过程中 CPU 核心数多可以指定并行编译来提速make TARGETAT32F437DEV -j$(nproc)-j$(nproc)会让 make 同时执行多个编译任务充分利用 CPU 性能。我习惯加上这个参数尤其在实体机上编译速度和单线程完全是一个量级到另一个量级的差距。4.3 如何修改配置并重新编译Betaflight 里大量的功能开关靠编译期宏实现。类似USE_MAX7456、USE_ACC_MPU6050、USE_BARO_BMP280这些宏定义在 target 头文件里。如果你要为自己的外设开启某个功能修改 target.h 之后重新编译即可。比如你的 AT32F437DEV 装了某个气压计而默认 target.h 里没有启用相关驱动那就需要在 target.h 中加上#define USE_BARO_BMP280保存之后重新执行make TARGETAT32F437DEV增量编译会非常快因为你只改了一个头文件大部分 .o 不需要重建。这时你能体会到本地源码编译的另一个优势每次调整配置不用等待全量编译基本十几秒就能得到一个全新的固件。4.4 在 VSCode 中配置一键编译任务虽然直接在终端敲 make 命令已经足够但我每次喜欢用 VSCode 的 Task 功能来触发编译这样可以一边读代码一边编译报错点还能直接跳转到源码行非常实用。在项目根目录创建.vscode/tasks.json{ version: 2.0.0, tasks: [ { label: Build AT32F437DEV, type: shell, command: make, args: [TARGETAT32F437DEV, -j$(nproc)], group: { kind: build, isDefault: true }, problemMatcher: [$gcc] } ] }保存之后按CtrlShiftBVSCode 就会直接运行编译命令。如果有人工加入的宏定义或调试选项也可以直接修改 args 数组。problemMatcher: [$gcc]的作用是让 VSCode 解析 gcc 输出的错误并把报错信息显示到“问题”面板点击即可跳转对应代码。这个配置我强烈建议做因为你后面改 target 配置时会频繁触发编译保留终端输出的同时又在 IDE 里展示错误排查体验完全不一样。5. 实用 VSCode 阅读技巧给编译流程加上代码导航5.1 配置 C/C 智能提示指向飞控源码仅仅能编译还不够大多数人的高频操作是在源码里搜索某个函数、查看结构体定义、以及修改 target 配置。VSCode 的 C/C 插件如果不做配置遇到飞控这种多目录的工程搜索和跳转会迟钝甚至失效。我通常会在项目根目录生成.vscode/c_cpp_properties.json把 ARM 工具链的 include 路径加进去。一个比较简化的配置示例{ configurations: [ { name: Linux, includePath: [ ${workspaceFolder}/src/main, ${workspaceFolder}/src/main/target/AT32F437DEV, ${workspaceFolder}/src/main/startup, ${workspaceFolder}/lib/main/** ], defines: [ STM32F437xx, USE_HAL_DRIVER, AT32F437DEV ], compilerPath: /usr/bin/arm-none-eabi-gcc, cStandard: c11 } ], version: 4 }配置好之后宏定义和头文件查找都能正确匹配Ctrl点击函数名能直接跳转到定义这个体验对理解 Betaflight 的驱动框架极有帮助。尤其是你在看电机 PWM 初始化代码时一路追踪到timerHardware数组的感觉会让之前一知半解的驱动逻辑一瞬间清晰起来。5.2 配合 Git 历史查看代码改动Betaflight 本身是一个高度活跃的开源项目版本升级时很多函数签名和行为都会变化。如果你下载的是 master 分支可能在某个提交之后接口就变了。这时候 GitLens 就非常有用了。选中一段代码可以看到这一行是谁在什么时候改的、提交信息是什么、以及改动前后的 diff。这种能力在排查“为什么我照抄旧代码编译不过”的场景下能节省一晚上的时间。还有一些时侯你在网上搜到某个老版本配置方法然后直接照着 master 分支改结果发现宏定义已经重命名。这种情况最好的方式是先看当前源码里 target.h 的#define列表再搜新芯片对应的宏名字别盲目照搬网上教程。我在一次适配新版本固件时就遇到USE_UART1被合并成USE_UART的情况卡了很久才定位到正是靠 Git 历史把旧版到新版的变化看明白的。6. 编译中遇到的常见问题与我的排查思路6.1 工具链相关报错找不到 arm-none-eabi-gcc如果你在 Ubuntu 上先装了 build-essential后来才装 gcc-arm-none-eabi理论上不会冲突但有时候终端会提示arm-none-eabi-gcc: command not found。这种大概率是工具链路径没加进 PATH。如果你是用 apt 安装的一般会自动放在/usr/bin/下不应该找不到。但你如果是手动下载的 ARM 官方 tar 包并解压到某个目录那必须手动把 bin 目录加进 PATH。export PATH$PATH:/home/yourname/arm-gnu-toolchain-12.3/bin想把路径永久生效可以把这行加到~/.bashrc末尾然后执行source ~/.bashrc。我强烈建议装完后先arm-none-eabi-gcc --version验证一次别等到编译报错再排查因为编译报错信息往往混杂了大量其他信息反而干扰判断。6.2 执行 make 时提示缺少依赖或子模块一个高发场景是拉取源码时忘了加--recursive。Betaflight 的子模块里面包含了一些关键的驱动库和启动文件缺少它们编译到一半会报缺失头文件比如fatal error: CMSIS/Include/core_cm4.h: No such file or directory解决办法是先更新子模块git submodule update --init --recursive这个命令多用几次也不会出错完全可以放心执行。如果在公司内网或网络不稳定环境下载子模块经常失败可以考虑配置 git 的缓存和代理设置但这属于网络优化范畴不是编译本身的问题。另外切换分支后子模块不会自动变一定要手动执行上面的命令。6.3 编译报错undefined reference 与 libgcc如果你在编译后期遇到大量 undefined reference 的报错比如__adddf3、__aeabi_dmul之类的符号缺失这通常不是代码逻辑问题而是工具链的浮点库或软硬浮点 ABI 配置不对。AT32F437 是 Cortex-M4F 内核带有硬件浮点单元但我们的编译命令和链接脚本必须匹配对应浮点模型。Betaflight 的 Makefile 对 AT32 系列基本已经做了正确配置如果你用的是旧版本源码 新版本工具链偶尔会出现 ABI 不匹配。遇到这种问题我一般先清理全部中间文件和依赖缓存make clean然后再重新编译。别小看 make clean很多时候改了工具链版本后旧的 .o 文件还带着旧标记链接器就会报各种莫名错误。清掉重来是最省事的修复方法。如果 clean 之后还报同样错误检查一下arm-none-eabi-gcc --version建议切换到 Betaflight 官方 CI 编译用的工具链版本区间目前主流的 4.5/4.6 分支用 10.3 到 12.3 都能顺利过但太老的 6.x、7.x 很可能不行太新的 13.x 偶发一些警告转错误的问题。6.4 Flash 空间不足或链接器溢出在给 AT32F437DEV 启用大量外设和特性之后编译有时会提示类似region FLASH overflowed by xxx bytes这说明 target 配置里启用的代码叠加之后超出了芯片 flash 大小。遇到这种情况不要第一时间去打删除驱动的主意义应该先看src/main/target/AT32F437DEV/target.h里哪些外设宏是你根本用不上的比如不用的接收机协议、不用的一堆传感器驱动都注释掉即可。多数飞控默认会启用很多可选项为的是板卡能适配不同装机配置但如果你本地编译只为自己的飞机用完全可以精简。比如我的某次自编译固件从默认配置里减掉了数传协议和几种用不到的气压计驱动flash 占用降了 15%。6.5 配置了 tasks.json 但 VSCode 编译命令不生效如果你按下CtrlShiftB什么反应都没有先确认 tasks.json 是否在正确目录必须是项目根目录下的.vscode/tasks.json同时 VSCode 是否打开了这个项目文件夹而不是单独打开了某个文件。还有一点Betaflight 源码根目录本身就有 .vscode 建议配置和我们的 tasks.json 同名也不会冲突只是覆盖优先级的问题。如果 VSCode 提示错误试着把 task 里的command改成绝对路径的make例如command: /usr/bin/make这样可以绕过 PATH 解析的偶发问题。6.6 不同电脑/虚拟机的编译耗时与资源占用Fit 来说Betaflight 编译对内存要求不算太高4GB 就能跑但 8GB 体验舒服。CPU 核心越多编译越快如果是在虚拟机上我给的建议是虚拟机设置里至少要分配 2 核以上否则第一次全量编译可能要等十几分钟而且期间电脑容易卡顿。如果你遇到编译几秒就报错先别怀疑电脑性能99% 是工具链或者源码环境问题。如果你实在不想忍受重复等待还有一个小技巧编译时选择make TARGETAT32F437DEV -j$(nproc)并行编译。实体机上效果好虚拟机里如果分配核心数有限-j过大反而因为线程切换拖慢速度可以改为-j4或-j2实验一下找到最适合自己配置环境的参数。7. Flashing 与后续扩展从编译到实飞的衔接经验7.1 本地固件刷写的两种常用方式编译出来的 hex 文件不是只能通过 Configurator 烧录。我一般有两种方式一是打开 Betaflight Configurator选择固件模式点“从本地文件刷写”直接选中obj/main/betaflight_4.5.1_AT32F437DEV.hex。这种方式最推荐适合绝大多数有 USB 连接的飞控和开发板。二是用命令行工具 dfu-util 烧录它适合自动化脚本或当你用 ST-Link 连接时。AT32F437DEV 的 DFU 模式和 STM32 不完全相同但如果你手动进入了 bootloader通过设备管理器或lsusb确认 USB 设备 ID 后执行 dfu-util 可以完成刷写。不过我在实际体验中Configurator 的本地刷写是最稳的命令行方式留到需要脚本化的时候再折腾。刷写之后务必先连接 Configurator检查“串行数字接收机”和“电机协议”配置是否与 target 默认一致。因为我见过不少人编译成功、刷写成功结果没注意板卡的默认资源映射导致接收机不识别、电机不转最后还以为是编译出了问题。AT32F437DEV 这种开发板引脚多外设映射并不见得和你手里的接线完全对应检查 CLI 里的 resource 信息是起飞前必须做的事。7.2 尝试加入自己的改动从宏开关到业务逻辑自己编译最爽的一刻就是你改完代码重新生成固件并刷进去。比如你想在飞控启动时通过 LED 闪烁次数表示当前固件版本那完全可以改src/main/drivers/light_led.c或者src/main/flight/下的初始化逻辑。改完之后重新 make生成的固件就带你的专属行为。这在原版预编译固件里不可能做到。不过也要提醒一点Betaflight 核心算法改动往往涉及飞行安全不要在不理解代码的情况下贸然改 PID 更新率或者滤波算法。在本地编译调试完也要先在安全环境下试飞确认没有异常再上真机。飞控固件和普通桌面软件不同代码 bug 不只是闪退还可能影响电机输出所以建议保留一个能通过编译、行为稳定的版本再做新改动。7.3 后续可以扩展的玩法本地编译环境搭好之后后续扩展空间其实很大。你可以打上自己优化过的滤波代码也可以参考 GitHub 上别人提交的新算法分支进行试用。甚至可以在团队协作时用 git 管理自己的改动分支上游更新时通过 rebase 把新功能合并过来然后一键编译。还有人会用这个环境来配合 J-Link 或 ST-Link 做在线调试。虽然 Betaflight 相对较少用断点调试但配合 OpenOCD 和 VSCode 的 Cortex-Debug 插件你可以实时看寄存器、看变量值、定位硬 fault这比反复刷写日志高效得多。不过这属于另一个大的话题了等你有需求了再深入也不迟。8. 写在最后的实操体会其实编译 Betaflight 固件这件事难度并不高真正困扰新手的往往是环境细节工具链版本不对、子模块没拉全、VSCode 没配置好 include 路径。我最初在 Ubuntu 上编译 AT32F437DEV 固件时也被一堆fatal error和undefined reference搞到怀疑人生后来才发现问题就是工具链版本太旧加忘了更新子模块。现在回头看只要把环境搞干净按部就班执行 make 命令整个流程反而比用 Configurator 下载还来得透明。最后再分享一个小技巧编译成功后顺手把这个固件的源码 commit 和编译配置信息写进一个 txt 文件放在 hex 同目录。飞控这东西过几个月再来维护时你会庆幸自己留了这么个备忘。毕竟换个电脑重装环境、重新拉源码再想完全复现某个固件的编译条件还真不是看一眼 hex 文件名就能搞定的。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进