質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.32%

シリアル通信で複数のセンサの値を送るには? Arduino→Android Bluetooth接続時

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 14K+

kt.tk.co

score 25

■目的・課題 

●目的
ArduinoからAndroidにBluetooth接続で複数センサの値を送りたいと考えております。

イメージ
イメージ

●手段
ArduinoとAndroidのBluetooth接続は参考サイトを見て、全く同じコードで問題なく動作させることができました。

そこで次に、Arduinoから複数のセンサの値をシリアル通信で送り、Androidで受信してセンサごとの変数へ代入を行いたいです。

●問題
ただし、調べたところシリアル通信は「データを1ビットずつ連続的に送受信する通信方式」のようです。
そのためAndroidでデータを受け取り画面に表示すると、センサの値の途中で途切れた中途半端な状態で表示されてしまいます。
(Arduinoから1ビットずつ値を送っているため、Arduinoが送られてきたシリアル通信を読み込むタイミングによってセンサの値の途中で表示してしまっているものと考えています)

・例 センサ12個の場合

  1. Arduinoから12個の値をシリアル通信で送り続ける。
    (送る値を、「ランダム,0,0,0,0,0,0,0,0,0,0,0¥n」にした場合)
    (一番最初をランダムな値、それ以外は0で、すべてカンマ区切り。最後に¥n)

  2. Androidに表示される値は次のようになります。
    イメージ説明
    イメージ説明
    イメージ説明
    イメージ説明

(上の写真の順番は時系列に添っていませんが、更新される度に前の値の続きから表示されるので、データの欠損などはなさそうです。)
対処療法としてAndroid側にてカンマ区切りで分離しようと考えていますが、処理が複雑になってしまいうまく実装できていない状態です。(カンマ区切りに分割した後、必要な箇所は変数に代入して、¥nより後の値は次のデータ取得時に持ち越す。 といった処理がうまくできていないです。)

そもそも、USBホストケーブルで行っていたとき(下にまとめました)には、センサごとにArduinoからデータを送ってもらい、Androidでセンサごとの変数へ代入できていました。(シリアル通信ではない?)
そういった処理を最初からできたら根本から解決できるかと考え調べていますが、なかなか良い方法が分からずに困っています。

●質問内容
Bluetoothの通信時に、Arduinoで読み取ったセンサの値(アナログ値の0~1024)を複数個まとめて、Androidに送る適切な方法をお教えいただけますと幸いです。

●考え
Bluetooth時にも、USBホストケーブルと同様の「Arduinoからの送信処理」を行えば実現できそうと考えていました。

acc.write(&pack, sizeof(Pack));


しかし、Arduinoのacc.writeコマンドはUSBホストケーブルでの接続時でしか実行できないようでした。
そのため、シリアル通信で同様の送信方法がないかと探しております。

このサイトより
「1回のSerial.write()で送れるデータは1バイト、すなわち数値として表現できる範囲は0~255です。」
とあることから、センサごとに値を送るのは難しいのかもしれません。

現在試行錯誤の真っ最中です。

回路
イメージ説明

■Bluetooth接続時のコード(簡略化のため、センサを2つだけに減らしたコードを載せました。)

Arduinoの送信部分

//文字を送るために、文字のバッファを定義
char buff[255];

////////////
//初期設定//
////////////

void setup(){

  // arduinoのシリアルモニタ用
  Serial.begin(19200); 

  // Bluetooth用のシリアルのポートを設定
  Serial1.begin(9600);

}

void loop(){

  //バッファに値を代入
  sprintf(buff, "%d,%d\n",analogRead(0),analogRead(1));

  //Arduinoにセンサの値を送る
  Serial1.write(buff);

}

Andrroidの受信部分

