文号
920943
阅读
15744
回复
15
收藏
6
求助有关姿态传感器的问题
2023/05/24
IP:广东

事情是这样的,我正在开发惯性导航的航电用于火箭,因为暂时不需要太高精度,所以只用普通的姿态传感器,mpu6050收集数据,但是不知道为什么,平放在桌上时,x轴角速度读数就有0.02rad/s,加速度有10m/s²,虽然看起来误差不大,但是随着时间的推移必然导致很大误差,运动状态产生的误差说不定更大。我假设是线性的修复了一下,但是不理想。我用的是四元数法,不管是我自己写还是去网上找代码,飘得都很厉害,运动缓慢还好,可以接受,但角速度一快精度就下滑得很离谱,我知道四元数法在角速度大的情况下精度低,但应该不至于低到这么离谱。我想问一下mpu6050都这样吗?但示数肉眼发现不对这有点离谱了吧……还是说我被坑了?但是我买了两次都这样……如果mpu6050不能用的话,应该选哪个模块呢?希望做过惯性导航的大佬能推荐下。最好50元以下,精度不必很高,因为我现在火箭算上下降也就滞空100s左右,没必要用精密的陀螺仪,但是别向这样错误明显漂移严重就行了。谢谢各位


3年5个月前IP:江苏
920952
1楼

没有校准吗?这个数据是不正常的。不过建议搭配电子罗盘使用。

引用
评论
3年5个月前IP:湖南
920959
2楼

电子陀螺仪是要校准的,静置于桌面上,那个读数基本是不变的,减掉就行。


而对于加速度计而言,你可以试一试自由落体状态下是什么样的。自由落体时读数为零。


最后,多看数据手册,网上的英文教程。

引用
评论
3年5个月前IP:湖南
920962
3楼

闲鱼有卖拆机的adis16505-2性能好,也不是很贵,但是是拆机的,量程对于火箭应用可能也不够大。

ICM42688p不贵,是消费级的单芯片产品。

或者博世的bmi088,比前两年便宜不少。

引用
评论
tmn_A作者
3年5个月前IP:广东
920963
4楼
引用Colin胡发表于2楼的内容
电子陀螺仪是要校准的,静置于桌面上,那个读数基本是不变的,减掉就行。而对于加速度计而言,你可以试一试自由落体状态下是什么样的。自由落体时读数为零。最后,多看数据手册,网上的英文教程。


是的,角速度一直是-0.02,0.00,0.00,可以x轴直接加上0.02吗?另外重力加速度平放是10左右,调整是减0.2还是乘0.98呢?

引用
评论
3年5个月前IP:广东
920966
5楼
引用tmn_A发表于4楼的内容
是的,角速度一直是-0.02,0.00,0.00,可以x轴直接加上0.02吗?另外重力加速度平放是10左右,调整是减0.2还是乘0.98呢?

速率陀螺减去零飘,加速度计要椭球矫正。

引用
评论
tmn_A作者
3年5个月前IP:广东
920994
6楼

谢谢各位,我又发现了个离谱的现象,有个操作是四元数的规范化处理,本来应该是若干次更新后来将四元数整理为规范四元数的,但是不知道为啥加了这一个之后精度反而降低了不少……我用的arduino,什么dmp都没有库,再加上也不需很高精度,干脆校准一下凑合凑合用了吧 sticker

引用
评论
tmn_A作者
3年5个月前IP:广东
921480
7楼

再次求助一下各位,我使用chatgpt帮助,再加以改动,直接开了dmp,读取四元数,但是仍然有许多问题,我排查了好几天,都没有找到哪里出了问题。希望能得到帮助。

首先,我怀疑问题可能出现再硬件上,我就先说下我的硬件,用的arduino

Nano,atmega328p。

以下是第一份代码,也是chatgpt在我提了一堆问题后修修补补出来的。以下是代码

#include <Wire.h>
#include "MPU6050.h"
#include "I2Cdev.h"
#include "helper_3dmath.h"

MPU6050 mpu;

Quaternion quat;
VectorFloat gravity;
VectorInt16 aaReal;
VectorInt16 aaWorld;
float ypr[3];

uint8_t mpuIntStatus;
bool dmpReady;
uint16_t packetSize;
uint16_t fifoCount;
uint8_t fifoBuffer[128];
unsigned long t;

