MPU6050解算(下)

第五章:Yaw 卡尔曼滤波增强

5.1 为什么 Mahony 的 Yaw 不准?

5.1.1 回顾 Mahony 的工作原理

在第四章中,我们学习了 Mahony 算法的核心思想:

加速度计测量的重力方向 ──┐
                        ├──→ 叉积 ──→ 误差 ──→ PI控制器 ──→ 修正四元数
从四元数估计的重力方向 ──┘

关键点:Mahony 用加速度计作为"教官",不断纠正陀螺仪的漂移。

5.1.2 问题来了:Yaw 没有"教官"

现在思考一个问题:加速度计能感知 Yaw 的变化吗?

做个实验:把 MPU6050 平放在桌上,然后水平旋转(像转盘一样)。

       俯视图

        旋转前              旋转后(Yaw 变了 90°)
          ↑                      →
          │                      │
    ──────●──────          ──────●──────
          │                      │
          ↓                      ↓

    重力方向:垂直向下        重力方向:还是垂直向下!
    加速度计:az = 1g        加速度计:az = 1g(没变!)

结论:无论怎么水平旋转,重力方向始终是垂直向下的。加速度计感知不到 Yaw 的变化!

5.1.3 三个角的"待遇"不同

角度 旋转时重力方向变化? 加速度计能感知? Mahony 能修正?
Roll 变(重力在 Y-Z 平面分配) :white_check_mark: :white_check_mark:
Pitch 变(重力在 X-Z 平面分配) :white_check_mark: :white_check_mark:
Yaw 不变(重力始终沿 Z 轴) :cross_mark: 不能 :cross_mark: 不能

Mahony 的 Yaw 本质上就是纯陀螺仪积分,没有任何修正!

5.1.4 纯陀螺仪积分的问题:零偏漂移

陀螺仪有一个固有缺陷:零偏

即使传感器完全静止,陀螺仪的读数也不是精确的 0,而是一个很小的值(比如 0.05°/s)。

理想情况:静止时 gz = 0
实际情况:静止时 gz = 0.05°/s(零偏)

Yaw 积分:yaw += gz × dt
        = yaw + 0.05 × 0.01
        = yaw + 0.0005°(每 10ms 漂移 0.0005°)

一分钟后:0.0005 × 100 × 60 = 3°
一小时后:3 × 60 = 180°(完全反向了!)

这就是为什么 Mahony 的 Yaw 会慢慢漂移。

5.1.5 实测现象

把 MPU6050 静止放置,观察 Mahony 输出的三个角度:

角度 现象 原因
Roll 稳定在 0° 附近 加速度计持续修正
Pitch 稳定在 0° 附近 加速度计持续修正
Yaw 缓慢漂移 无修正,零偏累积

5.1.6 怎么解决?

方案 原理 优缺点
加磁力计 用地磁场作为 Yaw 的绝对参考 最彻底,但需要额外硬件,受磁场干扰
卡尔曼滤波估计零偏 静止时学习零偏,运动时补偿 不需要额外硬件,但长时间仍会漂移

本章介绍第二种方案:用卡尔曼滤波估计陀螺仪零偏,改善 Yaw 漂移。


5.2 卡尔曼滤波基础

5.2.0 理解卡尔曼系统的 3 个核心问题

在学习公式之前,先问自己这 3 个问题:

核心问题 含义 例子(零偏估计)
我想估计什么? 状态 x 陀螺仪 Z 轴零偏
传感器能给我什么? 测量 z 静止时的 gz 读数
状态怎么随时间变化? 预测模型 零偏基本不变

回答完这 3 个问题,剩下的就是套公式和调参。

调参:Q(状态变化多剧烈)、R(传感器多不准)

5.2.1 从一个生活问题开始

场景:你想知道自己的真实体重

但你只有一个不太准的体重秤——每次称都显示不同的数字。

问题:怎么才能得到最准确的体重估计?

5.2.2 你有什么信息?

仔细想想,你其实有两个信息来源:

信息 1:今天的测量

早上称了一下,秤显示 70.5 kg

但你知道这个秤有误差,可能偏高或偏低 0.5 kg。

概念 例子中
测量值 70.5 kg
测量噪声 ±0.5 kg(秤不准)

信息 2:昨天的估计

昨天你估计自己是 70.0 kg,体重一天内不会变化太多。

概念 例子中
预测值 70.0 kg(昨天的估计)
过程噪声 ±0.1 kg(一天内的自然波动)

5.2.3 核心问题:信谁?

现在你面临一个选择:

昨天估计:70.0 kg
今天测量:70.5 kg

真实体重到底是多少?

最朴素的想法:取平均?

(70.0 + 70.5) / 2 = 70.25 kg

但这合理吗?

  • 如果秤很准(测量噪声小),应该更相信秤
  • 如果昨天的估计很确信,应该更相信昨天

关键问题:怎么决定各自的权重?

5.2.4 关键洞察:量化"不确信程度"

要决定信谁,我们需要知道:对每个信息有多确信?

信息 不确信程度 符号
昨天的估计 对 70.0 kg 有多不确信? P
今天的测量 对 70.5 kg 有多不确信? R

直觉

  • 不确信程度 → 不太可信 → 权重
  • 不确信程度 → 比较可信 → 权重

5.2.5 卡尔曼滤波的核心:自动计算最优权重

卡尔曼滤波的天才之处在于:它能自动算出最优的融合权重。

K = P / (P + R)     ← 这就是"卡尔曼增益"

然后用这个权重融合两个信息:

最优估计 = 预测值 + K × (测量值 - 预测值)
        = 70.0 + K × (70.5 - 70.0)
        = 70.0 + K × 0.5

5.2.6 K 的行为分析

情况 P 和 R 的关系 K 的值 结果
预测不确信,测量准 P 大,R 小 K → 1 ≈ 70.5(相信测量)
预测确信,测量不准 P 小,R 大 K → 0 ≈ 70.0(相信预测)
两者差不多 P ≈ R K ≈ 0.5 ≈ 70.25(各信一半)

K 的本质:就是一个自适应的权重,由不确信程度自动决定。

5.2.7 卡尔曼滤波公式

上面用体重秤的例子建立了直觉,现在给出正式的数学公式。

符号说明

符号 含义
$\hat{x}_k$ 第 k 时刻的状态估计值
$P_k$ 第 k 时刻的估计协方差(不确定程度)
$z_k$ 第 k 时刻的测量值
$Q$ 过程噪声(状态变化的不确定性)
$R$ 测量噪声(传感器的不确定性)
$K_k$ 第 k 时刻的卡尔曼增益

预测阶段

$$\hat{x}{k|k-1} = \hat{x}{k-1}$$

$$P_{k|k-1} = P_{k-1} + Q$$

  • $\hat{x}_{k|k-1}$ 表示"用 k-1 时刻的信息预测 k 时刻的状态"
  • 预测模型这里假设"状态不变",实际可根据系统调整

更新阶段

$$K_k = \frac{P_{k|k-1}}{P_{k|k-1} + R}$$

$$\hat{x}k = \hat{x}{k|k-1} + K_k(z_k - \hat{x}_{k|k-1})$$

$$P_k = (1 - K_k) \cdot P_{k|k-1}$$

  • $z_k - \hat{x}_{k|k-1}$ 称为新息(测量值与预测值的差)
  • $K_k$ 决定新息对估计的影响程度

5.2.8 完整的卡尔曼滤波流程

