这门叫 Aerial Robotics 的公开课,我拖了大半年才真正开始跟。第1和第2周的内容我连续过了两遍,第一遍以为看懂了,第二遍动手做课后题才发现漏掉大量符号细节。这篇学习记录不打算复述课件,而是把课后的推导、做题时卡住的地方,以及最后在仿真里踩的坑一起整理出来。如果你正在跟这门课,或者想真正理解四旋翼为什么能稳定悬停,这篇文章应该能帮你少花几个晚上。
先说明一个现实:空中机器人看起来是硬件领域,但前两周几乎全是数学——刚体运动学、旋转矩阵、牛顿-欧拉方程、级联控制结构。内容密度比一般课程大不少,后面所有章节都在默认你已经掌握这些基础。所以这两周不是“先修课”,而是真正的地基。把这里吃透,后面不管是轨迹规划还是状态估计,都会顺很多。
1. 前两周究竟在讲什么:一张我复盘出来的知识地图
1.1 这门课跟你想象的“无人机课”不一样
大多数爱好者第一次接触四旋翼,可能从玩具或者开源自驾仪开始。你调一调 PID,飞控自己就把飞机稳住了。但课程第1周把这些“黑箱”全部打开:四旋翼是一个六自由度、四个输入的欠驱动系统。位置和姿态强耦合,推力始终沿着机体坐标系的z轴,你没法直接推着飞机往水平方向走,只能先倾斜机身,让推力产生水平分量,然后位置才跟着动。
这个认知差异很重要。很多人玩无人机时总觉得“控制就是调PID”,但为什么偏偏要用这种级联结构、为什么内环频率要比外环高那么多,这些问题的答案都在前两周的动力学模型里。课程第1周从刚体运动学切入,引入位置、速度、姿态、角速度这些状态量;第2周则进入动力学和最基本的控制器设计。看似进度不快,但每一步都在为后面的内容铺路。
1.2 两周内容的四层结构
我复盘之后,把前两周的内容归纳成四层:
| 层次 | 第1/2周核心内容 | 我学习后最大的收获 |
|---|---|---|
| 运动学 | 旋转矩阵、欧拉角、角速度关系 | 姿态表示不是符号游戏,而是后续状态估计和控制的前提 |
| 刚体动力学 | 牛顿-欧拉方程、转动惯量、力与力矩来源 | 推力方向被机体姿态约束,所有控制目标只能通过姿态间接实现 |
| 控制结构 | 级联控制、PD/PID、外环位置加内环姿态 | 真正的难点不是公式,而是带宽匹配和饱和处理 |
| 状态估计 | IMU、视觉/运动捕捉的铺垫 | 第2周结束时就意识到,没有好估计就没有好控制 |
这四层里,运动学和动力学是纯理论,控制结构需要动手做题才能体会,状态估计部分目前只是铺垫,后面章节才会展开。学完前两周以后,我的感觉是:课程团队非常清楚知识之间的依赖关系,每个公式都不是为了炫技,而是后面某个控制器或者估计器里的必需品。
2. 四旋翼动力学模型:为什么状态里不能省掉旋转矩阵
2.1 六个自由度、四个输入,意味着什么
四旋翼的四个电机只能产生沿机体z轴的推力和三个方向的机体力矩。平动方向有三条轴,但直接可控的只有机体z轴这条推力线,所以水平方向的控制全部要靠机身倾斜来实现。第2周课程常讲的一个结论是:位置控制环的输出不能直接是“水平推力”,而是期望姿态角;真正的水平加速度要等姿态内环转到对应角度之后才产生。
这对控制系统设计有非常直接的推论:内环姿态响应必须明显快于外环位置响应。如果姿态内环带宽不够,飞机还没转到期望角度,外环的位置误差已经累积到很大,整个闭环就会振荡发散。前两周的课不会直接给你一套完整的调参表格,但理解了“内环快、外环慢”这个基本逻辑,后面看飞控代码时就能对号入座。
2.2 牛顿-欧拉方程每一项的物理含义
我采用世界系z轴向上的约定,重力方向沿 (-e_3)。状态包括位置 (p)、速度 (v)、姿态矩阵 (R)、机体角速度 (\omega)。动力学方程可以写成:
[ m\dot v = -mg e_3 + T R e_3 ]
[ I\dot\omega = -\omega \times I\omega + M ]
第一行是平动方程。重力只出现在世界系里,方向固定不变;推力大小是 (T),但它永远沿着机体z轴,所以要靠旋转矩阵 (R) 变换到世界坐标系再参与计算。第二行是转动方程,所有项都在机体系里表达,转动惯量 (I) 是一个常矩阵,这样比在世界系里处理方便得多。
很多人一开始会漏掉转动方程里的 (-\omega \times I\omega) 这一项。它是陀螺力矩,来自高速旋转的转子与机体转动的耦合。悬停时没多大影响,可一旦做大角速度机动,这一项会成为主要角色,漏掉它仿真结果会越来越离谱。所以每次写完运动方程,我都习惯先把这一项的物理含义写清楚,再继续下一步。
2.3 电机转速如何变成总推力和三个力矩
每个电机的转速 (\omega_i) 不是直接作为输入,而是通过二次关系产生推力和反扭矩:
[ f_i = k_F \omega_i^2 ]
[ m_i = k_M \omega_i^2 ]
总推力 (T) 是四个电机的推力之和。滚转力矩来自左右两侧电机的推力差,俯仰力矩来自前后两侧电机的推力差,偏航力矩来自对角电机反扭矩之差。把期望的总推力与三个力矩打包成一个向量,再乘以一个 (4 \times 4) 的分配矩阵,就能解出四个电机需要的转速平方。这个矩阵在课程里叫控制分配矩阵,实际上就是一条从“控制量”到“执行机构”的通道。
我在实际做习题时发现,理解分配矩阵最好的方法,是把四个电机按“十字型”或者“X型”摆开,然后逐个看每个电机推力变化对六个自由度里哪些量有贡献。刚学的同学可以先做到“会查矩阵”,后面仿真里要自己实现一遍,才能真正记住结构。
3. 姿态表示里最容易被忽略的三个细节
3.1 欧拉角、旋转矩阵、角速度不是同一件事的三种写法
很多初学者觉得欧拉角足够直观,姿态就用 ((\phi,\theta,\psi)) 三个角表示不就行了吗。直到某次我试着自己写仿真,当俯仰角接近90度时,姿态更新突然出现跳变,我才真正体会到欧拉角的问题:它本身不是一个全局无奇点的几何对象,旋转的组合也不直观,而且在特定角度会发生万向锁。旋转矩阵虽然要存9个数字,但组合、求逆、与角速度的关系都非常干净。
课程坚持用旋转矩阵作为姿态状态,而不是直接拿欧拉角当状态量,这个选择是经过工程权衡的。我的理解是:旋转矩阵的可微性、全局性以及和刚体角速度之间的线性关系,让它特别适合做数值积分和误差定义。若你要在状态估计器里定义“姿态误差”,用旋转矩阵或四元数定义误差也比欧拉角稳定得多。
3.2 一个必须亲手验算的结论:欧拉角速率不等于刚体角速度
这是第1周最容易踩的坑。欧拉角速率 ((\dot\phi, \dot\theta, \dot\psi)) 是三个“欧拉轴”上的角速度分量,而不是刚体角速度 (\omega) 的分量。只有在小角度近似下,(\omega \approx (\dot\phi, \dot\theta, \dot\psi)) 才近似成立。任意姿态下,两者之间隔着一个转换矩阵 (W(\eta))。
对于ZYX顺序、内旋旋转的常见约定,机体坐标系下的角速度可以写成:
[ \omega = \begin{bmatrix} 1 & 0 & -\sin\theta\ 0 & \cos\phi & \sin\phi\cos\theta\ 0 & -\sin\phi & \cos\phi\cos\theta \end{bmatrix} \begin{bmatrix} \dot\phi\ \dot\theta\ \dot\psi \end{bmatrix} ]
我建议你自己推导一遍,核心思路是:每一步欧拉角速率都绕“当前时刻的对应轴”转动,所以要把后发生的旋转逐级变换到机体坐标。具体写出来就是:
[ \omega = \dot\phi, e_1 + \dot\theta, R_x(\phi), e_2 + \dot\psi, R_x(\phi) R_y(\theta), e_3 ]
整理之后正好得到上面那个矩阵。验证方法也很简单:取小角度,矩阵近似变成单位阵,结果和直觉一致;再拿任意角速度数值算一遍,对比 (\dot R R^T) 的反对称部分,就能确认符号方向。
3.3 为什么课程里坚持用旋转矩阵而不是欧拉角做状态量
除了万向锁问题,旋转矩阵还带来两个工程层面的好处。第一,它可以直接参与力和力矩方向的计算,比如推力方向 (R e_3),不需要先转换成角度再算三角函数。第二,它支持更统一的误差定义,后续做轨迹跟踪时,姿态误差可以用 (R_\text{des}^T R) 之类的矩阵形式写,几何意义清楚,推导也方便。
真实飞控固件里为了减小计算量,通常会用四元数代替完整旋转矩阵。但四元数本身也有“双重覆盖”的符号问题,很多滤波器更新时要额外处理。课程先用旋转矩阵建立完整几何观,再让学习者后续自然过渡到四元数,是比较稳妥的教学路径。对我来说,先搞懂旋转矩阵再看四元数,基本上就不觉得四元数是玄学了。
4. 课后习题完整解答:四道题展示从“会算”到“会设计”
4.1 悬停推力分配:一道看似简单但实际常被忽略的题
题目:一架四旋翼质量是 0.8 kg,悬停时总推力需要多大?假设理想对称,每个电机要输出多少推力?若单电机推力系数 (k_F = 2 \times 10^{-8}\ \mathrm{N/rpm^2}),电机转速大约是多少?
解答:悬停时总推力与重力平衡:
[ T = mg = 0.8 \times 9.81 = 7.848\ \mathrm{N} ]
四个转子理想均分,每个电机承担:
[ F_i = \frac{T}{4} \approx 1.962\ \mathrm{N} ]
再把推力公式变形求转速:
[ F_i = k_F \omega_i^2 ]
[ \omega_i = \sqrt{\frac{1.962}{2\times 10^{-8}}} \approx 9905\ \mathrm{rpm} ]
这个转速数量级和常见小型四旋翼的悬停油门比较接近。实际中肯定会有偏差:重心不在几何中心、电调输出非线性、桨叶效率随转速变化、地面效应等等,都会让真实悬停油门偏离理论值。这也是为什么飞控里要有积分项,靠积分去吃掉这些固定偏差。
4.2 旋转矩阵的显式表达式:写对乘法顺序是关键
题目:按ZYX顺序、内在旋转的约定,给定欧拉角 (\phi=30^\circ, \theta=20^\circ, \psi=10^\circ),写出把机体向量变换到世界系的旋转矩阵 (R)。
先说约定。这里采用:
[ R = R_z(\psi) R_y(\theta) R_x(\phi) ]
注意矩阵乘法不可交换,顺序写反结果完全不同。代入数值并保留三位小数,得到:
[ R \approx \begin{bmatrix} 0.925 & 0.018 & 0.378\ 0.163 & 0.883 & -0.441\ -0.342 & 0.470 & 0.814 \end{bmatrix} ]
这个矩阵有三列,分别是机体x、y、z轴在世界系里的方向。因此第三列 ([0.378, -0.441, 0.814]^T) 其实就表示推力方向在当前姿态下指向哪里。画图你会发现,这个小角度下推力确实大致朝上,但已经有明显的前倾和侧倾分量,这正是机体产生水平加速度的来源。
检验方法:三列单位正交,行列式接近1。做题时如果结果不满足这些性质,多半是乘法顺序或者符号写错了。
4.3 欧拉角速率到角速度的转换矩阵推导
题目:证明或推导ZYX顺序下,从欧拉角速率到机体角速度的转换关系。
解答思路:按每一级旋转“贡献一个角速度分量”来叠加。第一层是绕世界/机体初始x轴的 (\dot\phi),在机体系里就是 (e_1);第二层是绕当前y轴的 (\dot\theta),需要先经过 (R_x(\phi)) 变换到当前机体系;第三层是绕当前z轴的 (\dot\psi),需要先经过 (R_x(\phi) R_y(\theta)) 变换。写成:
[ \omega = \dot\phi, e_1 + \dot\theta, R_x(\phi), e_2 + \dot\psi, R_x(\phi) R_y(\theta), e_3 ]
逐个计算得到矩阵的三列:
[ W(\eta) = \begin{bmatrix} 1 & 0 & -\sin\theta\ 0 & \cos\phi & \sin\phi\cos\theta\ 0 & -\sin\phi & \cos\phi\cos\theta \end{bmatrix} ]
讨论一个关键现象:当 (\theta = 90^\circ) 时,(\cos\theta = 0),矩阵会变成不可逆状态,角速度信息无法唯一还原成欧拉角速率。这就是万向锁的数学本质。真实飞行器虽然很少把俯仰角拉到90度,但做特技飞行或者高速机动时,欧拉角表示就会在数值计算里制造麻烦。
小角度特例验证:当 (\phi,\theta,\psi) 都接近0时,矩阵近似是单位阵,(\omega \approx (\dot\phi, \dot\theta, \dot\psi))。这解释了为什么很多简化模型敢直接拿欧拉角速度当角速度用,但也划清了它的适用范围。
4.4 设计姿态PD控制器:从仿真参数聊到饱和限制
题目:设计一个姿态PD控制器,使四旋翼能稳定悬停,并讨论执行机构饱和的影响。
前两周课程里控制器的核心结构是级联控制。位置外环给出期望加速度,进而转成期望姿态和总推力;姿态内环负责把当前姿态拉到期望姿态。姿态内环可以设计成:
[ \tau = k_p \eta_e - k_d \omega ]
其中 (\eta_e) 是欧拉角误差,(\omega) 是机体角速度。角速度项是阻尼,没有它系统会持续振荡。如果把每个轴近似成二阶系统,闭环特征方程大约是:
[ s^2 + k_d s + k_p = 0 ]
于是自然频率 (\omega_n = \sqrt{k_p}),阻尼比 (\zeta = \dfrac{k_d}{2\sqrt{k_p}})。想设计一个带宽约10 rad/s、阻尼比0.9的内环,可以先取 (k_p \approx 100),再取 (k_d \approx 18)。这组参数是很好的初始值,但不能直接照搬,因为真实系统的转动惯量、延迟和电机响应都会改变有效带宽。
执行机构饱和是另一个关键问题。电机有最大转速,转速平方不能为负,推力不能反向。控制器输出一旦超过限幅,闭环增益下降,积分项还可能继续累积误差,造成“越调越振”。第2周的习题往往会提醒你先在仿真里设置转速上下限,观察控制器输出被限幅后系统还能不能稳住。
我自己的经验是:姿态内环直接限幅力矩、限制期望角加速度,比事后加积分限幅更干净。处理饱和没有万能的银弹,但至少要把“控制器输出”和“执行机构真正输出”两个量分开记录,你才能判断发散是参数问题还是饱和引发的。
5. 把方程变成飞行:两周后我搭的最小仿真实验
5.1 一个最小可跑的悬停仿真需要哪些模块
前两周习题做完后,我按课程公式写了一个精简的Python悬停仿真。整体模块分成六块:状态初始化、位置外环、期望姿态解算、姿态内环、控制分配、状态更新。结构大致如下:
dt = 0.005 for step in range(int(10 / dt)): # 1. 外环位置控制:期望加速度 a_des = kp_pos * (p_des - p) + kd_pos * (v_des - v) # 2. 根据期望加速度计算总推力和期望推力方向 T_des = m * norm(a_des + g * e3) r3_des = (a_des + g * e3) / norm(a_des + g * e3) # 3. 内环姿态控制:期望力矩 e_att = compute_attitude_error(R_des, R) tau = -kp_att * e_att - kd_att * omega # 4. 控制分配:T_des 和 tau 映射到四个电机 rpm2 = mix_matrix @ np.array([T_des, tau[0], tau[1], tau[2]]) np.clip(rpm2, 0, rpm_max**2, out=rpm2) # 5. 力与力矩合成,再用 RK4 更新状态 forces = k_F * rpm2 T_actual = forces.sum() # 按电机几何位置计算滚转、俯仰、偏航力矩 ...第一次跑通悬停仿真时,我看着位置误差慢慢收敛,终于把前面课程里所有抽象公式串了起来。从“能算题”到“能飞起来”,中间差的不是一个公式,而是对整个信息流的理解:期望位置变成期望加速度,再变成期望姿态,姿态控制器输出力矩,力矩经过分配矩阵变成电机转速,最后才回到刚体动力学更新状态。
5.2 参数整定中三个让我浪费一晚上的坑
第一个坑:位置外环和姿态内环的带宽没有拉开。一开始我照抄一组参数,外环和内环都很慢,于是飞机在仿真里来回飘,怎么调都稳不住。后来把内环带宽提到外环的5倍左右,才看到希望。级联控制的基本要求就是内环要足够快,否则外环控制量变化时内环跟不上,耦合误差会叠加。
第二个坑:转动方程里漏掉了陀螺力矩项。悬停小角度机动时几乎没影响,但一旦我手动给飞机一个大的初始角速度,仿真状态直接发散。排查半天发现 (-\omega \times I\omega) 没写进状态更新。补上之后,大角速度工况才算正常。
第三个坑:单位混用。推力系数如果用 (\mathrm{N/(rad/s)^2}) 和 (\mathrm{N/rpm^2}) 是完全不同的数值,差别大约是 ((60/2\pi)^2) 倍,换算错了悬停转速会差几十倍。做题时还不觉得,写仿真时所有单位都混在一个脚本里,数字一跑偏就特别难找。建议一开始就统一用国际单位,所有角速度都用 rad/s,不要临时来回换算。
5.3 给正在学这门课的人几条实用建议
第一,作业里凡是要写旋转矩阵的地方,务必自己从 (\dot R R^T) 或者“逐级旋转叠加”推一遍,不要背矩阵。背下来的矩阵可能在作业里能得分,但到了后面课程,符号约定一变就会彻底找不到北。第二,仿真一定要先做悬停,先验证最基础的重力补偿和控制结构,再做轨迹跟踪。悬停都不稳,加轨迹只会更难排查问题。第三,遇到数值发散,不要上来就改参数。先检查符号约定,是不是世界系坐标轴方向和重力方向搞反了;再检查单位,最后才轮到PID参数。这个排查顺序能帮你省掉大量时间。
这几条建议,基本是我花了不少晚上换来的教训。如果你能把前两周的习题独立做完,而且仿真里那个小四旋翼能稳稳待在空中,那你已经完全具备继续往后学的底子了。接下来无论是轨迹生成、状态估计,还是更高级的几何控制,你都不会觉得公式是飘在空中的——因为它们都从这一套动力学模型长出来。