【CurieNano】对于此板指南针的个人研究

2017-06-101.7万
精华项目
AI 快速预览详细 收起
本文详细介绍了CurieNano V2.0板上指南针的校准方法。首先,识别指南针芯片型号(M883或L883),然后调整Configuration Register A和B的设置以解决HMC5883与HMC5883L之间的兼容性问题。接着,通过获取最大最小值并进行归一化处理,校准指南针数据。最后,提供了9轴数据融合的建议和注意事项。
我一直都想把curie的6-axis跟 板上的compass融合一下,问题是这个板上的compass太多bug了。所以我把我对此板compass的研究说一下,希望一些想9轴融合的老哥指点指点。
DFRobot的官方放出来的library是给HMC5883L用的,大家用的时候,出来的数字一定基本上是怪怪的,如果我没猜错的话(至少我是这样)。

经过多番研究,我终于发现了一个东西,我的板上的compass有几个小字:M883。然后经过我多番research,在某个网站上看到的是,如果compass上面是M883的是HMC5883(注意没L), 如果L883的才是HMC5883L (我暂时没法证明这个说法是对还是错,不过我M883的跟着没L的说明书来弄能运行)。

我的板是CurieNano V2.0, 所以有这批的老哥想用compass的话请注意你的compass是写着M883还是L883。你们可以下载看看datasheets:

【CurieNano】对于此板指南针的个人研究图2

【CurieNano】对于此板指南针的个人研究图2


(1) HMC5883跟HMC5883L的功能差不多,I2C的地址也一样,但是在Configuration Register A(datasheets的p12)的时候有些不一样。话不多说,放图:
【CurieNano】对于此板指南针的个人研究图1 【CurieNano】对于此板指南针的个人研究图2

对于HMC5883来说, CRA6跟CRA5需要是0才能正常工作。对于HMC5883L的话,CRA6跟CRA5可以是00,01,10,11 。所以一旦老哥们用的library是setSamples这个东西的,可能会导致HMC5883没L不工作。


(2) 对于Configuration Register B的时候(datasheets的p13),有L跟没L的CRB7 to CRB0位置都一样,但是他们的range跟gain不一样。
但是这个Gain是给人看的,后面校准的时候我会解释为什么这个gain没卵用,我们自己测gain。

(3)对于Mode Register的话(datasheets的p14), 两个差不多,一般都是写入0x00(Continuous-Measurement Mode)。HMC5883L的MR7是给High Speed的I2C用的,所以一般都是关掉的,所以一般有L没L都是写入0x00 。


问题分析完了,那么我就开始使用HMC5883没L了。对于这个CRB这个+/-1.9 Gauss这个range,因为我试了0.9跟1.2这两个range,我的板会返回-4096这些奇怪的数字,一看就知道这个不可以用。从1.9以后的range都没有这样的问题。对于这个问题,我暂时无法解释,datasheet 15页说overflow就是 -4096了。作为老哥,我们需要稳住。


我的setup是这样搞:

  1. #include<Wire.h>
  2. const uint8_t HMC5883 = 0x1E;               //有L没L都是这个地址了
  3. uint8_t outputData[6];           //这个一定要是 unsigned的,有LSB会装在里面,这是一个buffer
  4. void setup() {
  5.   Serial.begin(9600);
  6.   Wire.begin();
  7.   Wire.beginTransmission(HMC5883);
  8.   Wire.write(0x00);       //Configuration Resgister A
  9.   Wire.write(0b00010100); //30HZ
  10.   Wire.endTransmission();
  11.   Wire.beginTransmission(HMC5883);
  12.   Wire.write(0x01);     //Configuration Resgister B
  13.   Wire.write(0b01000000);       // +/- 1.9Ga Range
  14.   Wire.endTransmission();
  15.   Wire.beginTransmission(HMC5883);
  16.   Wire.write(0x02);     //Mode Register
  17.   Wire.write(0x00);     // Continuous-Measurement Mode
  18.   Wire.endTransmission();   
  19. }
  20. void loop() {
  21.   int16_t x,y,z;           //这里不能直接int。一般int是16位的但是intel这个int好像是32位的,所以用int会把MSB的第一个正负号bit变成数字,这里只能short或int16_t 。
  22.   Wire.beginTransmission(HMC5883);
  23.   Wire.write(0x03);         // 从0x03开始读,参考datasheet p11
  24.   Wire.endTransmission();   
  25.   delay(30);
  26.   Wire.requestFrom(HMC5883, 6);
  27.   if(6 <= Wire.available()) // If the number of bytes available for reading be <=6.
  28.     {
  29.         for(int i=0;i<6;i++)
  30.         {
  31.             outputData=Wire.read();  //Store the data in outputData buffer
  32.         }
  33.     }
  34.     Wire.endTransmission();
  35.     //下面这里看p11啦
  36.     x=outputData[0] << 8 | outputData[1]; //Combine MSB and LSB of X Data output register
  37.     z=outputData[2] << 8 | outputData[3]; //Combine MSB and LSB of Z Data output register
  38.     y=outputData[4] << 8 | outputData[5]; //Combine MSB and LSB of Y Data output register
  39.     Serial.print(x);
  40.     Serial.print(" ");
  41.     Serial.print(y);
  42.     Serial.print(" ");
  43.     Serial.println(z);
  44. }