void setup() {
  Wire.begin();
  Serial.begin(115200);
  mpu.initialize();
  mpu.dmpInitialize();
  mpu.setXGyroOffset(220);
  mpu.setYGyroOffset(76);
  mpu.setZGyroOffset(-85);
  mpu.setZAccelOffset(1788);

  if (!mpu.testConnection()) {
    Serial.println("MPU6050 connection failed");
    while (1) {}
  }
  mpu.setDMPEnabled(true);
  mpuIntStatus = mpu.getIntStatus();
  dmpReady = true;
 
  packetSize = mpu.dmpGetFIFOPacketSize();
}

void loop() {
  if (!dmpReady) return;
 
  while (fifoCount < packetSize) {
    fifoCount = mpu.getFIFOCount();
  }
 
  mpu.getFIFOBytes(fifoBuffer, packetSize);
 
  fifoCount -= packetSize;

  mpu.dmpGetQuaternion(&quat, fifoBuffer);
  mpu.dmpGetEuler(ypr, &quat);
  mpu.dmpGetGravity(&gravity, &quat);
  mpu.dmpGetAccel(&aaReal, fifoBuffer);
  mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &quat);
  memset(fifoBuffer, 0, packetSize);// 清空fifoBuffer数组
  if (millis()-t>100){
    Serial.print(" q0: ");
    Serial.print(quat.w);
    Serial.print(" q1: ");
    Serial.print(quat.x);
    Serial.print(" q2: ");
    Serial.print(quat.y);
    Serial.print(" q3: ");
    Serial.print(quat.z);
    Serial.print(" yaw: ");
    Serial.print(ypr[0]*180/M_PI);
    Serial.print(" pitch: ");
    Serial.print(ypr[1]*180/M_PI);
    Serial.print(" roll: ");
    Serial.print(ypr[2]*180/M_PI);
    Serial.print(" gx: ");
    Serial.print((float)gravity.x * 9.8);
    Serial.print(" gy: ");
    Serial.print((float)gravity.y * 9.8);
    Serial.print(" gz: ");
    Serial.print((float)gravity.z * 9.8);
    Serial.print(" arx: ");
    Serial.print(aaReal.x);
    Serial.print(" ary: ");
    Serial.print(aaReal.y);
    Serial.print(" arz: ");
    Serial.print(aaReal.z);
    Serial.print(" awx: ");
    Serial.print(aaWorld.x);
    Serial.print(" awy: ");
    Serial.print(aaWorld.y);
    Serial.print(" awz: ");
    Serial.println(aaWorld.z);    
    t = millis();
  }
}

这个代码的校准值都是chatgpt给的,肯定不对,但是先不管它。这个代码有以下几点问题。有时候,他会输出一段时间正常的,然后突然输出错误的数据,然后错误一段时间后输出正确的数据,然后几分钟后就停止输出了,有时候他会输出正常,但是几分钟后就停止输出了。输出正确的时候数据是没问题的,我手算了下四元数符合,就是校准问题数据在飘而已,还可以接受。这个问题我完全找不到原因。还有,他输出的加速度数据换算来只有0.5g往上

微信截图_20230607121345.png

接下来是第二个代码。我改了量程,并且重新校准,毕竟用在火箭上嘛。这个校准是没问题的,单独输出三轴加速度和角速度的时候非常完美。以下是代码

#include <Wire.h>
#include "MPU6050.h"
#include "I2Cdev.h"
#include "helper_3dmath.h"

MPU6050 mpu;

Quaternion quat;
VectorFloat gravity;
VectorInt16 aaReal;
VectorInt16 aaWorld;
float ypr[3];

uint8_t mpuIntStatus;
bool dmpReady;
uint16_t packetSize;
uint16_t fifoCount;
uint8_t fifoBuffer[128];
unsigned long t;

void setup() {
  Wire.begin();
  Serial.begin(115200);
  mpu.initialize();
  mpu.dmpInitialize();
  //设置量程
  mpu.setFullScaleAccelRange(MPU6050_ACCEL_FS_16);
  mpu.setFullScaleGyroRange(MPU6050_GYRO_FS_1000);
  //校准,不同传感器在这里改
  mpu.setXGyroOffset(36);
  mpu.setYGyroOffset(-2);
  mpu.setZGyroOffset(-3);
  mpu.setXAccelOffset(-3252);
  mpu.setYAccelOffset(1713);
  mpu.setZAccelOffset(1507);

  if (!mpu.testConnection()) {
    Serial.println("MPU6050 connection failed");
    while (1) {}
  }
  mpu.setDMPEnabled(true);
  mpuIntStatus = mpu.getIntStatus();
  dmpReady = true;
 
  packetSize = mpu.dmpGetFIFOPacketSize();
}

