どーでも日記

工作の備忘録や映画の感想を書いてきます

【Arduino】Processingへ符号付きint型のデータを送る

加速度センサMPU-6050で取った情報を視覚的にわかりやすく見るために,ProcessingとArduinoを連携させたい.

シリアル通信でArduinoからProcessingへデータを送る際には,データを1byteずつしか送れずかつArduinoでのint型は2byte,Processingでのint型は4byteであるため,データのやり取りに工夫が必要である.ここでは今回行った工夫をまとめる.

 

Arduino→Processingの通信の基本

まず,ArduinoからProcessingへ送るデータは冒頭にも書いた通り1byteずつしか送れない.よって,2byteのint型のデータを送信する場合は上位の8bit(上位ビット)と下位の8bit(下位ビット)に分ける必要がある.

やり方については以下のサイトを参考にした.(同じような説明と同じ画像を使った類似サイトはいっぱいありました)↓

https://kougaku-navi.hatenablog.com/entry/20141008/p1

しかし,このサイトで扱っているデータは正の整数しか扱っておらず,負の整数については述べていない.

言ってしまうと,負の整数は送信できないのである.

この問題はArduinoでのint型は2byte,Processingでのint型は4byteとデータの長さに違いがあるため,データ内での符号の表現方法が違うからである.符号の正負はデータの最上位ビットによって判断できるが,ArduonoとProcessingの最上位ビットはそれぞれ16bit目と32bit目でありズレている.このため,たとえデータを送信してもおかしな数値が受信されてしまう.

 

・正負の情報をデータの絶対値と共に送る

負の整数が送れないという問題を取り除くために,次の方法を使う.

参考にしたサイトでは,データを上位ビットと下位ビットに分けて送信する際,受信時に上位バイトを判別できるようにヘッダを一緒に送信している.

f:id:dodemo_yoshi:20190513205818p:plain

この方法を応用し,ヘッダの3bit目を正負の判定に用いることで正負の情報を組み込む.

f:id:dodemo_yoshi:20190513211124p:plain

このやり方であれば,通信データ量は増えず,ヘッダは常に128以上となるためヘッダとしての機能を維持する.送信するデータは絶対値にしておき,ヘッダが132以上であれば,元のデータの符号は負であると判断できる.

 

・実装例

Arduinoのスケッチ

void setup() {
  Serial.begin(9600);  //転送速度を9600bpsに設定
}