复制代码



上面这个program,我们需要各个方位乱动去尽量把x,y,z的raw data的最大最小拿到(xyz跟你地方的地磁线重叠就可以比较容易拿到,后面会说到不同地方的地磁线)。但是拿到raw data我们怎样用呢。话不多说,先上图:
【CurieNano】对于此板指南针的个人研究图5


这个是compass 的x跟y的plot,如果我plot了z,那么这个就是个球体/椭圆。我们可以看到我这个HMC5883的offset还是比较大的。
那么我们需要做的就是把这个球的圆心放到中间,所以 x_offset = (最大的x+最小的x)/2,y_offset = (最大的y+最小的y)/2,z_offset = (最大的z+最小的z)/2
然后我们把得到的raw data依次减去这些offset就好。


然后我们还需要把这些applied offset的raw data normalized了,因为减去offset以后,这个球/椭圆的圆心在origin了。正常情况下,我xyz轴跟地球磁线正向或反向重叠,我最大x需要跟最大y和最大z是一样得到的数才能说的过去,最小的同理。这些就是我最近一次得到的最大最小xyz (没有减去offset):
  1. #define MAG0MAX 575
  2. #define MAG0MIN -265
  3. #define MAG1MAX 586
  4. #define MAG1MIN -265
  5. #define MAG2MAX 378
  6. #define MAG2MIN -393
复制代码



可见z的peak-to-peak是771(约等于5883没L的datasheet p13给出的Gain:768),但x跟y的peak-to-peak是800多,所以说datasheet给出的gain没卵用,实际上gain就是xyz三轴的peak-to-peak。所以我要做这样的计算把它们normalize了:

  1. float magOffset[3] = { (MAG0MAX + MAG0MIN) / 2, (MAG1MAX + MAG1MIN) / 2, (MAG2MAX + MAG2MIN) / 2 };
  2. mx = (x-magOffset[0])/(MAG0MAX - MAG0MIN);
  3. my = (y-magOffset[1])/(MAG1MAX - MAG1MIN);
  4. mz = (z-magOffset[2])/(MAG2MAX - MAG2MIN);
复制代码

mx,my,mz就是normalize之后的以gauss为单位的值(这些值不一定是准确的值,但是他们三个是normalize之后的值,就是它们相除的时候会得到准确的比例,yaw就是跟x和y的比例依赖性很高)。

我们得到mx,my,mz,基本上就大功告成啦。

这里要说一下地球的地磁线,因为这东西对compass的影响很大:
【CurieNano】对于此板指南针的个人研究图6


地磁线就是上图那个蓝色的线,Declination只是一个真北跟磁北的区别,我们用compass的原因是我们希望得到一个yaw(heading)的Reference,所以无论是真北或者磁北对heading的References影响不大,因为如果想得到真北,只需要减去declination就好。

但是Inclination对于compass的影响,我可以说是比较大的。 因为当compass的Z轴跟这个field正反向重叠的时候,z的值会去到最大或者最小。这个时候x跟y的值会变的很小很小,然后会在0上下浮动。如果x是正,y是负,那么heading就在270-360度之间;如果x是负,y是正,那么heading就是90-180度之间。这就说明指南针失灵了。这个情况就像你在地磁南极或者北极,inclination是+/- 90度,你拿着个指南针平衡于地面,指南针就找不到北了。

Inclination是跟纬度的关系比较大,在赤道,大概是0度,然后广州大概是向下34度。越高纬度越向下。
https://www.ngdc.noaa.gov/geomag-web/#igrfgrid
这个网站能根据你当地的经纬给出declination跟inclination,在magnetic components那里选,然后最后calculate他会给你一个xml文件,里面有D跟I。

在calibration的时候,应该避免compass靠近高电流的电线,因为那个磁场会影响compass的reading。

Last: 对于老哥想用9轴的数据去得到一个很好的AHRS,如果用Madgwick的filter的话,记得把指南针的的坐标跟curie的坐标对齐(mx变成my,my变成-mx),然后在把他们放进 function。
这个是我对curie的Reference Axes的猜测(望老哥指正,curie里面的是BMI160),HMC5883的Reference Axes是对的(根据datasheet):
【CurieNano】对于此板指南针的个人研究图7


9轴的Madgwick或者是Mahony filter的code我就不放上来了,用update(9个东西) 取代 updateIMU(6个东西)。

其实Arduino里面的库可以搜索AHRS,它会显示三个result。三个src我都看过,adafruit那个比较完善,里面有Madgwick跟Mahony,然后这个Mahony还可以设置sample的frequency (在库里直接搜索ahrs得到的mahony不能设置frequency,直接默认512hz)。你们还可以试试两个filter有什么不同。


