惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
量子位
G
Google Developers Blog
J
Java Code Geeks
N
Netflix TechBlog - Medium
博客园 - 聂微东
宝玉的分享
宝玉的分享
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
月光博客
月光博客
The Cloudflare Blog
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
雷峰网
雷峰网
M
MIT News - Artificial intelligence
T
Tailwind CSS Blog
V
Visual Studio Blog
阮一峰的网络日志
阮一峰的网络日志
博客园 - 三生石上(FineUI控件)
Microsoft Azure Blog
Microsoft Azure Blog
aimingoo的专栏
aimingoo的专栏
Martin Fowler
Martin Fowler
有赞技术团队
有赞技术团队
T
The Blog of Author Tim Ferriss

博客园 - longyue

[BF学院_卷1] -- 第30讲:mixTable() 总结 —— Mixer 完整控制模型 [BF学院_卷1] -- 第29讲:applyMixToMotors() —— 最终 Motor Output 生成 [BF学院_卷1] -- 第28讲:applyMixerAdjustment() —— 为什么 Mixer 需要重新分配控制权限 [BF学院_卷1] -- 第27讲:PID Output 如何进入 Mixer —— 从控制需求到 motorMix [BF学院_卷1] -- 第26讲:calculateThrottleAndCurrentMotorEndpoints() —— Mixer 如何确定油门基础范围 [BF学院_卷1] -- 第25讲:mixTable() 总览 —— Mixer 在控制循环中的位置 [BF学院_卷1] -- 第24讲:mixer_init.c —— 飞控启动时如何准备 Mixer [BF学院_卷1] -- 第23讲:Mixer 数学模型 —— motorMixer_t 如何形成 Motor Output [BF学院_卷1] -- 第22讲:Betaflight 如何描述 Mixer —— mixer.h 数据结构 [BF学院_卷1] -- 第21讲:四旋翼为什么可以通过电机控制姿态 —— Mixer 背后的物理基础 [BF学院_卷1] -- 第20讲:从 PID Output 到 Motor Command(电机指令)——为什么飞控需要 Mixer [BF学院_卷1] -- 第19讲:PID Controller(PID控制器)——飞控到底如何理解误差并控制飞机? [BF学院_卷1] -- 第18讲:PID Controller 的最后一环——为什么飞控还需要 Feedforward(前馈控制)? [BF学院_卷1] -- 第17讲:D Term(微分控制项)——飞控如何提前感知运动变化? [BF学院_卷1] -- 第16讲:I Term(积分控制项)——飞机为什么总是差一点? [BF学院_卷1] -- 第14讲:Rate Error(角速度误差)——PID 真正控制的到底是什么? [BF学院_卷1] -- 第15讲:P Term(比例控制项)——飞控是如何利用误差开始控制飞机的? [BF学院_卷1] -- 第13讲:Outer Loop 的终点——为什么还要读取 Current Angular Rate(当前角速度)? [BF学院_卷1] -- 第十二讲:Yaw Compensation(偏航补偿) [BF学院_卷1] -- 第12讲脚本 [BF学院_卷1] -- 第十一讲:Current Angular Rate(当前角速度)——飞控怎样知道飞机现在转得有多快? [BF学院_卷1] -- 第十讲:Angle Error(姿态角误差)如何变成 Desired Angular Rate(目标角速度) [BF学院_卷1] -- 第九讲:Current Angle(当前姿态角)遇见 Target Angle(目标姿态角) [BF学院_卷1] -- 第八讲:飞手打杆以后,飞控首先得到什么? [BF学院_卷1] -- 第七讲:为什么 Pitch Flip 只在 180° 看起来正确 [BF学院_卷1] -- 第6讲:为什么 Pitch Flip 会骗人 [BF学院_卷1] -- 第6讲脚本 [BF学院_卷1] -- 第五讲:第一次修正——Pitch Flip 为什么看起来有效 [BF学院_卷1] -- 第5讲脚本 - longyue [BF学院_卷1] -- 第4讲:为什么"绕飞机自己转"不等于"朝世界转" - longyue
[BF学院_卷1] -- 第7讲脚本
longyue · 2026-08-03 · via 博客园 - longyue
import numpy as np
import matplotlib.pyplot as plt

