NOBのArduino日記!

NOBのArduino日記!

趣味は車・バイク・自転車・ラジコン・電子工作です。

MARGセンサーから三次元姿勢推定!(Processingで3D表示編)

イメージ 3
マルチ機能(MARG)センサー(ATD-M4S,GY-80)

 前回の記事(2018/4/4)から大分時間が空いてしまいましたオオハマリからの放置…(;'∀')
 完ぺきではないのですが、それらしくマルチセンサー※1から得られたデータを、フィルター※2を通して姿勢を推定し、リアルタイムにPC画面上の3Dオブジェクト※3に反映できました

※1:マルチセンサーとは、MARGセンサーの事で、xyz角速度・xyz加速度・xyz地磁気の計9軸の物理量を測定可能
※2:フィルターとは、相補フィルターの一種Madgwickフィルターを使用
※3:3Dオブジェクトは、ArduinoからPCに受け取ったデータをProcessing 3で3Dオブジェクトとして表示

1. 実物と3Dモデルが連動!

 処理の流れは以下1.1~1.3の通りです。

1.1「MARGセンサー」が動き「加速度・角速度・地磁気」データを送信
 回路接続図をFritzingを使って図1の様に描いてみました。 
 また図1の通り実際に繋げた状態を図2に示します。
 このブレッドボードを動かす事で、MARGセンサーからArduinoUNOに動きのデータが送信されます。
イメージ 5
図1:ブレッドボード回路図

イメージ 6
図2:実際に作ったもの

1.2ArduinoUNO」で受信し「Madgwick」で推定し「3D姿勢データ」を送信
 ArduinoUNOには図3に示す「受信→推定→送信」プログラムを事前に書き込んでおきますこのプログラムで図4の通り数百時間ハマりました(;'∀')
/*MARGセンサーの使い方!(Arduino+GY-80で三次元姿勢推定編)20180729NOBのArduino日記! */
#include <GY80.h>
GY80 sensor = GY80(); //GY80インスタンスを作成する
void a_set_scale(uint8_t scale);

float invSqrt(float x) {
  float halfx = 0.5f * x;
  float y = x;
  long i = *(long*)&y;
  i = 0x5f3759df - (i>>1);
  y = *(float*)&i;
  y = y * (1.5f - (halfx * y * y));
  y = y * (1.5f - (halfx * y * y));
  return y;}