void loop() {
  if (!dmpReady) return;
 
  while (fifoCount < packetSize) {
    fifoCount = mpu.getFIFOCount();
  }
 
  mpu.getFIFOBytes(fifoBuffer, packetSize);
 
  fifoCount -= packetSize;

  mpu.dmpGetQuaternion(&quat, fifoBuffer);
  mpu.dmpGetEuler(ypr, &quat);
  mpu.dmpGetGravity(&gravity, &quat);
  mpu.dmpGetAccel(&aaReal, fifoBuffer);
  mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &quat);
  memset(fifoBuffer, 0, packetSize);// 清空fifoBuffer数组
  if (millis()-t>100){
    Serial.print(" q0: ");
    Serial.print(quat.w);
    Serial.print(" q1: ");
    Serial.print(quat.x);
    Serial.print(" q2: ");
    Serial.print(quat.y);
    Serial.print(" q3: ");
    Serial.print(quat.z);
    Serial.print(" yaw: ");
    Serial.print(ypr[0]*180/M_PI);
    Serial.print(" pitch: ");
    Serial.print(ypr[1]*180/M_PI);
    Serial.print(" roll: ");
    Serial.print(ypr[2]*180/M_PI);
    Serial.print(" gx: ");
    Serial.print((float)gravity.x * 9.8);
    Serial.print(" gy: ");
    Serial.print((float)gravity.y * 9.8);
    Serial.print(" gz: ");
    Serial.print((float)gravity.z * 9.8);
    Serial.print(" arx: ");
    Serial.print(aaReal.x);
    Serial.print(" ary: ");
    Serial.print(aaReal.y);
    Serial.print(" arz: ");
    Serial.print(aaReal.z);
    Serial.print(" awx: ");
    Serial.print(aaWorld.x);
    Serial.print(" awy: ");
    Serial.print(aaWorld.y);
    Serial.print(" awz: ");
    Serial.println(aaWorld.z);    
    t = millis();
  }
}

但是这一次输出仍有代码一的问题,而且更要命的是,正常输出时数据也是错的,比如转90°它会显示到100多,而且四元数都是有问题的。

然后我又改了一遍

#include <Wire.h>
#include "MPU6050.h"
#include "I2Cdev.h"
#include "helper_3dmath.h"

MPU6050 mpu;

Quaternion quat;
VectorFloat gravity;
VectorInt16 aaReal;
VectorInt16 aaWorld;
float ypr[3];

uint8_t mpuIntStatus;
bool dmpReady;
uint16_t packetSize;
uint16_t fifoCount;
uint8_t fifoBuffer[128];
unsigned long t;

void setup() {
  Wire.begin();
  Serial.begin(115200);
  mpu.initialize();
  mpu.dmpInitialize();
  //设置量程
  mpu.setFullScaleAccelRange(MPU6050_ACCEL_FS_16);
  //校准,不同传感器在这里改
  mpu.setXAccelOffset(-3252);
  mpu.setYAccelOffset(1713);
  mpu.setZAccelOffset(1507);
  /*mpu.setXGyroOffset(220);
  mpu.setYGyroOffset(76);
  mpu.setZGyroOffset(-85);
  mpu.setZAccelOffset(1788);*/

  if (!mpu.testConnection()) {
    Serial.println("MPU6050 connection failed");
    while (1) {}
  }
  mpu.setDMPEnabled(true);
  mpuIntStatus = mpu.getIntStatus();
  dmpReady = true;
 
  packetSize = mpu.dmpGetFIFOPacketSize();
}