卡尔曼滤波分为两个阶段,不断循环:

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  【预测阶段】根据上一时刻,预测当前状态                        │
│                                                             │
│      x_pred = x              // 预测值(假设状态不变)        │
│      P_pred = P + Q          // 不确信程度增加               │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  【更新阶段】用测量值修正预测                                 │
│                                                             │
│      K = P_pred / (P_pred + R)           // 计算最优权重     │
│      x = x_pred + K × (z - x_pred)       // 融合得到新估计   │
│      P = (1 - K) × P_pred                // 更新不确信程度   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
                              │
                              ↓
                        下一时刻继续...

5.2.9 预测阶段的直觉

x_pred = x       // 体重不会突变,预测今天和昨天一样
P_pred = P + Q   // 但时间越久,越不确信(也许今天吃多了?)

Q(过程噪声)的意义:状态本身会变化多少。

  • Q 大 → 状态变化剧烈 → 预测很快变得不可信
  • Q 小 → 状态基本不变 → 预测长期可信

P 和 Q 的本质区别(重要!)

初学者常见困惑:P 和 Q 有什么区别?为什么 K 的公式用 P 而不是 Q?

Q(过程噪声) P(协方差)
是什么 每一步增加多少不确定性 此刻总共有多少不确定性
变化吗 :cross_mark: 不变(固定参数) :white_check_mark: 动态变化
类比 水龙头流速(固定) 桶里水量(动态)

P 受两个力作用

        Q 让 P 增大             测量让 P 减小
        (越来越不确信)         (获得信息,变确信)

            +Q        +Q        +Q
    P: ──→ ↗ ──→ ↘ ──→ ↗ ──→ ↘ ──→
          预测   更新   预测   更新
                 -K·P          -K·P

为什么 K 用 P 而不是 Q?

因为 K 回答的是"此刻该信谁",需要的是当前的不确定程度(P),而不是每步的增量(Q)。

如果用 Q 代替 P:K = Q / (Q + R) 就变成固定值了,那和互补滤波没区别!

5.2.10 更新阶段的直觉

K = P_pred / (P_pred + R)
  • 分子 P_pred:对预测的不确信程度
  • 分母 P_pred + R:总的不确信程度

K 的范围是 0 ~ 1

K 值 含义
K = 0 完全不信测量,只信预测
K = 1 完全信测量,不信预测
K = 0.3 信 30% 测量,70% 预测
x = x_pred + K × (z - x_pred)
  • z - x_pred:测量值和预测值的差距(称为"新息")
  • K × (z - x_pred):根据差距调整多少
P = (1 - K) × P_pred
  • 融合后,不确信程度降低了(因为获得了新信息)
  • K 越大(越信测量),P 降低越多

5.2.11 数值例子

初始状态

x = 70.0 kg(昨天估计)
P = 0.2   (对昨天估计的不确信程度)
Q = 0.01  (体重一天变化很小)
R = 0.5   (秤的测量噪声)

今天测量:z = 70.5 kg

预测阶段

x_pred = 70.0
P_pred = 0.2 + 0.01 = 0.21

更新阶段

K = 0.21 / (0.21 + 0.5) = 0.296

x = 70.0 + 0.296 × (70.5 - 70.0)
  = 70.0 + 0.296 × 0.5
  = 70.148 kg

P = (1 - 0.296) × 0.21 = 0.148

结果:估计体重从 70.0 更新为 70.148 kg,更接近测量值,但没有完全相信测量。

5.2.12 卡尔曼滤波 6 要素总结

从体重秤的例子中,我们自然地引出了卡尔曼滤波的全部要素:

要素 符号 体重例子 一句话解释
状态 x 真实体重 我们想估计的量
协方差 P 对体重估计的不确信程度 越大越不确信
过程噪声 Q 体重一天内的自然波动 状态会变化多少
测量值 z 秤的读数 传感器的输出
测量噪声 R 秤的误差 测量有多不准
卡尔曼增益 K 自动算出的权重 决定信谁多少

5.2.13 Q 和 R 的调参意义

参数 调大的效果 调小的效果
Q 预测快速变得不可信 → K 变大 → 更跟随测量 预测长期可信 → K 变小 → 更平滑
R 测量不可信 → K 变小 → 更平滑 测量可信 → K 变大 → 更跟随测量

调参口诀

  • 想要响应快:减小 R 或增大 Q
  • 想要更平滑:增大 R 或减小 Q
  • 本质上是调整 Q/R 的比值

5.2.14 与互补滤波的对比

对比项 互补滤波 卡尔曼滤波
权重 固定的 α(手动设定) 自适应的 K(自动计算)
不确定性 不考虑 核心要素
调参 调 α 调 Q 和 R
本质 固定权重的加权平均 最优权重的加权平均

一句话总结:卡尔曼滤波是"会自动调整权重的互补滤波"。

5.2.15 Q 和 R 的博弈:不是套娃,是闭环

初学者看公式时可能感觉"套娃":K 影响 P,P 又影响 K,Q 和 R 到处都有…

其实不是套娃,而是按顺序执行的闭环

每个周期就这 5 行代码

P = P + Q;                    // ① Q 让 P 变大
K = P / (P + R);              // ② 用 P 和 R 算 K
x = x + K * (z - x);          // ③ 用 K 更新状态
P = (1 - K) * P;              // ④ 用 K 让 P 变小
// 下一周期回到 ①

闭环图示

    ┌─────────────────────────────────────────────┐
    │                                             │
    ▼                                             │
   [P] ──(+Q)──→ [P_pred] ──────┐                │
                                │                │
                                ▼                │
                        K = P/(P+R)              │
                                │                │
                         ┌──────┴──────┐         │
                         ▼             ▼         │
                   更新状态x      P = (1-K)·P ───┘
                         │
                         ▼
                     输出结果

Q 和 R 在"拔河"

参数 作用 最终效果
Q 大 P 涨得快 → K 变大 更信测量,响应快
R 大 K 直接变小 → P 降得慢 更信预测,更平滑

本质:Q 和 R 像两个调节旋钮,通过 P 和 K 互相博弈,最终自动找到平衡。


5.3 应用:用卡尔曼估计陀螺仪零偏

学完卡尔曼滤波的理论,现在把它应用到实际问题:估计陀螺仪 Z 轴的零偏,改善 Yaw 漂移

5.3.1 回答 3 个核心问题

回顾 5.2.0 提出的框架,套用到零偏估计:

核心问题 答案 说明
我想估计什么? 陀螺仪 Z 轴零偏 $b$ 静止时 gz 应该是 0,实际却是 $b$
传感器能给我什么? 静止时的 gz 读数 静止时 $z = b$(测量值就是零偏)
状态怎么随时间变化? 零偏基本不变 $b_{k} = b_{k-1}$(预测模型)

5.3.2 为什么需要静止检测?

这是本方案的关键设计

问题:运动时 gz ≠ 零偏

状态 gz 读数 能否作为零偏测量?
静止 gz = 零偏 :white_check_mark: 可以
旋转 gz = 零偏 + 真实角速度 :cross_mark: 不行

如果运动时也更新卡尔曼,会把真实角速度当成零偏,估计值会被污染

解决方案:只在静止时更新

每个周期:
├── 检测是否静止
├── 如果静止 → 执行卡尔曼更新(学习零偏)
├── 如果运动 → 跳过卡尔曼更新(保持上次估计)
└── 用估计的零偏补偿 gz,然后积分得到 Yaw

静止检测条件

同时满足以下条件才认为静止:

条件 判断依据 阈值
加速度模值接近 1g $0.95 < \sqrt{a_x^2+a_y^2+a_z^2} < 1.05$ 排除加速运动
角速度都很小 $|g_x|, |g_y|, |g_z| < 1.5°/s$ 排除旋转运动

