回答计算过程需要给出代码级证据。
假设某时刻,多旋翼无人机的的实例和期望航向角均为0度(也即朝北),实际和期望横滚俯仰角均为0。自稳模式下的角度限制为60度,当遥控器的横滚俯仰摇杆向右前方打满时,问:
1、apm px4分别生成的期望欧拉角是?
2、机体z轴与大地z的夹角是多少?
3、机体x轴(机头)在大地的投影与大地x轴(正北)的夹角是多少
4、机体的(-z轴)在地面的投影,与正北的夹角是多少?
注:坐标系定义按照(前右下FRD)(北东地NED)。


按你给定的坐标系:

  • 大地系 NED:(X_N)=北,(Y_E)=东,(Z_D)=地
  • 机体系 FRD:(x_b)=前,(y_b)=右,(z_b)=下
  • 初始实际/期望航向均为 (0^\circ)
  • 初始 roll/pitch 都为 (0^\circ)
  • 右前满杆
  • 最大倾斜角设为 (60^\circ)
  • 下面计算的是满杆指令稳定下来后的期望姿态;不讨论输入滤波、姿态目标轨迹整形造成的瞬态

我查的是当前 ArduPilot master 的 Copter STABILIZE 和 PX4 main 的 Multicopter Stabilized 控制链。

先给结论

项目ArduPilot Copter STABILIZEPX4 Multicopter Stabilized
期望 roll (\phi_d)+37.761°+50.768°
期望 pitch (\theta_d)−50.768°−37.761°
期望 Euler yaw (\psi_d)−18.435°
(z_b) 与大地 (Z_D) 夹角60°60°
(x_b) 地面投影与正北夹角18.435°,偏西
(-z_b) 地面投影与正北夹角45°,偏东45°,偏东

这里最值得注意的是:

APM 和 PX4 最终得到的 (-z_b) 推力方向完全一样,但机头方向不一样。

也就是说,它们都让无人机向“东北 45°”方向倾斜 (60^\circ),但是它们选择的姿态分解方式不同。


1. ArduPilot 的计算

ArduPilot 的 ModeStabilize::run() 直接调用:

get_pilot_desired_lean_angles_rad(
    target_roll_rad,
    target_pitch_rad,
    attitude_control->lean_angle_max_rad(),
    attitude_control->lean_angle_max_rad());

然后把得到的 roll/pitch 送入 Euler 姿态控制器:

attitude_control->
    input_euler_angle_roll_pitch_euler_rate_yaw_rad(
        target_roll_rad,
        target_pitch_rad,
        target_yaw_rate_rads);

源码就在 ArduCopter/mode_stabilize.cpp。(GitHub)

get_pilot_desired_lean_angles_rad() 实际调用:

rc_input_to_roll_pitch_rad(
    channel_roll->norm_input_dz(),
    channel_pitch->norm_input_dz(),
    angle_max_rad,
    angle_limit_rad,
    roll_out_rad,
    pitch_out_rad);

(GitHub)

真正关键的是 AP_Math/control.cpp 中的实现:

thrust.x = -tanf(angle_max_rad * pitch_in_norm);
thrust.y =  tanf(angle_max_rad * roll_in_norm);

thrust.limit_length(thrust_limit);

pitch_out_rad = -atanf(thrust.x);

roll_out_rad =
    atanf(cosf(pitch_out_rad) * thrust.y);

其中

thrust_limit = tanf(angle_limit_rad);

(GitHub)

这说明 ArduPilot 不是简单地做

ϕ = 60 ∘ , θ = − 60 ∘ \phi=60^\circ,\qquad \theta=-60^\circ ϕ=60,θ=60

而是先构造一个二维“水平推力”向量,再限制这个向量的长度,最后反解 Euler 角。


APM 的满杆符号

右打满:

u r = + 1 u_r=+1 ur=+1

前打满:

u p = − 1 u_p=-1 up=1

ArduPilot 自己的 NE 速度映射也能验证这个符号:

vel_ne_ms = Vector2f(-pitch_out_norm,
                      roll_out_norm);

航向为 0 时:

  • -pitch > 0 → North
  • roll > 0 → East

因此“右前”就是

( u r , u p ) = ( + 1 , − 1 ) (u_r,u_p)=(+1,-1) (ur,up)=(+1,1)

(GitHub)


第一步:构造 thrust XY

设:

A = 60 ∘ A=60^\circ A=60

则:

t x = − tan ⁡ ( A u p ) t_x=-\tan(Au_p) tx=tan(Aup)

