APM/PX4如何根据遥控器rol/pit杆量生成期望姿态
回答计算过程需要给出代码级证据。
假设某时刻,多旋翼无人机的的实例和期望航向角均为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 STABILIZE | PX4 Multicopter Stabilized |
|---|---|---|
| 期望 roll (\phi_d) | +37.761° | +50.768° |
| 期望 pitch (\theta_d) | −50.768° | −37.761° |
| 期望 Euler yaw (\psi_d) | 0° | −18.435° |
| (z_b) 与大地 (Z_D) 夹角 | 60° | 60° |
| (x_b) 地面投影与正北夹角 | 0° | 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→ Northroll > 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 ∥t∥≤tan60∘=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.75−0.250.612372−0.250.750.612372−0.612372−0.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)


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