BetaFlight和iNavFlight都是出自CleanFlight分支,两者的侧重点不太一样。从四元数应用的角度来看,iNav不仅代码比较完整,且对四元数操作进行了相应的数学逻辑封装。
我们这里针对四元数的应用,结合代码进行一次整理和理解。
Q → = q 0 + q → \overrightarrow{Q} = q_0 + \overrightarrow{q} Q=q0+q
Q → = q 0 + q 1 ⋅ i + q 2 ⋅ j + q 3 ⋅ k \overrightarrow{Q} = q_0 + q_1 \cdot i + q_2 \cdot j + q_3 \cdot k Q=q0+q1⋅i+q2⋅j+q3⋅k
注:高中课本的复数就是超复数的一种特殊情况,其中 q 2 = q 3 = 0 q_2 = q_3 = 0 q2=q3=0。
Q → = c o s θ 2 + u → s i n θ 2 \overrightarrow{Q} = cos \cfrac{\theta}{2} + \overrightarrow{u} sin \cfrac{\theta}{2} Q=cos2θ+usin2θ
P → ± Q → = ( p 0 + p 1 ⋅ i + p 2 ⋅ j + p 3 ⋅ k ) ± ( q 0 + q 1 ⋅ i + q 2 ⋅ j + q 3 ⋅ k ) = ( p 0 ± q 0 ) + ( p 1 ± q 1 ) ⋅ i + ( p 2 ± q 2 ) ⋅ j + ( p 3 ± q 3 ) ⋅ k \overrightarrow{P} \pm \overrightarrow{Q} = (p_0 + p_1 \cdot i + p_2 \cdot j + p_3 \cdot k) \pm (q_0 + q_1 \cdot i + q_2 \cdot j + q_3 \cdot k) = (p_0 \pm q_0) + (p_1 \pm q_1) \cdot i + (p_2 \pm q_2) \cdot j + (p_3 \pm q_3) \cdot k P±Q=(p0+p1⋅i+p2⋅j+p3⋅k)±(q0+q1⋅i+q2⋅j+q3⋅k)=(p0±q0)+(p1±q1)⋅i+(p2±q2)⋅j+(p3±q3)⋅k
a ⋅ Q → = a ⋅ q 0 + a ⋅ q 1 ⋅ i + a ⋅ q 2 ⋅ j + a ⋅ q 3 ⋅ k a \cdot \overrightarrow{Q} = a \cdot q_0 + a \cdot q_1 \cdot i + a \cdot q_2 \cdot j + a \cdot q_3 \cdot k a⋅Q=a⋅q0+a⋅q1⋅i+a⋅q2⋅j+a⋅q3⋅k
P → ⋅ Q → = ∣ P ∣ ∣ Q ∣ c o s θ P Q \overrightarrow{P} \cdot \overrightarrow{Q} = \rvert P \rvert \rvert Q \rvert cos \theta_{PQ} P⋅Q=∣P∣∣Q∣cosθPQ
P → ⋅ Q → = ( p 0 + p 1 ⋅ i + p 2 ⋅ j + p 3 ⋅ k ) ⋅ ( q 0 + q 1 ⋅ i + q 2 ⋅ j + q 3 ⋅ k ) = p 0 q 0 + p 1 q 1 + p 2 q 2 + p 3 q 3 \overrightarrow{P} \cdot \overrightarrow{Q} = (p_0 + p_1 \cdot i + p_2 \cdot j + p_3 \cdot k) \cdot (q_0 + q_1 \cdot i + q_2 \cdot j + q_3 \cdot k) = p_0 q_0 + p_1 q_1 + p_2 q_2 + p_3 q_3 P⋅Q=(p0+p1⋅i+p2⋅j+p3⋅k)⋅(q0+q1⋅i+q2⋅j+q3⋅k)=p0q0+p1q1+p2q2+p3q3
P → × Q → = − Q → × P → \overrightarrow{P} \times \overrightarrow{Q} = -\overrightarrow{Q} \times \overrightarrow{P} P×Q=−Q×P
∣ P → × Q → ∣ = ∣ P ∣ ∣ Q ∣ s i n θ P Q \rvert \overrightarrow{P} \times \overrightarrow{Q} \rvert =\rvert P \rvert \rvert Q \rvert sin \theta_{PQ} ∣P×Q∣=∣P∣∣Q∣sinθPQ
P → × Q → = ( P → × Q → ) x + ( P → × Q → ) y + ( P → × Q → ) z \overrightarrow{P} \times \overrightarrow{Q} = (\overrightarrow{P} \times \overrightarrow{Q} )_x + (\overrightarrow{P} \times \overrightarrow{Q} )_y + (\overrightarrow{P} \times \overrightarrow{Q} )_z P×Q=(P×Q)x+(P×Q)y+(P×Q)z
(
P
→
×
Q
→
)
x
=
P
y
Q
z
−
P
z
Q
y
(\overrightarrow{P} \times \overrightarrow{Q} )_x = P_yQ_z - P_zQ_y
(P×Q)x=PyQz−PzQy
(
P
→
×
Q
→
)
y
=
P
z
Q
x
−
P
x
Q
z
(\overrightarrow{P} \times \overrightarrow{Q} )_y = P_zQ_x - P_xQ_z
(P×Q)y=PzQx−PxQz
(
P
→
×
Q
→
)
z
=
P
x
Q
y
−
P
y
Q
x
(\overrightarrow{P} \times \overrightarrow{Q} )_z = P_xQ_y - P_yQ_x
(P×Q)z=PxQy−PyQx
P → × Q → = ( p 0 + p 1 ⋅ i + p 2 ⋅ j + p 3 ⋅ k ) × ( q 0 + q 1 ⋅ i + q 2 ⋅ j + q 3 ⋅ k ) = ( p 0 q 0 − p 1 q 1 − p 2 q 2 − p 3 q 3 ) + ( p 0 q 1 + p 1 q 0 + p 2 q 3 − p 3 q 2 ) ⋅ i + ( p 0 q 2 + p 2 q 0 + p 3 q 1 − p 1 q 3 ) ⋅ j + ( p 0 q 3 + p 3 q 0 + p 1 q 2 − p 2 q 1 ) ⋅ k = r 0 + r 1 ⋅ i + r 2 ⋅ j + r 3 ⋅ k \overrightarrow{P} \times \overrightarrow{Q} = (p_0 + p_1 \cdot i + p_2 \cdot j + p_3 \cdot k) \times (q_0 + q_1 \cdot i + q_2 \cdot j + q_3 \cdot k) = (p_0q_0 - p_1q_1 - p_2q_2 - p_3q_3) + (p_0q_1 + p_1q_0 + p_2q_3 - p_3q_2) \cdot i + (p_0q_2 + p_2q_0 + p_3q_1 -p_1q_3) \cdot j +(p_0q_3 + p_3q_0 +p_1q_2 - p_2q_1) \cdot k = r_0 + r_1 \cdot i + r_2 \cdot j + r_3 \cdot k P×Q=(p0+p1⋅i+p2⋅j+p3⋅k)×(q0+q1⋅i+q2⋅j+q3⋅k)=(p0q0−p1q1−p2q2−p3q3)+(p0q1+p1q0+p2q3−p3q2)⋅i+(p0q2+p2q0+p3q1−p1q3)⋅j+(p0q3+p3q0+p1q2−p2q1)⋅k=r0+r1⋅i+r2⋅j+r3⋅k
换成矩阵表达式(便于后续计算机计算):
R = M ( P ) Q = M ′ ( Q ) P R = M(P)Q = M\rq(Q)P R=M(P)Q=M′(Q)P
[
r
0
r
1
r
2
r
3
]
=
M
(
P
)
Q
=
[
p
0
−
p
1
−
p
2
−
p
3
p
1
p
0
−
p
3
p
2
p
2
p
3
p
0
−
p
1
p
3
−
p
2
p
1
p
0
]
[
q
0
q
1
q
2
q
3
]
[
r
0
r
1
r
2
r
3
]
=
M
′
(
Q
)
P
=
[
q
0
−
q
1
−
q
2
−
q
3
q
1
q
0
q
3
−
q
2
q
2
−
q
3
q
0
q
1
q
3
q
2
−
q
1
q
0
]
[
p
0
p
1
p
2
p
3
]
P → ⊗ R → = 1 \overrightarrow{P} \otimes \overrightarrow{R} = 1 P⊗R=1
将记作: R → = P − 1 → = P ∗ → \overrightarrow{R} = \overrightarrow{P^{-1}}=\overrightarrow{P^*} R=P−1=P∗
P → ⊗ P ∗ → = ( p 0 + p 1 ⋅ i + p 2 ⋅ j + p 3 ⋅ k ) ⊗ ( p 0 − p 1 ⋅ i − p 2 ⋅ j − p 3 ⋅ k ) = q 0 2 + q 1 2 + q 2 2 + q 3 2 = ∥ P ∥ \overrightarrow{P} \otimes \overrightarrow{P^*} = (p_0 + p_1 \cdot i + p_2 \cdot j + p_3 \cdot k) \otimes (p_0 - p_1 \cdot i - p_2 \cdot j - p_3 \cdot k)= q_0^2 + q_1^2 + q_2^2 + q_3^2 = \rVert P \rVert P⊗P∗=(p0+p1⋅i+p2⋅j+p3⋅k)⊗(p0−p1⋅i−p2⋅j−p3⋅k)=q02+q12+q22+q32=∥P∥
所以: P − 1 → = P ∗ → ∥ P ∥ \overrightarrow{P^{-1}} =\cfrac{\overrightarrow{P^*}}{ \rVert P \rVert} P−1=∥P∥P∗
typedef struct {
float q0, q1, q2, q3;
} fpQuaternion_t;
static inline fpQuaternion_t * quaternionInitUnit(fpQuaternion_t * result)
{
result->q0 = 1.0f;
result->q1 = 0.0f;
result->q2 = 0.0f;
result->q3 = 0.0f;
return result;
}
static inline fpQuaternion_t * quaternionInitFromVector(fpQuaternion_t * result, const fpVector3_t * v)
{
result->q0 = 0.0f;
result->q1 = v->x;
result->q2 = v->y;
result->q3 = v->z;
return result;
}
注:进行转换的输入参数必须归一化。
static inline void quaternionToAxisAngle(fpAxisAngle_t * result, const fpQuaternion_t * q)
{
fpAxisAngle_t a = {.axis = {{1.0f, 0.0f, 0.0f}}, .angle = 0};
a.angle = 2.0f * acos_approx(constrainf(q->q0, -1.0f, 1.0f));
if (a.angle > M_PIf) {
a.angle -= 2.0f * M_PIf;
}
const float sinVal = sqrt(1.0f - q->q0 * q->q0);
// Axis is only valid when rotation is large enough sin(0.0057 deg) = 0.0001
if (sinVal > 1e-4f) {
a.axis.x = q->q1 / sinVal;
a.axis.y = q->q2 / sinVal;
a.axis.z = q->q3 / sinVal;
} else {
a.angle = 0;
}
*result = a;
}
static inline fpQuaternion_t * axisAngleToQuaternion(fpQuaternion_t * result, const fpAxisAngle_t * a)
{
fpQuaternion_t q;
const float s = sin_approx(a->angle / 2.0f);
q.q0 = cos_approx(a->angle / 2.0f);
q.q1 = -a->axis.x * s;
q.q2 = -a->axis.y * s;
q.q3 = -a->axis.z * s;
*result = q;
return result;
}
问题1:为什么 q 1 q 2 q 3 q_1 q_2 q_3 q1q2q3是负值??? 按照定义的角度看应该是正的呀,有知道的朋友请评论批注,谢谢!
static inline fpQuaternion_t * quaternionAdd(fpQuaternion_t * result, const fpQuaternion_t * a, const fpQuaternion_t * b)
{
fpQuaternion_t p;
p.q0 = a->q0 + b->q0;
p.q1 = a->q1 + b->q1;
p.q2 = a->q2 + b->q2;
p.q3 = a->q3 + b->q3;
*result = p;
return result;
}
static inline fpQuaternion_t * quaternionScale(fpQuaternion_t * result, const fpQuaternion_t * a, const float b)
{
fpQuaternion_t p;
p.q0 = a->q0 * b;
p.q1 = a->q1 * b;
p.q2 = a->q2 * b;
p.q3 = a->q3 * b;
*result = p;
return result;
}
略.
注:貌似点乘在实际飞控应用中用的不多,代码中未见定义。
根据叉乘定义和右手螺旋法则,我们知道叉乘结果是两个矢量模与 s i n θ sin \theta sinθ乘积,方向垂直于两个叉乘矢量。
所以,在实际应用中,归一化的矢量叉乘结果往往用于表达两个矢量的相关性。当两个物理量表达的是同一个物理特性时,叉乘结果的值越趋于零,表示误差越小。
static inline fpQuaternion_t * quaternionMultiply(fpQuaternion_t * result, const fpQuaternion_t * a, const fpQuaternion_t * b)
{
fpQuaternion_t p;
p.q0 = a->q0 * b->q0 - a->q1 * b->q1 - a->q2 * b->q2 - a->q3 * b->q3;
p.q1 = a->q0 * b->q1 + a->q1 * b->q0 + a->q2 * b->q3 - a->q3 * b->q2;
p.q2 = a->q0 * b->q2 - a->q1 * b->q3 + a->q2 * b->q0 + a->q3 * b->q1;
p.q3 = a->q0 * b->q3 + a->q1 * b->q2 - a->q2 * b->q1 + a->q3 * b->q0;
*result = p;
return result;
}
static inline fpVector3_t * quaternionRotateVector(fpVector3_t * result, const fpVector3_t * vect, const fpQuaternion_t * ref)
{
fpQuaternion_t vectQuat, refConj;
vectQuat.q0 = 0;
vectQuat.q1 = vect->x;
vectQuat.q2 = vect->y;
vectQuat.q3 = vect->z;
quaternionConjugate(&refConj, ref);
quaternionMultiply(&vectQuat, &refConj, &vectQuat);
quaternionMultiply(&vectQuat, &vectQuat, ref);
result->x = vectQuat.q1;
result->y = vectQuat.q2;
result->z = vectQuat.q3;
return result;
}
static inline fpVector3_t * quaternionRotateVectorInv(fpVector3_t * result, const fpVector3_t * vect, const fpQuaternion_t * ref)
{
fpQuaternion_t vectQuat, refConj;
vectQuat.q0 = 0;
vectQuat.q1 = vect->x;
vectQuat.q2 = vect->y;
vectQuat.q3 = vect->z;
quaternionConjugate(&refConj, ref);
quaternionMultiply(&vectQuat, ref, &vectQuat);
quaternionMultiply(&vectQuat, &vectQuat, &refConj);
result->x = vectQuat.q1;
result->y = vectQuat.q2;
result->z = vectQuat.q3;
return result;
}
static inline float quaternionNormSqared(const fpQuaternion_t * q)
{
return sq(q->q0) + sq(q->q1) + sq(q->q2) + sq(q->q3);
}
static inline fpQuaternion_t * quaternionConjugate(fpQuaternion_t * result, const fpQuaternion_t * q)
{
result->q0 = q->q0;
result->q1 = -q->q1;
result->q2 = -q->q2;
result->q3 = -q->q3;
return result;
}
static inline fpQuaternion_t * quaternionNormalize(fpQuaternion_t * result, const fpQuaternion_t * q)
{
float mod = fast_fsqrtf(quaternionNormSqared(q));
if (mod < 1e-6f) {
// Length is too small - re-initialize to zero rotation
result->q0 = 1;
result->q1 = 0;
result->q2 = 0;
result->q3 = 0;
}
else {
result->q0 = q->q0 / mod;
result->q1 = q->q1 / mod;
result->q2 = q->q2 / mod;
result->q3 = q->q3 / mod;
}
return result;
}
问题2:这里为什么NED (sensor frame) 和 NEU (navigation)是这个关系,逻辑在哪里???
void imuTransformVectorBodyToEarth(fpVector3_t * v)
{
// From body frame to earth frame
quaternionRotateVectorInv(v, v, &orientation);
// HACK: This is needed to correctly transform from NED (sensor frame) to NEU (navigation)
v->y = -v->y;
}
void imuTransformVectorEarthToBody(fpVector3_t * v)
{
// HACK: This is needed to correctly transform from NED (sensor frame) to NEU (navigation)
v->y = -v->y;
// From earth frame to body frame
quaternionRotateVector(v, v, &orientation);
}
四元数在四轴飞控方面关于姿态和导航方面的应用具有举足轻重的意义。尤其对PID的的误差计算带来了便捷,但是这个在物理概念上需要明确的符号推导和现实意义,才能在后续传感融合上得到长远的发展。
为此,整理了上述基本概念,但是截止目前为止,还存在以下两个问题:
【1】三角式转复数式的时候,什么 q 1 q 2 q 3 q_1 q_2 q_3 q1q2q3是负值??? 按照定义的角度看应该是正的,如何理解???
==》这里搜索了iNav的代码,没有看到axisAngleToQuaternion函数在代码中使用。因此即使有问题,也没有太多关系。
【2】NED (sensor frame) 和 NEU (navigation)的指向是如何定义的?
==》请参考补充资料
如果有同学知道原因,也请多多指点,谢谢先!
【1】Comparison of 3-D Coordinate Systems
【2】About Aerospace Coordinate Systems
【3】Flight controller is different from the airframe coordinate system? #11903