MPU6050陀螺仪数据分析:零偏标定与卡尔曼姿态融合

发布时间:2026/11/3 13:46:20
MPU6050陀螺仪数据分析:零偏标定与卡尔曼姿态融合

简介:这份面向嵌入式初学者与自平衡小车爱好者的MPU6050数据分析文档,聚焦加速度计与陀螺仪原始数据的处理思路,帮助读者理解三轴加速度求倾角、角速度积分以及传感器融合的基本方法。压缩包内仅含1个docx文件,大小约689KB,篇幅集中,适合作为学习笔记或项目参考快速查阅。文档结合作者实践心得,梳理了加速度计三轴分量与重力加速度的分解关系、小角度下正弦与弧度的线性近似换算、陀螺仪角速度累加积分,以及用卡尔曼滤波融合两者角度以减小误差的思路,并给出MPU6050与Arduino连接的参考资料。目前已有893人学习,适合正在搭建自平衡小车、需要补齐姿态解算基础的读者按需查阅。

1. 陀螺仪数据分析先从 MPU6050 的原始值拆起:三个量程和一条时间轴

自平衡车调姿时,串口里三条曲线一抖,很多人第一反应是卡尔曼系数没调好;真正翻车的地方,往往是 MPU6050 的原始值还没拆干净。陀螺仪数据分析这件事,说到底是把加速度计的重力分量、陀螺仪的角速度积分和一条可信的时间轴对齐。这套笔记围绕 MPU6050 从寄存器读数、零偏标定、倾角换算到卡尔曼融合,适合正在做自平衡车、云台或姿态 demo 的 Arduino 用户,也适合想从“能读出数”走到“敢拿数据做控制”的开发者。读之前只要会一点三角函数、C 语言和基本串口调试,后面的公式和代码都能照着复现。

2. 加速度计算倾角:从 gx/g 到 262 系数,先把静态角度钉住

2.1 重力分解与小角度线性化:为什么用 ax/262 而不是 asin

MPU6050 的加速度计输出的是三个轴的加速度分量,单位不是角度,而是 LSB。量程设为 ±2g 时,灵敏度是 16384 LSB/g,水平静置时某一个轴理论上读到接近 16384,另外两个轴接近 0。车体倾斜后,重力加速度 g 被分解到 x、y、z 三个方向,x 轴分量 gx 与总重力 g 的比值满足sinθ = gx / g。如果直接用asin求角度,在单片机里也能算,但浮点三角函数开销不小,而且小角度下线性近似已经够用。

小角度线性化的核心是sinθ ≈ θ,θ 用弧度表示。自平衡车的摆动范围通常不大,输入资料里取了一个 0.92 的系数,把近似成立的范围扩到大约 ±45 度。设角度为 d,弧度换算为d * π / 180,于是有gx / g = k * d * π / 180,整理得到d = 180 * gx / (k * π * g)。当量程为 ±2g、g 取 16384 LSB/g、k 取 0.92 时,分母约为0.92 * 3.1416 * 16384 / 180 ≈ 262,所以代码里常见ax_angle = ax / 262.0。这个 262 不是玄学常数,它把量程、重力灵敏度和线性化系数揉在了一起。

参数含义本文采用值改动后的影响
加速度量程加速度计满量程±2g改大量程会降低分辨率,262 系数要重算
加速度灵敏度每 g 对应的 LSB16384 LSB/g由量程决定,不能和陀螺仪灵敏度混用
线性化系数 k小角度近似补偿0.92取 1.0 时角度范围窄,取太小会压低斜率
角度系数1 / 262约 0.003816换量程后必须重算

注意:ax / 262只适合 ±2g 量程和这里的小角度近似。把量程改成 ±4g 或 ±8g 后,灵敏度变成 8192 或 4096 LSB/g,继续用 262 会把角度算小一半或四分之一。

2.2 水平静置标定与 ax_angle 代码落地

加速度计有零偏,模块焊在板上、板子固定在车体上,原始值都不会刚好是 0。输入资料里给出AX_ZERO = -1476这样的修正值,这不是通用常数,而是某一块模块在某个安装方向下水平静置测出来的。我一般会先做一次水平静置标定:把车体放在水平桌面,确保 y 轴和车轴方向一致,连续读 2000 次,求平均值。采样间隔不用太短,2 ms 左右即可,让传感器输出稳定下来。标定前先上电预热几十秒,温漂会让零偏慢慢爬,冷机标出来的值下午可能就不准了。

