2、坐标系与姿态表示:地球坐标系、机体坐标系、欧拉角、旋转矩阵、四元数基础

各位同学,欢迎来到第二章。

这一章,咱们聊聊飞控里最基础、也最容易搞混的东西——坐标系和姿态表示。说实话,我当年刚入行时,在这上面栽过不少跟头。有一次调试四旋翼,明明代码逻辑看着都对,飞机就是翻跟头。查了两天,最后发现是坐标系定义反了。嗯,从那以后,我对坐标系就格外敬畏。

好,咱们正式开始。

2.1 为什么要搞这么多坐标系?

你想想看,飞机在天上飞,它自己感觉的「向前」和地面看到的「向前」是一回事吗?

当然不是。

飞机低头俯冲时,它机头指向地面,但地面坐标系里的「北」还是那个「北」。所以我们需要两套坐标系:一套固定在地球上,一套固定在飞机上。这就是地球坐标系和机体坐标系的由来。

2.2 地球坐标系(NED)

地球坐标系,也叫导航坐标系。我习惯用 NED 表示:

  • N(North):指向地理北
  • E(East):指向地理东
  • D(Down):指向地心(也就是向下)

为什么向下是正?因为飞控里加速度计测的是重力,重力方向朝下。这样定义,很多计算可以少写负号。我个人觉得这是前人留下的好习惯,咱们接着用就行。

重要:NED 坐标系是右手系。你伸出右手,拇指指北,食指指东,中指自然指向下——这就是 NED。

2.3 机体坐标系(Body Frame)

机体坐标系固定在飞机上。原点在飞机重心,三个轴分别是:

  • X轴:指向机头(前进方向)
  • Y轴:指向飞机右侧
  • Z轴:指向飞机下方

注意,这也是右手系。我见过有人把 Y 轴指向左侧,结果旋转方向全反了。所以定义坐标系时,一定要统一标准。

小技巧:写代码时,把坐标系定义写在最前面,加个注释。比如:// Body frame: X-front, Y-right, Z-down。这样半年后回来看代码,不会骂自己。

2.4 欧拉角:最直观的姿态表示

欧拉角,说白了就是用三个角度描述飞机转了多少。

三个角分别是:

  • 滚转角(Roll,φ):绕 X 轴旋转。飞机侧倾的角度。
  • 俯仰角(Pitch,θ):绕 Y 轴旋转。飞机抬头低头的角度。
  • 偏航角(Yaw,ψ):绕 Z 轴旋转。飞机机头指向的角度。

旋转顺序很重要。我习惯用 Z-Y-X 顺序,也就是先偏航、再俯仰、最后滚转。为什么是这个顺序?因为这样最符合人的直觉:先看机头指向哪(偏航),再抬头低头(俯仰),最后侧倾(滚转)。

避坑指南:欧拉角有个致命问题——万向锁。当俯仰角接近 ±90° 时,滚转和偏航会耦合,丢失一个自由度。我曾经在无人机倒飞时遇到过这个问题,姿态解算直接炸了。所以,如果你做全姿态飞行(比如特技无人机),别只用欧拉角。

2.5 旋转矩阵:数学上的优雅表达

欧拉角虽然直观,但做数学运算不方便。这时候旋转矩阵就派上用场了。

旋转矩阵是一个 3×3 的矩阵,它能把机体坐标系下的向量,转换到地球坐标系下。反过来也行。

从机体到地球的旋转矩阵(Z-Y-X 顺序)长这样:

R = Rz(ψ) * Ry(θ) * Rx(φ)

其中:
Rz(ψ) = | cosψ  -sinψ   0 |
         | sinψ   cosψ   0 |
         |   0      0     1 |

Ry(θ) = | cosθ   0   sinθ |
         |   0    1    0   |
         | -sinθ  0   cosθ |

Rx(φ) = | 1    0      0    |
         | 0   cosφ  -sinφ |
         | 0   sinφ   cosφ |

你可能会问:为什么要乘三个矩阵?因为一次旋转对应一个矩阵,三次旋转就是三个矩阵相乘。注意顺序:先滚转、再俯仰、最后偏航,所以矩阵乘法是从右往左读。

实际应用:在飞控代码里,我们经常用旋转矩阵把加速度计测到的「机体加速度」转换到「地球坐标系」,然后积分得到速度。这一步做错了,位置估计全完蛋。

2.6 四元数:飞控的终极武器

欧拉角有万向锁,旋转矩阵有9个参数(计算量大),那有没有更好的办法?

有,四元数。

四元数用4个参数表示旋转:

q = w + xi + yj + zk

其中 w 是实部,x、y、z 是虚部。
满足:w² + x² + y² + z² = 1

四元数的好处:

  • 没有万向锁
  • 计算量小(只有4个参数)
  • 插值平滑(做姿态平滑时特别好用)

我记得第一次在 STM32 上跑四元数姿态解算时,看到它比旋转矩阵快了将近一倍,当时就觉得:嗯,以后就用它了。

2.7 四元数与旋转矩阵的转换

实际写代码时,我们经常需要在四元数和旋转矩阵之间来回转换。比如传感器输出四元数,但控制算法需要旋转矩阵。

从四元数到旋转矩阵:

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²) |

从旋转矩阵到四元数:

w = sqrt(1 + R[0][0] + R[1][1] + R[2][2]) / 2
x = (R[2][1] - R[1][2]) / (4 * w)
y = (R[0][2] - R[2][0]) / (4 * w)
z = (R[1][0] - R[0][1]) / (4 * w)

经验之谈:转换时注意数值稳定性。如果 w 接近 0,用其他公式算。我一般会加个判断:if (w < 0.001) { 用另一种方法 }。别问我怎么知道的——调试时遇到过 NaN。

2.8 实战:姿态初始化

飞机上电时,我们不知道它的姿态。怎么办?

我的做法是:

  1. 读取加速度计,得到重力方向(确定俯仰和滚转)
  2. 读取磁力计,得到地磁方向(确定偏航)
  3. 用这两个信息初始化四元数

代码大概长这样:

// 从加速度计计算俯仰和滚转
float pitch = atan2(-accel_x, sqrt(accel_y*accel_y + accel_z*accel_z));
float roll  = atan2(accel_y, accel_z);

// 从磁力计计算偏航(需要先补偿俯仰和滚转)
float yaw = atan2(mag_y, mag_x);

// 用欧拉角初始化四元数
quaternion_from_euler(roll, pitch, yaw, &q);

注意:磁力计很容易受干扰。我曾在钢筋水泥楼里调试,磁力计读数乱跳,偏航角直接漂了 30 度。后来加了磁校准和地磁模型补偿,才算稳定下来。

2.9 本章小结

好了,这一章的内容就这些。咱们捋一下:

  • 地球坐标系(NED):固定在地球上,导航用
  • 机体坐标系:固定在飞机上,传感器用
  • 欧拉角:直观,但有万向锁
  • 旋转矩阵:数学严谨,但计算量大
  • 四元数:飞控首选,没有万向锁,计算快

下一章,咱们会把这些知识用到实际代码里,写一个完整的姿态解算器。到时候你会发现,今天学的这些,全是基础中的基础。

有什么问题,欢迎在评论区留言。咱们下章见。