其中:

  • (t_x):ArduPilot 构造的水平推力向量第一分量
  • (A):最大倾斜角
  • (u_p):归一化 pitch 摇杆输入

代入 (u_p=-1):

t x = tan ⁡ 60 ∘ = 3 t_x=\tan60^\circ=\sqrt3 tx=tan60=3

另一轴:

t y = tan ⁡ ( A u r ) t_y=\tan(Au_r) ty=tan(Aur)

其中:

  • (t_y):水平推力向量第二分量
  • (u_r):归一化 roll 摇杆输入

所以:

t y = tan ⁡ 60 ∘ = 3 t_y=\tan60^\circ=\sqrt3 ty=tan60=3

原始长度:

$$
\sqrt{t_x2+t_y2}

\sqrt6
$$

但代码限制:

∥ t ∥ ≤ tan ⁡ 60 ∘ = 3 \|\mathbf t\|\le\tan60^\circ=\sqrt3 ttan60=3

因此整体缩放:

$$
t_x=t_y

\frac{\sqrt3}{\sqrt2}

\sqrt{\frac32}

1.224745
$$


第二步:反算 pitch

源码:

pitch_out_rad = -atanf(thrust.x);

所以:

$$
\theta_d

-\arctan\sqrt{\frac32}
$$

其中:

  • (\theta_d):期望 pitch Euler 角

得到:

θ d = − 50.76848 ∘ \boxed{\theta_d=-50.76848^\circ} θd=50.76848


第三步:反算 roll

源码:

roll_out_rad =
    atanf(cosf(pitch_out_rad) * thrust.y);

所以:

$$
\phi_d

\arctan
\left(
\cos\theta_d\sqrt{\frac32}
\right)
$$

其中:

  • (\phi_d):期望 roll Euler 角

因为:

$$
\cos50.76848^\circ

\sqrt{\frac25}
$$

于是:

$$
\phi_d

\arctan\sqrt{\frac35}
$$

得到:

ϕ d = + 37.76124 ∘ \boxed{\phi_d=+37.76124^\circ} ϕd=+37.76124

yaw 摇杆为零,且初始 yaw target 为 (0^\circ)。ArduPilot 该接口把 yaw 作为 Euler yaw rate 输入;源码明确对 yaw-rate target 做整形,并在无 feed-forward shaping 情况下直接:

_euler_angle_target_rad.z +=
    euler_yaw_rate_rads * _dt_s;

因此 (r_{\psi,d}=0) 时稳态 yaw target 不变化。(GitHub)

所以:

$$
\boxed{
(\phi_d,\theta_d,\psi_d)_\text{APM}

(37.761\circ,-50.768\circ,0^\circ)
}
$$


2. PX4 的计算

PX4 的做法明显不同。

当前 mc_att_control_main.cpp 里明确写着:

控制两个量:tilt angle 和 maximum tilt direction。

然后:

Vector2f v = Vector2f(
    _manual_control_setpoint.roll * _man_tilt_max,
   -_manual_control_setpoint.pitch * _man_tilt_max);

float v_norm = v.norm();

if (v_norm > _man_tilt_max) {
    v *= _man_tilt_max / v_norm;
}

Quatf q_sp_rp =
    AxisAnglef(v(0), v(1), 0.f);

(GitHub)

这和 APM 完全不是同一种参数化。

PX4 的 ManualControlSetpoint.msg 还明确规定:

roll  # move right, positive roll rotation
pitch # move forward, negative pitch rotation, nose down

所以右前满杆对应:

u r = + 1 , u p = + 1 u_r=+1,\qquad u_p=+1 ur=+1,up=+1

注意 PX4 的 pitch stick 输入为 +1 表示向前,但对应的是负 pitch 旋转。(GitHub)


第一步:生成 axis-angle 向量

最大 tilt:

A = 60 ∘ A=60^\circ A=60

满杆:

$$
\mathbf v

\begin{bmatrix}
A\
-A
\end{bmatrix}
$$

长度:

$$
|\mathbf v|

\sqrt2A>A
$$

所以代码将它缩放到长度 (A):

$$
\mathbf v

\begin{bmatrix}
A/\sqrt2\
-A/\sqrt2
\end{bmatrix}
$$

因此旋转轴为:

$$
\mathbf u

\begin{bmatrix}
1/\sqrt2\
-1/\sqrt2\
0
\end{bmatrix}
$$

旋转角:

α = 60 ∘ \alpha=60^\circ α=60


