三轴加速度计计算俯仰角与横滚角
一、前置基础说明
1. 核心前提
只有当传感器静止或匀速直线运动时,加速度计测量到的信号才完全等于重力加速度(约 9.8m/s² 或 1g)。此时,重力加速度在三个轴上的分量分布,唯一决定了传感器相对于水平面的倾斜角度。
2. 标准坐标系定义(消费电子 / 无人机通用)
- X 轴:向前(手表顶部 / 无人机机头方向)
- Y 轴:向右(手表右侧 / 无人机右翼方向)
- Z 轴:向下(手表背面 / 无人机底部方向)
- 俯仰角 (Pitch, θ):绕 Y 轴旋转的角度,抬头为正,范围 ±90°
- 横滚角 (Roll, φ):绕 X 轴旋转的角度,右倾为正,范围 ±180°
3. 最终计算公式(最常用、最准确)
使用atan2函数(四象限反正切),避免了单纯asin/acos只能覆盖 ±90° 的缺陷:
俯仰角 θ = atan2(-Ax, Az) × (180/π)
横滚角 φ = atan2(Ay, Az) × (180/π)
其中:
- Ax, Ay, Az:分别为 X、Y、Z 轴的加速度值(单位:g)
- 180/π:弧度转角度的系数(≈57.2958)
二、四个典型场景的数值计算演示
场景 1:完全水平静止(基准状态)
已知条件:传感器水平放置,无任何倾斜
- Ax = 0.000 g
- Ay = 0.000 g
- Az = 1.000 g(重力完全沿 Z 轴向下)
计算过程:
- 俯仰角:θ = atan2 (-0.000, 1.000) × 57.2958 = 0.000°
- 横滚角:φ = atan2 (0.000, 1.000) × 57.2958 = 0.000°
物理意义:传感器完全水平,既不抬头也不倾斜。这是所有姿态计算的零点基准。
场景 2:纯抬头 30°(只有俯仰角变化)
已知条件:传感器绕 Y 轴向上旋转 30°,无横滚
- Ax = -sin (30°) = -0.500 g(重力沿 X 轴向后的分量)
- Ay = 0.000 g
- Az = cos (30°) ≈ 0.866 g(重力沿 Z 轴向下的分量)
计算过程:
- 俯仰角:θ = atan2 (-(-0.500), 0.866) × 57.2958= atan2 (0.500, 0.866) × 57.2958≈ 0.5236 rad × 57.2958 ≈ 30.00°
- 横滚角:φ = atan2 (0.000, 0.866) × 57.2958 = 0.00°
物理意义:传感器精确抬头 30°,左右保持水平。这正是手表 "抬腕亮屏" 功能检测的典型姿态。
场景 3:纯右倾 45°(只有横滚角变化)
已知条件:传感器绕 X 轴向右旋转 45°,无俯仰
- Ax = 0.000 g
- Ay = sin (45°) ≈ 0.707 g(重力沿 Y 轴向右的分量)
- Az = cos (45°) ≈ 0.707 g(重力沿 Z 轴向下的分量)
计算过程:
- 俯仰角:θ = atan2 (-0.000, 0.707) × 57.2958 = 0.00°
- 横滚角:φ = atan2 (0.707, 0.707) × 57.2958≈ 0.7854 rad × 57.2958 ≈ 45.00°
物理意义:传感器精确向右倾斜 45°,前后保持水平。
场景 4:同时抬头 20°+ 右倾 30°(复杂实际场景)
已知条件:传感器先抬头 20°,再右倾 30°(最接近实际使用情况)
- Ax = -sin(20°) ≈ -0.342 g
- Ay = sin(30°) × cos(20°) ≈ 0.5 × 0.940 ≈ 0.470 g
- Az = cos(30°) × cos(20°) ≈ 0.866 × 0.940 ≈ 0.814 g
计算过程:
- 俯仰角:θ = atan2 (-(-0.342), 0.814) × 57.2958= atan2 (0.342, 0.814) × 57.2958≈ 0.397 rad × 57.2958 ≈ 22.7°
- 横滚角:φ = atan2 (0.470, 0.814) × 57.2958≈ 0.523 rad × 57.2958 ≈ 30.0°
重要说明:当同时存在俯仰和横滚时,计算出的俯仰角会与原始旋转角度略有差异(22.7° vs 20°)。这是因为旋转顺序会影响最终姿态,属于正常的欧拉角特性。
三、关键注意事项
-
无法测量偏航角:加速度计只能测量俯仰角和横滚角,完全无法测量偏航角(绕 Z 轴的旋转)。因为重力加速度在水平面上没有分量,无论传感器如何旋转,只要保持水平,Ax 和 Ay 始终为 0。偏航角需要磁力计来测量。
-
动态运动误差:上述公式仅适用于静止或匀速直线运动。当传感器加速运动时(如走路、跑步、无人机飞行),加速度计会同时测量到重力加速度和运动加速度,导致计算出的姿态角出现严重误差。这就是为什么实际设备中必须结合陀螺仪进行数据融合(卡尔曼滤波 / 互补滤波)。
-
角度范围:
- 俯仰角:使用
atan2(-Ax, Az)计算,范围为 **-180°~+180°**,但实际物理意义上,当俯仰角超过 ±90° 时,横滚角会发生 180° 跳变(万向锁问题)。 - 横滚角:使用
atan2(Ay, Az)计算,范围为 **-180°~+180°**。
- 俯仰角:使用
-
校准的重要性:实际的 MEMS 加速度计存在零点偏移和灵敏度误差。例如,水平放置时 Az 可能不是 1.000g,而是 0.980g 或 1.020g。必须进行校准才能得到准确的角度计算结果。
四、实际应用中的数值阈值示例
- 手表抬腕亮屏:当检测到俯仰角在 **20°~60°之间,且横滚角在-30°~+30°** 之间时,触发亮屏。
- 无人机悬停控制:飞行控制系统会努力将俯仰角和横滚角控制在 **±0.5°** 以内,以保持稳定悬停。
- 跌倒检测:当检测到俯仰角或横滚角突然变化超过60°,且伴随自由落体(Az≈0g)和撞击(加速度 > 5g)时,判断为跌倒。
五、三轴加速度计角度计算与低通滤波 C 语言实现
#include <stdio.h>
#include <math.h>
// 数学常数定义
#define PI 3.14159265358979323846f
#define RAD_TO_DEG (180.0f / PI)
#define DEG_TO_RAD (PI / 180.0f)
// 低通滤波系数(0.0~1.0)
// 数值越小,滤波效果越强,但延迟越大
// 可穿戴设备推荐: 0.1~0.3
// 无人机推荐: 0.3~0.5
#define LPF_ALPHA 0.2f
// 三轴加速度计数据结构体
typedef struct {
float x; // X轴原始加速度(g)
float y; // Y轴原始加速度(g)
float z; // Z轴原始加速度(g)
float x_filtered; // X轴滤波后加速度(g)
float y_filtered; // Y轴滤波后加速度(g)
float z_filtered; // Z轴滤波后加速度(g)
float pitch; // 俯仰角(度)
float roll; // 横滚角(度)
} AccelData;
/**
* @brief 一阶低通滤波函数
* @param raw 原始输入值
* @param prev_filtered 上一次滤波后的值
* @param alpha 滤波系数(0.0~1.0)
* @return 本次滤波后的值
*/
float low_pass_filter(float raw, float prev_filtered, float alpha) {
return alpha * raw + (1.0f - alpha) * prev_filtered;
}
/**
* @brief 计算俯仰角和横滚角
* @param data 加速度计数据结构体指针
* @note 使用滤波后的加速度数据进行计算
*/
void calculate_angles(AccelData* data) {
// 防止除以零错误(当Z轴加速度接近0时)
if (fabsf(data->z_filtered) < 0.001f) {
data->z_filtered = 0.001f;
}
// 计算俯仰角(绕Y轴)
data->pitch = atan2f(-data->x_filtered, data->z_filtered) * RAD_TO_DEG;
// 计算横滚角(绕X轴)
data->roll = atan2f(data->y_filtered, data->z_filtered) * RAD_TO_DEG;
}
/**
* @brief 初始化加速度计数据结构体
* @param data 加速度计数据结构体指针
*/
void accel_init(AccelData* data) {
data->x = 0.0f;
data->y = 0.0f;
data->z = 1.0f; // 初始水平状态
data->x_filtered = 0.0f;
data->y_filtered = 0.0f;
data->z_filtered = 1.0f;
data->pitch = 0.0f;
data->roll = 0.0f;
}
/**
* @brief 更新加速度计数据并计算角度
* @param data 加速度计数据结构体指针
* @param new_x 新的X轴原始加速度(g)
* @param new_y 新的Y轴原始加速度(g)
* @param new_z 新的Z轴原始加速度(g)
*/
void accel_update(AccelData* data, float new_x, float new_y, float new_z) {
// 更新原始数据
data->x = new_x;
data->y = new_y;
data->z = new_z;
// 应用低通滤波
data->x_filtered = low_pass_filter(data->x, data->x_filtered, LPF_ALPHA);
data->y_filtered = low_pass_filter(data->y, data->y_filtered, LPF_ALPHA);
data->z_filtered = low_pass_filter(data->z, data->z_filtered, LPF_ALPHA);
// 计算角度
calculate_angles(data);
}
/**
* @brief 打印加速度计数据和角度
* @param data 加速度计数据结构体指针
*/
void print_accel_data(AccelData* data) {
printf("原始加速度: X=%.3fg, Y=%.3fg, Z=%.3fg\n",
data->x, data->y, data->z);
printf("滤波后加速度: X=%.3fg, Y=%.3fg, Z=%.3fg\n",
data->x_filtered, data->y_filtered, data->z_filtered);
printf("姿态角: 俯仰角=%.2f°, 横滚角=%.2f°\n\n",
data->pitch, data->roll);
}
int main() {
AccelData accel;
accel_init(&accel);
printf("=== 三轴加速度计角度计算与低通滤波演示 ===\n\n");
// 场景1: 完全水平静止
printf("场景1: 完全水平静止\n");
accel_update(&accel, 0.000f, 0.000f, 1.000f);
print_accel_data(&accel);
// 场景2: 纯抬头30°
printf("场景2: 纯抬头30°\n");
accel_update(&accel, -0.500f, 0.000f, 0.866f);
print_accel_data(&accel);
// 场景3: 纯右倾45°
printf("场景3: 纯右倾45°\n");
accel_update(&accel, 0.000f, 0.707f, 0.707f);
print_accel_data(&accel);
// 场景4: 同时抬头20°+右倾30°
printf("场景4: 同时抬头20°+右倾30°\n");
accel_update(&accel, -0.342f, 0.470f, 0.814f);
print_accel_data(&accel);
// 模拟带噪声的实际传感器数据
printf("场景5: 带噪声的水平数据(模拟实际传感器)\n");
// 模拟5次采样,每次加入±0.02g的随机噪声
for (int i = 0; i < 5; i++) {
float noise_x = ((float)rand() / RAND_MAX - 0.5f) * 0.04f;
float noise_y = ((float)rand() / RAND_MAX - 0.5f) * 0.04f;
float noise_z = ((float)rand() / RAND_MAX - 0.5f) * 0.04f;
accel_update(&accel, 0.0f + noise_x, 0.0f + noise_y, 1.0f + noise_z);
printf("第%d次采样:\n", i+1);
print_accel_data(&accel);
}
return 0;
}
更多推荐



所有评论(0)