# ==========================================================
# Rodrigues Rotation
# ==========================================================
def rotate(v, axis, angle):

    axis = axis / np.linalg.norm(axis)

    return (
        v * np.cos(angle)
        + np.cross(axis, v) * np.sin(angle)
        + axis * np.dot(axis, v) * (1 - np.cos(angle))
    )

# ==========================================================
# Initial Attitude
# ==========================================================
pitch_deg = 30

pitch = np.deg2rad(pitch_deg)

nose0 = np.array([
    np.cos(pitch),
    0,
    np.sin(pitch)
])

earth_z = np.array([0, 0, 1])

body_z = np.array([
    -np.sin(pitch),
    0,
    np.cos(pitch)
])

body_z /= np.linalg.norm(body_z)

# ==========================================================
# Sample Yaw Angles
# ==========================================================
yaw_samples = [
    0,
    15,
    30,
    45,
    60,
    75,
    90,
    105,
    120,
    135,
    150,
    165,
    180
]

earth_points = []
body_points = []

for yaw_deg in yaw_samples:

    yaw = np.deg2rad(yaw_deg)

    earth_points.append(
        rotate(
            nose0,
            earth_z,
            -yaw
        )
    )

    body_points.append(
        rotate(
            nose0,
            body_z,
            -yaw
        )
    )

earth_points = np.array(earth_points)
body_points = np.array(body_points)

# ==========================================================
# Pitch Flip Prediction
# ==========================================================

pitch_flip_points = earth_points.copy()

pitch_flip_points[:,2] *= -1

# ==========================================================
# Layout
# ==========================================================
fig = plt.figure(
    figsize=(14, 12)
)

# ==========================================================
# 3D View
# ==========================================================
ax3d = plt.subplot2grid(
    (3,2),
    (0,0),
    rowspan=2,
    colspan=2,
    projection='3d'
)

# ----------------------------------------------------------
# Ground Plane
# ----------------------------------------------------------
xx, yy = np.meshgrid(
    np.linspace(-1.2,1.2,10),
    np.linspace(-1.2,1.2,10)
)

zz = np.zeros_like(xx)

ax3d.plot_surface(
    xx,
    yy,
    zz,
    color='lightgray',
    alpha=0.25,
    edgecolor='none'
)

# ----------------------------------------------------------
# Earth Z
# ----------------------------------------------------------
ax3d.plot(
    [0,0],
    [0,0],
    [0,1.2],
    color='blue',
    linewidth=2,
    label='Earth Z'
)

# ----------------------------------------------------------
# Body Z'
# ----------------------------------------------------------
ax3d.plot(
    [0,body_z[0]],
    [0,body_z[1]],
    [0,body_z[2]],
    '--',
    color='red',
    linewidth=2,
    label="Body Z'"
)

# ----------------------------------------------------------
# Reference Circle
# ----------------------------------------------------------
theta = np.linspace(
    0,
    2*np.pi,
    300
)

r = np.cos(pitch)

ref_x = r*np.cos(theta)
ref_y = r*np.sin(theta)
ref_z = np.ones_like(theta)*np.sin(pitch)

ax3d.plot(
    ref_x,
    ref_y,
    ref_z,
    '--',
    color='black',
    alpha=0.4
)

# ----------------------------------------------------------
# Earth Final Points
# ----------------------------------------------------------
ax3d.plot(
    earth_points[:,0],
    earth_points[:,1],
    earth_points[:,2],
    color='blue',
    linewidth=2,
    label='Earth Yaw Final'
)

# ----------------------------------------------------------
# Body Final Points
# ----------------------------------------------------------
ax3d.plot(
    body_points[:,0],
    body_points[:,1],
    body_points[:,2],
    color='red',
    linewidth=2,
    label='Body Yaw Final'
)