#include "Wire.h" #include "I2Cdev.h" #include "MPU6050.h" MPU6050 mpu; int16_t ax, ay, az, gx, gy, gz; void setup() { Wire.begin(); Serial.begin(38400); mpu.initialize(); mpu.setFullScaleAccelRange(MPU6050_ACCEL_FS_2); mpu.setFullScaleGyroRange(MPU6050_GYRO_FS_250); long sumAx = 0; long sumGx = 0; const int N = 2000; for (int i = 0; i < N; i++) { mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); sumAx += ax; sumGx += gx; delay(2); } Serial.print("AX_ZERO="); Serial.println(sumAx / (float)N); Serial.print("GX_ZERO="); Serial.println(sumGx / (float)N); } void loop() {}

这段代码只做一件事:在静止状态下求原始读数的平均值。N = 2000是采样次数,delay(2)让两次读取之间留出一点时间,避免 I2C 总线被连续读操作压满。sumAx用long而不是int16_t,因为 2000 次累加很容易超过 16 位范围。串口打印出来的AX_ZERO和GX_ZERO要抄回主程序,但只对当前模块、当前安装方向、当前量程有效。换一块模块、拆一次固定螺丝、改一次量程,都要重新标。

标定完成后,角度换算可以写成一个独立函数,主循环只负责读数和调用。下面这段代码把零偏修正和 262 系数放在一起,rawAx是getMotion6读出来的原始 x 轴加速度:

const int16_t AX_ZERO = -1476; // 示例值,必须换成自己标定的结果 const float ACCEL_SCALE_2G = 16384.0; const float TILT_K = 0.92; float accelAngleX(int16_t rawAx) { float ax = rawAx - AX_ZERO; // θ ≈ 180 * ax / (K * π * 16384),分母约等于 262 return ax / 262.0; }

AX_ZERO先减掉,得到“纯重力分量”;262.0是0.92 * 3.1416 * 16384 / 180的近似结果。代码里的正负号取决于模块安装方向:如果车体向前倾时ax变小,而你想让前倾角为正,就在返回值前加一个负号。不要一边用加速度算正角度、一边用陀螺仪算反角度,后面融合时符号会打架,曲线会像两条互相拉扯的橡皮筋。

2.3 安装方向、动态加速度与量程边界

输入资料里把 y 轴和车轴放在一条直线上,小车摆动时 y 轴与桌面夹角始终接近 0,x 轴和 z 轴的夹角变化一致,所以只算 x 轴倾角就能反映车体倾斜。这个假设成立的前提是模块装正:y 轴平行车轴,x 轴指向车头或车尾,z 轴垂直底盘。如果模块装歪了 10 度,静止时读出来的ax_angle就不是 0,后面所有控制目标都要带一个固定偏置。我一般会在车体上画一条基准线,模块边和基准线对齐后再拧螺丝,比事后在代码里补角度省事。

加速度计算倾角最大的边界是动态加速度污染。车体静止或缓慢倾斜时,加速度计测到的只有重力分量;电机一发力,车体前进或后退的加速度会叠加到 x 轴上,ax / 262算出来的角度会突然跳一下。这个跳变不是传感器坏了,而是物理量混在一起。常见做法是先用加速度计做静态校准和慢速姿态参考,动态控制靠陀螺仪短时积分撑住,再用卡尔曼滤波把两者按可信度加权。不要指望加速度计在电机猛加速时还能给出干净角度,那是把两种加速度混为一谈。

量程边界也要注意。±2g 量程下,超过 1g 的动态加速度会让原始值接近满量程,甚至削顶。自平衡车正常调姿一般不会长期超过 2g,但摔倒、撞击、电机堵转时可能瞬时超限。原始值削顶后,角度曲线会出现平顶,滤波再漂亮也补不回来。调试阶段可以先把量程放到 ±4g 看原始值有没有削顶,确认动态范围后再回到 ±2g 换分辨率;换量程时 262 系数、零偏值、甚至卡尔曼的R_angle都要重新看一遍。

3. 陀螺仪积分与卡尔曼融合:角速度乘 dt,别让零偏把角度带跑

3.1 角速度积分的时间基准:用 millis 差值,不用固定 10ms

