极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 109083|回复: 27

我的自平衡小车D2——加速度计与陀螺仪获取姿态参数的差异

  [复制链接]
发表于 2012-3-23 11:00:50 | 显示全部楼层 |阅读模式
滤波的算法还没有研究明白,先放上一段从传感器直接采样的结果。
  1. #include <Wire.h>
  2. #define Acc 0x1D
  3. #define Gyr 0x69
  4. #define Mag 0x1E
  5. #define Gry_offset -13    // 陀螺仪偏移量
  6. #define Gyr_Gain 0.07     // 满量程2000dps时灵敏度(dps/digital)
  7. #define pi 3.14159

  8. float angleG;
  9. unsigned long timer = 0;  // 采样时间
  10. void setup() {
  11.     sensor_init();        // 配置传感器
  12.     Serial.begin(19200);  // 开启串口以便监视数据
  13.     delay(1000);
  14.   }

  15. void loop() {
  16.     long o_timer = timer;                   // 上一次采样时间(ms)
  17.     float Y_Accelerometer = gDat(Acc, 1);   // 获取向前的加速度
  18.     float Z_Accelerometer = gDat(Acc, 2);   // 获取向下的加速度
  19.     float angleA = atan(Y_Accelerometer / Z_Accelerometer) * 180 / pi;
  20.                                             // 根据加速度分量得到的角度(degree)
  21.     timer = millis();                       // 当前时间(ms)
  22.     int dt = timer - o_timer;               // 微分时间
  23.     angleG = angleG + Gyr_Gain * (gDat(Gyr, 0) +  Gry_offset) * dt / 1000;
  24.                                             // 对角速度积分得到的角度(degree)
  25.     Serial.print(timer);
  26.     Serial.print(",");
  27.     Serial.print(angleA, 6);
  28.     Serial.print(",");
  29.     Serial.print(angleG, 6);
  30.     Serial.print(";");                      // 输出数据
  31.     delay(10);
  32.   }

  33. int gDat(int device, int axis) {

  34. // 读九轴姿态传感器寄存器函数
  35. // For Arduino, by 黑马
  36. // 调用参数表
  37. //   type    device      axis
  38. //                    0   1   2
  39. // ADXL345     Acc    x   y   z
  40. // L3G4200D    Gyr    x   y   z
  41. // HMC5883L    Mag    x   z   y
  42. // Example
  43. // 00 #include <Wire.h>
  44. // 01 #define Acc 0x1D;
  45. // 02 #define Gyr 0x69;
  46. // 03 #define Mag 0x1E;
  47. // 04
  48. // 05  void setup() {
  49. // 06    sensor_init();
  50. // 07    delay(1000);
  51. // 08  }
  52. // 09
  53. // 10  void loop() {
  54. // 11    int Z-Gyroscope;
  55. // 12    Z-Gyroscope = gDat(Gyr, 2);
  56. // 13    delay(50);
  57. // 14  }

  58.     int v;
  59.     byte vL, vH, address;               // 存放byte数值
  60.     if (device == Acc) address = 0x32;  // ADXL345的读数地址
  61.     if (device == Gyr) address = 0xA8;  // L3G4200D的读数地址
  62.     if (device == Mag) address = 0x03;  // HMC5883L的读数地址
  63.     address = address + axis * 2;       // 数据偏移-坐标轴
  64.     Wire.beginTransmission(device);     // 开始传输数据
  65.     Wire.send(address);                 // 发送指针
  66.     Wire.requestFrom(device, 2);        // 请求2 byte数据
  67.     while(Wire.available() < 2);        // 成功获取前等待
  68.     vL = Wire.receive();
  69.     vH = Wire.receive();                // 读取数据
  70.     Wire.endTransmission();             // 结束传输
  71.     if (device == Mag) v = (vL << 8) | vH;
  72.     else v = (vH << 8) | vL;            // 将byte数据合并为Int
  73.     return v;                           // 返回读书值
  74. }

  75. void sensor_init() {                         // 配置九轴姿态传感器
  76.     writeRegister(Acc, 0x2D, 0b00001000);    // 测量模式
  77.                             // 配置ADXL345
  78.     writeRegister(Gyr, 0x20, 0b00001111);    // 设置睡眠模式、x, y, z轴使能
  79.     writeRegister(Gyr, 0x21, 0b00000000);    // 选择高通滤波模式和高通截止频率
  80.     writeRegister(Gyr, 0x22, 0b00000000);    // 设置中断模式
  81.     writeRegister(Gyr, 0x23, 0b00110000);    // 设置量程(2000dps)、自检状态、SPI模式
  82.     writeRegister(Gyr, 0x24, 0b00000000);    // FIFO & 高通滤波
  83.                             // 配置L3G4200D(2000 deg/sec)
  84.     writeRegister(Mag, 0x02, 0x00);          // 连续测量
  85.                             // 配置HMC5883L
  86. }

  87. void writeRegister(int device, byte address, byte val) {    // 写寄存器
  88.     Wire.beginTransmission(device);
  89.     Wire.send(address);
  90.     Wire.send(val);
  91.     Wire.endTransmission();
  92. }