void loop(){ int value = -500; int Value; byte head, high, low; //valueの送信準備.ヘッダ,上位バイト,下位バイトに分割 Value = abs(value); if(value >= 0) head = ((Value >> 14) & 127) + 128; //headの8bit目を1に else head = ((Value >> 14) & 127) + 128+4; //valueが負ならばheadの8,3bit目を1に
//3bit目は正負判定用 high = (Value >> 7) & 127; //8bit目が0の上位バイト low = Value & 127; //8bit目が0の下位バイト Serial.write(head); Serial.write(high); Serial.write(low); }

 

Processingのスケッチ

import processing.serial.*;    //arduinoと通信をするためのライブラリを読み込む

Serial port;                   //シリアル通信を行うための変数の定義が必要

int value;

void setup(){
    size(255, 255);            //255x255ドットの画面を作成
    port = new Serial(this,"COM3",9600);
                               //通信ポートと速度の設定  //thisはこういうものだと思い込むべし
    background(192);  //背景を明るい灰色にする
    stroke(0);        //線の色を黒
    strokeWeight(5);  //線の太さを5ptに
    fill(0);          //文字の色を黒に
    textSize(24);  //文字の大きさを24ptに
}

void draw(){
    if(port.available() > 8){  //データが9以上送られてきたら
    
      //axRawの受け取り
      int head = port.read();
      if ( head >= 128 ) { // 128以上であれば先頭データなので、それに続くデータを読み取る
        int high = port.read(); // 2番目のデータ
        int low  = port.read(); // 3番目のデータ

        // データを復元する
        value = ((head & 0x03) << 14) + (high << 7) + low; 
        if ( head >= 132 ) value = (-1)*value;  //ヘッダが132以上(8,3bit目が1)のときvalueは負となる
} background(192); //背景を明るい灰色にして更新 text(value,10,30); //画面左上に受信した値を表示 }
}

 

【Arduino】六軸センサMPU-6050の設定方法

前回の記事でまったく理解できていなかったarduinoにおけるセンサの設定方法について理解が進んだのでまとめておく.マイコンをよく使っている人からすると当たり前のことを書いている気がする.

 

・基本スケッチ

I2C通信にはwireライブラリ使う.以下,前回の記事のスケッチから,センサからのデータをarduinoで取得する部分までを抜粋した.このスケッチをもとに解説していく.

#include <Wire.h>

int16_t axRaw, ayRaw, azRaw, gxRaw, gyRaw, gzRaw, temperature;

void setup() {
  Serial.begin(9600);  //転送速度を9600bpsに設定
  Wire.begin();
  TWBR = 12;

  Wire.beginTransmission(0x68);
  Wire.write(0x6B);
  Wire.write(0x00);
  Wire.endTransmission();

  //Gyro初期設定
  Wire.beginTransmission(0x68);
  Wire.write(0x1C);
  Wire.write(0x10);
  Wire.endTransmission();

  //加速度センサー初期設定
  Wire.beginTransmission(0x68);
  Wire.write(0x1B);
  Wire.write(0x08);
  Wire.endTransmission();

  //LPF設定
  Wire.beginTransmission(0x68);
  Wire.write(0x1A);
  Wire.write(0x05);
  Wire.endTransmission();
  
}


void loop() {
  Wire.beginTransmission(0x68);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(0x68, 14);
  while (Wire.available() < 14);
  axRaw = Wire.read() << 8 | Wire.read();
  ayRaw = Wire.read() << 8 | Wire.read();
  azRaw = Wire.read() << 8 | Wire.read();
  temperature = Wire.read() << 8 | Wire.read();
  gxRaw = Wire.read() << 8 | Wire.read();
  gyRaw = Wire.read() << 8 | Wire.read();
  gzRaw = Wire.read() << 8 | Wire.read();

}

  

・初期設定

まず,setupの中身から

Wire.begin();

Wireを開始する.今回はarduino側がI2C通信におけるmastarなので引数は省略.slave側とする場合は7bitの引数を入れると,それがslave側のアドレスとなる.

TWBR = 12;

いきなりで申し訳ないが不明.わかり次第追記する.

 

Wire.beginTransmission(0x68);

0x68というアドレスのslaveデバイスと通信を開始する.MPU-6050のアドレスはデフォルトで0x68である.(MPU-6050レジスタマップ資料p.45参照) 指定するアドレスは16進数,10進数,2進数のどれでもok.

 

Wire.write(0x1B);

Wire.write(0x00);

引数に入っている数値を送る.arduino側からはMPU-6050に数値を送っているだけであるが,MPU-6050側では

最初のWire.write(0x1B)でレジスタアドレスを0x6Bに指定,

二つ目のWire.write(0x00)でレジスタアドレス0x6Bの8bitのレジスタに0x00格納する作業を行っている.(と思われる.あくまで自分の理解)

f:id:dodemo_yoshi:20190302132103p:plain

↑アドレス0x6Bのレジスタ0~7bitに0x00を格納する.つまり,すべて0になる.

MPU-6050レジスタマップ資料p.40によると,0~2bitのCLKSELに数値を格納することで内部クロックを設定する.とりあえず,ここに0を格納すると,I2C通信がスタートするらしい.

 

Wire.endTransmission();

この関数をもってはじめてMPU-6050にWire.beginTransmission(0x68),Wire.write(0x1B),Wire.write(0x00)の内容を送信する.

 

ここまででI2C通信の設定が完了である.とはいっても内部クロックしか設定していないが...

 

//Gyro初期設定
  Wire.beginTransmission(0x68);
  Wire.write(0x1C);
  Wire.write(0x10);
  Wire.endTransmission();

次のかたまりではGyro(角加速度)のデータ取得に関する初期設定(レンジの設定)を行っている.

先ほどと同じ流れで上から,

・0x68というアドレスのslaveデバイス(MPU-6050)と通信を開始する.

・引数に入っている数値を送る.最初のWire.write(0x1C)でレジスタアドレスを0x1Cに指定,二つ目のWire.write(0x10)でレジスタアドレス0x1Cの8bitのレジスタに0x10格納する.0x1Cのアドレスは,角加速度計測に関する数値を格納しているレジスタを示している.(MPU-6050レジスタマップ資料p.15参照)アドレス0x1CのレジスタのBit4に1を格納し,そのほかのBitには0を格納する.

 

その後は同様のやり方で加速度センサとLPF(ローパスフィルタ)の初期設定を行っている.

後は資料を読んで好みの設定にしていこう.

 

・データの取得

センサで計測されたデータは,センサ内のレジスタに一旦格納される.そのデータを指定してarduinoで受け取る必要がある.

 

改めてloopの中身

void loop() {
  Wire.beginTransmission(0x68);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(0x68, 14);
  while (Wire.available() < 14);
  axRaw = Wire.read() << 8 | Wire.read();
  ayRaw = Wire.read() << 8 | Wire.read();
  azRaw = Wire.read() << 8 | Wire.read();
  temperature = Wire.read() << 8 | Wire.read();
  gxRaw = Wire.read() << 8 | Wire.read();
  gyRaw = Wire.read() << 8 | Wire.read();
  gzRaw = Wire.read() << 8 | Wire.read();

}

 

Wire.beginTransmission(0x68);

初期設定と同じで0x68というアドレスのslaveデバイス(MPU-6050)と通信を開始す.

Wire.write(0x3B);

レジスタアドレスを0x3Bに指定.

Wire.endTransmission();

レジスタアドレスを指定だけして送信. 

 

Wire.requestFrom(0x68, 14);

0x3Bのレジスタから14byteのレジスタには加速度,温度,角加速度のデータが格納されている.この関数でMPU-6050のアドレス0x3B(Wire.write(0x3B);で指定済み)のレジスタから以下14byteのデータを要求する.

 

while (Wire.available() < 14);

取得できる(readで読める)データが14より小さければ次のプログラムを行う.無くてもいい?

axRaw = Wire.read() << 8 | Wire.read();

x軸加速度を取得し,変数(axRaw)に格納する.

最初のWire.read()によってWire.requestFromで要求したデータの最初の1byte(指定したアドレス0x3Bに格納されたデータ)を読み込む.この8bitはビットシフトすることでaxRawの上位8bit(15~8bit)に格納.

二回目のWire.read()で先ほど読み込んだデータの次の1byte(アドレス0x3Cのデータを読み込む.この8bitはaxRawの下位8bit(7~0bit)に格納する.

 

Wire.read()について

1Byteずつデータを読み込む関数.Wire.read()で任意のアドレスのデータを読みこんだ後,もう一度Wire.read()を使うと,その前に読み込んだデータの次のアドレスのデータ(1Byte)となる.つまり,Wire.write()でアドレスを指定しないかぎり,Wire.read()で読み込まれるデータのアドレスは関数を使うたびに移動する.

 

この後はayRaw, azRaw, gxRaw, gyRaw, gzRaw, temperatureもそれぞれ同様にデータを取得する.

 

あとはSerial.print()などで表示するなり,計算に使うなどすればよい.

 

 

参考:https://qiita.com/MergeCells/items/20c3c1a0adfb222a19cd

↑I2C通信のmasterとslaveの関係などをわかりやすく書かれている

【Arduino】六軸センサMPU-6050を使ってみる

学生時代に所属していた研究室ではドローンに関する研究をしていたのだが,研究自体はもっぱらドローンに付随する周辺機器の開発であったためにドローンそのものを作ったことがなかった.(プラットフォームとしてのドローンなら何度かDJIのキットを組んで運用したことがある.)大学を卒業して一年経とうとしているが,このことがどうしても心残りなのでドローンを自作してみる.

 

どの程度の自作かといえば,ドローンのフレームはさすがに製作環境がないのでアマゾンに売ってるものを使うが,飛行制御に使うフライトコントローラやプログラム,電装系は基本的に自作する.

 

フライトコントローラに使うマイコンは“Arduino uno”を使う.

このブログはマイコンの勉強も兼ねるので,プログラムの内容はできる限り詳しく書いていきたい.

 

・6軸センサ動作テスト

というわけで,一番最初の準備段階としてドローンの制御に必要不可欠な六軸センサの動作テストを行う.六軸センサにはアマゾンで2個600円ほどと格安だった“MPU-6050”を使う.I2Cで通信するのでArduinoにつなげる線が少なくて済む.

 

写真のようにMPU-6050(下の方で光ってる小さい基盤)とArduino(上の基盤)を固定する台を製作した.MPU-6050からArduinoへはそれぞれVCC→3.3V,GND→GND,SCL→SCL,SDA→SDAと繋げるだけでよい.

 

f:id:dodemo_yoshi:20190226231506j:plain

テスト用ボード

 

https://garchiving.com/how-to-use-mpu6050-in-arduino/

↑この辺を参考にプログラムを組む.正直,setup()の中身は意味不明なので追々調べるとする.

(追記:MPU-6050のI2C通信設定について)

dodemo-yoshi.hatenablog.com

 

・スケッチ

以下,今回用いたスケッチ. 

#include <Wire.h>

int16_t axRaw, ayRaw, azRaw, gxRaw, gyRaw, gzRaw, temperature;
        //a:acceleration  g:gyro(角加速度)
float acc_x,acc_y,acc_z;
float acc_angle_x,acc_angle_y,acc_angle_z;
float gyr_x,gyr_y,gyr_z;

void setup() {
  Serial.begin(9600);
  Wire.begin();
  TWBR = 12;

  Wire.beginTransmission(0x68);
  Wire.write(0x6B);
  Wire.write(0x00);
  Wire.endTransmission();

  //Gyro初期設定
  Wire.beginTransmission(0x68);
  Wire.write(0x1C);
  Wire.write(0x10);
  Wire.endTransmission();

  //加速度センサー初期設定
  Wire.beginTransmission(0x68);
  Wire.write(0x1B);
  Wire.write(0x08);
  Wire.endTransmission();

  //LPF設定
  Wire.beginTransmission(0x68);
  Wire.write(0x1A);
  Wire.write(0x05);
  Wire.endTransmission();
  
}


void loop() {
  Wire.beginTransmission(0x68);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(0x68, 14);
  while (Wire.available() < 14);
  axRaw = Wire.read() << 8 | Wire.read();
  ayRaw = Wire.read() << 8 | Wire.read();
  azRaw = Wire.read() << 8 | Wire.read();
  temperature = Wire.read() << 8 | Wire.read();
  gxRaw = Wire.read() << 8 | Wire.read();
  gyRaw = Wire.read() << 8 | Wire.read();
  gzRaw = Wire.read() << 8 | Wire.read();

  // 取得した加速度値を分解能で割って加速度(G)に変換する
  acc_x = axRaw / 4096.0; //スケールファクタの設定(分解能?)
  acc_y = ayRaw / 4096.0; //FS_SEL_2 4096LSB/g (Least Significant Bit:量子化単位)
  acc_z = azRaw / 4096.0;

  // 加速度からセンサ対地角を求める
  acc_angle_x = atan2(acc_y, acc_z) * 360 / (2.0*PI);
  acc_angle_y = atan2(acc_x, acc_z) * 360 / (2.0*PI);
  acc_angle_z = atan2(acc_x, acc_y) * 360 / (2.0*PI);

  // 取得した角速度値を分解能で割って角速度(deg/s)に変換する
  gyr_x = gxRaw / 65.5; //スケールファクタの設定(分解能?)
  gyr_y = gyRaw / 65.5; //FS_SEL_1 65.5LSB/(deg/s)
  gyr_z = gzRaw / 65.5;


  Serial.print("acc_x:"); Serial.print(acc_x); Serial.print(",");
  Serial.print("acc_y:"); Serial.print(acc_y); Serial.print(",");
  Serial.print("acc_z:"); Serial.print(acc_z); Serial.print(",");
  Serial.print("gyr_x:"); Serial.print(gyr_x); Serial.print(",");
  Serial.print("gyr_y:"); Serial.print(gyr_y); Serial.print(",");
  Serial.print("gyr_z:"); Serial.print(gyr_z); Serial.print(",");
  //Serial.print("ang_x:"); Serial.print(acc_angle_x); Serial.print(",");
  Serial.print("\n");

}

 

・出力結果

f:id:dodemo_yoshi:20190226235531p:plain

加速度と角速度は簡単に出せたが,センサは机の上に置いているだけにかかわらず加速度が検出されてしまっている.いろいろ調べるとフィルタを使って補正を入れたり,角速度から加速度を算出した時のドリフトをなんとかしないといけなかったり一筋縄ではいかないみたい.

 

データの表示はスケッチモニタ上にしか表示してないので,センサの状態を把握するにはどこかにデータを保存できるようにしないといけない.今後のデバックを容易にするためにも.

 

今後,理解と改善が進み次第記事を追記していく.