InputStream mmInStream = null;

        Message valueMsg = new Message(); //メッセージを入れる変数
        valueMsg.what = VIEW_STATUS; //メッセージがステータスである とする
        valueMsg.obj = "connecting..."; //ステータスに書き込むテキストを指定
        mHandler.sendMessage(valueMsg); //テキスト書き換え関数を実行する

        try{

            // 取得したデバイス名を使ってBluetoothでSocket接続
            mSocket = mDevice.createRfcommSocketToServiceRecord(MY_UUID);
            mSocket.connect();
            mmInStream = mSocket.getInputStream();
            mmOutputStream = mSocket.getOutputStream();

            // InputStreamのバッファを格納
            byte[] buffer = new byte[1024];

            // 取得したバッファのサイズを格納
            int bytes;
            valueMsg = new Message();
            valueMsg.what = VIEW_STATUS; //メッセージがステータスである とする
            valueMsg.obj = "connected."; //ステータスに書き込むテキストを指定
            mHandler.sendMessage(valueMsg); //テキスト書き換え関数を実行する

            //接続中である とする
            connectFlg = true;

            //フラグが立っていれば、Arduinoからシリアル通信のデータを読み込み
            while(isRunning){

                // InputStreamの読み込み
                bytes = mmInStream.read(buffer); //シリアル通信で送られてきたデータサイズを取得
                Log.i(TAG,"bytes="+bytes); //データサイズをログに書き出し
                // String型に変換
                String readMsg = new String(buffer, 0, bytes); //データそのもの,?,データサイズ

                // 「null以外」 かつ 「空欄ではない」  なら表示
                if(readMsg.trim() != null && !readMsg.trim().equals("")){
                     Log.i(TAG,"value="+readMsg.trim());

                     valueMsg = new Message();
                     valueMsg.what = VIEW_INPUT; //メッセージがシリアル通信のINPUTである とする
                     valueMsg.obj = readMsg; //Arduinoから取得したメッセージを代入
                     mHandler.sendMessage(valueMsg); //テキスト書き換え関数を実行する
                }
                else{
                   // Log.i(TAG,"value=nodata");
                }
           }
        }catch(Exception e){

        }


■USBホストケーブルでの通信

Bluetoothで無線化をする前は、USBホストケーブルでセンサの値を送っていました。
処理は問題なく、Androidでセンサごとにデータを受け取ることができておりました。

イメージ説明

■USBホストケーブル接続時のコード

Arduinoの送信部分

//AndroidのUSBアクセサリ接続
#include <Max3421e.h>
#include <Usb.h>
#include <AndroidAccessory.h>


// AndroidAccessoryオブジェクト作成 
AndroidAccessory acc("MyManufacturer",         // 製造者名
                     "MyModel",                     // モデル名
                     "テスト",   // 説明文
                     "1.0",                         // バージョン名
                     "http://android.com",          // URL
                     "0000000012345678");           // シリアル番号


// Andloidにデータを送るためのパッケージを定義
struct Pack {
  long sensor1;   /* 4バイト */
  long sensor2;   /* 4バイト */
};

struct Pack pack;

////////////
//初期設定//
////////////
void setup()
{
  Serial.begin(9600);

  acc.powerOn(); //USB Host機能を有効にする。(Androidの通信用)
}

void loop(){

  //装置(Android)に接続していたら
  if (acc.isConnected()) {

    //メッセージ生成 (packの中に送りたいメッセージを詰め込む)
    pack.sensor1 = analogRead(0);
    pack.sensor2 = analogRead(1);

    //データをAndroidに送る
    acc.write(&pack, sizeof(Pack));
  }
}

Andrroidの受信部分