复制代码
通过串口上传3个数据:采样时间、通过加速度计算得到的角度AngleA、通过角速度积分得到的角度AngleG。

在pc端得到的数据曲线

红色曲线是AngleA,绿色是AngleG,范围±10°,整个测量时间为20s

可以看到最前面静止区域,两条曲线噪声都不算严重,至少可以说明系统噪声不会是太严重的问题。

A部分是敲击桌子产生的,由于加速度计是通过测量两个方向的加速度来计算偏转角的。理想情况我们想要得到的是这两个方向的重力加速度分量。但是任何机械系统不可避免存在的平动振动也会产生相当大的加速度,而加速度计完全无法区分这两种加速度,所以振动加速度在我们的测量中引入了强烈的噪声。

B部分是用手偏移一个角度的结果,而C部分是系统晃动时采集的数据,此时不可避免的引入振动,加速度计过分敏感的神经再次受到强烈的刺激{:soso_e113:} 。

而陀螺仪对振动不敏感,所以基本不会受到什么影响。我认为最前面加速度计测量得到的噪声也主要来源于桌面的震动,毕竟我桌子上的电脑风扇也是一个很大的噪声源。陀螺仪积分过程本身还有一个好处,时间积分为0的随机噪声也在此过程中平滑掉了。

短时间来看,陀螺仪已经可以非常好的对姿态进行判定,但是对于较长的时间,误差也随积分的过程不断累积,尽管有做过修正量Gry_offset来调校,随着时间累积,这个调校总归是不可靠的。

下面是100s的静态测试


如果系统是动态的,受到系统反应时间的影响,误差会更大,所以用加速度计进行修正是必要的。
卡尔曼滤波应该是比较"简单"而成熟的算法,但是要完全看懂对于我这样的初鸟来说也不太容易。D3应该就是关于卡尔曼滤波的一些心得了。其实这些对于论坛里各位DX来说可能是相当浅显的内容,不过毕竟也是我个人从0到1的过程,希望DX们不吝指教,见笑了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

发表于 2012-3-23 12:10:26 | 显示全部楼层
不错的文章,值得一看
回复 支持 反对

使用道具 举报

发表于 2012-3-23 12:13:41 | 显示全部楼层
滤波方法有很多额,光卡尔曼滤波就分了三种
卡尔曼用在平衡车上不一定是最合适的
楼主可以试试别的,比如互补,MK飞控的滤波方法
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-3-23 13:39:31 | 显示全部楼层
Malc 发表于 2012-3-23 12:13
滤波方法有很多额,光卡尔曼滤波就分了三种
卡尔曼用在平衡车上不一定是最合适的
楼主可以试试别的,比如 ...

感谢Malc的意见,搜了一下互补滤波,我的理解是这种滤波方式就是按一定的权重对两个值进行叠加,很直观的融合方式。

