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

做垂直起降控制,第一件事是什么?

不是调PID,也不是写代码。而是先把「我在哪」和「飞机朝哪」说清楚。

说白了,坐标系和姿态表示,是整个飞控的「语言」。语言不通,后面全是扯淡。我见过不少新手,上来就调参数,结果飞机原地打转,查了半天发现是坐标系定义反了。嗯,这种坑我踩过不止一次。

2.1 两个核心坐标系

垂直起降阶段,我们主要跟两个坐标系打交道:

  • 地球坐标系(NED):北东地。原点在地面某点,X轴指北,Y轴指东,Z轴指向地心。说白了,就是地面上的「绝对参考系」。
  • 机体坐标系(Body):原点在飞机重心,X轴指机头,Y轴指右翼,Z轴指向机腹。这是飞机自己的「身体坐标系」。

为什么需要两个?

因为传感器数据(比如加速度计、陀螺仪)是在机体坐标系下测量的,而导航指令(比如「飞到北边10米」)是在地球坐标系下给出的。你得在两者之间来回翻译。

关键点:垂直起降阶段,我们通常用NED坐标系作为导航参考。但控制输出(比如电机推力)必须转换到机体坐标系下执行。

我个人习惯,在代码里用 ned_framebody_frame 两个结构体来区分。别偷懒混在一起,否则调试时你会哭的。

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

欧拉角是什么?就是三个角度:滚转(Roll)、俯仰(Pitch)、偏航(Yaw)

你想想看,飞机在空中,无非就是绕三个轴转:

  • 绕X轴转 → 滚转(左右倾斜)
  • 绕Y轴转 → 俯仰(抬头低头)
  • 绕Z轴转 → 偏航(机头左右转)

欧拉角的好处是直观。你一看「滚转30度」,脑子里就能想象出飞机是什么姿态。

但欧拉角有个致命问题——万向锁(Gimbal Lock)

我记得有一次做半实物仿真,飞机在俯仰接近90度时突然失控。查了半天,发现是欧拉角在俯仰±90度附近出现了奇点,导致姿态解算炸了。从那以后,我对欧拉角就多了一份敬畏。

避坑指南:我曾经在垂直起降的过渡阶段(飞机从悬停转平飞),俯仰角变化剧烈,差点触发万向锁。后来我强制在俯仰角超过80度时切换成四元数解算,才稳住。

所以,欧拉角适合小角度姿态描述。大角度机动,请用四元数。

2.3 旋转矩阵:坐标系之间的桥梁

旋转矩阵,说白了就是一张「翻译表」。它能把机体坐标系下的向量,翻译到地球坐标系下,或者反过来。

从机体到地球的旋转矩阵,通常记作 R_body_to_ned。它的形式是3x3的矩阵,由三个欧拉角的正余弦值组合而成。

// 从欧拉角构建旋转矩阵(ZYX顺序)
float roll = 0.3f;   // 滚转 17度
float pitch = 0.1f;  // 俯仰 5.7度
float yaw = 0.5f;    // 偏航 28.6度

float cr = cosf(roll);
float sr = sinf(roll);
float cp = cosf(pitch);
float sp = sinf(pitch);
float cy = cosf(yaw);
float sy = sinf(yaw);

// 旋转矩阵 R_body_to_ned
float R[3][3] = {
    {cp*cy, sr*sp*cy - cr*sy, cr*sp*cy + sr*sy},
    {cp*sy, sr*sp*sy + cr*cy, cr*sp*sy - sr*cy},
    {-sp,   sr*cp,            cr*cp}
};

有了这个矩阵,你就可以把机体坐标系下的加速度、角速度、磁场等数据,转换到地球坐标系下使用。

个人经验:我建议把旋转矩阵的计算封装成一个函数,输入欧拉角,输出3x3矩阵。这样代码干净,也方便单元测试。

另外,注意旋转顺序。飞控里常用的是ZYX顺序(先偏航,再俯仰,最后滚转)。别搞混了。

2.4 四元数:飞控的「真香」选择

四元数是什么?

它是一个四维复数,形式是 q = w + xi + yj + zk,其中 w 是实部,x, y, z 是虚部。在飞控里,它用来表示旋转。

为什么用四元数?三个理由:

  1. 无万向锁:随便你怎么转,不会出现奇点。
  2. 计算效率高:比旋转矩阵少几个乘法和加法。
  3. 插值平滑:做姿态插值时,四元数的球面线性插值(SLERP)非常平滑。

我记得第一次在STM32上跑四元数解算,发现比欧拉角快了将近30%。对于实时性要求高的飞控来说,这很关键。

四元数和旋转矩阵可以互相转换:

// 四元数转旋转矩阵
float qw = 0.98f, qx = 0.02f, qy = 0.01f, qz = 0.15f;

float R_q[3][3] = {
    {1 - 2*(qy*qy + qz*qz), 2*(qx*qy - qw*qz),     2*(qx*qz + qw*qy)},
    {2*(qx*qy + qw*qz),     1 - 2*(qx*qx + qz*qz), 2*(qy*qz - qw*qx)},
    {2*(qx*qz - qw*qy),     2*(qy*qz + qw*qx),     1 - 2*(qx*qx + qy*qy)}
};

核心建议:在飞控内部,姿态解算和更新全部用四元数。只在需要输出给用户或做可视化时,才转成欧拉角。

这样既避免了万向锁,又保持了直观性。

2.5 知识体系总览

下面这张图,是我自己梳理的坐标系与姿态表示的知识结构。你把它理解了,后面控制算法的推导就顺了。

坐标系与姿态表示知识体系 地球坐标系 (NED) X: 北, Y: 东, Z: 地 绝对参考系,导航指令在此定义 传感器融合后输出在此坐标系 机体坐标系 (Body) X: 机头, Y: 右翼, Z: 机腹 传感器测量在此坐标系 控制输出(电机推力)在此执行 R_body_to_ned R_ned_to_body 欧拉角 Roll, Pitch, Yaw 直观,但有万向锁 适合小角度、可视化 旋转矩阵 3x3 正交矩阵 坐标系转换的桥梁 计算量适中 四元数 q = w + xi + yj + zk 无万向锁,计算高效 飞控内部首选 三者可以互相转换 欧拉角 ↔ 旋转矩阵 ↔ 四元数 实际飞控中:内部用四元数,输出用欧拉角

2.6 实战中的选择建议

场景 推荐表示 原因
姿态初始化 欧拉角 直观,方便设置初始姿态
姿态解算(IMU融合) 四元数 无万向锁,计算快,适合实时更新
坐标系转换 旋转矩阵 直接做向量旋转,物理意义清晰
垂直起降过渡段 四元数 + 旋转矩阵 大角度机动,必须避开欧拉角奇点
地面站可视化 欧拉角 人眼看得懂,调试方便

我的习惯:在飞控代码里,姿态核心变量用四元数存储。每次传感器数据进来,用四元数更新姿态。需要输出控制量时,转成旋转矩阵做向量旋转。只有发给地面站时,才转成欧拉角。

这样既保证了精度和效率,又兼顾了可读性。

好了,坐标系和姿态表示就聊到这。这些东西看起来基础,但真的是飞控的「地基」。地基不稳,楼盖得再高也得塌。你把这些概念吃透了,后面讲姿态控制算法时,就会觉得顺理成章。


公众号:蓝海资料掘金营,微信deep3321