//センサの値を入れる変数
        int SensorVal[] = new int[2];

        //受信データを入れる変数
        byte [] buffer = new byte[100000];//受信した全てのデータ

        //初めは必ず読み取りに行く
        int ret = 0; //受信データの長さ「ret」を定義する

        // Arduinoから受信
        try {
            //入力ストリームの読み込み
            ret = mInputStream.read(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //取得したデータを、センサごとに分ける。
        byte[] arr = new byte[ret];
        System.arraycopy(buffer,0,arr,0,ret);

        ByteBuffer byteBuffer = ByteBuffer.wrap(arr);
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);

    //得られた値を変数に入れる
        SensorVal[0] = byteBuffer.getInt();       // 4バイト
        SensorVal[1] = byteBuffer.getInt();       // 4バイト

カンマ区切りでの通信

どうしても場当たり的な例外の処理が多く、無駄にコンピューターリソースを使っています。
このような処理の要らないシリアル通信での送受信方法を知ることが最優先事項です。

//前回以降の残りのデータを入れる文字列型の変数
    String leftover = "";


    //カンマ区切りを分離して、表示する関数
    public void DataSplit(String msgStr){

        boolean StartFlag = false;
        boolean EndFlag = false;

        //配列の添字用にセンサ番号をカウントする
        int counter = 0;

        //前回以降の残りのデータと、今回のデータを繋げる
        msgStr = leftover + msgStr;

        //シリアル通信から送られてくるデータを分割する
        String[] SplitData = msgStr.split(",", 0);


        //取得したデータの数だけ繰り返す
        for (int i=0; i<SplitData.length; i++){

            //終了フラグが立っていなければ
            if (!EndFlag){

                //前回までにスタートがあれば
                if (StartFlag){

                    //スタート以降にエンドがあるなら
                    if (SplitData[i].equals("E")){

                        EndFlag = true;

                        //Eがあれば一度空にして、残ったものだけを引き次ぐ
                        leftover = "";

                    }else{

                        // 「null以外」 かつ 「空欄ではない」  なら (,区切りが先頭または最後に来ていると、残りデータとの結合時に,,となってしまう可能性があるため)
                        if(SplitData[i].trim() != null && !SplitData[i].trim().equals("")){
                            //センサーデータの配列に入れる
                            SensorData[counter] = SplitData[i]; //スタートの次からデータ配列に入れ始める
                            counter++;//カウンターを増やす
                        }

                    }

                //今がスタートであれば
                }else if (SplitData[i].equals("S")){

                    StartFlag = true;

                }

            }else{

                //カンマ区切りの最後の数値 かつ 文字の一番最後にカンマがない
                if (i == SplitData.length - 1 && msgStr.charAt(msgStr.length()-1) !=','  ){

                    //最後なら、一番最後に,を入れない!

                    //終了フラグが立っていれば、一つずつ残りデータに入れていく
                    leftover = leftover + "," +SplitData[i]; //前後にカンマを入れる。カンマは連続しても問題ない(カンマ区切りで抽出したものが空白になるため除外できる)が、カンマ無しで数値が繋がると問題なため。

                }else{

                    //終了フラグが立っていれば、一つずつ残りデータに入れていく
                    leftover = leftover + "," +SplitData[i] + ","; //前後にカンマを入れる。カンマは連続しても問題ない(カンマ区切りで抽出したものが空白になるため除外できる)が、カンマ無しで数値が繋がると問題なため。

                }

            }

        }


        //エンドがあれば、センサのデータ配列に入れ、あまりを繰り越す
        if (EndFlag){

            //出力文字を定義
            String OutputString = "";

            //出力用にセンサーの文字を結合する
            for (int i=0; i<12; i++){
                OutputString = OutputString +"センサ " + String.valueOf(i) + " = " + SensorData[i] + "\n";
            }

            //画面に出力する
            Message valueMsg = new Message();
            valueMsg.what = VIEW_INPUT; //メッセージがシリアル通信のINPUTである とする
            valueMsg.obj = OutputString; //Arduinoから取得したメッセージを代入
            mHandler.sendMessage(valueMsg); //テキスト書き換え関数を実行する


        }else{//エンドがなければ、何もせずにシリアル通信のデータを繰り越す

            //Eがない場合はそのまま引き継ぐため代入。
            leftover = msgStr;

        }

    }
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • MasahikoHirata

    2016/11/21 18:40

    添付の図が表示されません。

    キャンセル

  • kt.tk.co

    2016/11/21 22:23

    ご指摘ありがとうございます。スマートフォンから編集を行ったことで、何故か画像が表示されなくなってしまったようです。パソコンから「編集」を行ったことで画像が無事に表示されるようになりました。ありがとうございます。

    キャンセル

回答 1

checkベストアンサー

0

USB接続でもarduinoは基本的に9600bpsだから速度が原因ではないと思います。
また時系列を崩さず送信するには、いったん配列に格納し、それを順に送り出す方法が考えられます。(アナログデータなどのサンプリングを一気にやって、できる限りタイムラグを生じさせない)。これである程度の時系列のずれは無くなると思います。(全部読むのに何秒もかからない)
問題はbluetoothとのハンドシェークかなぁ。でもシリアル以上の速度での通信が可能ですから、まずはサンプルと通信を分けて実装をためしtみ

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/22 03:49

    ご回答頂きありがとうございます。
    とても参考にさせていただきました。

    あれから色々と試したところ、よくわからない症状と新たな問題を見つけました。
    まだ検証している最中であることと、現在の状況を整理できていないためもう少し自身で考えてみることに致します。
    また、「ハンドシェーク」という単語をしらなかったため、ブルートゥースモジュールでの通信におけるハンドシェークを調べて見る予定です。

    今月末まで他の作業を行う必要も出てきたため、来月以降に再度集中してArduinoとAndroidの実装に着手していく予定です。いままでよりも確認や検証が遅くなってしまうため、一度teratailでの質問を終了いたします。予定としては、来月の中盤には大きな問題のない状態で組み上げたいと考えているため、時間を見つけて少しずつ検証していきたいと思います。一つの問題や課題でずっと悩むのは非常に疲れますが、おかげさまで検討するアイデアが尽きずに実装の作業はとても楽しめています。

    本日は夜も更けてきたため睡眠を取ろうと思います。

    ---

    私自身の覚え書きのために簡単に現状をまとめさせて頂きます。

    ・arduinoとbluetoothのどちらも115200bpsへ変更して動作確認しました
    また、配列へ格納した後にシリアル通信で送ることを試してみました。
    ご指摘の通り時間あたりに送信できる値の量が増えました。
    ただし、Arduinoから送られてくるタイミングと、Androidで読み込む処理が非同期なのは変わらず、カンマ区切りで値を分離してあげる必要がありそうです。

    ・通信の干渉??
    Arduinoのシリアルポートもしくはシリアルプロッタへ値を書き出す処理(Serial.println( "" );など)を入れると、Androidへの送信が低速になっていることを発見しました。
    実際の症状としては、Arduinoのシリアルポートもしくはシリアルプロッタの画面を開くとAndroidでの値の送信が高速に行われ、画面を開いていないとAndroidへの値の送信が非常に低速になるという症状です。Arduinoのシリアルポートもしくはシリアルプロッタへ値を書き出す処理を消すことで問題なく実行できますが、問題の発生条件が不思議で原因を理解できていません。

    ・カンマ区切りでの値の分離を実装
    結局のところArduinoからカンマ区切りの文字列型へセンサの値を集約した後Androidへを送り、Androidでカンマを排除してセンサごとの変数へ代入する処理をプログラミングしました。
    質問欄の最後に載せましたが、場当たり的な例外の処理が多くきれいなものではありません。
    また、bluetoothの速度が9600bps の状態では問題なく動いていましたが、115200bpsにしてから処理が途中で止まっているようでした。
    恐らくAndroidの処理スピードが追いついていないようで、得られたデータが処理される前にガーベージコレクションにて破棄されているようでした。


    ・シリアル通信処理によるサンプリング回数の低下
    Arduinoの環境下において、「BluetoothでAndroidにシリアル通信で送る」という処理を入れているとArduinoの処理スピードが落ちているようでした。
    そのため、analogReadによってセンサの値を取得する際に、単位時間あたりのサンプリング回数が落ちておりました。今後検討予定です。


    ・bluetoothモジュールによるセンサの値へのノイズ
    「analogReadによってセンサを取得する処理」をarduino microにて行っています。そのままだと問題ないのですが、arduinoにbluetoothモジュールを付け足すことでセンサの値にノイズが乗っていることを確認しました。そのため、bluetooth用の電源は別に取ることを検討中です。(あまり装置を大型化したくないですが・・・)

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.32%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る