void loop() {
  if (!dmpReady) return;
 
  while (fifoCount < packetSize) {
    fifoCount = mpu.getFIFOCount();
  }
 
  mpu.getFIFOBytes(fifoBuffer, packetSize);
 
  fifoCount -= packetSize;

  mpu.dmpGetQuaternion(&quat, fifoBuffer);
  mpu.dmpGetEuler(ypr, &quat);
  mpu.dmpGetGravity(&gravity, &quat);
  mpu.dmpGetAccel(&aaReal, fifoBuffer);
  mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &quat);
  memset(fifoBuffer, 0, packetSize);// 清空fifoBuffer数组
  if (millis()-t>100){
    Serial.print(" q0: ");
    Serial.print(quat.w);
    Serial.print(" q1: ");
    Serial.print(quat.x);
    Serial.print(" q2: ");
    Serial.print(quat.y);
    Serial.print(" q3: ");
    Serial.print(quat.z);
    Serial.print(" yaw: ");
    Serial.print(ypr[0]*180/M_PI);
    Serial.print(" pitch: ");
    Serial.print(ypr[1]*180/M_PI);
    Serial.print(" roll: ");
    Serial.print(ypr[2]*180/M_PI);
    Serial.print(" gx: ");
    Serial.print((float)gravity.x * 9.8);
    Serial.print(" gy: ");
    Serial.print((float)gravity.y * 9.8);
    Serial.print(" gz: ");
    Serial.print((float)gravity.z * 9.8);
    Serial.print(" arx: ");
    Serial.print(aaReal.x);
    Serial.print(" ary: ");
    Serial.print(aaReal.y);
    Serial.print(" arz: ");
    Serial.print(aaReal.z);
    Serial.print(" awx: ");
    Serial.print(aaWorld.x);
    Serial.print(" awy: ");
    Serial.print(aaWorld.y);
    Serial.print(" awz: ");
    Serial.println(aaWorld.z);    
    t = millis();
  }
}

也就是说,只把加速度量程调到16g,角速度还是250°/s。现在就只有代码一的问题了。看样子导致像代码二那样完全没法输出正确的原因还是和角速度有关。

但是,我无法继续排查问题,我真的找不到问题出在哪里。我感觉问题很底层,甚至在硬件上,但这只是感觉。我没有学过单片机,我打算学stm32,但是最近真的很忙没时间,9月才能开始学。希望大家能给点帮助,帮忙看看问题出在哪里。谢谢各位了。

引用
评论
3年5个月前IP:广东
921523
8楼

max21100是一个高性能imu,只有存货可以买,数量还比较多。

可以不用dmp,用梯度下降法求解姿态。

陀螺仪校准方法是静止得到零位。加速度计校准方法是在所有方向缓慢转动,然后用椭球去拟合(加速度的矢量和应该是不变的)。

引用
评论
3年5个月前修改于 3年5个月前IP:广东
921527
9楼

现阶段ChatGPT 适合在你懂原理的情况下加速开发用。IMU 的校准值并非常量,应定期校准,速率陀螺的零偏可以每次上电时消去。你买个开源飞控玩玩就明白了。