5.3.3 代码实现解析

参数定义

#define KALMAN_Q_BIAS   0.0001f  /* 过程噪声:零偏变化多快 */
#define KALMAN_R_BIAS   0.01f    /* 测量噪声:陀螺仪多不准 */
#define STATIC_ACCEL_MIN 0.95f   /* 静止检测:加速度下限 */
#define STATIC_ACCEL_MAX 1.05f   /* 静止检测:加速度上限 */
#define STATIC_GYRO_MAX  1.5f    /* 静止检测:角速度阈值 */

状态变量

static float kal_bias = 0.0f;    /* 状态:零偏估计值 */
static float kal_p_bias = 1.0f;  /* 协方差:对估计的不确定程度 */
static float kal_yaw = 0.0f;     /* Yaw 角积分结果 */

核心代码(逐行对应公式)

void kalman_yaw_update(mpu6050_t *data, float dt)
{
    float gz = data->gz;
    float accel_norm, gz_corrected;
    float k, p_pred;
    uint8_t is_static;

    /* ========== 第一步:静止检测 ========== */
    accel_norm = sqrtf(data->ax*data->ax + data->ay*data->ay + data->az*data->az);
    gz_corrected = gz - kal_bias;  /* 用当前估计修正 */

    is_static = (accel_norm > STATIC_ACCEL_MIN &&
                 accel_norm < STATIC_ACCEL_MAX &&
                 fabsf(data->gx) < STATIC_GYRO_MAX &&
                 fabsf(data->gy) < STATIC_GYRO_MAX &&
                 fabsf(gz_corrected) < STATIC_GYRO_MAX);

    /* ========== 第二步:卡尔曼更新(仅静止时)========== */
    if (is_static)
    {
        /* 预测阶段 */
        // x_pred = x;           (零偏不变,省略)
        p_pred = kal_p_bias + KALMAN_Q_BIAS;   // P = P + Q

        /* 更新阶段 */
        k = p_pred / (p_pred + KALMAN_R_BIAS); // K = P / (P + R)
        kal_bias = kal_bias + k * (gz - kal_bias);  // x = x + K(z - x)
        kal_p_bias = (1.0f - k) * p_pred;      // P = (1 - K) * P
    }

    /* ========== 第三步:Yaw 积分 ========== */
    kal_yaw += gz_corrected * dt;

    /* 角度归一化到 -180 ~ 180 */
    while (kal_yaw > 180.0f)  kal_yaw -= 360.0f;
    while (kal_yaw < -180.0f) kal_yaw += 360.0f;

    data->yaw = kal_yaw;
}

代码与公式对照

代码 对应公式 含义
p_pred = kal_p_bias + Q $P_{k k-1} = P_{k-1} + Q$
k = p_pred / (p_pred + R) $K = \frac{P}{P+R}$ 计算卡尔曼增益
kal_bias = kal_bias + k*(gz - kal_bias) $\hat{x} = \hat{x} + K(z - \hat{x})$ 融合测量更新估计
kal_p_bias = (1-k) * p_pred $P = (1-K) \cdot P$ 更新不确定程度

5.3.4 参数选择与调参

Q 和 R 的物理意义

参数 物理意义
Q = 0.0001 很小 零偏变化很慢(温漂是缓慢的)
R = 0.01 较小 陀螺仪噪声不大(静止时读数比较稳定)

Q/R 比值的影响

$$\frac{Q}{R} = \frac{0.0001}{0.01} = 0.01$$

比值很小,说明:

  • 系统更相信预测(零偏不会突变)
  • 卡尔曼增益 K 会比较小
  • 零偏估计变化平滑缓慢

调参建议

现象 调整方向
零偏估计跟踪太慢 增大 Q 或减小 R
零偏估计抖动太大 减小 Q 或增大 R
静止时 Yaw 仍漂移 检查静止检测阈值是否合理

5.3.5 算法流程图

┌─────────────────────────────────────────────────────────────┐
│                     每个采样周期                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐                                           │
│  │ 读取传感器  │  ax, ay, az, gx, gy, gz                   │
│  └──────┬──────┘                                           │
│         ↓                                                   │
│  ┌─────────────┐                                           │
│  │  静止检测   │  加速度≈1g 且 角速度≈0?                  │
│  └──────┬──────┘                                           │
│         ↓                                                   │
│    ┌────┴────┐                                             │
│    ↓         ↓                                             │
│  [静止]    [运动]                                          │
│    ↓         ↓                                             │
│  卡尔曼     跳过                                            │
│  更新零偏   (保持上次)                                      │
│    ↓         ↓                                             │
│    └────┬────┘                                             │
│         ↓                                                   │
│  ┌─────────────┐                                           │
│  │ gz - 零偏   │  补偿后的角速度                           │
│  └──────┬──────┘                                           │
│         ↓                                                   │
│  ┌─────────────┐                                           │
│  │ Yaw += ω*dt │  积分得到 Yaw 角                          │
│  └─────────────┘                                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5.3.6 小结

要点 说明
核心思想 静止时学习零偏,运动时用零偏补偿
为什么有效 静止时 gz = 零偏,是完美的测量机会
局限性 长时间不静止,零偏估计无法更新
适用场景 间歇运动的应用(会有静止时刻)

5.3.7 实际效果

这里我们用匿名上位机来观测,第一个roll我们改成偏移,第二个是hwt101的yaw,第三个是我们的yaw

    ano_send_euler(&huart1, kalman_get_bias(), HWT101_GetYaw(&hwt101), mpu_data.yaw);

5.3.8 方法的缺陷

虽然卡尔曼零偏估计能改善 Yaw 漂移,但它不是完美的解决方案

缺陷一:依赖静止时刻

场景 问题
长时间持续运动 无法更新零偏,估计值可能过时
无人机飞行 飞行过程中几乎不会静止
车辆行驶 只有停车时才能更新

后果:零偏会随温度缓慢变化(温漂),如果长时间不静止,估计值会逐渐偏离真实值。

缺陷二:静止检测可能误判

误判类型 原因 后果
假阳性(运动误判为静止) 阈值设置过宽松 真实角速度被当成零偏,估计被污染
假阴性(静止误判为运动) 阈值设置过严格 错过学习机会,零偏更新变慢

缺陷三:无绝对参考

与 Roll/Pitch 不同,Yaw 没有绝对参考

角度 绝对参考 能否完全修正漂移?
Roll 重力方向 :white_check_mark: 能(加速度计)
Pitch 重力方向 :white_check_mark: 能(加速度计)
Yaw :cross_mark: 只能减缓,无法消除

结论:卡尔曼零偏估计只能减缓 Yaw 漂移,无法消除。长时间运行后仍会累积误差。

缺陷四:初始收敛需要时间

系统启动时,零偏估计从初始值(通常是 0)开始收敛到真实值,需要一定时间:

启动 → 静止等待 → 零偏逐渐收敛 → 估计可用

如果启动后立即运动,零偏估计可能不准。

彻底解决 Yaw 漂移的方案

方案 原理 优缺点
加磁力计 用地磁场作为 Yaw 的绝对参考 :white_check_mark: 彻底解决漂移
:cross_mark: 受磁场干扰(电机、铁器)
加 GPS 用航向角作为参考 :white_check_mark: 不受磁场干扰
:cross_mark: 需要运动才有航向,室内不可用
视觉定位 用摄像头识别环境特征 :white_check_mark: 高精度
:cross_mark: 计算量大,成本高

