四旋翼无人机作为典型的欠驱动系统,仅通过四个电机的转速调节就要实现空间六自由度的完全控制,这本身就是个极具挑战性的控制问题。我在2018年第一次尝试自己组装四旋翼时就深刻体会到了这一点——当时飞控板上的基础PID控制器根本无法应对室外突发的阵风扰动,导致多次炸机。
串级PID架构之所以成为行业主流解决方案,核心在于它完美契合了四旋翼的动态特性。飞行器的姿态控制(内环)需要毫秒级的快速响应,而位置控制(外环)则更关注稳态精度。这种时间尺度上的天然分层,正好对应串级控制的内外环分工。在近五年的工程实践中,我测试过从单环PID到MPC的各种方案,最终在消费级无人机项目上还是回归了串级PID——不是因为它最先进,而是因为它最可靠。
这次选择复现的论文是2016年发表在IEEE Transactions on Industrial Electronics上的经典工作《Cascade PID Control of a Quadrotor UAV》。该论文的创新点主要体现在三个方面:
特别值得注意的是论文中的实验数据——在3m/s的持续侧风干扰下,位置跟踪误差仍能控制在±0.15m以内。这个指标即使放在今天看也相当出色,这也是我选择复现它的主要原因。
工欲善其事,必先利其器。为了保证复现结果的可信度,我放弃了简单的MATLAB/Simulink方案,转而采用更接近真实物理的Gazebo+ROS仿真环境:
bash复制# 安装PX4仿真环境
sudo apt install ros-noetic-gazebo-ros-pkgs ros-noetic-mavros
git clone https://github.com/PX4/PX4-Autopilot.git --recursive
make px4_sitl_default gazebo
这个环境包含了:
在参数配置上完全参照论文中的Crazyflie 2.0微型无人机规格:
关键提示:务必关闭Gazebo的实时因子同步(--disable-real-time-factor),否则可能导致仿真步长不稳定影响控制性能。
论文中的内环设计颇有独到之处,采用角速度+角加速度的双反馈结构:
code复制θ̈_cmd = kp(θ_des - θ) - kdω - kddω̇
其中ω̇的获取是个难点。在实际工程中,我测试了三种方案:
具体实现代码如下:
python复制class AttitudeController:
def __init__(self):
self.prev_gyro = np.zeros(3)
self.dt = 0.002 # 500Hz控制频率
def update(self, setpoint, attitude, gyro):
# 角加速度计算
gyro_dot = (gyro - self.prev_gyro) / self.dt
self.prev_gyro = gyro
# 论文式(12)的实现
torque_x = 0.15*(setpoint.roll - attitude[0])
- 0.035*gyro[0]
- 0.0012*gyro_dot[0]
# 同样实现pitch/yaw通道
return [torque_x, torque_y, torque_z]
参数整定时的经验法则:
外环的核心创新在于引入了速度前馈补偿:
code复制a_cmd = kp(p_des - p) + kv(v_des - v) + kffv_des
这里有个工程细节论文没有明说:前馈增益kff需要根据无人机质量自适应调整。经过实测,我发现最优值约在1.05~1.15倍质量之间:
| 无人机质量(g) | 最优kff |
|---|---|
| 27 | 1.12 |
| 50 | 1.08 |
| 100 | 1.05 |
位置控制器的输出需要转换为姿态指令,这里涉及到一个关键转换:
python复制def position_to_attitude(acc_cmd, yaw):
# 论文式(18)
roll_des = np.arctan2(acc_cmd[1], 9.81)
pitch_des = -np.arctan2(acc_cmd[0], 9.81)
return [roll_des, pitch_des, yaw]
重要细节:必须对加速度指令做限幅处理(建议±3m/s²),否则大机动时会导致姿态指令突变引发失稳。
在复现论文图6的快速机动测试时,最初总是出现奇怪的震荡。通过日志分析发现是电机指令达到了100%饱和:
code复制[WARNING] [motor_controller]: Motor 3 saturated at 100%
解决方法是在控制器输出后增加动态限幅模块:
python复制def dynamic_saturation(u, u_prev, max_change=0.15):
delta = np.clip(u - u_prev, -max_change, max_change)
return u_prev + delta
这个0.15的限幅值不是随便定的,是根据电机阶跃响应测试得出的最大允许变化率。
当内外环运行在不同频率时(常见于多核处理器架构),会出现时间不同步问题。我的解决方案是:
python复制if gyro is None:
gyro = self.prev_gyro + self.gyro_dot * dt_predict
虽然论文声称能抗3m/s的风,但初始复现时2m/s风就导致位置漂移。通过频谱分析发现是外环积分增益不足。改进措施:
python复制if abs(position_error) < 0.5: # 只在小误差时积分
self.integral += error * dt
python复制wind_estimate = low_pass_filter(acc_measure - acc_cmd)
论文附录给出了理论上的参数计算方法,但实际工程中我总结出更实用的"三阶段整定法":
我的参数记录表供参考:
| 参数 | 理论值 | 调整后 | 调整依据 |
|---|---|---|---|
| kp_pos | 1.2 | 1.05 | 减少超调 |
| ki_pos | 0.1 | 0.08 | 避免积分饱和 |
| kff | 1.0 | 1.12 | 质量补偿 |
完成基础复现后,我尝试了几个论文未涉及的优化方向:
通过在线识别系统惯性矩,实现参数自动调整:
python复制def inertia_estimation(torque, angular_acc):
# 递归最小二乘法估计
J_est += K * (torque - J_est * angular_acc)
return J_est
用小型NN补偿模型误差:
python复制class Compensator(nn.Module):
def forward(self, state):
return self.net(state) * 0.1 # 小增益确保稳定
将控制器部署到Pixhawk飞控进行实时测试,需要注意:
最终在真实飞行测试中,达到了位置控制误差±0.12m(无风)和±0.21m(3m/s侧风)的精度,略优于论文指标。这主要得益于现代传感器噪声水平的提升和处理器计算能力的进步。