以下是代码:
  1. #include <Wire.h>
  2. #define Acc 0x1D;
  3. #define Gyr 0x69;
  4. #define Mag 0x1E;

  5. void setup() {
  6.     sensor_init();
  7.     Serial.begin(115200);
  8.     delay(1000);
  9.   }

  10.   void loop() {
  11.     Z-Gyroscope = gDat(Gyr, 2);
  12.     delay(50);
  13.   }

  14. #include <Wire.h>
  15. #define Acc 0x1D
  16. #define Gyr 0x69
  17. #define Mag 0x1E
  18. #define Gry_offset -13    // 陀螺仪偏移量
  19. #define Gyr_Gain 0.07     // 满量程2000dps时灵敏度(dps/digital)
  20. #define pi 3.14159

  21. float Com_angle;
  22. float y1, Com2_angle;
  23. float angleG;
  24. long timer = 0;  // 采样时间
  25. void setup() {
  26.     sensor_init();        // 配置传感器
  27.     Serial.begin(19200);  // 开启串口以便监视数据
  28.     delay(1000);
  29.   }

  30. void loop() {
  31.     long o_timer = timer;                   // 上一次采样时间(ms)
  32.     float Y_Accelerometer = gDat(Acc, 1);   // 获取向前的加速度
  33.     float Z_Accelerometer = gDat(Acc, 2);   // 获取向下的加速度
  34.     float angleA = atan(Y_Accelerometer / Z_Accelerometer) * 180 / pi;
  35.                                             // 根据加速度分量得到的角度(degree)
  36.     timer = millis();                       // 当前时间(ms)
  37.     float omega =  Gyr_Gain * (gDat(Gyr, 0) +  Gry_offset);
  38.     float dt = (timer - o_timer) / 1000.0;  // 微分时间(s)
  39.     angleG = angleG + omega * dt;           // 对角速度积分得到的角度(degree)
  40. // 一阶互补算法
  41.     float K;
  42.     K = 0.075;                              // 对加速度计取值的权重
  43.     float A = K / (K + dt);
  44.     Com_angle = A * (Com_angle + omega * dt) + (1-A) * angleA;
  45. // 二阶互补算法
  46.     K = 0.5;
  47.     float x1 = (angleA - Com2_angle) * K * K;
  48.     y1 = y1 + x1 * dt;
  49.     float x2 = y1 + 2 * K *(angleA - Com2_angle) + omega;
  50.     Com2_angle = Com2_angle + x2 * dt;
  51.    
  52.     Serial.print(timer);
  53.     Serial.print(",");
  54.     Serial.print(angleA, 6);
  55.     Serial.print(",");
  56.     Serial.print(angleG, 6);
  57.     Serial.print(",");
  58.     Serial.print(Com_angle, 6);
  59.     Serial.print(",");
  60.     Serial.print(Com2_angle, 6);
  61.     Serial.print(";");                      // 输出数据
  62.     delay(50);
  63.   }

  64. int gDat(int device, int axis) {

  65. // 读九轴姿态传感器寄存器函数
  66. // For Arduino, by 黑马
  67. // 调用参数表
  68. //   type    device      axis
  69. //                    0   1   2
  70. // ADXL345     Acc    x   y   z
  71. // L3G4200D    Gyr    x   y   z
  72. // HMC5883L    Mag    x   z   y
  73. // Example
  74. // 00 #include <Wire.h>
  75. // 01 #define Acc 0x1D;
  76. // 02 #define Gyr 0x69;
  77. // 03 #define Mag 0x1E;
  78. // 04
  79. // 05  void setup() {
  80. // 06    sensor_init();
  81. // 07    delay(1000);
  82. // 08  }
  83. // 09
  84. // 10  void loop() {
  85. // 11    int Z-Gyroscope;
  86. // 12    Z-Gyroscope = gDat(Gyr, 2);
  87. // 13    delay(50);
  88. // 14  }

  89.     int v;
  90.     byte vL, vH, address;               // 存放byte数值
  91.     if (device == Acc) address = 0x32;  // ADXL345的读数地址
  92.     if (device == Gyr) address = 0xA8;  // L3G4200D的读数地址
  93.     if (device == Mag) address = 0x03;  // HMC5883L的读数地址
  94.     address = address + axis * 2;       // 数据偏移-坐标轴
  95.     Wire.beginTransmission(device);     // 开始传输数据
  96.     Wire.send(address);                 // 发送指针
  97.     Wire.requestFrom(device, 2);        // 请求2 byte数据
  98.     while(Wire.available() < 2);        // 成功获取前等待
  99.     vL = Wire.receive();
  100.     vH = Wire.receive();                // 读取数据
  101.     Wire.endTransmission();             // 结束传输
  102.     if (device == Mag) v = (vL << 8) | vH;
  103.     else v = (vH << 8) | vL;            // 将byte数据合并为Int
  104.     return v;                           // 返回读书值
  105. }

  106. void sensor_init() {                         // 配置九轴姿态传感器
  107.     writeRegister(Acc, 0x2D, 0b00001000);    // 测量模式
  108.                             // 配置ADXL345
  109.     writeRegister(Gyr, 0x20, 0b00001111);    // 设置睡眠模式、x, y, z轴使能
  110.     writeRegister(Gyr, 0x21, 0b00000000);    // 选择高通滤波模式和高通截止频率
  111.     writeRegister(Gyr, 0x22, 0b00000000);    // 设置中断模式
  112.     writeRegister(Gyr, 0x23, 0b00110000);    // 设置量程(2000dps)、自检状态、SPI模式
  113.     writeRegister(Gyr, 0x24, 0b00000000);    // FIFO & 高通滤波
  114.                             // 配置L3G4200D(2000 deg/sec)
  115.     writeRegister(Mag, 0x02, 0x00);          // 连续测量
  116.                             // 配置HMC5883L
  117. }

  118. void writeRegister(int device, byte address, byte val) {    // 写寄存器
  119.     Wire.beginTransmission(device);
  120.     Wire.send(address);
  121.     Wire.send(val);
  122.     Wire.endTransmission();
  123. }