本方案的定位:在无额外传感器的条件下,尽可能改善 Yaw 漂移。是一种低成本的折中方案


5.4 本章总结

问题回顾

Mahony 算法用加速度计修正陀螺仪漂移,但加速度计感知不到 Yaw 旋转(水平转动时重力方向不变),导致 Yaw 本质上是纯陀螺仪积分,会因零偏而漂移。

解决思路

问题:Yaw 没有"教官"(绝对参考)
      ↓
洞察:静止时,陀螺仪读数 = 零偏(这就是测量机会)
      ↓
方案:用卡尔曼滤波估计零偏,运动时补偿

卡尔曼滤波核心认知

要点 说明
本质 两个不完美信息的最优融合
3个核心问题 估计什么?测量什么?怎么预测?
P vs Q P 是动态的不确定程度,Q 是每步增量
公式 预测 → 计算K → 更新状态 → 更新P

零偏估计方案要点

要点 内容
状态 陀螺仪 Z 轴零偏
测量 静止时的 gz 读数
关键设计 静止检测——只在静止时更新
效果 静止时 Yaw 不漂移
局限 长时间运动无法更新,只能减缓不能消除

各章算法对比

章节 算法 Roll/Pitch Yaw 复杂度
第二章 纯加速度计 静止准,动态抖 :cross_mark: 无法解算 :star:
第三章 互补滤波 :white_check_mark: 融合 :cross_mark: 无法解算 :star::star:
第四章 Mahony :white_check_mark: 四元数融合 :warning: 纯积分,漂移 :star::star::star:
第五章 Mahony + 卡尔曼 :white_check_mark: 四元数融合 :white_check_mark: 零偏补偿,减缓漂移 :star::star::star::star:

如果还想更好?

方向 方案
彻底消除 Yaw 漂移 加磁力计(9轴融合)
更高精度 扩展卡尔曼滤波(EKF)
工业级方案 IMU + GPS + 视觉融合
#include "mpu6050.h"
#include <math.h>

#define I2C_TIMEOUT     100
#define RAD_TO_DEG      57.2957795f
#define DEG_TO_RAD      0.0174533f
#define CF_ALPHA        0.98f

/* Mahony算法参数 */
#define MAHONY_KP       1.0f
#define MAHONY_KI       0.005f

/* Yaw卡尔曼滤波参数 */
#define KALMAN_Q_BIAS   0.0001f  /* 零偏过程噪声 */
#define KALMAN_R_BIAS   0.01f    /* 零偏测量噪声 */
#define STATIC_ACCEL_MIN 0.95f   /* 静止检测:加速度模值下限 */
#define STATIC_ACCEL_MAX 1.05f   /* 静止检测:加速度模值上限 */
#define STATIC_GYRO_MAX  1.5f    /* 静止检测:角速度阈值 (°/s) */

/* 当前量程配置 */
static uint8_t gyro_fsr  = MPU6050_GYRO_FSR_2000DPS;
static uint8_t accel_fsr = MPU6050_ACCEL_FSR_2G;

/* 量程对应的灵敏度 (LSB/单位) */
static const float gyro_scale_table[4]  = {131.0f, 65.5f, 32.8f, 16.4f};
static const float accel_scale_table[4] = {16384.0f, 8192.0f, 4096.0f, 2048.0f};

/* 零偏校准值 */
static mpu6050_offset_t offset = {0};

/* Mahony算法状态 */
static quaternion_t q = {1.0f, 0.0f, 0.0f, 0.0f};
static float integral_ex = 0.0f, integral_ey = 0.0f, integral_ez = 0.0f;

/* Yaw卡尔曼滤波状态 */
static float kal_yaw  = 0.0f;    /* 估计的Yaw角 */
static float kal_bias = 0.0f;    /* 估计的陀螺仪零偏 */
static float kal_p_bias = 1.0f;  /* 零偏估计的协方差 */

/**
 * @brief 快速平方根倒数
 * @param x 输入值
 * @return 1/sqrt(x) 的近似值
 */
static float inv_sqrt(float x)
{
    float halfx = 0.5f * x;
    float y = x;
    long i = *(long *)&y;
    i = 0x5f3759df - (i >> 1);
    y = *(float *)&i;
    y = y * (1.5f - (halfx * y * y));
    return y;
}

/**
 * @brief 写一个字节到MPU6050寄存器
 * @param hi2c I2C句柄
 * @param reg 寄存器地址
 * @param data 要写入的数据
 * @return MPU6050_OK成功,MPU6050_ERR_I2C失败
 */
static uint8_t mpu6050_write_byte(I2C_HandleTypeDef *hi2c, uint8_t reg, uint8_t data)
{
    if (HAL_I2C_Mem_Write(hi2c, MPU6050_ADDR, reg, 1, &data, 1, I2C_TIMEOUT) != HAL_OK)
    {
        return MPU6050_ERR_I2C;
    }
    return MPU6050_OK;
}

/**
 * @brief 从MPU6050寄存器读一个字节
 * @param hi2c I2C句柄
 * @param reg 寄存器地址
 * @param data 存储读取数据的指针
 * @return MPU6050_OK成功,MPU6050_ERR_I2C失败
 */
static uint8_t mpu6050_read_byte(I2C_HandleTypeDef *hi2c, uint8_t reg, uint8_t *data)
{
    if (HAL_I2C_Mem_Read(hi2c, MPU6050_ADDR, reg, 1, data, 1, I2C_TIMEOUT) != HAL_OK)
    {
        return MPU6050_ERR_I2C;
    }
    return MPU6050_OK;
}

/**
 * @brief 从MPU6050连续读取多个字节
 * @param hi2c I2C句柄
 * @param reg 起始寄存器地址
 * @param buf 存储数据的缓冲区
 * @param len 读取字节数
 * @return MPU6050_OK成功,MPU6050_ERR_I2C失败
 */
static uint8_t mpu6050_read_bytes(I2C_HandleTypeDef *hi2c, uint8_t reg, uint8_t *buf, uint16_t len)
{
    if (HAL_I2C_Mem_Read(hi2c, MPU6050_ADDR, reg, 1, buf, len, I2C_TIMEOUT) != HAL_OK)
    {
        return MPU6050_ERR_I2C;
    }
    return MPU6050_OK;
}

#ifdef MPU6050_AD0_GPIO_ENABLE
/**
 * @brief 初始化AD0引脚
 */
static void mpu6050_ad0_init(void)
{
    GPIO_InitTypeDef gpio = {0};

    MPU6050_AD0_GPIO_CLK_EN();

    gpio.Pin   = MPU6050_AD0_GPIO_PIN;
    gpio.Mode  = GPIO_MODE_OUTPUT_PP;
    gpio.Pull  = GPIO_PULLUP;
    gpio.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(MPU6050_AD0_GPIO_PORT, &gpio);

    /* AD0拉低,I2C地址为0x68 */
    MPU6050_AD0_SET(0);
}
#endif

/**
 * @brief 初始化MPU6050
 * @param hi2c I2C句柄
 * @return MPU6050_OK成功,错误码失败
 * @note 初始化流程参考正点原子例程
 */