/*------------------------以下Madgwickフィルター--------------------------*/
//システム定数
float deltat = 0.001f;// 実測値、1秒間のサンプリング周期(秒単位)デフォルトは「0.001」
float lastt = 0;
float ct = 0;
float beta=2.5f;  //--------------------------初期値、デフォルトは「2.5」
//初期化終了後のbeta値
void Beta() {beta =0.5f;} //--------------------------デフォルト0.041f----------------------0.2でノイズ減を除去する!
//グローバルシステム変数
float q0 = 1.0f,q1 = 0.0f,q2 = 0.0f,q3 = 0.0f;// 初期条件付き推定方位四元数要素
void filterUpdate(float gg_x, float gg_y, float gg_z, float aa_x, float aa_y, float aa_z, float mm_x, float mm_y, float mm_z){ //w:角速度計、a:加速度計、m:地磁気
  float recipNorm;
  float s0, s1, s2, s3;
  float qDot1, qDot2, qDot3, qDot4;
  float hx, hy;
  float _2q0mx, _2q0my, _2q0mz, _2q1mx, _2bx, _2bz, _4bx, _4bz, _2q0, _2q1, _2q2, _2q3, _2q0q2, _2q2q3, q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3;
  // ジャイロスコープ度/秒をラジアン/秒に変換する
  gg_x *= 0.0174533f;
  gg_y *= 0.0174533f;
  gg_z *= 0.0174533f;
  // ジャイロスコープによる四元数の変化率
  qDot1 = 0.5f * (-q1 * gg_x - q2 * gg_y - q3 * gg_z);
  qDot2 = 0.5f * (q0 * gg_x + q2 * gg_z - q3 * gg_y);
  qDot3 = 0.5f * (q0 * gg_y - q1 * gg_z + q3 * gg_x);
  qDot4 = 0.5f * (q0 * gg_z + q1 * gg_y - q2 * gg_x);
  // 加速度計の測定が有効な場合にのみフィードバックを計算する(加速度計の正規化でNaNを回避する)
  if(!*1 > 3.0f || val.g_x > 20000 || val.g_x < -20000){;}else{gx = val.g_x;}
   if (abs*2 > 3.0f || val.g_y > 20000 || val.g_y < -20000){;}else{gy = val.g_y;}
   if (abs*3 > 3.0f || val.g_z > 20000 || val.g_z < -20000){;}else{gz = val.g_z;}
   if (val.a_x > 700 || val.a_x < -700){;}else{ax = val.a_x;}  //700
   if (val.a_y > 700 || val.a_y < -700){;}else{ay = val.a_y;} 
   if (val.a_z > 700 || val.a_z < -700){;}else{az = val.a_z;}
   if (val.m_x > 400 || val.m_x < -400){;}else{mx = val.m_x;}  //400
   if (val.m_y > 400 || val.m_y < -400){;}else{my = val.m_y;}
   if (val.m_z > 400 || val.m_z < -400){;}else{mz = val.m_z;}
 //ジャイロスコープ静止状態ゼロ点補正---------------------------------------------------------------------------------------------------------
   gxOld2=gxOld1,gyOld2=gyOld1,gzOld2=gzOld1,gxOld1=val.g_x,gyOld1=val.g_y,gzOld1=val.g_z;
  xG = convertRawGyro(gx - CoefficientGX); //静止状態のgx平均値でゼロ点補正
  yG = convertRawGyro(gy - CoefficientGY); //静止状態のgy平均値でゼロ点補正
  zG = convertRawGyro(gz - CoefficientGZ); //静止状態のgz平均値でゼロ点補正
 //Madgwickフィルタによる角速度・加速度・地磁気から四元数推定----------------------------------------------------------------------------------
   filterUpdate(xG,yG,zG,ax,ay,az,val.m_x,val.m_y,val.m_z);//g:角速度計、a:加速度計、m:地磁気
 //四元数から姿勢(ピッチ・ヨウ・ロール)計算----------------------------------------------------------------------------------------------------
    pitch = asinf(-2.0f * (q1*q3 - q0*q2))* 57.29578f;
    yaw = atan2f(q1*q2 + q0*q3, 0.5f - q2*q2 - q3*q3)* 57.29578f;
    roll = atan2f(q0*q1 + q2*q3, 0.5f - q1*q1 - q2*q2)* 57.29578f;
//姿勢推定結果の出力----------------------------------------------------------------------------------------------------------------------------
   Serial.print("Orientation: ");
   Serial.print(-yaw+ 180.0f);Serial.print(" ");  // def:X * -1 + 180.0f
   Serial.print(-pitch+ 180.0f);Serial.print(" ");// def:X * -1 + 180.0f
   Serial.println(-roll);
   }

//補正係数計算
void Coefficient() {
//ジャイロスコープゼロ点補正係数
  int jx=0,jy=0,jz=0;
  for(int i=0;i<100;i++){   //------------------------------50~100回平均をする
    GY80_raw val = sensor.read_raw();
    //外れ値は除去して平均値を計算する
    if (val.g_x > 50 || val.g_x < -50){;}else{CoefficientGX += val.g_x;jx+=1;}
    if (val.g_y > 50 || val.g_y < -50){;}else{CoefficientGY += val.g_y;jy+=1;}
    if (val.g_z > 50 || val.g_z < -50){;}else{CoefficientGZ += val.g_z;jz+=1;}}
  CoefficientGX = -CoefficientGX/jx ;//x軸角速度補正値計算
  CoefficientGY = -CoefficientGY/jy ;//y軸角速度補正値計算
  CoefficientGZ = -CoefficientGZ/jz ;//z軸角速度補正値計算
  }

void setup(){  
  Serial.begin(115200);  // 毎秒9600,115200ビットでシリアル通信を初期化する
  sensor.begin();//センサーを初期化する
  for(int i=0;i<100;i++){Measurement();}
  Coefficient();
  for(int i=0;i<100;i++){Measurement();}
  Coefficient();
  for(int i=0;i<300;i++){Measurement();}
  Coefficient();
  Beta();}

void loop(){Measurement();}
イメージ 1
図3プログラムの内容

イメージ 1
図4:日の目を見る事のなかったArduinoIDEプログラムの数々…合掌

1.3Processing」で3Dモデルの動きに反映!
 図3のプログラムで姿勢を推定したデータがPCに出力されてくるので、それを図5のProcessingプログラムで3Dオブジェクトに反映させます。
 実験した結果が動画1の通りですこのプログラムで図6の通り数十時間ハマりました(;'∀')
イメージ 4
   
import processing.serial.*;
Serial myPort;
float yaw = 0.0;
float pitch = 0.0;
float roll = 0.0;
 
void setup(){
  println(Serial.list());
  size(1200,1000, P3D);
  myPort = new Serial(this,Serial.list()[1], 115200); 
  textSize(16);// set text size
  textMode(SHAPE); // set textmode to shape
}
 
