Flix:ESP32 四旋翼从零制作、调试与源码学习教程
基于 okalachev/flix 当前 master 分支文档与关键源码整理。目标不是“把 README 翻译一遍”,而是给出一条真正可执行的制作、验证、首飞、仿真和二次开发路径。
项目定位:Flix 到底解决什么问题
Flix(flight + X)是一套“从通用元件开始做一台能飞的四旋翼”的开源方案。仓库同时包含飞控固件、硬件/装配文档、Gazebo 仿真、Python 控制库和日志分析工具。
| 能力 | 当前实现 | 学习价值 |
|---|---|---|
| 飞控固件 | Arduino/C++,主循环约 1000 Hz,代码规模较小 | 适合看懂飞控最小闭环 |
| 姿态估计 | 陀螺积分 + 加速度计修正 + 低通滤波 | 理解 IMU、姿态和漂移修正 |
| 控制 | RAW / ACRO / STAB / AUTO;姿态环 → 角速度环 | 理解级联 PID 与电机混控 |
| 通信 | MAVLink,经 Wi‑Fi / ESP‑NOW | 学习地面站、遥测、参数与控制协议 |
| 仿真 | Gazebo 11 运行原 Arduino 逻辑 | 先仿真再上真机,便于调参与开发 |
| Python | pyflix:遥测、参数、CLI、AUTO 姿态/角速度控制 | 快速做自动飞行实验 |
| 位置控制 | 项目仍标注开发中;pyflix 的 position / velocity 目标暂不可用 | 适合作为后续扩展课题 |
架构与源码:先把数据流看懂
2.1 仓库主结构
2.2 1000 Hz 主循环
flix/flix.ino 的主循环非常直白,几乎就是整套飞控的数据流图:
void loop() {
readIMU();
step();
readRC();
estimate();
control();
sendMotors();
handleConsole();
processMavlink();
readVoltage();
logData();
syncParameters();
}
这也是推荐的源码阅读顺序:先看数据从哪里来,再看它如何被估计、控制和输出。
2.3 姿态估计
estimate.ino先对 gyro 做低通滤波,默认注释约 40 Hz。- 每个周期用角速度 ×
dt形成旋转向量,再更新四元数姿态。 - 无人机静止/落地时,用加速度计重力方向修正姿态漂移。
- 飞行中不直接信任加速度计作为重力方向,而是用很弱的 level correction 抑制长期漂移。
2.4 级联控制与电机混控
control.ino 默认模式为 STAB。姿态控制先把姿态误差变成角速度目标,再由角速度 PID 产生三轴 torque target,最后与总推力一起混到四个电机。
FL = thrust + roll - pitch + yaw
FR = thrust - roll - pitch - yaw
RL = thrust + roll + pitch - yaw
RR = thrust - roll + pitch + yaw
源码默认参数可作为阅读起点,而不是直接当作“任何机架都通用”的最终调参:roll/pitch rate PID 约 P=.05 I=.2 D=.001,yaw rate P 约 .3;姿态 P 则 roll/pitch 约 6、yaw 约 3。
2.5 其他关键模块
| 文件 | 职责 | 看什么 |
|---|---|---|
imu.ino | 读取 IMU、校准 | 总线、采样率、偏置与安装方向 |
rc.ino | RC 输入与校准 | 通道映射、控制量归一化 |
estimate.ino | 姿态估计 | gyro 积分、acc 修正、四元数 |
control.ino | 控制器 | STAB/ACRO/AUTO、级联 PID、mixer |
motors.ino | PWM 输出 | 频率、分辨率、ESC/有刷映射 |
mavlink.ino | MAVLink | 遥测、地面站、Python 控制入口 |
parameters.ino | NVS 参数 | ESP32 Preferences 持久化 |
console.ino | 串口/MAVLink CLI | 调试命令与运行状态 |
BOM:先准备哪些硬件和工具
| 类别 | 建议/仓库当前说明 | 数量 |
|---|---|---|
| 主控 | ESP32 Mini;也支持 ESP32-S3 / ESP32-C3 | 1 |
| IMU | GY-91 / MPU-9250/6500、ICM-20948、MPU-6050 等受支持型号 | 1 |
| 电机 | 8520 3.7V 有刷电机,注意轴径与桨孔匹配 | 4 |
| 螺旋桨 | 55 mm 或 65 mm,A/B 桨按旋向匹配 | 4 |
| MOSFET | UMW 100N03A 或兼容型号;仓库特别提醒不要混用不可靠同名厂牌 | 4 |
| 电阻 | 10 kΩ,作为下拉/电压测量网络 | 若干 |
| 电池 | 1S 3.7V Li‑Po;README 建议足够放电倍率,示例要求 25C 或更高 | 1 |
| 机架 | 仓库提供 STL/STEP;建议层高 0.2 mm、线宽 0.4 mm、100% infill | 1 套 |
| 遥控 | 可选:SBUS 遥控接收机;或 Wi‑Fi + 手机/QGC;或 USB 双摇杆 | 1 |
| 工具 | 3D 打印机、烙铁、焊锡/助焊剂、螺丝刀、万用表 | — |
硬件制作:从机架到完整接线
打印并检查机架
从仓库 README 中下载主机架、上盖/ESP32 holder、IMU 垫片 STL/STEP。打印后检查四个电机孔是否紧,IMU 安装面是否平整。
规划电源与 MOSFET 区域
先在机架上摆放 ESP32、4 个 MOSFET、电池线和 IMU,确认不会压到螺旋桨扫掠区。MOSFET 需要下拉电阻,线路尽量短、固定牢靠。
安装四个电机
电机必须非常紧。仓库明确指出,松动和振动会破坏姿态估计并导致飞行不稳。如果孔偏松,用很小的胶带片做紧固,不要让电机在孔内晃动。
按默认机架映射接电机
| 电机 | 位置 | 旋向 | 桨 | 线色 | ESP32 默认 GPIO |
|---|---|---|---|---|---|
| M0 | 后左 | 逆时针 | B | 黑/白 | GPIO12 |
| M1 | 后右 | 顺时针 | A | 蓝/红 | GPIO13 |
| M2 | 前右 | 逆时针 | B | 黑/白 | GPIO14 |
| M3 | 前左 | 顺时针 | A | 蓝/红 | GPIO15 |
安装 IMU
使用垫片和 M3×5 螺丝固定 IMU;先保证板子不翘曲、安装方向可确定。后续通过 IMU_ROT_ROLL/PITCH/YAW 参数把传感器坐标系对到机体坐标系。
接 IMU 总线
README 的 ESP32 Mini + SPI 示例为:SCK→GPIO18、MOSI→GPIO23、MISO→GPIO19、CS→GPIO5,供电 3.3V/GND。若用 I²C 或其他主控,以参数配置和板卡能力为准。
固定 ESP32 与上盖
官方简要装配文档使用双面胶把 ESP32 固定到上框,再用 M1.4×5 螺丝固定上框。重点不是“胶带必须相同”,而是主控不能在飞行中移动或接触桨叶。
可选:SBUS 接收机与电压监测
SBUS 示例信号接 GPIO4;电池电压监测要通过分压后进入 ADC1 引脚,不能把 4.2V 电池直接送进 ESP32 ADC。
通电前万用表检查
- VCC 与 GND 无短路。
- 电池极性正确。
- 每个 MOSFET 的 Gate 没有悬空。
- IMU 供电是 3.3V。
- 电机线没有裸露互碰。
固件烧录:两条路线
路线 A:预编译固件,最快让板子跑起来
- 按 ESP32 / ESP32-S3 / ESP32-C3 / Flix2 板型下载仓库文档提供的 merged binary。
- 打开 ESP32 Web Flasher,连接板子的串口。
- 选择下载的固件。
- Flash address 设置为 0。
- Program,完成后重启。
适合“先验证硬件/网络/地面站”的第一轮。
路线 B:源码构建,适合学习与二次开发
Arduino IDE
- Arduino IDE 2.x。
- 安装 ESP32 core 3.3.10。
- Library Manager 安装
FlixPeriph最新版。 - 安装
MAVLink 2.0.33。 - 打开
flix/flix.ino。 - ESP32 Mini 选择 WEMOS D1 MINI ESP32;S3 常用 ESP32S3 Dev Module。
- Tools → Core Debug Level → Error。
- Build & Upload。
命令行
git clone https://github.com/okalachev/flix.git
cd flix
make
make upload
make upload monitor
Makefile 会安装指定 ESP32 core 和 Arduino 库。S3/C3 可以通过 BOARD 覆盖 FQBN:
make BOARD=esp32:esp32:esp32s3 uploadflix Wi‑Fi 出现,Console/QGroundControl 基础链路能工作,就说明构建与网络部分大体正确。首飞前配置:按这个顺序,不要跳步
上电后连接 Wi‑Fi flix,默认密码 flixwifi。启动 QGroundControl,通常会自动看到遥测。
USB 串口使用 115200;无线方式:QGroundControl → Analyze Tools → MAVLink Console。先执行 help。
设置 IMU_MODEL、IMU_BUS 与 SPI/I²C 引脚参数,然后 reboot。执行 imu,要求 status: OK,采样率约 1000 Hz。
通过 IMU_ROT_ROLL / PITCH / YAW 对齐机体坐标。机体坐标系为 X 向前、Y 向左、Z 向上。
Console 执行 ca,严格按照终端提示做多姿态校准。
非默认引脚设置 MOTOR_PIN_FL/FR/RL/RR。若改用无刷 ESC,还要重新设置 PWM stop/min/max 与频率。
执行 mfr、mfl、mrl、mrr,逐个核对“位置 + 旋向 + A/B 桨”。此时不要装桨。
拿起机体做 roll/pitch/yaw,QGroundControl 姿态指示必须与真实方向一致。方向错时先修 IMU orientation,不要用“反向电机”去补。
控制与首飞:先求可控,再求性能
7.1 三种人工控制方式
- 手机:QGroundControl Virtual Joystick 或 Mavlink Joystick,经 Wi‑Fi。
- SBUS 遥控:设置
RC_RX_PIN,用rc检查接收,再执行cr校准。 - USB 双摇杆:电脑运行 QGroundControl,经 Wi‑Fi 连接无人机,在 Vehicle Setup → Joystick 中校准。
7.2 默认摇杆与解锁
默认左摇杆:Throttle + Yaw;右摇杆:Pitch + Roll。
| 动作 | 手势 | 源码对应 |
|---|---|---|
| Arm | 油门最低 + Yaw 最右 | throttle < .05 && yaw > .95 |
| Disarm | 油门最低 + Yaw 最左 | throttle < .05 && yaw < -.95 |
7.3 第一趟真实飞行建议
- 满电电池,检查 4 个桨均无裂纹。
- 先在地面解锁,观察四电机是否平稳、无强振动。
- 轻推油门,只做短暂“减重/离地”。
- 如果机体明显向固定方向翻,立刻收油,回到电机旋向、桨型、IMU 朝向检查。
- 能够稳定离地后,再尝试 0.5–1 m 悬停。
- 先保持默认 STAB,不要第一飞就切 ACRO/AUTO。
Gazebo 仿真:最值得先跑通的一步
Flix 的模拟器使用 Gazebo 11,并把飞控 Arduino 逻辑放进虚拟物理环境。项目文档推荐 Ubuntu 20.04;近期 macOS 上 Gazebo 11 兼容性不理想。
安装与启动
git clone https://github.com/okalachev/flix.git
cd flix
# 安装 Arduino CLI 后,安装 Gazebo 11 / libgazebo11-dev / SDL2 等依赖
make simulator
模拟器三层结构
仿真启动后,可以像真机一样用 QGroundControl 手机虚拟摇杆或 USB 摇杆控制。这样可以在没有螺旋桨风险的情况下熟悉 arm/disarm、参数、控制模式和 pyflix。
Python / pyflix:遥测与自动飞行实验
9.1 安装与连接
pip install pyflix
from pyflix import Flix
flix = Flix() # 等待网络中的 Flix
print(flix.connected)
print(flix.attitude_euler)
print(flix.rates)
print(flix.voltage)
真机要先连接 flix Wi‑Fi;模拟器则让脚本与运行 Gazebo 的主机处于同一网络。
9.2 参数、CLI 与事件
pitch_p = flix.get_param('CTL_ATT_P_P')
flix.set_param('CTL_ATT_P_P', 5)
print(flix.cli('imu'))
print(flix.cli('time'))
flix.on('connected', lambda: print('Connected'))
att = flix.wait('attitude_euler', timeout=3)9.3 AUTO 模式
pyflix 当前可发姿态目标、角速度目标,也可以直接设置 raw motor outputs。教程建议先只使用“姿态/角速度目标”,不要从 raw motor outputs 开始。
flix.set_mode('AUTO')
# 姿态目标:roll / pitch / yaw + thrust
flix.set_attitude([0.0, 0.0, 0.0], 0.25)
# 停止推力:AUTO 下会自动 disarm
flix.set_attitude([0.0, 0.0, 0.0], 0.0)
当前文档明确标注 set_position 和 set_velocity 仍在开发、暂不可用。
9.4 与 QGroundControl 同时使用
flix-proxy然后在 QGroundControl → Comm Links 新建 UDP link,端口 14560,即可让 pyflix 脚本和 QGC 同时工作。
日志与排错:飞不稳时怎么定位
10.1 日志
固件默认把飞行日志暂存在 RAM,文档默认容量约 10 秒、100 Hz。Wi‑Fi 连上无人机后:
make log
# CSV 保存在 tools/log/*.csv
make plot
# 用 PlotJuggler 打开最近一次日志
make log plot也可以把 CSV 转成 ULog 给 FlightPlot,或转 MCAP 给 Foxglove Studio。
10.2 常见故障矩阵
| 现象 | 优先检查 | 验证方式 |
|---|---|---|
| 编译失败 | ESP32 core 版本、FlixPeriph/MAVLink 版本、板型 | 对照 3.3.10 / MAVLink 2.0.33 |
| QGC 不连接 | Wi‑Fi、VPN、Firewall | 确认连到 flix,临时关闭阻断项 |
| 乱码 | 串口波特率 | 115200 |
| IMU 无数据 | 型号、SPI/I²C、引脚、供电 | imu:status OK / rate≈1000 |
| 姿态方向反 | IMU rotation | 手动翻滚并看 QGC 姿态仪 |
| 起飞即翻 | 电机位置/旋向、A/B 桨、IMU 方向 | 拆桨逐个 mfr/mfl/mrl/mrr |
| 高频抖动 | 机架振动、PID、桨/电机不平衡 | 先机械紧固,再看 rates / motor log |
| 推力不足 | 电池电压/放电能力、电机/桨、接线损耗 | 满电约 4.2V;万用表 + 日志 |
二次开发:怎么改代码最稳妥
11.1 增加一个新子系统
官方架构文档给出的推荐方式:
- 新建一个
*.ino。 - 定义
setupXxx()与loopXxx()。 - 需要降频时使用
Rate,不要在飞行主线程里做阻塞延时。 - 在
flix.ino的setup()/loop()注册调用。
Rate mySubsystemRate(100); // 100 Hz
void loopMySubsystem() {
if (!mySubsystemRate) return;
// do something lightweight
}11.2 增加参数
参数系统基于 ESP32 Preferences 存到非易失存储。一般流程:定义全局变量 → 加入 parameters 表 → 需要时绑定参数变化回调。这样 QGroundControl、Console 和 pyflix 都能继续复用统一参数入口。
11.3 不要在飞行主线程 delay()
官方文档明确警告:这是经典 Arduino 单线程设计,飞行时命令里使用阻塞 delay() 可能直接让飞控失去实时性。地面命令需要暂停时使用项目自己的 pause(),以保持 MAVLink 连接处理。
11.4 值得做的二次开发方向
- 扩展 IMU 或磁力计/气压计支持,并评估更强的姿态/高度估计。
- 把 AUTO 从姿态/角速度目标推进到位置/速度闭环。
- 给 pyflix 增加实验脚本、轨迹生成与安全状态机。
- 为日志增加更多控制误差字段,并自动生成调参图表。
- 用 Gazebo 建立参数扫描/回归测试,减少真机试错。
源码学习路线:按 7 个阶段吃透
裸 ESP32 刷固件 → Wi‑Fi → QGC → Console。目标:理解工具链与通信。
Gazebo → arm/disarm → STAB 手飞。目标:建立“参数变化 ↔ 飞行响应”的直觉。
flix.ino → util/time → imu → estimate。目标:搞懂 1 kHz 数据链。
control.ino → pid.h → motors.ino。自己手算一次姿态误差、rate error 和 mixer。
mavlink.ino → console.ino → parameters.ino。目标:知道 QGC/pyflix 怎么进飞控。
pyflix 读遥测 → 参数读取 → AUTO 姿态目标 → 事件等待。
装配 → 校准 → 低高度首飞 → 下载日志 → 找误差 → 一次只改一类参数。