姿态解算基础:欧拉角与四元数、旋转矩阵、坐标系定义

各位同学,欢迎来到飞控参数深度解析的第二讲。

今天我们要聊的,是整个飞控系统里最基础、也最容易让人头晕的部分——姿态解算。说白了,就是回答一个问题:飞机现在到底朝哪个方向?

我刚开始做飞控时,觉得这玩意儿不就是算个角度嘛,有什么难的?结果第一次试飞,飞机在天上疯狂自旋,差点炸机。后来才发现,是坐标系搞反了,欧拉角万向锁没处理好。嗯,从那以后,我再也不敢小看这一章了。

核心观点:姿态解算是飞控的“眼睛”。如果眼睛瞎了,再好的PID控制算法也是白搭。本章所有内容,都是为了让你能准确、稳定地描述飞机的空间朝向。

1. 坐标系定义:先把“东南西北”说清楚

做飞控,第一件事就是定规矩——坐标系。没有统一的坐标系,你算出来的角度就是鸡同鸭讲。

1.1 NED坐标系(北东地)

NED,全称North-East-Down。这是飞控里最常用的全局参考系。

  • X轴:指向正北(North)
  • Y轴:指向正东(East)
  • Z轴:指向地心(Down)

注意这个Z轴是向下的。为什么?因为重力加速度是向下的,这样定义后,加速度计读出的重力分量就是正的。我见过不少新手把Z轴朝上定义,结果加速度数据符号搞反,姿态解算直接崩掉。

1.2 机体坐标系(Body Frame)

机体坐标系是固定在飞机上的,跟着飞机一起转。

  • X轴:指向机头方向(前进方向)
  • Y轴:指向飞机右侧(右翼方向)
  • Z轴:指向飞机下方(符合右手定则)

这两个坐标系之间的转换关系,就是姿态解算要干的事。

坐标系 X轴 Y轴 Z轴 典型应用
NED 北 东 地 GPS导航、航点规划
机体 机头 右翼 机腹 IMU数据、姿态控制

我的小习惯:在代码里,我习惯用ned_和body_作为变量前缀,比如ned_velocity、body_accel。这样一眼就能看出数据在哪个坐标系下,避免混淆。

2. 欧拉角:最直观,但最危险

欧拉角是描述姿态最直观的方式。三个角度:横滚(Roll)、俯仰(Pitch)、偏航(Yaw)。

  • Roll(φ):绕X轴旋转,范围±180°
  • Pitch(θ):绕Y轴旋转,范围±90°
  • Yaw(ψ):绕Z轴旋转,范围±180°

为什么说它危险?因为万向锁(Gimbal Lock)。当Pitch接近±90°时,Roll和Yaw会失去一个自由度,导致姿态解算失效。

我曾经踩过的坑:有一次做固定翼飞控,飞机做大仰角爬升时,Pitch超过了85°,结果欧拉角解算直接跳变,飞机瞬间失控。后来我强制限制了Pitch范围,并改用四元数做内部解算,才彻底解决这个问题。

所以,欧拉角适合人机交互(比如地面站显示),但不适合内部计算。内部计算,我们得用四元数。

3. 四元数:数学上的“瑞士军刀”

四元数听起来高大上,其实就是一个四维复数:q = w + xi + yj + zk,其中w是实部,x、y、z是虚部。

它的好处是:无万向锁、计算效率高、适合插值。

四元数表示旋转的公式很简单:

// 绕单位向量 (x, y, z) 旋转 θ 角度
q.w = cos(θ/2)
q.x = x * sin(θ/2)
q.y = y * sin(θ/2)
q.z = z * sin(θ/2)

你想想看,一个四元数就能表示任意轴任意角度的旋转,比欧拉角三个角度要优雅得多。

3.1 四元数与欧拉角的转换

实际项目中,我们经常需要在两者之间来回切换。下面是我常用的转换代码:

// 四元数 → 欧拉角
float roll  = atan2(2*(q.w*q.x + q.y*q.z), 1 - 2*(q.x*q.x + q.y*q.y));
float pitch = asin(2*(q.w*q.y - q.z*q.x));
float yaw   = atan2(2*(q.w*q.z + q.x*q.y), 1 - 2*(q.y*q.y + q.z*q.z));

// 欧拉角 → 四元数
float cy = cos(yaw * 0.5);
float sy = sin(yaw * 0.5);
float cp = cos(pitch * 0.5);
float sp = sin(pitch * 0.5);
float cr = cos(roll * 0.5);
float sr = sin(roll * 0.5);

q.w = cr * cp * cy + sr * sp * sy;
q.x = sr * cp * cy - cr * sp * sy;
q.y = cr * sp * cy + sr * cp * sy;
q.z = cr * cp * sy - sr * sp * cy;

注意:上面的asin函数在Pitch接近±90°时会有数值问题。我一般会在asin之前加一个限幅,确保输入在[-1, 1]范围内。

4. 旋转矩阵:连接两个世界的桥梁

旋转矩阵是一个3x3的矩阵,能把机体坐标系下的向量转换到NED坐标系下(反之亦然)。

从四元数到旋转矩阵的公式:

R = | 1-2(y²+z²)   2(xy-wz)     2(xz+wy)   |
    | 2(xy+wz)     1-2(x²+z²)   2(yz-wx)   |
    | 2(xz-wy)     2(yz+wx)     1-2(x²+y²) |

有了这个矩阵,你就可以把机体上的加速度、磁场等数据转换到NED坐标系下,进行导航计算。

举个例子:

// 机体加速度 → NED加速度
Vector3f body_accel = {ax, ay, az};  // IMU读取
Vector3f ned_accel = R * body_accel; // 旋转矩阵乘法

我个人习惯把旋转矩阵的计算封装成一个函数,输入四元数,输出矩阵。这样代码干净,也方便调试。

5. 知识体系总览

下面这张图,是我自己总结的本章知识结构。你可以把它当作一个“地图”,随时回来对照。

姿态解算知识体系 姿态解算核心知识体系 坐标系定义 NED 机体 姿态表示方法 欧拉角 四元数 旋转矩阵 💡 核心逻辑: 1. 定义好NED和机体坐标系 → 2. 用四元数做内部姿态解算(避免万向锁) → 3. 通过旋转矩阵在坐标系间转换数据 → 4. 输出欧拉角给地面站显示 IMU数据融合 姿态控制 导航解算 图:姿态解算知识体系与数据流向

6. 总结与避坑指南

好了,这一章的内容就到这里。我帮你把重点再拎一下:

  • 坐标系:NED和机体,搞反了会炸机
  • 欧拉角:直观但小心万向锁,别在Pitch接近±90°时用它做计算
  • 四元数:内部计算的首选,没有奇点
  • 旋转矩阵:坐标系转换的桥梁,代码里封装好复用

我曾经踩过的另一个坑:四元数归一化。每次更新四元数后,一定要做归一化处理,否则累积误差会让四元数模长偏离1,导致旋转矩阵不正交,姿态解算越来越偏。我见过有人忘了这步,飞了5分钟后姿态漂移了30度。