uint8_t mpu6050_init(I2C_HandleTypeDef *hi2c)
{
    uint8_t id;

#ifdef MPU6050_AD0_GPIO_ENABLE
    mpu6050_ad0_init();
#endif

    /* 软件复位 */
    mpu6050_write_byte(hi2c, MPU6050_PWR_MGMT_1, 0x80);
    HAL_Delay(100);

    /* 唤醒,使用内部时钟 */
    mpu6050_write_byte(hi2c, MPU6050_PWR_MGMT_1, 0x00);
    HAL_Delay(10);

    /* 检查器件ID */
    if (mpu6050_read_byte(hi2c, MPU6050_WHO_AM_I, &id) != MPU6050_OK)
    {
        return MPU6050_ERR_I2C;
    }
    if (id != 0x68)
    {
        return MPU6050_ERR_ID;
    }

    /* 陀螺仪量程: ±2000°/s */
    mpu6050_set_gyro_fsr(hi2c, MPU6050_GYRO_FSR_2000DPS);

    /* 加速度计量程: ±2g */
    mpu6050_set_accel_fsr(hi2c, MPU6050_ACCEL_FSR_2G);

    /* 采样率: 50Hz */
    mpu6050_set_rate(hi2c, 50);

    /* 关闭所有中断 */
    mpu6050_write_byte(hi2c, MPU6050_INT_ENABLE, 0x00);

    /* 关闭I2C主模式 */
    mpu6050_write_byte(hi2c, MPU6050_USER_CTRL, 0x00);

    /* 关闭FIFO */
    mpu6050_write_byte(hi2c, MPU6050_FIFO_EN, 0x00);

    /* INT引脚低电平有效 */
    mpu6050_write_byte(hi2c, MPU6050_INT_PIN_CFG, 0x80);

    /* 时钟源: X轴陀螺仪PLL */
    mpu6050_write_byte(hi2c, MPU6050_PWR_MGMT_1, 0x01);

    /* 使能加速度计和陀螺仪 */
    mpu6050_write_byte(hi2c, MPU6050_PWR_MGMT_2, 0x00);

    return MPU6050_OK;
}

/**
 * @brief 设置陀螺仪量程
 * @param hi2c I2C句柄
 * @param fsr 0=±250°/s, 1=±500°/s, 2=±1000°/s, 3=±2000°/s
 * @return MPU6050_OK成功
 */
uint8_t mpu6050_set_gyro_fsr(I2C_HandleTypeDef *hi2c, uint8_t fsr)
{
    gyro_fsr = fsr & 0x03;
    return mpu6050_write_byte(hi2c, MPU6050_GYRO_CONFIG, gyro_fsr << 3);
}

/**
 * @brief 设置加速度计量程
 * @param hi2c I2C句柄
 * @param fsr 0=±2g, 1=±4g, 2=±8g, 3=±16g
 * @return MPU6050_OK成功
 */
uint8_t mpu6050_set_accel_fsr(I2C_HandleTypeDef *hi2c, uint8_t fsr)
{
    accel_fsr = fsr & 0x03;
    return mpu6050_write_byte(hi2c, MPU6050_ACCEL_CONFIG, accel_fsr << 3);
}

/**
 * @brief 设置数字低通滤波器带宽
 * @param hi2c I2C句柄
 * @param lpf 截止频率 (Hz)
 * @return MPU6050_OK成功
 * @note lpf >= 188Hz -> 带宽188Hz
 *       lpf >= 98Hz  -> 带宽98Hz
 *       lpf >= 42Hz  -> 带宽42Hz
 *       lpf >= 20Hz  -> 带宽20Hz
 *       lpf >= 10Hz  -> 带宽10Hz
 *       lpf < 10Hz   -> 带宽5Hz
 */
uint8_t mpu6050_set_lpf(I2C_HandleTypeDef *hi2c, uint16_t lpf)
{
    uint8_t cfg;

    if (lpf >= 188)      cfg = 1;
    else if (lpf >= 98)  cfg = 2;
    else if (lpf >= 42)  cfg = 3;
    else if (lpf >= 20)  cfg = 4;
    else if (lpf >= 10)  cfg = 5;
    else                 cfg = 6;

    return mpu6050_write_byte(hi2c, MPU6050_CONFIG, cfg);
}

/**
 * @brief 设置采样率
 * @param hi2c I2C句柄
 * @param rate 采样率 (4~1000 Hz)
 * @return MPU6050_OK成功
 * @note 采样率 = 1kHz / (1 + SMPLRT_DIV)
 *       同时设置DLPF为采样率的一半
 */
uint8_t mpu6050_set_rate(I2C_HandleTypeDef *hi2c, uint16_t rate)
{
    uint8_t div;

    if (rate > 1000) rate = 1000;
    if (rate < 4)    rate = 4;

    div = 1000 / rate - 1;

    if (mpu6050_write_byte(hi2c, MPU6050_SMPLRT_DIV, div) != MPU6050_OK)
    {
        return MPU6050_ERR_I2C;
    }

    return mpu6050_set_lpf(hi2c, rate >> 1);
}

/**
 * @brief 读取加速度计数据
 * @param hi2c I2C句柄
 * @param data 数据结构指针
 * @return MPU6050_OK成功
 */
uint8_t mpu6050_read_accel(I2C_HandleTypeDef *hi2c, mpu6050_t *data)
{
    uint8_t buf[6];
    float scale = accel_scale_table[accel_fsr];

    if (mpu6050_read_bytes(hi2c, MPU6050_ACCEL_XOUT_H, buf, 6) != MPU6050_OK)
    {
        return MPU6050_ERR_I2C;
    }

    data->accel_x_raw = (int16_t)((buf[0] << 8) | buf[1]);
    data->accel_y_raw = (int16_t)((buf[2] << 8) | buf[3]);
    data->accel_z_raw = (int16_t)((buf[4] << 8) | buf[5]);

    data->ax = data->accel_x_raw / scale;
    data->ay = data->accel_y_raw / scale;
    data->az = data->accel_z_raw / scale;

    return MPU6050_OK;
}

/**
 * @brief 读取陀螺仪数据
 * @param hi2c I2C句柄
 * @param data 数据结构指针
 * @return MPU6050_OK成功
 */
uint8_t mpu6050_read_gyro(I2C_HandleTypeDef *hi2c, mpu6050_t *data)
{
    uint8_t buf[6];
    float scale = gyro_scale_table[gyro_fsr];

    if (mpu6050_read_bytes(hi2c, MPU6050_GYRO_XOUT_H, buf, 6) != MPU6050_OK)
    {
        return MPU6050_ERR_I2C;
    }

    data->gyro_x_raw = (int16_t)((buf[0] << 8) | buf[1]);
    data->gyro_y_raw = (int16_t)((buf[2] << 8) | buf[3]);
    data->gyro_z_raw = (int16_t)((buf[4] << 8) | buf[5]);

    data->gx = data->gyro_x_raw / scale;
    data->gy = data->gyro_y_raw / scale;
    data->gz = data->gyro_z_raw / scale;

    return MPU6050_OK;
}

/**
 * @brief 读取温度
 * @param hi2c I2C句柄
 * @param data 数据结构指针
 * @return MPU6050_OK成功
 */
uint8_t mpu6050_read_temp(I2C_HandleTypeDef *hi2c, mpu6050_t *data)
{
    uint8_t buf[2];

    if (mpu6050_read_bytes(hi2c, MPU6050_TEMP_OUT_H, buf, 2) != MPU6050_OK)
    {
        return MPU6050_ERR_I2C;
    }

    data->temp_raw = (int16_t)((buf[0] << 8) | buf[1]);
    data->temperature = data->temp_raw / 340.0f + 36.53f;

    return MPU6050_OK;
}

/**
 * @brief 读取所有传感器数据 (加速度+陀螺仪+温度)
 * @param hi2c I2C句柄
 * @param data 数据结构指针
 * @return MPU6050_OK成功
 * @note 自动应用校准偏移量
 */
