1. 项目缘起与整体方案拆解
1.1 为什么我会盯上"69元四旋翼"这个方向
先说结论:这个项目的核心吸引力不在于"便宜",而在于它把无人机开发里最劝退新手的几个门槛——飞控算法、无线通信、传感器融合、上位机协议——全部压缩到了一个可以在一周业余时间内跑通的体量里。我前后做过几版不同规模的多旋翼,从几百元的玩具级到上千元的自组穿越机,踩过的坑基本都集中在"硬件选型不匹配"和"调试链路太长"这两件事上。69元这个价位段,恰好卡在一个很微妙的位置:它逼着你放弃"堆料"的思路,转而认真思考每一个模块的必要性。
这个项目的本质,是一台基于 ESP32 主控的开源迷你四旋翼,具备手机 Web 遥控、IMU 校准、MAVLink 协议适配、定高定点飞行这几项能力。它解决的问题很具体:让一个没有飞控开发经验的人,能用最低的硬件成本,完整走一遍"传感器采集→姿态解算→电机控制→无线指令下发→地面站监控"的全链路。适合谁来参考?我认为有三类人:一是想入门嵌入式飞控的学生或爱好者;二是想给课程设计、毕业设计找一个有完整技术栈的选题的人;三是已经会写单片机代码、但没碰过姿态控制和无线遥控的开发者。
需要提前说明的是,69元这个数字通常指的是核心电子物料成本,不含机架结构件、电池、充电器、遥控手机这些"复用资产"。如果你从零开始,实际到手能飞的总投入大概在120到180元之间。这个预期要先摆正,否则做到一半发现预算翻倍会很难受。
1.2 整机架构是怎么划分的
我把整机拆成五个功能层,这样后面调试的时候出问题能快速定位到是哪一层:
| 层级 | 组成 | 核心职责 |
|---|---|---|
| 感知层 | IMU(加速度计+陀螺仪)、气压计 | 采集姿态角速度、加速度、高度 |
| 控制层 | ESP32 主控 | 姿态解算、PID 运算、电机混控 |
| 执行层 | 4×有刷电机 + 驱动 MOS | 接收 PWM 信号,输出推力 |
| 通信层 | ESP32 内置 WiFi | 手机 Web 页面遥控 + 遥测回传 |
| 协议层 | MAVLink 精简实现 | 与地面站/上位机对接 |
这个分层的好处是:每一层都可以单独验证。比如你怀疑是姿态解算的问题,就先把电机拆了,只看串口打印的欧拉角;怀疑是通信问题,就先不装桨,只看 Web 页面能不能收到遥测。我强烈建议按这个顺序逐层打通,而不是一上来就整机通电——整机通电一旦出问题,你根本不知道是哪一层炸的。
1.3 为什么选 ESP32 而不是 STM32
这是整个项目里最关键的选型决策,值得展开说。传统飞控几乎清一色用 STM32F4 系列,理由是浮点运算强、定时器资源丰富、生态成熟。但在这个项目里,我最终选了 ESP32,理由有三条:
第一,ESP32 自带 WiFi。如果用 STM32,你还得额外挂一个无线模块(比如 NRF24 或 ESP8266),不仅增加成本,还要多写一套串口通信协议。ESP32 把无线和主控合二为一,直接省掉一个模块和一层协议。
第二,双核架构。ESP32 有两个核心,我可以把 WiFi 协议栈和 Web 服务跑在 Core 0 上,把姿态解算和 PID 控制跑在 Core 1 上。这样网络抖动不会直接影响控制环的实时性。实测下来,控制环稳定在 500Hz 左右,网络任务偶尔卡顿几十毫秒也不会导致炸机。
第三,成本。一颗 ESP32 模组的价格在 10 到 15 元区间,而 STM32F405 加上无线模块轻松超过 40 元。在 69 元的总预算下,这个差价是决定性的。
当然代价也有:ESP32 的硬件浮点性能不如 F4,定时器精度也稍逊。但对于一台 65mm 轴距的迷你机来说,这些差距在实际飞行中几乎感知不到。选型的本质是匹配需求,不是堆参数。
1.4 手机 Web 遥控这个方案为什么值得做
很多人第一反应是"为什么不用遥控器"。原因很现实:一个像样的航模遥控器加接收机,价格就超过整机预算了。而手机 Web 遥控的好处是零额外硬件成本,且跨平台——安卓、iOS、甚至带浏览器的电脑都能用。
技术实现上,ESP32 开一个 HTTP 服务,手机浏览器访问它的 IP 地址,页面用 JavaScript 的touchstart/touchmove事件捕获摇杆操作,通过 WebSocket 把油门、俯仰、横滚、偏航四个通道的数值实时推给飞控。WebSocket 比轮询 HTTP 延迟低得多,实测端到端延迟在 30 到 60ms 之间,对于定高定点这种温和飞行模式完全够用。
注意:Web 遥控不适合做激烈机动飞行,因为浏览器的事件采样率和网络抖动不可控。它的定位是"低成本入门验证平台",不是"竞技飞行方案"。这个心理预期要提前建立。
2. 硬件选型与核心细节解析
2.1 物料清单与成本拆解
先把账算清楚。下面是我这一版实际用到的物料,价格按批量采购的均价估算:
| 物料 | 规格 | 数量 | 单价(元) | 小计(元) |
|---|---|---|---|---|
| 主控模组 | ESP32-WROOM-32 | 1 | 13 | 13 |
| IMU | MPU6050 | 1 | 6 | 6 |
| 气压计 | BMP280 | 1 | 5 | 5 |
| 有刷电机 | 8520 空心杯 | 4 | 4 | 16 |
| 驱动 MOS | SI2302 | 4 | 0.5 | 2 |
| 机架 | 65mm 塑料/碳纤 | 1 | 12 | 12 |
| 螺旋桨 | 31mm 正反桨 | 4 | 1 | 4 |
| 电池 | 1S 3.7V 300mAh | 1 | 8 | 8 |
| 杂项 | 电阻、电容、PCB、线材 | - | 3 | 3 |
| 合计 | 69 |
可以看到,电机和机架占了大头。如果你想进一步压成本,可以把机架换成 3D 打印件,能再省 5 到 8 元,但结构强度会打折扣,炸机时容易断臂。
2.2 MPU6050 与 BMP280 的搭配逻辑
姿态感知用 MPU6050,这是最经典也最便宜的六轴 IMU,集成三轴加速度计和三轴陀螺仪,I2C 接口,价格常年稳定在 6 元左右。它的缺点是陀螺仪零偏随温度漂移明显,所以上电必须做校准,这一点后面会详细讲。
高度感知用 BMP280,气压计,同样 I2C 接口。为什么定高需要气压计而不是只用加速度计?因为加速度计积分两次得到高度,误差会随时间平方级累积,飞个十几秒高度就飘得没法看了。气压计测的是绝对气压换算的高度,虽然受气流和温度影响,但短期稳定性远好于纯惯性积分。两者融合(加速度计做高频补偿,气压计做低频基准)才能得到可用的高度估计。
实操心得:MPU6050 和 BMP280 可以挂在同一条 I2C 总线上,地址不冲突(前者 0x68,后者 0x76 或 0x77)。但布线时尽量让这两颗芯片远离电机驱动走线,否则 MOS 开关产生的高频噪声会串进 I2C,导致读数跳变。我第一版就是把 IMU 放在电机正上方,结果陀螺仪数据里全是尖刺,后来挪到机架中心偏后位置才干净。
2.3 有刷电机与驱动电路的关键细节
8520 空心杯电机是这类迷你机的标配,工作电压 3.7V,空载转速约 40000 转,堵转电流能到 3A 以上。驱动用 SI2302 这颗 N 沟道 MOS,导通电阻约 60mΩ,配合 ESP32 输出的 PWM 信号就能调速。
这里有个必须注意的坑:ESP32 的 GPIO 输出高电平是 3.3V,而 SI2302 的开启电压(Vgs(th))在 0.65V 到 1.5V 之间,3.3V 完全能驱动它进入低阻导通区,所以不需要额外的电平转换。但如果你用的是逻辑电平阈值更高的 MOS,就可能出现"半开"状态,MOS 发热严重甚至烧毁。选 MOS 时一定要看 Vgs(th) 参数,确保 3.3V 能可靠开启。
PWM 频率我设在 20kHz 以上,超出人耳听觉范围,避免电机发出刺耳的啸叫。ESP32 的 LEDC 外设支持这个频率,用ledcSetup()配置即可。占空比分辨率设 10 位(0 到 1023),对应油门 0 到 100%。
2.4 电源方案与噪声抑制
整机用 1S 锂电池供电,标称 3.7V,满电 4.2V,放电截止 3.3V。ESP32 和传感器需要 3.3V 稳定供电,所以要么用低压差稳压器(LDO),要么直接用电池电压(因为 1S 电压范围刚好在 ESP32 工作区间内)。
我选择的是直接电池供电 + 大电容滤波的方案。原因是用 LDO 会引入额外的压降和发热,而 1S 电池的电压范围(3.3 到 4.2V)正好在 ESP32 的 3.0 到 3.6V 推荐工作区间边缘——等等,这里有个问题:满电 4.2V 超过了 ESP32 的 3.6V 上限。所以严格来说,必须加 LDO 或者用带保护板的电池。
我最终的方案是:电池经过一颗 3.3V LDO 给 ESP32 和传感器供电,电机则直接从电池取电(电机不怕电压波动)。这样电机的大电流波动不会影响主控供电。同时在电机电源两端并联 100μF 电解电容和 0.1μF 陶瓷电容,吸收换向噪声。
注意:电机和主控共地是必须的,否则 PWM 信号没有参考电平。但共地会带来噪声耦合,所以地线要走粗、走短,最好用星型接地——所有地线汇聚到一个点,而不是串成一串。
3. 实操过程与核心环节实现
3.1 开发环境搭建与基础验证
第一步是把开发环境跑通。我用的是 Arduino IDE 配合 ESP32 开发板支持包,版本选 2.0.x 系列,比较稳定。安装步骤:
- 在 Arduino IDE 的"首选项"里添加 ESP32 的板管理器网址。
- 在"开发板管理器"里搜索 esp32,安装最新稳定版。
- 开发板选择"ESP32 Dev Module",上传速率设 921600。
- 先用一个最简单的 Blink 程序验证烧录链路正常。
这一步看似简单,但新手最容易卡在驱动上。ESP32 开发板通常用 CP2102 或 CH340 串口芯片,Windows 下可能需要手动装驱动。如果设备管理器里看不到串口,先换一根数据线试试——很多线是"只充电不传数据"的。
基础验证通过后,先单独测试 MPU6050。烧一段 I2C 扫描代码,确认能在 0x68 地址上找到设备。然后读取原始加速度和角速度数据,静止放置时,加速度计的 Z 轴应该接近 1g(约 16384 LSB,如果量程设 ±2g),陀螺仪三轴应该接近 0(会有零偏,正常)。
3.2 IMU 校准的完整流程
IMU 校准是这个项目里最不能省的一步。陀螺仪零偏如果不校准,飞机会持续往一个方向漂,你打杆修正都来不及。
校准分两部分:
陀螺仪零偏校准:把飞机水平静止放置,采集 2000 个样本,求每个轴的平均值,这个平均值就是零偏。后续每次读取陀螺仪数据都减去这个零偏。代码逻辑大致是:
float gyroBias[3] = {0, 0, 0}; for (int i = 0; i < 2000; i++) { readMPU6050(); gyroBias[0] += gyroX; gyroBias[1] += gyroY; gyroBias[2] += gyroZ; delay(2); } gyroBias[0] /= 2000.0; gyroBias[1] /= 2000.0; gyroBias[2] /= 2000.0;加速度计校准:加速度计需要校准的是比例因子和零偏。简单做法是六面校准——把飞机分别朝上、朝下、朝前、朝后、朝左、朝右六个方向静止放置,每个方向采集数据,理论上每个方向应该读到 ±1g。通过这六组数据可以解算出每个轴的零偏和比例误差。
实操心得:校准时的"水平"很重要。我一开始随手把飞机放在桌上就校准,结果桌面本身有 2 度倾斜,导致飞机起飞后一直往一个方向飘。后来我用手机的水平仪 App 找了个真正水平的台面,问题才解决。校准环境还要远离震动源,比如别放在洗衣机旁边。
校准数据要存到 ESP32 的 NVS(非易失存储)里,这样每次上电不用重新校准。但要注意,温度变化大的时候零偏会漂,所以如果从室内拿到室外飞,最好重新校准一次。
3.3 姿态解算:从原始数据到欧拉角
有了校准后的数据,下一步是姿态解算。我用的是 Mahony 互补滤波算法,比卡尔曼滤波简单,计算量小,在 ESP32 上跑 500Hz 毫无压力。
算法的核心思想是:陀螺仪积分得到姿态,短期准但长期漂;加速度计测重力方向,长期准但短期噪声大。两者互补融合,用加速度计去修正陀螺仪的漂移。
具体实现上,Mahony 算法通过加速度计测量值和估计重力方向的叉积得到误差,用这个误差去修正陀螺仪的角速度,再积分得到四元数,最后转成欧拉角。关键参数是两个增益:Kp和Ki。Kp决定加速度计修正的强度,Ki消除稳态误差。
我实测下来,Kp = 2.0、Ki = 0.005这组参数在 500Hz 采样率下表现比较均衡。Kp太大姿态会跟着加速度计噪声抖,太小则修正太慢、漂移明显。这个参数需要根据你的实际噪声水平微调。
// Mahony 互补滤波核心片段 float recipNorm = invSqrt(ax*ax + ay*ay + az*az); ax *= recipNorm; ay *= recipNorm; az *= recipNorm; // 估计重力方向 float vx = 2*(q1*q3 - q0*q2); float vy = 2*(q0*q1 + q2*q3); float vz = q0*q0 - q1*q1 - q2*q2 + q3*q3; // 误差 = 测量叉乘估计 float ex = ay*vz - az*vy; float ey = az*vx - ax*vz; float ez = ax*vy - ay*vx; // 修正陀螺仪 gx += Kp*ex + Ki*exIntegral; // ... 四元数更新3.4 PID 控制环的设计与调参
姿态解算出来的是角度,控制的目标是让角度稳定在期望值。这里用串级 PID:外环是角度环,输入是期望角度和实际角度的差,输出是期望角速度;内环是角速度环,输入是期望角速度和陀螺仪实测角速度的差,输出是电机修正量。
为什么用串级而不是单环?因为角速度环响应快,能快速抑制扰动;角度环响应慢,负责跟踪目标。串级结构让系统既有快速抗扰能力,又有准确的稳态跟踪。
调参顺序是先内环后外环。先把外环参数设为零,只调内环,让飞机在手里能感受到明显的"抵抗"——你转动它,电机会反向补偿。内环P从 0.5 开始加,加到电机开始高频抖动就回退一点。然后调外环,P从 2.0 开始,加到飞机能快速回正但不振荡。
| 环 | 参数 | 起始值 | 调整方向 | 现象判断 |
|---|---|---|---|---|
| 角速度内环 | P | 0.5 | 增大 | 电机高频抖动说明过大 |
| 角速度内环 | I | 0.002 | 微增 | 消除稳态误差 |
| 角速度内环 | D | 0.001 | 微增 | 抑制超调 |
| 角度外环 | P | 2.0 | 增大 | 回正慢说明过小,振荡说明过大 |
注意:调参一定要拆桨!我见过太多人带桨调参,一振荡就飞起来撞墙。拆桨后用手感受电机响应,安全又直观。
3.5 手机 Web 遥控页面的实现
Web 页面这块,我用了一个极简方案:HTML + CSS + 原生 JavaScript,不依赖任何框架。页面主体是两个虚拟摇杆(左摇杆控制油门和偏航,右摇杆控制俯仰和横滚),用 Canvas 或 div 加触摸事件实现。
通信走 WebSocket。ESP32 端用WebSocketsServer库开一个服务,端口 81。手机连上后,每次触摸事件触发就发送一个 JSON 字符串,包含四个通道的值。ESP32 收到后解析并更新目标姿态。
// 前端发送逻辑 const ws = new WebSocket('ws://' + location.hostname + ':81/'); function sendControl() { const cmd = { thr: throttle, // 0-1000 yaw: yawRate, // -500 to 500 pit: pitch, // -500 to 500 rol: roll // -500 to 500 }; ws.send(JSON.stringify(cmd)); }ESP32 端要做失控保护:如果超过 500ms 没收到新指令,自动把油门降到零。这个逻辑必须有,否则手机断连后飞机会一直往上飞。
3.6 MAVLink 协议适配与定高定点
MAVLink 是一套轻量级的无人机通信协议,地面站(比如 QGroundControl)通过它显示遥测和发送指令。在 ESP32 上实现完整的 MAVLink 不现实,但实现一个精简子集是可行的。
我实现的消息类型包括:HEARTBEAT(心跳)、ATTITUDE(姿态)、GLOBAL_POSITION_INT(位置,这里用气压高度代替)、RC_CHANNELS_OVERRIDE(遥控通道)。这些消息通过串口或 WiFi UDP 发给地面站,就能在地面站上看到飞机的实时姿态和高度。
定高功能的实现逻辑是:气压计测高度,和期望高度做差,经过一个 PID 控制器输出油门修正量,叠加到基础油门上。这里的关键是气压计数据要滤波,因为气流和温度会让气压读数抖动。我用的是滑动平均加低通滤波,窗口大小 10,截止频率 2Hz。
定点功能在只有 IMU 和气压计的情况下,严格来说做不到真正的水平定点(那需要光流或 GPS)。但可以做"姿态锁定"——即飞机受到扰动后自动回到水平姿态,这在无风环境下视觉上接近定点。如果要真正的水平定点,需要加一个光流模块,成本会增加 20 元左右。
4. 常见问题与排查技巧实录
4.1 上电后电机不转或只转一个
这是最高频的问题。排查顺序如下:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 全部不转 | 电源未接通/电池没电 | 万用表测电池电压 |
| 全部不转 | PWM 引脚配置错误 | 检查代码引脚定义 |
| 只转一个 | 该路 MOS 损坏 | 替换 MOS 测试 |
| 只转一个 | 该路电机损坏 | 直接给电机加电压测试 |
| 转速异常 | PWM 频率不对 | 示波器看波形 |
我遇到过一次"只转一个"的情况,查了半天代码没问题,最后发现是那颗 MOS 的栅极电阻虚焊。所以焊接质量是这类小项目里最容易被忽视的变量。建议用放大镜检查每个焊点,尤其是 MOS 和电机接线。
4.2 起飞后往一个方向猛飘
这个问题几乎都和 IMU 校准有关。排查步骤:
- 确认校准时的水平度。用手机水平仪验证台面。
- 检查校准数据是否正确写入 NVS。可以在串口打印出来看。
- 检查电机安装方向。四个电机的正反桨和旋转方向必须正确,否则会产生持续的偏航力矩。
- 检查机架重心。重心不在几何中心会导致悬停时需要额外的姿态补偿。
实操心得:我建议在机架中心画一个十字标记,装机时确保电池和所有重物都对称分布。重心偏差 5mm 就足以让飞机明显往一边飘。
4.3 姿态数据抖动严重
如果串口打印的欧拉角一直在跳,先看原始数据。如果原始数据就抖,那是硬件问题;如果原始数据干净但解算后抖,那是算法参数问题。
硬件层面的抖动通常来自:电源噪声(加电容)、I2C 走线太长(缩短或加屏蔽)、电机干扰(IMU 远离电机)。算法层面则是Kp太大,把加速度计噪声放大了。
4.4 Web 遥控延迟高或断连
WiFi 遥控的稳定性受环境影响很大。几个优化点:
- 把 ESP32 的 WiFi 模式设为 AP 模式,手机直连,不经过路由器,延迟最低。
- WebSocket 心跳间隔设短一点,比如 1 秒,及时发现断连。
- 手机浏览器别开太多后台标签,会抢占网络资源。
- 飞行时手机和飞机的距离别超过 20 米,迷你机的 WiFi 天线很小,远了信号就弱。
4.5 定高时高度来回振荡
这是高度 PID 参数没调好。气压计本身有噪声,如果P太大,油门会跟着噪声剧烈变化。解决方法是先加大气压计的滤波强度,再调 PID。另外,飞机在室内飞时,螺旋桨产生的气流会冲击气压计,导致读数偏低。可以在气压计上贴一块海绵做气流缓冲,效果立竿见影。
5. 飞行测试与迭代优化
5.1 首飞前的检查清单
首飞是最容易炸机的环节,我整理了一份检查清单,每次首飞前都过一遍:
- 电池电压是否在 3.8V 以上
- 螺旋桨是否装紧、方向是否正确
- IMU 是否已校准、校准数据是否加载
- 遥控链路是否连通、失控保护是否生效
- 电机转向是否正确(对角同向)
- 机架螺丝是否拧紧
- 飞行区域是否空旷、有无人员
5.2 从悬停到定高的渐进测试
不要一上来就开定高。正确的测试顺序是:
- 地面测试:拆桨,推油门看电机响应是否线性。
- 低空悬停:手动控制,离地 20cm,感受姿态稳定性。
- 姿态模式:松开摇杆,看飞机能否自动回正。
- 定高模式:在 1 米高度开启,观察高度保持效果。
- 定点模式:在无风环境测试姿态锁定。
每一步稳定了再进下一步。我见过太多人跳过步骤直接开定高,结果飞机一飞冲天撞天花板。
5.3 续航与重量的平衡
1S 300mAh 电池的理论续航约 5 到 7 分钟,实际因为电压下降和电机效率降低,大概 4 分钟。想延长续航有两条路:一是减重,二是换更大容量电池。但换大电池会增加重量,反而可能缩短续航。这里有个经验公式:电池重量每增加 1g,续航增加约 0.5%,但超过某个临界点后续航反而下降。对于 65mm 机架,电池重量控制在 10g 以内比较合适。
5.4 后续可扩展的方向
这个平台跑通后,可以往几个方向扩展:加光流模块实现真正的水平定点;加超声波模块提升低空定高精度;把 Web 遥控换成真正的遥控器接收机;甚至加一个简单的图传模块。每加一个模块,都是对飞控代码的一次重构练习,技术收益很高。
我个人在实际操作中的体会是,这类迷你四旋翼最大的价值不在于飞得多好,而在于它把整个飞控技术栈压缩到了一个你能完全掌控的规模。你改一行 PID 参数,立刻能在飞行中感受到变化;你换一个滤波算法,姿态数据马上不一样。这种即时反馈是学习嵌入式控制最宝贵的东西。最后再分享一个小技巧:每次改完代码,先在串口里把关键数据打印出来看一遍,确认逻辑对了再上电试飞,能省下大量炸机维修的时间。