复制代码

红-加速度计
绿-陀螺仪
白-一阶互补滤波
黄-二阶互补滤波
从曲线上看互补滤波效果还是不错滴,二阶滤波K取0.5时曲线看起来已经很漂亮了,而且并没有明显的迟滞。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

发表于 2012-3-23 15:15:31 | 显示全部楼层
呵呵 等过段时间有money了我也要开始研究平衡车了~
回复 支持 反对

使用道具 举报

发表于 2012-5-25 12:23:56 | 显示全部楼层
感谢分享,PID算法一直都是我想碰不敢碰的东西。分析的很精辟,呵呵~~PFPF
回复 支持 反对

使用道具 举报

发表于 2012-5-30 11:41:33 | 显示全部楼层
求教。 您的代码已经拜读多遍。
求教一个代码以外的东东
就是你最后贴出的图,是用什么方式做出来的呢?
经过坛子里这么多朋友的讲解,数据发送到串口上已经没问题了,但是后续如何做出图呢??
我的本办法是导出到EXCEL,然后用图形工具,但是似乎您这里有更好的分析工具啊,呵呵!
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-5-30 13:55:20 | 显示全部楼层
darkorigin 发表于 2012-5-30 11:41
求教。 您的代码已经拜读多遍。
求教一个代码以外的东东
就是你最后贴出的图,是用什么方式做出来的呢?
...

呵呵我是在VB环境下读的,编译了一个小程序:

http://www.geek-workshop.com/forum.php?mod=viewthread&tid=708

比较简陋,不嫌弃的话试试合不合用吧
回复 支持 反对

使用道具 举报

发表于 2012-5-30 14:49:03 | 显示全部楼层
黑马 发表于 2012-5-30 13:55
呵呵我是在VB环境下读的,编译了一个小程序:

http://www.geek-workshop.com/forum.php?mod=viewthrea ...


郁闷,代码测试起来用你的监视器还是木有显示出来。。。
void setup()
{
    Serial.begin(19200);
}

void loop()
{
    Serial.print(analogRead(1));
    Serial.print(",");
    Serial.print(analogRead(2));   
    Serial.print(",");
    Serial.print(analogRead(3));
    Serial.print(",");
    delay(500);
}
出来的图就是空白的

COM口是COM3,设置是3,1024,3  
我是COM3口,数值理论最大1021,3组数据
不知哪里错鸟

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

发表于 2012-6-10 10:06:17 | 显示全部楼层
我用的楼主的代码~~测试的。也显示不出来,不知道什么原因
回复 支持 反对

使用道具 举报

发表于 2012-6-10 19:59:23 | 显示全部楼层
第一个加速度和陀螺仪比较这个程序,用串口看不到数据,不知道怎么回事?
回复 支持 反对

使用道具 举报

发表于 2012-7-26 16:01:24 | 显示全部楼层
请问,我只有一个UNO和一个三轴加速度传感器MMA7361,只用一个KP调节,根本站不起来,

是因为没有陀螺仪的问题么?

只用一个加速度传感器能否可以自平衡?谢!!!!
回复 支持 反对

使用道具 举报

发表于 2012-8-28 16:13:19 | 显示全部楼层
darkorigin 发表于 2012-5-30 14:49
郁闷,代码测试起来用你的监视器还是木有显示出来。。。
void setup()
{

第一个数据为时间轴,后面分别为n组数据用并","分隔。最后的应该用“;”分隔数据包。

三组数据时:
    Serial.print(time);
    Serial.print(",");
    Serial.print(analogRead(1));
    Serial.print(",");
    Serial.print(analogRead(2));   
    Serial.print(",");
    Serial.print(analogRead(3));
    Serial.print(";");
回复 支持 反对

使用道具 举报

发表于 2012-8-31 20:06:04 | 显示全部楼层
mark~~~学习
回复 支持 反对

使用道具 举报

发表于 2012-9-2 15:29:00 | 显示全部楼层
vellonj 发表于 2012-8-28 16:13
第一个数据为时间轴,后面分别为n组数据用并","分隔。最后的应该用“;”分隔数据包。

三组数据时:

谢谢~~~呵呵,我用了另外的一个开源的东西搞起来了,很强大。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 需要先绑定手机号

Archiver|联系我们|极客工坊

GMT+8, 2024-4-25 23:23 , Processed in 0.052098 second(s), 26 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表