void draw(){
  serialEvent();  // read and parse incoming serial message
  background(255);// set background to white
  lights();
  translate(width/2, height/2,500);// set position to centre
  pushMatrix();// begin object
  float c1 = cos(radians(roll-0));//-10flont up
  float s1 = sin(radians(roll-0));
  float c2 = cos(radians(pitch+0));//+25Right up
  float s2 = sin(radians(pitch+0));
  float c3 = cos(radians(yaw+0));//+10cloc Rlight
  float s3 = sin(radians(yaw+0));
applyMatrix( c2*c3, s1*s3+c1*c3*s2, c3*s1*s2-c1*s3, 0,
               -s2, c1*c2, c2*s1, 0,
               c2*s3, c1*s2*s3-c3*s1, c1*c3+s1*s2*s3, 0,
               0, 0, 0, 1); 
  drawArduino();
  popMatrix();// end of object
  print(roll);
  print("\t");
  print(pitch);
  print("\t");
  print(yaw);
  println();
}
 
void serialEvent(){
  int newLine = 13; // new line character in ASCII
  String message;
  do{
    message = myPort.readStringUntil(newLine); // read from port until new line
    if (message != null) {
      String[] list = split(trim(message), "");
      if (list.length>= 4 && list[0].equals("Orientation:")) {
        yaw = float(list[1]); //convert to float yaw
        pitch = float(list[2]); //convert to float pitch
        roll = float(list[3]); //convert to float roll
      }
    }
  } while(message != null);
}
 
void drawArduino(){
  stroke(0,90, 90); // 輪郭の色を暗い色に設定する
  fill(0,130, 130); // 塗りつぶしの色をより明るい青色に設定する
  box(200,10, 300); // Arduinoボードの基本形状を描く
  stroke(0);// アウトラインカラーを黒に設定する
  fill(80);// 塗りつぶし色を濃いグレーに設定する
  translate(90,-10, -55); // Arduinoボックスの端から端まで設定する
  box(10,20, 170); // ピンヘッダーをボックスとして描く
  translate(-180,0, 15); // 位置をArduinoボックスの他の端に設定する
  box(10,20, 210); // 他のピンヘッダーをボックスとして描く
}
イメージ 3
図5:プログラムの内容

イメージ 2
図6:日の目を見る事のなかったProcessingプログラムの数々…合掌

動画1:ArduinoUNO実物と3Dオブジェクトが同期!

2. まとめ

 電子工作ブログを始めてから最大のハマりとなりましたが、何とかMARGセンサーを使えそうな感じになりましたこれで自作ドローンが作れるかも!

イメージ 1イメージ 3
励みになりますのでよければクリック下さい(^o^)/



この商品は、Amazon.co.jp が販売、発送します。 ギフトラッピングを利用できます。