这个放个code给老哥们得到最大最小的xyz,不要靠近高电流的地方,这个程序会不断记录你知道现在为止最大或最小的xyz:

  1. #include<Wire.h>
  2. const uint8_t HMC5883 = 0x1E;
  3. uint8_t outputData[6];
  4. int16_t maxmagx = 0;
  5. int16_t minmagx = 0;
  6. int16_t maxmagy = 0;
  7. int16_t minmagy = 0;
  8. int16_t maxmagz = 0;
  9. int16_t minmagz = 0;
  10. void setup() {
  11.   Serial.begin(9600);
  12.   Wire.begin();
  13.   Wire.beginTransmission(HMC5883);
  14.   Wire.write(0x00);       //Configuration Resgister A
  15.   Wire.write(0b00010100); //30HZ
  16.   Wire.endTransmission();
  17.   Wire.beginTransmission(HMC5883);
  18.   Wire.write(0x01);
  19.   Wire.write(0b01000000);
  20.   Wire.endTransmission();
  21.   Wire.beginTransmission(HMC5883);
  22.   Wire.write(0x02);
  23.   Wire.write(0x00);
  24.   Wire.endTransmission();   
  25. }
  26. void loop() {
  27.   int16_t x,y,z;
  28.   Wire.beginTransmission(HMC5883);
  29.   Wire.write(0x03);
  30.   Wire.endTransmission();   
  31.   delay(30);
  32.   Wire.requestFrom(HMC5883, 6);
  33.   if(6 <= Wire.available()) // If the number of bytes available for reading be <=6.
  34.     {
  35.         for(int i=0;i<6;i++)
  36.         {
  37. <i style="background-color: initial;">            outputData=Wire.read();  //Store the data in outputData buffer
  38.         }
  39.     }
  40.     Wire.endTransmission();
  41.     x=outputData[0] << 8 | outputData[1]; //Combine MSB and LSB of X Data output register
  42.     z=outputData[2] << 8 | outputData[3]; //Combine MSB and LSB of Z Data output register
  43.     y=outputData[4] << 8 | outputData[5]; //Combine MSB and LSB of Y Data output register
  44.     if (x > maxmagx) maxmagx = x;
  45.     else if (x < minmagx) minmagx = x;
  46.     if (y > maxmagy) maxmagy = y;
  47.     else if (y < minmagy) minmagy = y;
  48.     if (z > maxmagz) maxmagz = z;
  49.     else if (z < minmagz) minmagz = z;
  50.     Serial.print("Orientation: ");
  51.     Serial.print(maxmagx);
  52.     Serial.print(" ");
  53.     Serial.print(minmagx);
  54.     Serial.print(" ");
  55.     Serial.print(maxmagy);
  56.     Serial.print(" ");
  57.     Serial.print(minmagy);
  58.     Serial.print(" ");
  59.     Serial.print(maxmagz);
  60.     Serial.print(" ");
  61.     Serial.println(minmagz);
  62. }</i>
复制代码

我看论坛有个老哥搞蓝牙跟6轴的,然后想搞9轴,我希望我这个文章可以帮到你。对于您抖动的情况, 那个setgyrorate跟setAccelxxxxxRate可以把它弄到50,filter.begin(50) 然后下面microsPerReading = 1000000 / 50。 如果你想快速roll跟pitch,可以把那个setxxxxrange调高, 下图。不过在convertRaw的方程里面你要把相应的数字放进去。
【CurieNano】对于此板指南针的个人研究图8【CurieNano】对于此板指南针的个人研究图9

创作许可协议

本项目采用 None(不开放任何权利,保留所有权利) 进行许可。

评论(18)
三春牛-创客
三春牛-创客
赞赞赞!
三春牛-创客
三春牛-创客
厉害厉害
花生编程
花生编程
不错不错
花生编程
花生编程
厉害厉害
派大星ym
派大星ym
。。。。
派大星ym
派大星ym
牛牛牛牛牛
gray6666
gray6666
厉害。。。。。。。。。。。
xhuaihe
xhuaihe
厉害了
hnyzcj
hnyzcj
厉害了
Lay
Lay
好厉害
nicho
nicho
稳! 赞一个!!
Angleo
Angleo
厉害, 挖得有点深!
422234
422234
确实是883

422234
422234回复Lingo
好的,非常感谢!!!回头我试试
Lingo
Lingo
作者
回复422234
我的是M883,按道理是HMC5883
如果是L883, 应该才是HMC5883L
(我看到外arduino官方的论坛有个老哥是这样说的,根据我的经历,好像也是这样)
老哥你可以试试我贴上的第一堆code, 那个是直接输出 xyz三轴的raw data。
如果数字一直有变的话就是用我的code能动,因为http://www.eeboard.com/bbs/thread-95424-1-1.html这个老哥用了DFRobot在github上找的hmc5883L的code(DFRobot给的library里的示例)出现这样的问题。
我也有这样的问题,所以我才做了一系列的研究才写自己的code来运行。
pATAq
pATAq
感谢分享
Grey
Grey
老哥,这波稳,服!
O_oYYQ
O_oYYQ
好帖 顶一个
422234
422234
非常棒的文章啊!!!!赞
R2D2
R2D2
专业贴,必须赞一个。
- 没有更多了 -