引用
评论
tmn_A作者
3年5个月前IP:广东
921562
10楼
引用warmonkey发表于8楼的内容
max21100是一个高性能imu,只有存货可以买,数量还比较多。可以不用dmp,用梯度下降法求解姿态。陀螺仪校准方法是静止得到零位。加速度计校准方法是在所有方向缓慢转动,然后用椭球去拟合(加速度的矢...

谢谢,我再chatgpt的帮助下解决了大部分问题,但是剩下的那个就是改变量程后的校准,请问校准加速度的具体操作是什么啊,我不认为我的手可以做到缓慢且平稳,我也没有云台之类的。另外我想问一下时怎么用椭球拟合呢?是把输出的三轴加速度值带进解析式里去算出abc吗?然后又怎么做呢?

引用
评论
tmn_A作者
3年5个月前IP:广东
921563
11楼
引用信仰は儚き人間の為に发表于9楼的内容
现阶段ChatGPT 适合在你懂原理的情况下加速开发用。IMU 的校准值并非常量,应定期校准,速率陀螺的零偏可以每次上电时消去。你买个开源飞控玩玩就明白了。

谢谢,已经再chatgpt的帮助下解决了大部分问题,但实在没钱去买飞控了 sticker

引用
评论
tmn_A作者
3年5个月前IP:广东
921564
12楼
引用warmonkey发表于8楼的内容
max21100是一个高性能imu,只有存货可以买,数量还比较多。可以不用dmp,用梯度下降法求解姿态。陀螺仪校准方法是静止得到零位。加速度计校准方法是在所有方向缓慢转动,然后用椭球去拟合(加速度的矢...

对了还想请教一下mpu6050(还在用这个,毕竟便宜资源多)如果超过量程dmp会输出什么呢?单独输出三轴加速度和角速度的话,超过量程就会显示最大示数。但是在实验中发现,如果经历较大加速度,角速度一定时间的话,再次静止有可能输出错误,甚至停止输出。虽然最终运用时大概率不会出现这种情况,毕竟到时候用+-16g,+-1000°/s,但经历了的话就感觉有点难处理了。如果有偏差没关系,毕竟超过量程时间肯定不长,不会有太大影响,但是如果出现在空中运行时输出混乱数据或者停机那麻烦就大了。

引用
评论(2)
3年5个月前修改于 3年5个月前IP:广东
921580
13楼
引用tmn_A发表于10楼的内容
谢谢,我再chatgpt的帮助下解决了大部分问题,但是剩下的那个就是改变量程后的校准,请问校准加速度的具体操作是什么啊,我不认为我的手可以做到缓慢且平稳,我也没有云台之类的。另外我想问一下时怎么用椭球...

简易校准可以用六个位置的方法,3d打印一个测试工装,6个面各朝下放置一次。

高级校准可以把模块塞进一个球,并且加配重增加质量,以减小振动加速度。缓慢滚动这个球,加速度矢量和应该不变。


ChatGPT提问:

a=vector(3), scale=vector(3), offset=vector(3)
b=(a-offset).*scale, norm(a)=1, solve offset and scale from b
b is in a csv file, use python curve_fit to find out b

回答:

To use Python's `curve_fit` function to solve for `offset` and `scale` from `b`, 
we first need to define a function that takes `a`, `offset`, and `scale` as inputs 
and returns the value of `b`. We can do this using the NumPy library, which provides 
efficient array operations. Here's an example implementation:

```python
import numpy as np
from scipy.optimize import curve_fit

def func(a, offset, scale):
    return (a - offset) * scale

# Load b from a CSV file
b = np.loadtxt('b.csv', delimiter=',')

# Define initial guesses for offset and scale
offset_guess = np.zeros(3)
scale_guess = np.ones(3)

# Define bounds for offset and scale (optional)
offset_bounds = (-np.inf, np.inf)
scale_bounds = (0, np.inf)
bounds = (offset_bounds, scale_bounds)

# Use curve_fit to solve for offset and scale
popt, pcov = curve_fit(func, a, b, p0=(offset_guess, scale_guess), bounds=bounds)

# Extract the optimized values of offset and scale
offset_opt = popt[0]
scale_opt = popt[1]

# Print the optimized values
print('Optimized offset:', offset_opt)
print('Optimized scale:', scale_opt)
```

In this example, we first load `b` from a CSV file using NumPy's `loadtxt` function. 
We then define a function `func` that takes `a`, `offset`, and `scale` as inputs and 
returns the value of `b`. We use `curve_fit` to optimize the values of `offset` and 
`scale` that minimize the difference between the actual values of `b` and the values of 
`b` predicted by `func`. We provide initial guesses for `offset` and `scale` using 
the `p0` argument, and we set bounds on `offset` and `scale` using the `bounds` argument. 
Finally, we extract the optimized values of `offset` and `scale` from the `popt` array.


引用
评论
tmn_A作者
3年4个月前IP:广东
922182
14楼
引用warmonkey发表于13楼的内容
简易校准可以用六个位置的方法,3d打印一个测试工装,6个面各朝下放置一次。高级校准可以把模块塞进一个球,并且加配重增加质量,以减小振动加速度。缓慢滚动这个球,加速度矢量和应该不变。ChatGPT提问:...

十分感谢,因为之前有点忙,没看kc,最近使用这个稍微改了一下的程序大概完成了校准,代码最困难的地方也接近尾声了。我做这个东西的直接目的是为发射准备,在最高点开伞,(我不太相信气压计的数据,伯努利效应难搞),而最根本的目的是想积累技术,造可用于以后更好的火箭的惯导。经过实验,发现确实可以较精确的在最高点开伞,这次发射是够用了。但只能实现一次,因为误差积累的较大(没有,可能是程序还有一点漏洞,我明天会继续改正,再次校准),我想问一下,mpu6050得到的位移数据大概误差有多大呢?是否需要卡尔曼滤波?

引用
评论
3年1个月前IP:天津
925897
15楼
引用tmn_A发表于14楼的内容
十分感谢,因为之前有点忙,没看kc,最近使用这个稍微改了一下的程序大概完成了校准,代码最困难的地方也接近尾声了。我做这个东西的直接目的是为发射准备,在最高点开伞,(我不太相信气压计的数据,伯努利效应难...

位移误差我没测算过,加上卡尔曼滤波以后只能说差强人意,建议换好一点的模块

引用
评论
笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的