*1:aa_x == 0.0f) && (aa_y == 0.0f) && (aa_z == 0.0f))) {

    // 加速度計の測定値を正規化する
    recipNorm = invSqrt(aa_x * aa_x + aa_y * aa_y + aa_z * aa_z);
    aa_x *= recipNorm;
    aa_y *= recipNorm;
    aa_z *= recipNorm;
    // 磁力計の測定を正規化する
    recipNorm = invSqrt(mm_x * mm_x + mm_y * mm_y + mm_z * mm_z);
    mm_x *= recipNorm;
    mm_y *= recipNorm;
    mm_z *= recipNorm;
    // 繰り返し演算を避けるための補助変数
    _2q0mx = 2.0f * q0 * mm_x;
    _2q0my = 2.0f * q0 * mm_y;
    _2q0mz = 2.0f * q0 * mm_z;
    _2q1mx = 2.0f * q1 * mm_x;
    _2q0 = 2.0f * q0;
    _2q1 = 2.0f * q1;
    _2q2 = 2.0f * q2;
    _2q3 = 2.0f * q3;
    _2q0q2 = 2.0f * q0 * q2;
    _2q2q3 = 2.0f * q2 * q3;
    q0q0 = q0 * q0;
    q0q1 = q0 * q1;
    q0q2 = q0 * q2;
    q0q3 = q0 * q3;
    q1q1 = q1 * q1;
    q1q2 = q1 * q2;
    q1q3 = q1 * q3;
    q2q2 = q2 * q2;
    q2q3 = q2 * q3;
    q3q3 = q3 * q3;
    // 地球の磁場の基準方向
    hx = mm_x * q0q0 - _2q0my * q3 + _2q0mz * q2 + mm_x * q1q1 + _2q1 * mm_y * q2 + _2q1 * mm_z * q3 - mm_x * q2q2 - mm_x * q3q3;
    hy = _2q0mx * q3 + mm_y * q0q0 - _2q0mz * q1 + _2q1mx * q2 - mm_y * q1q1 + mm_y * q2q2 + _2q2 * mm_z * q3 - mm_y * q3q3;
    _2bx = sqrtf(hx * hx + hy * hy);
    _2bz = -_2q0mx * q2 + _2q0my * q1 + mm_z * q0q0 + _2q1mx * q3 - mm_z * q1q1 + _2q2 * mm_y * q3 - mm_z * q2q2 + mm_z * q3q3;
    _4bx = 2.0f * _2bx;
    _4bz = 2.0f * _2bz;
    // 勾配適切なアルゴリズム修正ステップ
    s0 = -_2q2 * (2.0f * q1q3 - _2q0q2 - aa_x) + _2q1 * (2.0f * q0q1 + _2q2q3 - aa_y) - _2bz * q2 * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mm_x) + (-_2bx * q3 + _2bz * q1) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - mm_y) + _2bx * q2 * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mm_z);
    s1 = _2q3 * (2.0f * q1q3 - _2q0q2 - aa_x) + _2q0 * (2.0f * q0q1 + _2q2q3 - aa_y) - 4.0f * q1 * (1 - 2.0f * q1q1 - 2.0f * q2q2 - aa_z) + _2bz * q3 * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mm_x) + (_2bx * q2 + _2bz * q0) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - mm_y) + (_2bx * q3 - _4bz * q1) * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mm_z);
    s2 = -_2q0 * (2.0f * q1q3 - _2q0q2 - aa_x) + _2q3 * (2.0f * q0q1 + _2q2q3 - aa_y) - 4.0f * q2 * (1 - 2.0f * q1q1 - 2.0f * q2q2 - aa_z) + (-_4bx * q2 - _2bz * q0) * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mm_x) + (_2bx * q1 + _2bz * q3) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - mm_y) + (_2bx * q0 - _4bz * q2) * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mm_z);
    s3 = _2q1 * (2.0f * q1q3 - _2q0q2 - aa_x) + _2q2 * (2.0f * q0q1 + _2q2q3 - aa_y) + (-_4bx * q3 + _2bz * q1) * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mm_x) + (-_2bx * q0 + _2bz * q2) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - mm_y) + _2bx * q1 * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mm_z);
    recipNorm = invSqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // ステップの大きさを正規化する
    s0 *= recipNorm;
    s1 *= recipNorm;
    s2 *= recipNorm;
    s3 *= recipNorm;
    // フィードバックステップを適用する
    qDot1 -= beta * s0;
    qDot2 -= beta * s1;
    qDot3 -= beta * s2;
    qDot4 -= beta * s3;}
  // 四元数の変化率を積分して四元数を生成する
  q0 += qDot1 * deltat;
  q1 += qDot2 * deltat;
  q2 += qDot3 * deltat;
  q3 += qDot4 * deltat;
  // 四元数を正規化する
  recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
  q0 *= recipNorm;
  q1 *= recipNorm;
  q2 *= recipNorm;
  q3 *= recipNorm;}
/*------------------------以上Madgwickフィルター--------------------------*/

// 角速度生データを角度に変換-------------------------------------------------------------------------------------------------------------
float convertRawGyro(int gRaw) {
  // since we are using 250 degrees/seconds range
  // -250 maps to a raw value of -32768
  // +250 maps to a raw value of 32767
  float g = (gRaw * 2300.0) / 32768.0; //--------------------------------------------------------def:2500.0?
  return g;}

//MARGセンサーからデータ取得
float gxOld2=0,gyOld2=0,gzOld2=0,gxOld1=1,gyOld1=1,gzOld1=1,xG,yG,zG,xA,yA,zA,gx,gy,gz,ax,ay,az,mx,my,mz,CoefficientGX,CoefficientGY,CoefficientGZ;
float axOld2=0,ayOld2=0,azOld2=0,axOld1=1,ayOld1=1,azOld1=1,mxOld2=0,myOld2=0,mzOld2=0,mxOld1=1,myOld1=1,mzOld1=1;
double pitch=0,yaw=0,roll=0,pi1=0,ya1=0,ro1=0,pi2=0,ya2=0,ro2=0;
void Measurement(){
//実行時間間隔のバラツキ補正------------------------------------------------------------------------------------------------------------------
   GY80_raw val = sensor.read_raw();
   ct = millis();
   deltat = (ct - lastt) * 0.001;
   lastt = ct;
 //MARGセンサー測定外れ値の除去(ジャイロスコープのみ前回測定値からの傾き変化も考慮して補正)---------------------------------------------------
   if (abs((val.g_x-gxOld1)/(gxOld1-gxOld2

*2:val.g_y-gyOld1)/(gyOld1-gyOld2

*3:val.g_z-gzOld1)/(gzOld1-gzOld2