基于Arduino与PID控制的平衡木无人机:从传感器融合到姿态稳定实战

发布时间:2026/7/28 4:12:06
基于Arduino与PID控制的平衡木无人机:从传感器融合到姿态稳定实战
1. 项目概述从零打造一台会“走钢丝”的无人机看到“平衡木”这三个字你可能想到的是体操运动员的优雅身姿。但如果主角换成一台无人机呢这就是我们今天要聊的玩意儿一台能自主在狭窄横梁上保持平衡甚至实现定点悬停的“平衡木无人机”。这听起来像是实验室里的高级玩具但核心原理其实很接地气就是Arduino、无刷电机和PID控制这三兄弟的经典组合。我折腾这个项目的初衷是想抛开复杂的商业飞控从最底层理解多旋翼无人机是如何“站”稳的。市面上大多数教程都停留在让四轴飞起来但“走钢丝”这个场景把姿态控制的精度和响应速度要求直接拉满是对PID算法理解的一次绝佳实战。简单来说这个项目就是构建一个简化版的“两轴”无人机系统。想象一下把一台四轴无人机的两只“脚”放在一根横梁上它只能前后俯仰和左右横滚两个方向发生倾倒。我们的任务就是让飞控板Arduino通过读取陀螺仪和加速度计的数据实时计算出无人机的倾斜角度和角速度然后通过PID算法快速计算出纠正力矩最后通过电调驱动无刷电机改变推力对抗倾倒维持平衡。整个过程就像你用手指顶着一根长杆需要不断微调手指的位置来保持杆子直立。只不过我们的“手指”是算法“肌肉”是无刷电机。这个项目适合谁如果你是电子、自动化或机器人相关专业的学生想找一个能串联单片机、传感器、电机控制和经典控制理论的课程设计或毕业设计那它再合适不过。如果你是一名创客或硬件爱好者已经玩腻了让小车循迹、让机械臂抓取想挑战更动态、更实时的控制系统这里有你想要的刺激和成就感。当然前提是你得有焊接、调试和那么一点点“摔机”的心理准备。整个系统涉及嵌入式编程、传感器融合、电机驱动和闭环控制是一个典型的机电一体化综合实践。2. 核心硬件选型与系统架构设计动手之前得先把家伙事儿备齐并想清楚它们怎么协同工作。硬件选型直接决定了系统的性能上限和调试难度而一个清晰的系统架构则是软件开发的蓝图。2.1 主控与传感器系统的“大脑”与“小脑”主控芯片我选择了Arduino Nano。原因很简单够用、便宜、生态好。对于这个平衡木项目我们不需要处理图像或运行复杂的SLAM算法核心任务就是高速读取传感器数据、执行PID运算、输出PWM信号。Nano基于ATmega328P16MHz的主频和2KB的RAM对于单个PID环的实时计算绰绰有余。它的引脚也足够连接MPU6050和两个电调。当然如果你追求更高的性能储备和更多的外设比如将来想加超声波定高Arduino Mega 2560或ESP32是更好的选择尤其是ESP32自带蓝牙/Wi-Fi方便无线调参。但初期Nano的简洁和低门槛是巨大优势。传感器方面姿态感知是平衡的基础。我直接使用了经典的MPU6050六轴传感器模块。它集成了三轴陀螺仪和三轴加速度计通过I2C通信价格低廉资料丰富。陀螺仪测量角速度积分后可以得到角度但存在漂移加速度计测量比力在静态或慢速下可以通过反正切计算倾角但对振动敏感。两者优缺点互补需要通过软件进行“传感器融合”。这里我采用了轻量级的互补滤波算法而不是更复杂的卡尔曼滤波。原因在于平衡木场景下机体运动频率较高互补滤波在保证实时性的同时足以提供稳定、低延迟的姿态角。MPU6050模块通常自带电平转换可以直接与Arduino的5V引脚连接但注意其I2C引脚SDA, SCL需要接上拉电阻通常模块已集成。注意MPU6050的安装位置至关重要。必须尽可能紧密、牢固地固定在机架的中心板上并且其芯片坐标系通常印在模块上与机体的前-后、左-右方向严格对齐。任何安装倾斜或松动都会给姿态解算引入固定误差让PID调参变成噩梦。2.2 动力与机架系统的“肌肉”与“骨骼”无刷电机和电调是动力的来源。对于平衡木无人机我们不需要它真正飞行因此推重比要求不高。我选用的是2204 2300KV的无刷电机搭配5寸桨叶。这个规格在小型穿越机上很常见扭矩和响应速度足够。KV值表示每伏特电压下的空载转速2300KV属于中等偏高在3S锂电池约12V下转速可观。关键是要配对两个电机分别安装在横梁的两侧一个负责纠正向前倾倒一个负责纠正向后倾倒。电调电子调速器我选择了BLHeli_S系列的20A电调。它支持标准PWM信号输入频率通常为50Hz脉宽1000-2000us对应油门0-100%并且固件开源可以通过BLHeliSuite软件进行参数配置比如启用“双向DShot”协议以获得更快的响应但初期用PWM更简单。机架需要自制。核心是一根作为“平衡木”的横梁我选用了一根长约60cm、截面2cm*2cm的方形铝管质轻且坚固。在横梁的正中心需要设计一个平台来固定飞控板、传感器和电池。电机的安装位置需要仔细计算。它们应该对称地安装在横梁两侧距离中心点的距离力臂直接影响控制力矩。力臂越长相同的电机推力产生的纠正力矩越大控制效率越高但也会增加机体的转动惯量影响响应速度。经过几次试验我将电机安装点定在距中心15cm的位置这是一个兼顾控制力和灵敏度的折中点。整个机架的重心必须低于平衡木的支点即横梁的顶点就像不倒翁一样这样才能产生一个使机体回归平衡位置的重力恢复力矩这是系统能够被控制的前提。电池一块1000mAh 3S锂电池通常安装在平台下方以降低重心。2.3 系统架构与信号流整个系统的信号流是这样的感知MPU6050以几百Hz的频率输出原始的陀螺仪和加速度计数据通过I2C总线。解算Arduino读取数据进行校准、单位转换然后通过互补滤波算法融合出当前机体的俯仰角Pitch和横滚角Roll。对于平衡木我们主要关注俯仰角。决策将融合得到的角度作为当前值与期望角度平衡时为0度做差得到角度误差。将此误差送入PID控制器。PID控制器同时还会利用陀螺仪输出的角速度作为微分项D项的输入以预测变化趋势增加阻尼。执行PID控制器输出一个控制量例如-255到255之间的整数。这个控制量经过映射转换为两个电机各自的PWM脉宽值。例如当机体前倾时PID输出一个正的控制量这会增加后方电机的推力减少前方电机的推力产生一个抬头的力矩。动力Arduino将PWM信号发送给两个电调电调驱动无刷电机旋转改变推力从而产生纠正力矩。这个闭环在毫秒级别内不断循环形成“感知-决策-执行”的反馈控制对抗各种干扰如风、自身振动维持平衡。3. 软件核心姿态解算与PID控制器实现硬件是躯体软件才是灵魂。这一部分我们将深入代码看看如何让MPU6050“说人话”以及如何实现一个稳健的PID控制器。3.1 MPU6050数据读取与传感器融合首先你需要导入Wire.h和I2Cdev.h、MPU6050.h库后者来自Jeff Rowberg的MPU6050库非常经典。初始化阶段除了基本的设备启动必须进行传感器校准。将无人机水平静止放置采集数百组陀螺仪和加速度计数据计算其平均值这些平均值就是零偏。在后续读数中每次都要减去对应的零偏。这个步骤能显著减少静态误差。读取到的原始数据单位通常是LSB最低有效位需要根据数据手册提供的灵敏度系数转换为物理量。例如我设置的陀螺仪量程为±250°/s灵敏度是131 LSB/(°/s)。那么角速度 陀螺仪原始值 / 131。接下来是核心的互补滤波。算法思想很简单用高通滤波器处理陀螺仪积分得到的角度抑制其长期漂移用低通滤波器处理加速度计计算出的角度抑制其高频振动噪声然后将两者按权重相加。一个典型的实现代码如下片段float互补滤波(float accelAngle, float gyroRate, float dt, float alpha) { // accelAngle: 由加速度计atan2计算出的瞬时角度 // gyroRate: 陀螺仪测得的角速度 // dt: 本次循环与上次循环的时间间隔秒 // alpha: 互补滤波系数通常取0.98-0.995越大越信任陀螺仪 static float angle 0; // 滤波后的角度 angle alpha * (angle gyroRate * dt) (1 - alpha) * accelAngle; return angle; }这里的dt必须精确测量可以使用micros()函数获取微秒时间戳然后除以1e6得到秒。alpha系数是调参关键。如果机体振动大加速度计噪声明显alpha应取大值如0.99更信任陀螺仪的短期精度。如果希望角度对快速变化响应更灵敏可以适当调小alpha。在我的平衡木项目中由于电机振动是主要噪声源我将alpha设为0.985。实操心得不要迷信库函数里“现成”的姿态解算。很多库提供的getAngleX()之类函数内部可能用了复杂的DMP数字运动处理器或滤波但其延迟和特性未必适合你的高速控制环。自己实现一个简单的互补滤波虽然简陋但延迟可控参数透明在调试PID时你能确切知道角度数据是怎么来的出了问题也容易定位。3.2 PID控制器的编写与离散化PID是比例Proportional、积分Integral、微分Derivative控制的合称。在微控制器中我们实现的是离散数字PID。公式如下输出 Kp * 误差 Ki * 误差积分 Kd * 误差微分在代码中我们需要维护几个变量上一次的误差用于计算微分、误差的累积和用于积分。同时必须引入积分限幅和输出限幅这是工程实践中防止系统失控的关键。class PID { public: float Kp, Ki, Kd; float integral, prevError; float integralLimit, outputLimit; PID(float p, float i, float d, float iLim, float oLim) { Kp p; Ki i; Kd d; integralLimit iLim; outputLimit oLim; integral 0; prevError 0; } float compute(float setpoint, float measurement, float dt) { float error setpoint - measurement; // 比例项 float P_out Kp * error; // 积分项带限幅和抗饱和处理 integral error * dt; // 积分限幅 if (integral integralLimit) integral integralLimit; if (integral -integralLimit) integral -integralLimit; // 可选当输出饱和时停止积分抗饱和积分 // if (fabs(lastOutput) outputLimit error * lastOutput 0) { // integral - error * dt; // 回退本次积分 // } float I_out Ki * integral; // 微分项使用测量值的微分而非误差的微分对设定值变化更平滑 float derivative (measurement - prevMeasurement) / dt; // 假设有prevMeasurement // 或者使用误差的微分 // float derivative (error - prevError) / dt; float D_out Kd * derivative; prevError error; // prevMeasurement measurement; float output P_out I_out - D_out; // 注意通常D项用负号提供阻尼 // 输出限幅 if (output outputLimit) output outputLimit; if (output -outputLimit) output -outputLimit; return output; } };注意微分项的处理。我注释掉了基于误差微分的常见写法因为它会在设定值突变时产生很大的微分冲击设定值突变时误差瞬间变化。对于平衡木设定值始终是0所以用误差微分或测量值微分差别不大。但在更一般的跟踪场景使用测量值的微分或者对测量值进行低通滤波后再微分是更好的实践能减少设定值变化带来的扰动。3.3 控制量分配与电机混控PID控制器输出一个总的控制量output范围假设在 -255 到 255。我们需要将这个控制量合理地分配给两个电机。这就是“混控”。对于俯仰轴控制当output 0表示需要产生一个“抬头”力矩机体前倾了需要后推。因此后方电机推力增加前方电机推力减少。设motorBase为平衡状态时电机的基准油门值例如1300us略高于电机启动阈值。则motorRear motorBase output;motorFront motorBase - output;同时必须确保分配给每个电机的PWM值在电调安全接收范围内例如1000us-2000us需要进行限幅。int baseThrottle 1300; // 基准油门需实际测试确定 int control pid.compute(0, currentPitchAngle, dt); // 目标角度0度 int pwmRear baseThrottle control; int pwmFront baseThrottle - control; pwmRear constrain(pwmRear, 1000, 2000); pwmFront constrain(pwmFront, 1000, 2000); // 使用 servo.writeMicroseconds() 或 analogWrite() 映射后输出PWM这个混控策略非常直接。关键在于baseThrottle的确定。它需要足够大以克服机架重量产生的力矩使电机在平衡点附近有足够的推力调节裕量既能增又能减。这需要在实际平衡木上通过实验调整先让无人机在平衡木上用手扶住平衡慢慢增加baseThrottle直到感觉电机推力刚好能“托住”机体不至于明显下坠此时的值作为起点。4. 系统集成、调试与参数整定把所有部分组装起来才是挑战的开始。调试是一个“胆大心细”的过程需要有条不紊地进行。4.1 分步组装与静态测试第一步最小系统测试。不装桨叶只连接Arduino、MPU6050和电调电机。编写一个简单的程序读取MPU6050数据并通过串口打印出互补滤波后的角度。用手缓慢倾斜整个模块观察角度输出是否平滑、响应是否及时、零点是否准确。同时写一个程序让电机以缓慢的转速旋转检查电调初始化通常需要给一个1000us的低油门信号再回到中位和电机转向是否正确。务必确保两个电机的转向相反这样它们产生的扭矩方向才能一致。例如当需要抬头力矩时两个电机都应产生向上的推力但由于它们分居横梁两侧转向必须相反才能产生同向的扭矩。第二步机架组装与重心调整。将所有部件安装到机架上。用两根细绳吊起机架找到其重心点。这个重心点必须低于平衡木的接触点。可以通过调整电池位置或增加底部配重来实现。这是物理稳定性的基础重心越高系统越不稳定对控制算法的要求就越高。第三步开环响应测试。仍然不装桨叶安全第一。编写程序让电机推力与角度传感器读数直接关联例如角度前倾1度后方电机增加10个单位的PWM。用手推动无人机感受电机推力变化的方向是否正确。这一步是检查整个信号链传感器-处理-控制输出的逻辑是否正确。4.2 PID参数整定从零到稳的“玄学”艺术PID调参是核心环节。遵循“先P后D再I”的经典步骤。初始化将Ki和Kd设为0。设定一个较小的Kp例如1.0。baseThrottle设为测试好的基准值。积分限幅和输出限幅先设一个宽松的值。纯比例控制P扶住无人机置于平衡位置附近然后松手。观察现象如果无人机缓慢地倒向一边说明Kp太小控制力不足无法克服重力矩。逐步增大Kp。如果无人机在平衡位置附近高频振荡快速抖动说明Kp太大了。减小Kp。目标是找到一个临界Kp使得无人机松手后能快速摆向平衡位置但会在平衡点附近来回摆动几次衰减振荡。此时系统有响应但缺乏阻尼。加入微分控制D保持刚才的Kp引入一个较小的Kd例如0.05。微分项相当于“阻尼器”能预测运动趋势并抑制振荡。增大Kd观察振荡是否被快速抑制。目标是让无人机从倾斜状态回到平衡点时平稳停下没有或只有一次轻微过冲。注意Kd过大会引入高频噪声因为微分会放大传感器噪声导致电机剧烈抽搐。如果传感器数据噪声大需要对测量值进行低通滤波后再用于D项计算。加入积分控制I积分项用于消除稳态误差。在只有PD控制时无人机可能无法精确回到0度而是停留在一个很小的角度上因为需要持续的推力来抵消不对称的摩擦力或微小的安装误差。设定一个很小的Ki例如0.001。观察无人机是否能更精确地回到水平位置。警告积分项非常危险。Ki过大会导致积分累积过快引起系统超调甚至发散。必须设置严格的积分限幅例如限制为最大输出值的20%。在调试时可以先用PD控制让系统基本稳定再小心翼翼地加入I。调参心法实录安全第一调参初期务必用绳子或支架限制无人机的倾倒范围防止“炸机”。小步快跑每次只调整一个参数并且调整幅度要小比如Kp每次增减0.5Kd每次增减0.01Ki每次增减0.0005。观察现象不要只看数据要听电机声音是否尖叫、看机体振动是否剧烈。理想的平衡状态是电机发出均匀、低沉的嗡鸣机体只有极其微小的、肉眼难辨的抖动。利用工具如果条件允许可以通过串口将角度、角速度、PID各项输出、电机PWM值实时发送到电脑用串口绘图工具如Arduino IDE自带的Serial Plotter或更专业的Vofa、PlotJuggler可视化。观察波形能帮你理解系统是欠阻尼振荡还是过阻尼响应慢。接受不完美物理系统没有完美的PID参数。要在响应速度、稳定性和抗干扰能力之间取得平衡。在平衡木上能稳定30秒以上就算巨大成功。4.3 动态测试与抗干扰优化当PID参数能让无人机在无干扰下稳定平衡后可以进行动态测试轻推测试用手指轻轻推一下无人机看它能否在1-2秒内恢复平衡。持续干扰用小风扇对着无人机吹模拟风扰观察其抗干扰能力。此时可能会发现单纯的PID在应对持续或突变的干扰时力不从心。可以考虑以下优化前馈控制如果干扰是可测量的例如已知风扇的风力大致对应一个额外的力矩可以在PID输出上直接加上一个前馈量来补偿。设定值平滑虽然我们目标是0度但可以给设定值加一个斜坡函数或低通滤波让目标角度缓慢变化到0避免设定值阶跃跳变对系统的冲击。变参数PID根据误差的大小切换不同的PID参数组。大误差时用强P快速回归小误差时用弱P强D精细稳定。5. 常见故障排查与进阶优化方向即使按照步骤来你也一定会遇到各种问题。下面是一些我踩过的坑和解决方案。5.1 典型问题速查表现象可能原因排查与解决思路上电后电机不转/乱叫1. 电调未正确初始化油门信号顺序不对。2. PWM信号脉宽范围不对。3. 电源功率不足或接触不良。1. 确保上电时先发送最低油门1000us信号至少2秒听到“哔哔”提示音后再进入正常控制。2. 用示波器或逻辑分析仪检查Arduino输出的PWM脉宽是否在1000-2000us内。3. 检查电池电压确保所有电源连接牢固尝试单独给电调供电。角度数据跳动剧烈1. MPU6050安装不牢固或受振动影响。2. 传感器未校准或校准环境不水平。3. 互补滤波系数alpha不合适。1. 用海绵胶或硅胶垫减震并紧固传感器。2. 重新进行水平静止校准。3. 增大alpha值更信任陀螺仪或对加速度计计算出的角度进行更强的低通滤波。无人机单向倾倒无法平衡1. 重心不在支点正下方或重心过高。2. 电机推力不对称或安装不水平。3. 基准油门baseThrottle设置不当。4. PID输出限幅导致控制力不足。1. 重新调整配重确保重心低且居中。2. 单独测试每个电机在相同PWM下的推力校准或更换电机。检查安装平面。3. 在平衡点附近手动调整baseThrottle找到能“托住”机体的值。4. 适当放宽输出限幅或增大Kp。平衡时持续低频摆动积分项I太弱或积分限幅太小无法抵消稳态误差。谨慎地、极小幅度地增大Ki并观察效果。确保积分项在起作用。平衡时高频抖动电机啸叫1. 微分项Kd过大放大了传感器噪声。2. 比例项Kp过大。3. 电机/电调响应过于灵敏。1. 减小Kd。对用于微分计算的测量值角度或角速度进行低通滤波。2. 减小Kp。3. 在电调设置中降低电机启动功率或调整PWM频率。响应迟钝推一下恢复很慢1. 微分项Kd过大导致系统过阻尼。2. 比例项Kp过小。3. 电机推力不足。1. 减小Kd。2. 增大Kp。3. 检查电池电量或换用更大KV值/更大尺寸的电机和桨叶。5.2 从平衡到行走进阶可能性当你的无人机能在平衡木上稳如泰山时就可以尝试更酷的玩法了平衡木行走这需要引入位置控制。在横梁上贴上色带或二维码在机腹加装一个向下的光学流传感器如Pixy2或小型摄像头如OpenMV识别自身在横梁上的位置。然后外环使用一个PID控制器来控制位置设定点为横梁中心其输出作为内环姿态PID的角度设定值。这样无人机就能通过主动倾斜来产生水平方向的力从而在横梁上移动。状态空间控制PID虽然经典但对于像倒立摆平衡木无人机本质上是一个轮式倒立摆这样的非线性、不稳定系统线性二次型调节器LQR等基于状态空间模型的控制方法往往能获得更优的性能。你可以尝试用Matlab/Simulink先建立系统的动力学模型并进行仿真再将其转化为离散控制器在Arduino上实现。无线调试与参数整定给Arduino Nano加装一个NRF24L01无线模块或者直接换用ESP32开发板。编写一个简单的上位机程序可以用Processing或Python的Tkinter实时接收无人机姿态、PID输出等数据并绘图同时能无线发送调整后的PID参数。这能让调参过程从“盲拧”变成“可视化操作”。增加更多传感器加入一个超声波模块或激光测距仪如VL53L0X来测量离地高度实现高度保持。这样你的平衡木无人机就真的能“飞”起来了成为一个简化但功能完整的自平衡飞行器。折腾这个项目的几个月里最大的体会是理论上的传递函数和伯德图最终都要落实到那一行行代码、一个个焊点和一次次参数微调上。PID的三个数字背后是系统惯性、电机延迟、传感器噪声和现实世界摩擦力的复杂博弈。成功的那一刻不是屏幕上的波形完美而是你松开手看着那个由自己组装、编程的“铁疙瘩”颤颤巍巍却又顽强地立在横梁上时那种纯粹的、属于创造者的快乐。最后一个小建议多买点桨叶它们比你想象中脆弱。