第二步:axis-angle → quaternion

对应 quaternion:

$$
q_{rp}

\begin{bmatrix}
\cos(\alpha/2)\
u_x\sin(\alpha/2)\
u_y\sin(\alpha/2)\
u_z\sin(\alpha/2)
\end{bmatrix}
$$

其中:

  • (q_{rp}):roll/pitch tilt 对应的姿态四元数
  • (\alpha):总倾斜角
  • (\mathbf u):倾斜旋转轴

代入:

$$
q_{rp}

\begin{bmatrix}
0.8660254\
0.3535534\
-0.3535534\
0
\end{bmatrix}
$$

初始 yaw setpoint 为零,因此:

Quatf q_sp_yaw(cosf(yaw_setpoint / 2.f),
               0.f, 0.f,
               sinf(yaw_setpoint / 2.f));

Quatf q_sp = q_sp_yaw * q_sp_rp;

当 yaw setpoint = 0:

q s p = q r p q_{sp}=q_{rp} qsp=qrp

(GitHub)


3. 为什么 PX4 最终 Euler yaw 不是 0?

这是最容易产生误解的地方。

PX4 源码自己专门提醒:

// The axis angle can change the yaw as well
// (noticeable at higher tilt angles).

甚至在紧接着的注释中直接给出了这个 induced-yaw 的公式。(GitHub)

也就是说:

yaw_setpoint = 0 并不意味着把最终 quaternion 转成 3-2-1 Euler 后得到的 (\psi) 必然还是 0。

PX4 的 Euler.hpp 明确采用 3-2-1 intrinsic Tait-Bryan,即通常所说的 yaw-pitch-roll / (Z-Y-X) Euler 定义,并通过 DCM 提取:

theta() = asin(-dcm(2,0));
phi()   = atan2(dcm(2,1), dcm(2,2));
psi()   = atan2(dcm(1,0), dcm(0,0));

(GitHub)

将上述 quaternion 转换后:

ϕ d = + 50.76848 ∘ \boxed{\phi_d=+50.76848^\circ} ϕd=+50.76848

θ d = − 37.76124 ∘ \boxed{\theta_d=-37.76124^\circ} θd=37.76124

ψ d = − 18.43495 ∘ \boxed{\psi_d=-18.43495^\circ} ψd=18.43495

所以:

$$
\boxed{
(\phi_d,\theta_d,\psi_d)_\text{PX4}

(50.768\circ,-37.761\circ,-18.435^\circ)
}
$$

这里 (-18.435^\circ) 表示机头地面投影相对北偏西。


4. 机体 (z_b) 与大地 (Z_D) 的夹角

对 FRD/NED 的 3-2-1 Euler:

$$
C^n_b

R_z(\psi)R_y(\theta)R_x(\phi)
$$

其中:

  • (C^n_b):机体系向量变换到 NED 的方向余弦矩阵
  • (\phi):roll
  • (\theta):pitch
  • (\psi):yaw

机体 (z_b) 在 NED 中就是矩阵第三列。

其与大地 (Z_D) 的夹角 (\gamma) 满足:

$$
\cos\gamma

\cos\phi\cos\theta
$$


ArduPilot

$$
\cos\gamma

\cos37.761^\circ
\cos50.768^\circ
=0.5
$$

所以:

γ APM = 60 ∘ \boxed{\gamma_\text{APM}=60^\circ} γAPM=60

PX4

虽然 Euler 分量不同,但 PX4 是直接构造了一个 (60^\circ) 的 tilt axis-angle,所以当然:

γ PX4 = 60 ∘ \boxed{\gamma_\text{PX4}=60^\circ} γPX4=60

因此问题 2 的答案:

APM = 60 ∘ , P X 4 = 60 ∘ \boxed{\text{APM}=60^\circ,\qquad PX4=60^\circ} APM=60,PX4=60


5. 机体 (x_b) 在地面的投影

对于 3-2-1 Euler,机体 (x_b) 在 NED 中:

$$
{}^n\mathbf x_b

\begin{bmatrix}
\cos\psi\cos\theta\
\sin\psi\cos\theta\
-\sin\theta
\end{bmatrix}
$$

其中:

  • 第一项:North 分量
  • 第二项:East 分量
  • 第三项:Down 分量

地面投影只保留 NE:

$$
{}^{NE}\mathbf x_b

\begin{bmatrix}
\cos\psi\cos\theta\
\sin\psi\cos\theta
\end{bmatrix}
$$

因此,只要 (|\theta|<90^\circ),它的地面方位角就是:

χ x b = ψ \chi_{x_b}=\psi χxb=ψ


ArduPilot

ψ = 0 ∘ \psi=0^\circ ψ=0

所以:

χ x b = 0 ∘ \boxed{\chi_{x_b}=0^\circ} χxb=0

即:

机头地面投影仍然严格指北。


PX4

ψ = − 18.43495 ∘ \psi=-18.43495^\circ ψ=18.43495

因此:

χ x b = − 18.435 ∘ \boxed{\chi_{x_b}=-18.435^\circ} χxb=18.435

如果方位角按 NED 常见定义:

  • 北 = (0^\circ)
  • 东 = (90^\circ)
  • 顺时针为正

则它也可以写成:

341.565 ∘ \boxed{341.565^\circ} 341.565

也就是:

机头在地面的投影相对正北向西偏 18.435°。

所以问题 3:

APM : 0 ∘ PX4 : 18.435 ∘ ,向西 \boxed{ \begin{aligned} \text{APM}&:0^\circ\\ \text{PX4}&:18.435^\circ\text{,向西} \end{aligned}} APMPX4:0:18.435,向西


6. (-z_b) 在地面的投影

这其实是最重要的物理量,因为多旋翼总推力方向基本就是:

− z b -\mathbf z_b zb

对于 ArduPilot 那组 Euler:

$$
(\phi,\theta,\psi)

(37.761\circ,-50.768\circ,0^\circ)
$$

计算得到:

$$
{}^n(-\mathbf z_b)

\begin{bmatrix}
0.612372\
0.612372\
-0.5
\end{bmatrix}
$$

其中:

  • North = (+0.612372)
  • East = (+0.612372)
  • Down = (-0.5),即向上

所以地面投影:

$$
{}^{NE}(-\mathbf z_b)

\begin{bmatrix}
0.612372\
0.612372
\end{bmatrix}
$$

方位角:

$$
\beta

\operatorname{atan2}(E,N)

\operatorname{atan2}(0.612372,0.612372)
$$

所以:

β = 45 ∘ \boxed{\beta=45^\circ} β=45

即东北方向。


PX4 的 quaternion 对应 DCM 为:

C b n = [ 0.75 − 0.25 − 0.612372 − 0.25 0.75 − 0.612372 0.612372 0.612372 0.5 ] C^n_b= \begin{bmatrix} 0.75&-0.25&-0.612372\\ -0.25&0.75&-0.612372\\ 0.612372&0.612372&0.5 \end{bmatrix} Cbn= 0.750.250.6123720.250.750.6123720.6123720.6123720.5

所以:

$$
{}^n(-\mathbf z_b)

\begin{bmatrix}
0.612372\
0.612372\
-0.5
\end{bmatrix}
$$

和 ArduPilot 完全相同。

因此:

$$
\boxed{
\beta_\text{APM}

\beta_\text{PX4}

45^\circ
}
$$

即:

两者的 (-z_b) 地面投影都严格指向东北 45°。


最后把这个差异画成一句话

右前对角满杆、最大 tilt (60^\circ) 时:

ArduPilot

它倾向于保持:

ψ = 0 ∘ \boxed{\psi=0^\circ} ψ=0

然后寻找一组 Euler roll/pitch,使推力方向指向东北:

ϕ = 37.761 ∘ , θ = − 50.768 ∘ \boxed{ \phi=37.761^\circ,\quad \theta=-50.768^\circ } ϕ=37.761,θ=50.768

所以:

机头仍指北,但机体向东北倾斜。


PX4

它首先直接规定一个:

东北方向、总 tilt = 60 ∘ \boxed{\text{东北方向、总 tilt}=60^\circ} 东北方向、总 tilt=60

的 axis-angle 旋转。

由这个 quaternion 再换算 Euler 后自然得到:

ϕ = 50.768 ∘ , θ = − 37.761 ∘ , ψ = − 18.435 ∘ \boxed{ \phi=50.768^\circ,\quad \theta=-37.761^\circ,\quad \psi=-18.435^\circ } ϕ=50.768,θ=37.761,ψ=18.435

所以:

推力仍指东北,但机头会相对北向西偏约 18.4°。

这不是 PX4 “错误地产生了 yaw 指令”,而是 axis-angle 定义的倾斜姿态用 3-2-1 Euler 表示时产生的 Euler-yaw 耦合;PX4 源码甚至专门对此作了注释。(GitHub)


在这里插入图片描述

在这里插入图片描述

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