# ----------------------------------------------------------
# Pitch Flip Prediction
# ----------------------------------------------------------
ax3d.plot(
    pitch_flip_points[:,0],
    pitch_flip_points[:,1],
    pitch_flip_points[:,2],

    color='green',
    linewidth=2,

    label='Pitch Flip Prediction'
)

# ----------------------------------------------------------
# Connect Error Lines
# ----------------------------------------------------------
for i in range(len(yaw_samples)):

    ax3d.plot(
        [
            earth_points[i,0],
            body_points[i,0]
        ],
        [
            earth_points[i,1],
            body_points[i,1]
        ],
        [
            earth_points[i,2],
            body_points[i,2]
        ],
        color='gray',
        alpha=0.5
    )

# ----------------------------------------------------------
# Earth -> Pitch Flip
# ----------------------------------------------------------
for i in range(len(yaw_samples)):

    ax3d.plot(
        [
            earth_points[i,0],
            pitch_flip_points[i,0]
        ],
        [
            earth_points[i,1],
            pitch_flip_points[i,1]
        ],
        [
            earth_points[i,2],
            pitch_flip_points[i,2]
        ],

        color='green',
        linestyle='--',
        alpha=0.4
    )

# ----------------------------------------------------------
# Labels
# ----------------------------------------------------------
important = [
    45,
    90,
    135,
    180
]

for yaw_deg in important:

    idx = yaw_samples.index(yaw_deg)

    ax3d.text(
        earth_points[idx,0],
        earth_points[idx,1],
        earth_points[idx,2]+0.05,
        f"{yaw_deg}°",
        color='blue'
    )

    ax3d.text(
        body_points[idx,0],
        body_points[idx,1],
        body_points[idx,2]-0.05,
        f"{yaw_deg}°",
        color='red'
    )
    
    ax3d.text(
        pitch_flip_points[idx,0],
        pitch_flip_points[idx,1],
        pitch_flip_points[idx,2],
        f"P{yaw_deg}",
        color='green'
    )

# ----------------------------------------------------------
# Start Point
# ----------------------------------------------------------
ax3d.scatter(
    nose0[0],
    nose0[1],
    nose0[2],
    color='black',
    s=80
)

ax3d.text(
    nose0[0],
    nose0[1],
    nose0[2]+0.05,
    "Start"
)

# ----------------------------------------------------------
# Axis
# ----------------------------------------------------------
ax3d.set_title(
    "Pitch Flip Failure Curve"
)

ax3d.set_xlim(-1.2,1.2)
ax3d.set_ylim(-1.2,1.2)
ax3d.set_zlim(-1.2,1.2)

ax3d.set_box_aspect([1,1,1])

ax3d.legend()

# ==========================================================
# Top View
# ==========================================================
ax_top = plt.subplot2grid(
    (3,2),
    (2,0)
)

ax_top.plot(
    earth_points[:,0],
    earth_points[:,1],
    '-o',
    color='blue'
)

ax_top.plot(
    body_points[:,0],
    body_points[:,1],
    '-o',
    color='red'
)

ax_top.plot(
    pitch_flip_points[:,0],
    pitch_flip_points[:,1],
    '-o',
    color='green'
)

ax_top.grid(True)

ax_top.set_aspect('equal')

ax_top.set_title(
    "Top View"
)

# ==========================================================
# Side View
# ==========================================================
ax_side = plt.subplot2grid(
    (3,2),
    (2,1)
)

ax_side.plot(
    earth_points[:,0],
    earth_points[:,2],
    '-o',
    color='blue'
)

ax_side.plot(
    body_points[:,0],
    body_points[:,2],
    '-o',
    color='red'
)

ax_side.plot(
    pitch_flip_points[:,0],
    pitch_flip_points[:,2],
    '-o',
    color='green'
)

ax_side.axhline(
    0,
    color='gray',
    linestyle='--'
)

ax_side.axhline(
    np.sin(pitch),
    color='black',
    linestyle='--'
)

ax_side.grid(True)

ax_side.set_title(
    "Side View"
)

plt.tight_layout()
plt.show()