uint8_t mpu6050_read_all(I2C_HandleTypeDef *hi2c, mpu6050_t *data)
{
    uint8_t buf[14];
    float a_scale = accel_scale_table[accel_fsr];
    float g_scale = gyro_scale_table[gyro_fsr];

    if (mpu6050_read_bytes(hi2c, MPU6050_ACCEL_XOUT_H, buf, 14) != MPU6050_OK)
    {
        return MPU6050_ERR_I2C;
    }

    data->accel_x_raw = (int16_t)((buf[0] << 8) | buf[1]) - offset.accel_x;
    data->accel_y_raw = (int16_t)((buf[2] << 8) | buf[3]) - offset.accel_y;
    data->accel_z_raw = (int16_t)((buf[4] << 8) | buf[5]) - offset.accel_z;
    data->temp_raw    = (int16_t)((buf[6] << 8) | buf[7]);
    data->gyro_x_raw  = (int16_t)((buf[8] << 8) | buf[9]) - offset.gyro_x;
    data->gyro_y_raw  = (int16_t)((buf[10] << 8) | buf[11]) - offset.gyro_y;
    data->gyro_z_raw  = (int16_t)((buf[12] << 8) | buf[13]) - offset.gyro_z;

    data->ax = data->accel_x_raw / a_scale;
    data->ay = data->accel_y_raw / a_scale;
    data->az = data->accel_z_raw / a_scale;

    data->gx = data->gyro_x_raw / g_scale;
    data->gy = data->gyro_y_raw / g_scale;
    data->gz = data->gyro_z_raw / g_scale;

    data->temperature = data->temp_raw / 340.0f + 36.53f;

    return MPU6050_OK;
}

/**
 * @brief 零偏校准
 * @param hi2c I2C句柄
 * @param samples 采样次数 (建议500~1000)
 * @return MPU6050_OK成功
 * @note 调用前确保传感器静止水平放置,Z轴朝上
 */
uint8_t mpu6050_calibrate(I2C_HandleTypeDef *hi2c, uint16_t samples)
{
    int32_t ax_sum = 0, ay_sum = 0, az_sum = 0;
    int32_t gx_sum = 0, gy_sum = 0, gz_sum = 0;
    uint8_t buf[14];

    for (uint16_t i = 0; i < samples; i++)
    {
        if (mpu6050_read_bytes(hi2c, MPU6050_ACCEL_XOUT_H, buf, 14) != MPU6050_OK)
        {
            return MPU6050_ERR_I2C;
        }

        ax_sum += (int16_t)((buf[0] << 8) | buf[1]);
        ay_sum += (int16_t)((buf[2] << 8) | buf[3]);
        az_sum += (int16_t)((buf[4] << 8) | buf[5]);
        gx_sum += (int16_t)((buf[8] << 8) | buf[9]);
        gy_sum += (int16_t)((buf[10] << 8) | buf[11]);
        gz_sum += (int16_t)((buf[12] << 8) | buf[13]);

        HAL_Delay(2);
    }

    offset.accel_x = ax_sum / samples;
    offset.accel_y = ay_sum / samples;
    offset.accel_z = az_sum / samples - (int16_t)accel_scale_table[accel_fsr];
    offset.gyro_x  = gx_sum / samples;
    offset.gyro_y  = gy_sum / samples;
    offset.gyro_z  = gz_sum / samples;

    return MPU6050_OK;
}

/**
 * @brief 获取当前偏移量
 * @return 偏移量结构体指针
 */
mpu6050_offset_t *mpu6050_get_offset(void)
{
    return &offset;
}

/**
 * @brief 纯加速度计姿态解算
 * @param data 传感器数据指针,需先调用mpu6050_read_all
 * @note Roll = atan2(ay, az), Pitch = atan2(-ax, sqrt(ay² + az²))
 *       仅适用于静态或低动态场景,运动时误差大
 */
void mpu6050_calc_attitude_accel(mpu6050_t *data)
{
    data->roll  = atan2f(data->ay, data->az) * RAD_TO_DEG;
    data->pitch = atan2f(-data->ax, sqrtf(data->ay * data->ay + data->az * data->az)) * RAD_TO_DEG;
}

/**
 * @brief 一阶互补滤波姿态解算
 * @param data 传感器数据指针,需先调用mpu6050_read_all
 * @param dt 采样周期 (秒)
 * @note angle = α*(angle + gyro*dt) + (1-α)*accel_angle
 */
void mpu6050_calc_attitude_cf(mpu6050_t *data, float dt)
{
    float accel_roll, accel_pitch;

    accel_roll  = atan2f(data->ay, data->az) * RAD_TO_DEG;
    accel_pitch = atan2f(-data->ax, sqrtf(data->ay * data->ay + data->az * data->az)) * RAD_TO_DEG;

    data->roll  = CF_ALPHA * (data->roll + data->gx * dt) + (1 - CF_ALPHA) * accel_roll;
    data->pitch = CF_ALPHA * (data->pitch + data->gy * dt) + (1 - CF_ALPHA) * accel_pitch;
}

/**
 * @brief Mahony姿态解算初始化
 * @note 将四元数重置为单位四元数,清零积分误差
 */
void mahony_init(void)
{
    q.w = 1.0f;
    q.x = 0.0f;
    q.y = 0.0f;
    q.z = 0.0f;
    integral_ex = 0.0f;
    integral_ey = 0.0f;
    integral_ez = 0.0f;
}

/**
 * @brief Mahony姿态解算更新
 * @param data 传感器数据指针
 * @param dt 采样周期 (秒)
 * @note 使用加速度计作为重力参考修正陀螺仪漂移
 *       算法流程:
 *       1. 归一化加速度计
 *       2. 从四元数估算重力方向
 *       3. 向量叉积计算误差
 *       4. PI控制器修正陀螺仪
 *       5. 四元数积分更新
 *       6. 归一化四元数
 *       7. 转换为欧拉角
 */
void mahony_update(mpu6050_t *data, float dt)
{
    float norm;
    float vx, vy, vz;
    float ex, ey, ez;
    float gx, gy, gz;
    float ax, ay, az;
    float half_dt;
    float qw, qx, qy, qz;

    ax = data->ax;
    ay = data->ay;
    az = data->az;
    gx = data->gx * DEG_TO_RAD;
    gy = data->gy * DEG_TO_RAD;
    gz = data->gz * DEG_TO_RAD;

    /* 加速度计归一化 */
    norm = inv_sqrt(ax * ax + ay * ay + az * az);
    if (norm > 1000.0f)
    {
        return;
    }
    ax *= norm;
    ay *= norm;
    az *= norm;

    /* 从四元数估算的重力方向 (机体坐标系下) */
    vx = 2.0f * (q.x * q.z - q.w * q.y);
    vy = 2.0f * (q.w * q.x + q.y * q.z);
    vz = q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z;

    /* 向量叉积: 加速度计测量 × 估算值 = 误差 */
    ex = ay * vz - az * vy;
    ey = az * vx - ax * vz;
    ez = ax * vy - ay * vx;

    /* 积分误差 (Ki部分) */
    if (MAHONY_KI > 0.0f)
    {
        integral_ex += MAHONY_KI * ex * dt;
        integral_ey += MAHONY_KI * ey * dt;
        integral_ez += MAHONY_KI * ez * dt;
        gx += integral_ex;
        gy += integral_ey;
        gz += integral_ez;
    }

    /* 比例误差修正 (Kp部分) */
    gx += MAHONY_KP * ex;
    gy += MAHONY_KP * ey;
    gz += MAHONY_KP * ez;

    /* 四元数微分方程积分 */
    half_dt = 0.5f * dt;
    qw = q.w;
    qx = q.x;
    qy = q.y;
    qz = q.z;

    q.w += (-qx * gx - qy * gy - qz * gz) * half_dt;
    q.x += ( qw * gx + qy * gz - qz * gy) * half_dt;
    q.y += ( qw * gy - qx * gz + qz * gx) * half_dt;
    q.z += ( qw * gz + qx * gy - qy * gx) * half_dt;

    /* 四元数归一化 */
    norm = inv_sqrt(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z);
    q.w *= norm;
    q.x *= norm;
    q.y *= norm;
    q.z *= norm;

    /* 四元数转欧拉角 */
    data->roll  = atan2f(2.0f * (q.w * q.x + q.y * q.z),
                         1.0f - 2.0f * (q.x * q.x + q.y * q.y)) * RAD_TO_DEG;
    data->pitch = asinf(2.0f * (q.w * q.y - q.z * q.x)) * RAD_TO_DEG;
    data->yaw   = atan2f(2.0f * (q.w * q.z + q.x * q.y),
                         1.0f - 2.0f * (q.y * q.y + q.z * q.z)) * RAD_TO_DEG;
}