陀螺仪输出的是角速度,不是角度。量程设为 ±250 °/s 时,灵敏度是 131 LSB/(°/s),原始值除以 131 得到 °/s,再乘以两次采样的时间差,得到这段时间转过的角度。把所有角度增量累加,就是陀螺仪积分角度。输入资料里的公式是X = (w1*t1 + w2*t2 + ... + wn*tn) / 1000,这里的t是毫秒,除以 1000 换成秒。代码里常见gx_angle = gyro * dt,dt用秒,累加时注意符号。

积分最怕两件事:零偏和时间不准。陀螺仪静止时输出不会刚好是 0,输入资料里给了一个GX_ZERO = -30.5的示例修正值。这个值同样要水平静置标定,而且会随温度变化。零偏 0.5 °/s 看起来很小,积分 60 秒就是 30 度漂移,曲线慢慢歪到姥姥家。另一个坑是固定dt:代码里写dt = 0.01,实际循环因为串口打印、I2C 读取、滤波计算,可能 12 ms 才跑一圈,积分角度就会偏小。血泪经验是时间差一定用millis()实测,不要拍脑袋写固定值。

static unsigned long lastTime = 0; unsigned long now = millis(); if (lastTime == 0) { lastTime = now; return; } unsigned long dt_ms = now - lastTime; lastTime = now; if (dt_ms == 0 || dt_ms > 100) { return; // 过滤异常长间隔,避免一次积分跳变 } float dt = dt_ms / 1000.0;

lastTime用static保存上一次采样时刻,dt_ms是两次循环的真实间隔。dt_ms > 100时直接丢弃这一拍,常见于串口阻塞或调试器暂停,硬积分会把角度拉飞。dt换成秒后,gyro * dt的单位才是度。注意millis()大约 49 天溢出一次,做长期运行设备要做无符号差值处理;自平衡车短时运行一般碰不到,但做数据记录时要留意。

3.2 卡尔曼滤波的两步与 Q/R 的取舍

卡尔曼滤波在这里干的事不复杂:预测阶段用陀螺仪积分更新角度,同时估计陀螺仪零偏;更新阶段用加速度计算出的角度修正预测值。状态量取两个:角度Angle和陀螺仪零偏Q_bias。输入资料里的Kalman_Filter就是二维卡尔曼的展开写法,核心步骤是角度预测、协方差预测、计算增益、更新角度和零偏。Q_angle控制角度预测的可信度,Q_gyro控制零偏漂移的可信度,R_angle控制加速度计测量的噪声。三个参数没有标准答案,和模块噪声、车体振动、采样周期都有关系。

float Angle = 0.0; float Q_bias = 0.0; float Q_angle = 0.9; // 输入资料取值,跟踪快但噪声偏大 float Q_gyro = 0.001; float R_angle = 0.5; float PP[2][2] = {{1, 0}, {0, 1}}; const float C_0 = 1.0; void Kalman_Filter(float accel, float gyro, float dt) { // 预测:角度 += (角速度 - 零偏) * 时间 Angle += (gyro - Q_bias) * dt; float Pdot[4]; Pdot[0] = Q_angle - PP[0][1] - PP[1][0]; Pdot[1] = -PP[1][1]; Pdot[2] = -PP[1][1]; Pdot[3] = Q_gyro; PP[0][0] += Pdot[0] * dt; PP[0][1] += Pdot[1] * dt; PP[1][0] += Pdot[2] * dt; PP[1][1] += Pdot[3] * dt; // 更新:用加速度角度修正预测角度 float Angle_err = accel - Angle; float PCt_0 = C_0 * PP[0][0]; float PCt_1 = C_0 * PP[1][0]; float E = R_angle + C_0 * PCt_0; if (E == 0) return; float K_0 = PCt_0 / E; float K_1 = PCt_1 / E; float t_0 = PCt_0; float t_1 = C_0 * PP[0][1]; PP[0][0] -= K_0 * t_0; PP[0][1] -= K_0 * t_1; PP[1][0] -= K_1 * t_0; PP[1][1] -= K_1 * t_1; Angle += K_0 * Angle_err; Q_bias += K_1 * Angle_err; }

Angle是融合后的角度,Q_bias是陀螺仪零偏的在线估计。预测阶段把gyro - Q_bias乘dt累加到角度上,零偏估计会慢慢把静态漂移压下去。Pdot和PP是协方差矩阵的展开,写成四个元素是为了在单片机上少用动态内存。Angle_err是加速度角度和预测角度的差,K_0、K_1是卡尔曼增益,E == 0的保护是防止除零。代码里Q_angle = 0.9来自输入资料,跟踪速度快,但加速度计噪声会更多进到融合角度里;实际调试可以从 0.001 到 0.01 起,看曲线再调。

参数作用调大后的现象调小后的现象
Q_angle角度预测噪声更信陀螺仪,跟踪快,噪声大更信加速度计,平滑但滞后
Q_gyro零偏漂移噪声零偏估计更活跃零偏估计更保守
R_angle加速度测量噪声更信陀螺仪,抗动态干扰更信加速度计,静态准但抖

3.3 三列串口数据与曲线判读

调试时不要只打印融合角度,至少打印三列:加速度角度、陀螺仪积分角度、卡尔曼融合角度。输入资料里用串口绘图工具看三条曲线,红色是陀螺仪积分,绿色是加速度计,黄色是融合结果。三列数据放在一行,用逗号分隔,串口绘图工具按逗号拆列。这样一眼能看出谁在漂、谁在抖、融合角度跟谁走。如果黄色曲线明显滞后于绿色,通常是R_angle偏大或Q_angle偏小,滤波器太信陀螺仪;如果黄色曲线跟着绿色一起抖,是R_angle太小,加速度噪声灌进来了。

void loop() { static unsigned long last = 0; unsigned long now = millis(); if (last == 0) { last = now; return; } unsigned long dt_ms = now - last; last = now; if (dt_ms == 0 || dt_ms > 100) return; mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz); float dt = dt_ms / 1000.0; float ax_angle = (ax - AX_ZERO) / 262.0; float gyro = (gy - GX_ZERO) / 131.0; // 符号按安装方向统一:这里让积分角度和加速度角度同向 float gyro_for_angle = -gyro; total_angle += gyro_for_angle * dt; Kalman_Filter(ax_angle, gyro_for_angle, dt); Serial.print(ax_angle); Serial.print(","); Serial.print(total_angle); Serial.print(","); Serial.println(Angle); }

gyro先减零偏再除以 131,得到 °/s;gyro_for_angle的负号是安装方向修正,实际接线后如果车头前倾时积分角度反而减小,就把负号去掉。total_angle是纯陀螺仪积分,用来观察漂移;Angle是融合结果,用来做控制。串口三列的顺序要和绘图工具配置一致,不要今天ax,total,Angle、明天Angle,total,ax,否则曲线判读全乱。串口波特率 38400 在 8 MHz 和 16 MHz 的板子上都比较好用,打印频率太高会拖慢循环,dt会变大,积分和滤波都会受影响。

4. 接线与库配置:I2C 四根线、量程寄存器和 38400 串口

4.1 I2C 接线、地址与电平

MPU6050 模块和 Arduino 之间走 I2C,四根线:VCC、GND、SCL、SDA。以常见 AVR 板为例,SCL 接 A5,SDA 接 A4;换其他板子要按引脚复用表改,不能照抄。模块供电常见 3.3 V 或带稳压的 5 V 兼容版,接线前看清楚模块丝印。I2C 是开漏总线,模块和主控之间电平要匹配;如果主控是 5 V 逻辑、模块是 3.3 V 逻辑,直接接可能读不到数据,常见做法是确认模块自带电平转换,或者加电平转换电路。地址方面,AD0 拉低时默认 0x68,拉高时 0x69;代码里MPU6050 accelgyro;默认用 0x68,如果模块 AD0 接了高电平,就要在构造函数里传 0x69。

MPU6050 模块Arduino 常见 AVR 板说明
VCC5V 或 3.3V按模块丝印供电,不要反接
GNDGND共地,接触不良会随机掉线
SCLA5I2C 时钟线
SDAA4I2C 数据线
AD0悬空或 GND默认地址 0x68;接 VCC 变 0x69

接线看着简单,坑主要在接触电阻和线长。杜邦线松、面包板簧片老化、地线只接一根,都会让 I2C 读不到数据或偶发全 0。调试阶段先用短跳线,确认能稳定读到数再装车;装车后电机线、电源线尽量远离 SCL/SDA,I2C 不是差分总线,长线加电机干扰很容易把波形拉坏。读不到数据时先看testConnection()的返回值,不要一上来就改滤波参数,那是把黑匣子越埋越深。

4.2 库初始化和量程寄存器修改

代码依赖 Wire、I2Cdev、MPU6050 三个库。Wire.begin()加入 I2C 总线,mpu.initialize()初始化寄存器,testConnection()检查连接。量程不要只靠库默认值,最好在setup()里显式设置:setFullScaleAccelRange(MPU6050_ACCEL_FS_2)设 ±2g,setFullScaleGyroRange(MPU6050_GYRO_FS_250)设 ±250 °/s。输入资料里提到改库文件mpu6050.cpp的initialize()函数,把量程写死;这种做法能跑,但升级库或换工程时容易忘,改成在草图里调用设置函数更稳。如果所用库没有公开设置函数,再考虑改库,并且把改动记录在工程说明里。

void setup() { Wire.begin(); Serial.begin(38400); mpu.initialize(); if (!mpu.testConnection()) { Serial.println("MPU6050 offline"); while (1) { delay(100); } } mpu.setFullScaleAccelRange(MPU6050_ACCEL_FS_2); mpu.setFullScaleGyroRange(MPU6050_GYRO_FS_250); mpu.setSleepEnabled(false); }

testConnection()失败时停在while (1),是为了让串口明确报错,而不是带着空数据继续跑。setSleepEnabled(false)确保模块退出休眠,有些模块上电默认睡,读出来全是 0。量程设置放在初始化之后、读数据之前,顺序不要颠倒。Serial.begin(38400)的波特率要和串口绘图工具、采集脚本一致;改波特率不会影响 I2C 读数,但会影响循环周期,dt会跟着变,滤波参数要重新看。

4.3 串口采集脚本与 CSV 落盘

调参不能只看实时曲线,最好把三列数据落成 CSV,后面用 Python 算漂移率、看阶跃响应。采集脚本用 pyserial 读串口,按逗号拆三列,加时间戳写文件。串口名按系统实际端口改,Windows 常见COM3,Linux 常见/dev/ttyUSB0,不要照抄。采样时长先设 30 秒,够看静态漂移;做阶跃测试时手动倾斜车体,同时观察 CSV 里的响应。

import csv import time import serial ser = serial.Serial('COM3', 38400, timeout=1) with open('mpu6050_log.csv', 'w', newline='') as f: writer = csv.writer(f) writer.writerow(['t', 'ax_angle', 'total_angle', 'kalman_angle']) t0 = time.time() while time.time() - t0 < 30: line = ser.readline().decode('ascii', 'ignore').strip() if not line: continue parts = line.split(',') if len(parts) != 3: continue try: vals = [float(x) for x in parts] except ValueError: continue writer.writerow([round(time.time() - t0, 3), *vals]) ser.close()

timeout=1防止串口没有数据时一直阻塞;decode('ascii', 'ignore')丢掉乱码字节;len(parts) != 3过滤掉启动阶段的半截行。t是主机时间戳,不是单片机millis(),做严格时序分析时要在 Arduino 端把millis()也打印出来。CSV 落盘后可以用表格软件或 Python 画图,重点看三件事:静态时total_angle是否持续单向漂移,ax_angle的噪声峰峰值多大,kalman_angle对阶跃倾斜的响应时间是多少。采集脚本不要和串口绘图工具同时打开同一个端口,系统会报占用。

5. 避坑与排查:角度漂移、曲线滞后和 I2C 掉线的五个现场

5.1 上电角度不为零,零偏值抄了别人的

现象:车体水平放好,串口打印的ax_angle不是 0,可能是 3 度、5 度,甚至十几度;融合角度慢慢往一边偏。原因:AX_ZERO用了示例值或别人代码里的值,没有对当前模块标定;模块安装歪了,y 轴没有和车轴平行;标定时桌面本身不水平。解决:先确认模块安装基准,再把车体放在水平台上,上电预热后跑 2000 次平均标定;把标定值写回代码,重新上电看静止角度是否在 ±0.5 度内。如果每次上电零偏都不一样,检查供电和 I2C 接触,接触不良会让读数随机跳。

5.2 陀螺仪积分角度越跑越偏,零偏和 dt 一起作怪

现象:车体静止不动,total_angle在几十秒内从 0 漂到 10 度、20 度,融合角度也被慢慢带跑。原因:陀螺仪零偏没扣干净,或者GX_ZERO只标了一次但温漂变了;dt用固定值,实际循环周期受串口打印影响变大,积分比例不对;积分符号和安装方向不一致。解决:静止标定GX_ZERO,上电预热后再标;把dt改成millis()差值,过滤异常长间隔;打印原始gy和gyro,静止时看gyro是否在 ±0.5 °/s 内。如果零偏随温度变化明显,要么加在线零偏估计,要么在静止时做自动零偏更新,不要硬扛。

5.3 卡尔曼曲线滞后,参数和采样周期不匹配

现象:手动快速倾斜车体,绿色加速度角度已经变了,黄色融合角度慢半拍;或者黄色曲线抖动明显,跟着绿色一起跳。原因:R_angle太大或Q_angle太小,滤波器太信陀螺仪,修正慢;dt和实际采样周期不一致,预测和更新节奏错位;加速度计动态噪声大,R_angle太小把噪声全放进来。解决:先把dt改成实测值,确认串口打印不拖慢循环;再把Q_angle从 0.001 到 0.01 之间试,R_angle从 0.1 到 1.0 之间试,每次只改一个参数,记录阶跃响应曲线。不要同时改三个参数,否则曲线变好了也不知道是谁的功劳。

5.4 I2C 读不到数据,先查线和地址

现象:串口一直打印MPU6050 offline,或者读出来的ax/ay/az全是 0,偶尔能读到又掉线。原因:SDA/SCL 接反、杜邦线松、模块供电不对、AD0 电平与代码地址不匹配、总线上没有上拉电阻。解决:先断电用万用表查 VCC 和 GND,再查 SCL 是否接 A5、SDA 是否接 A4;确认模块 AD0 悬空或接地时用 0x68,接高时用 0x69;换短跳线,避开电机线;如果模块没有上拉,加 4.7 kΩ 上拉到 3.3 V 或 5 V(按模块电平)。读不到时不要改滤波,先把连接测试跑通。

5.5 串口曲线乱跳,打印阻塞和数据类型在捣鬼

现象:曲线出现尖刺,角度偶尔跳到几百,或者dt忽大忽小。原因:Serial.print太多拖慢循环;int16_t原始值减去零偏后溢出;量程设置和系数不匹配;串口绘图工具解析到半截行。解决:降低打印频率,或者只打印三列必要数据;零偏修正用float或long中间变量,别在int16_t里硬减;确认量程是 ±2g 和 ±250 °/s,系数 262 和 131 对得上;采集端过滤长度不对的行。曲线乱跳时先把原始ax/gy打出来看,原始值干净再谈滤波。

6. 进阶验证:用静态台架和阶跃测试把参数调明白

调参最怕“看着差不多”。我习惯做三组验证:静态漂移、阶跃响应、慢速正弦摆动。静态漂移把车体水平固定,采 5 分钟 CSV,算total_angle的斜率,单位 °/min;斜率大于 1 °/min 先回去查零偏和dt,不要急着调卡尔曼。阶跃测试把车体从水平快速倾斜到 20 度左右,保持不动,看融合角度上升到 63% 的时间,通常控制在 0.3 到 0.8 秒之间;太慢跟不住车体,太快噪声大。慢速正弦摆动用手左右倾斜,频率大约 0.5 Hz,看融合角度和加速度角度的相位差,相位差太大说明滤波器滞后。

验证项操作观察指标常见调整
静态漂移水平固定 5 分钟total_angle斜率重标GX_ZERO,检查dt
阶跃响应快速倾斜 20 度融合角度到 63% 的时间调Q_angle、R_angle
正弦摆动0.5 Hz 左右往复融合与加速度角度相位差减小R_angle或增大Q_angle
动态噪声电机怠速转动融合角度峰峰值增大R_angle,检查减震
import numpy as np # 假设已从 CSV 读出 total_angle 列,单位度,采样周期 0.02 秒 # drift_rate = (total_angle[-1] - total_angle[0]) / (len(total_angle) * 0.02 / 60) # 阶跃响应时间:找到第一次超过目标值 63% 的索引 # t63 = np.argmax(total_angle >= 0.63 * target) * 0.02

上面两行是验证脚本的核心算式,drift_rate的单位是 °/min,t63的单位是秒。采样周期按实际 CSV 时间戳算,不要固定 0.02 秒。调参记录要写清楚:改了哪个参数、从多少改到多少、静态漂移和阶跃时间各是多少。从那以后我每次焊完板子先做三件事:水平标零、记录真实dt、把原始三列存盘,再谈滤波。希望帮到你。工程文件我按 Arduino 草图、Python 串口采集脚本、标定记录表分开放,需要复现时直接取用。

本文还有配套的精品资源,点击获取