/**
 * @brief 获取当前四元数
 * @return 四元数指针
 */
quaternion_t *mahony_get_quaternion(void)
{
    return &q;
}

/**
 * @brief Yaw卡尔曼滤波器初始化
 * @note 重置状态估计和协方差
 */
void kalman_yaw_init(void)
{
    kal_yaw  = 0.0f;
    kal_bias = 0.0f;
    kal_p_bias = 1.0f;
}

/**
 * @brief Yaw卡尔曼滤波更新
 * @param data 传感器数据指针
 * @param dt 采样周期 (秒)
 * @note 静止时用gz直接测量零偏,运动时用估计的零偏补偿积分
 *       支持任意姿态角,将机体角速度转换为地理系Yaw角速度
 */
void kalman_yaw_update(mpu6050_t *data, float dt)
{
    float gz = data->gz;
    float accel_norm;
    float gz_corrected;
    float yaw_rate;
    float cos_roll, sin_roll, cos_pitch, sin_pitch;
    float k;
    float p_pred;
    uint8_t is_static;

    /* 计算加速度模值 */
    accel_norm = sqrtf(data->ax * data->ax + data->ay * data->ay + data->az * data->az);

    /* 用当前零偏估计修正gz */
    gz_corrected = gz - kal_bias;

    /* 静止检测:加速度接近1g且所有角速度都很小 */
    is_static = (accel_norm > STATIC_ACCEL_MIN && accel_norm < STATIC_ACCEL_MAX &&
                 data->gx > -STATIC_GYRO_MAX && data->gx < STATIC_GYRO_MAX &&
                 data->gy > -STATIC_GYRO_MAX && data->gy < STATIC_GYRO_MAX &&
                 gz_corrected > -STATIC_GYRO_MAX && gz_corrected < STATIC_GYRO_MAX) ? 1 : 0;

    /* ===== 零偏估计(一维卡尔曼)===== */
    if (is_static)
    {
        p_pred = kal_p_bias + KALMAN_Q_BIAS;
        k = p_pred / (p_pred + KALMAN_R_BIAS);
        kal_bias = kal_bias + k * (gz - kal_bias);
        kal_p_bias = (1.0f - k) * p_pred;
    }

    /* ===== 机体角速度转换为地理系Yaw角速度 ===== */
    cos_roll  = cosf(data->roll * DEG_TO_RAD);
    sin_roll  = sinf(data->roll * DEG_TO_RAD);
    cos_pitch = cosf(data->pitch * DEG_TO_RAD);
    sin_pitch = sinf(data->pitch * DEG_TO_RAD);

    yaw_rate = gz_corrected * cos_roll * cos_pitch +
               data->gy * sin_roll * cos_pitch -
               data->gx * sin_pitch;

    /* ===== Yaw角积分 ===== */
    kal_yaw += yaw_rate * dt;

    /* 保持角度在 -180 ~ 180 范围 */
    while (kal_yaw > 180.0f)  kal_yaw -= 360.0f;
    while (kal_yaw < -180.0f) kal_yaw += 360.0f;

    /* 输出 */
    data->yaw = kal_yaw;
}

/**
 * @brief 获取卡尔曼估计的陀螺仪Z轴零偏
 * @return 零偏估计值 (°/s)
 */
float kalman_get_bias(void)
{
    return kal_bias;
}

#ifndef __MPU6050_H__
#define __MPU6050_H__

#include "stm32f1xx_hal.h"

/*----------------------------------------------------------------------------*/
/* 配置选项                                                                    */
/*----------------------------------------------------------------------------*/

/* AD0引脚控制:取消注释以启用GPIO控制,否则AD0默认接地 */
// #define MPU6050_AD0_GPIO_ENABLE

#ifdef MPU6050_AD0_GPIO_ENABLE
#define MPU6050_AD0_GPIO_PORT       GPIOA
#define MPU6050_AD0_GPIO_PIN        GPIO_PIN_15
#define MPU6050_AD0_GPIO_CLK_EN()   __HAL_RCC_GPIOA_CLK_ENABLE()
#define MPU6050_AD0_SET(x)          HAL_GPIO_WritePin(MPU6050_AD0_GPIO_PORT, MPU6050_AD0_GPIO_PIN, (x) ? GPIO_PIN_SET : GPIO_PIN_RESET)
#endif

/* I2C地址 (7位: 0x68, HAL库使用8位: 0xD0) */
#define MPU6050_ADDR                0xD0

/*----------------------------------------------------------------------------*/
/* 寄存器地址                                                                  */
/*----------------------------------------------------------------------------*/

#define MPU6050_SELF_TEST_X         0x0D    /* 自检寄存器X */
#define MPU6050_SELF_TEST_Y         0x0E    /* 自检寄存器Y */
#define MPU6050_SELF_TEST_Z         0x0F    /* 自检寄存器Z */
#define MPU6050_SELF_TEST_A         0x10    /* 自检寄存器A */
#define MPU6050_SMPLRT_DIV          0x19    /* 采样率分频器 */
#define MPU6050_CONFIG              0x1A    /* 配置寄存器 */
#define MPU6050_GYRO_CONFIG         0x1B    /* 陀螺仪配置 */
#define MPU6050_ACCEL_CONFIG        0x1C    /* 加速度计配置 */
#define MPU6050_FIFO_EN             0x23    /* FIFO使能 */
#define MPU6050_I2C_MST_CTRL        0x24    /* I2C主控制 */
#define MPU6050_I2C_SLV0_ADDR       0x25    /* I2C从机0地址 */
#define MPU6050_I2C_SLV0_REG        0x26    /* I2C从机0寄存器 */
#define MPU6050_I2C_SLV0_CTRL       0x27    /* I2C从机0控制 */
#define MPU6050_INT_PIN_CFG         0x37    /* 中断引脚配置 */
#define MPU6050_INT_ENABLE          0x38    /* 中断使能 */
#define MPU6050_INT_STATUS          0x3A    /* 中断状态 */
#define MPU6050_ACCEL_XOUT_H        0x3B    /* 加速度X轴高字节 */
#define MPU6050_ACCEL_XOUT_L        0x3C    /* 加速度X轴低字节 */
#define MPU6050_ACCEL_YOUT_H        0x3D    /* 加速度Y轴高字节 */
#define MPU6050_ACCEL_YOUT_L        0x3E    /* 加速度Y轴低字节 */
#define MPU6050_ACCEL_ZOUT_H        0x3F    /* 加速度Z轴高字节 */
#define MPU6050_ACCEL_ZOUT_L        0x40    /* 加速度Z轴低字节 */
#define MPU6050_TEMP_OUT_H          0x41    /* 温度高字节 */
#define MPU6050_TEMP_OUT_L          0x42    /* 温度低字节 */
#define MPU6050_GYRO_XOUT_H         0x43    /* 陀螺仪X轴高字节 */
#define MPU6050_GYRO_XOUT_L         0x44    /* 陀螺仪X轴低字节 */
#define MPU6050_GYRO_YOUT_H         0x45    /* 陀螺仪Y轴高字节 */
#define MPU6050_GYRO_YOUT_L         0x46    /* 陀螺仪Y轴低字节 */
#define MPU6050_GYRO_ZOUT_H         0x47    /* 陀螺仪Z轴高字节 */
#define MPU6050_GYRO_ZOUT_L         0x48    /* 陀螺仪Z轴低字节 */
#define MPU6050_USER_CTRL           0x6A    /* 用户控制 */
#define MPU6050_PWR_MGMT_1          0x6B    /* 电源管理1 */
#define MPU6050_PWR_MGMT_2          0x6C    /* 电源管理2 */
#define MPU6050_FIFO_COUNT_H        0x72    /* FIFO计数高字节 */
#define MPU6050_FIFO_COUNT_L        0x73    /* FIFO计数低字节 */
#define MPU6050_FIFO_R_W            0x74    /* FIFO读写 */
#define MPU6050_WHO_AM_I            0x75    /* 器件ID */

/*----------------------------------------------------------------------------*/
/* 陀螺仪量程                                                                  */
/*----------------------------------------------------------------------------*/

#define MPU6050_GYRO_FSR_250DPS     0       /* ±250°/s */
#define MPU6050_GYRO_FSR_500DPS     1       /* ±500°/s */
#define MPU6050_GYRO_FSR_1000DPS    2       /* ±1000°/s */
#define MPU6050_GYRO_FSR_2000DPS    3       /* ±2000°/s */

/*----------------------------------------------------------------------------*/
/* 加速度计量程                                                                */
/*----------------------------------------------------------------------------*/

#define MPU6050_ACCEL_FSR_2G        0       /* ±2g */
#define MPU6050_ACCEL_FSR_4G        1       /* ±4g */
#define MPU6050_ACCEL_FSR_8G        2       /* ±8g */
#define MPU6050_ACCEL_FSR_16G       3       /* ±16g */

/*----------------------------------------------------------------------------*/
/* 错误码                                                                      */
/*----------------------------------------------------------------------------*/

#define MPU6050_OK                  0       /* 成功 */
#define MPU6050_ERR_I2C             1       /* I2C通信错误 */
#define MPU6050_ERR_ID              2       /* 器件ID错误 */

/*----------------------------------------------------------------------------*/
/* 数据结构                                                                    */
/*----------------------------------------------------------------------------*/

typedef struct
{
    float w, x, y, z;
} quaternion_t;

typedef struct
{
    int16_t accel_x_raw;    /* 加速度X轴原始值 */
    int16_t accel_y_raw;    /* 加速度Y轴原始值 */
    int16_t accel_z_raw;    /* 加速度Z轴原始值 */
    int16_t gyro_x_raw;     /* 陀螺仪X轴原始值 */
    int16_t gyro_y_raw;     /* 陀螺仪Y轴原始值 */
    int16_t gyro_z_raw;     /* 陀螺仪Z轴原始值 */
    int16_t temp_raw;       /* 温度原始值 */

    float ax;               /* 加速度X轴 (g) */
    float ay;               /* 加速度Y轴 (g) */
    float az;               /* 加速度Z轴 (g) */
    float gx;               /* 陀螺仪X轴 (°/s) */
    float gy;               /* 陀螺仪Y轴 (°/s) */
    float gz;               /* 陀螺仪Z轴 (°/s) */
    float temperature;      /* 温度 (°C) */

    float roll;             /* 横滚角 (°) */
    float pitch;            /* 俯仰角 (°) */
    float yaw;              /* 偏航角 (°) */
} mpu6050_t;

typedef struct
{
    int16_t accel_x;        /* 加速度X轴偏移 */
    int16_t accel_y;        /* 加速度Y轴偏移 */
    int16_t accel_z;        /* 加速度Z轴偏移 (已减去1g) */
    int16_t gyro_x;         /* 陀螺仪X轴偏移 */
    int16_t gyro_y;         /* 陀螺仪Y轴偏移 */
    int16_t gyro_z;         /* 陀螺仪Z轴偏移 */
} mpu6050_offset_t;

/*----------------------------------------------------------------------------*/
/* 函数声明                                                                    */
/*----------------------------------------------------------------------------*/

/** @brief 初始化MPU6050 */
uint8_t mpu6050_init(I2C_HandleTypeDef *hi2c);

/** @brief 设置陀螺仪量程 */
uint8_t mpu6050_set_gyro_fsr(I2C_HandleTypeDef *hi2c, uint8_t fsr);

/** @brief 设置加速度计量程 */
uint8_t mpu6050_set_accel_fsr(I2C_HandleTypeDef *hi2c, uint8_t fsr);

/** @brief 设置数字低通滤波器带宽 */
uint8_t mpu6050_set_lpf(I2C_HandleTypeDef *hi2c, uint16_t lpf);

/** @brief 设置采样率 (4~1000 Hz) */
uint8_t mpu6050_set_rate(I2C_HandleTypeDef *hi2c, uint16_t rate);

/** @brief 读取加速度计数据 */
uint8_t mpu6050_read_accel(I2C_HandleTypeDef *hi2c, mpu6050_t *data);

/** @brief 读取陀螺仪数据 */
uint8_t mpu6050_read_gyro(I2C_HandleTypeDef *hi2c, mpu6050_t *data);

/** @brief 读取温度 */
uint8_t mpu6050_read_temp(I2C_HandleTypeDef *hi2c, mpu6050_t *data);

/** @brief 读取所有传感器数据 (加速度+陀螺仪+温度) */
uint8_t mpu6050_read_all(I2C_HandleTypeDef *hi2c, mpu6050_t *data);

/** @brief 零偏校准,需在传感器静止水平放置时调用 */
uint8_t mpu6050_calibrate(I2C_HandleTypeDef *hi2c, uint16_t samples);

/** @brief 获取当前偏移量 */
mpu6050_offset_t *mpu6050_get_offset(void);

/** @brief 纯加速度计姿态解算 */
void mpu6050_calc_attitude_accel(mpu6050_t *data);

/** @brief 一阶互补滤波姿态解算 */
void mpu6050_calc_attitude_cf(mpu6050_t *data, float dt);

/** @brief Mahony姿态解算初始化 */
void mahony_init(void);

/** @brief Mahony姿态解算更新 */
void mahony_update(mpu6050_t *data, float dt);

/** @brief 获取当前四元数 */
quaternion_t *mahony_get_quaternion(void);

/** @brief Yaw卡尔曼滤波器初始化 */
void kalman_yaw_init(void);

/** @brief Yaw卡尔曼滤波更新 */
void kalman_yaw_update(mpu6050_t *data, float dt);

/** @brief 获取卡尔曼估计的陀螺仪Z轴零偏 */
float kalman_get_bias(void);

#endif