openFrameworks, serial通信, arduino, 1 byte, 2 bytes, multi-data

git

https://github.com/keitasumiya/oF/tree/master/serial

初めに

oFのGPIOで色々制御したいなと思った時に, serial通信が1つの柔軟な方法のようなので勉強しました.
あまり速度が速くないそうですが, 知っておくと色々便利そうなのでまぁよし.
皆さんが実際にどう制御しているのかを知りたくて調べたんですが, 思った以上に見つからず.
serial通信自体は常套手段の1つだと思うので, 皆さんがどうされているのか是非知りたいところです.

前置きが長いですが, 全部スルーして

本題: codes

に行っても結構です.

本題の前に

一番最初は
openFrameworks + Arduinoをserial通信してswitchのon/offをinputする
http://qiita.com/shu223/items/a3754f8015875c1cc4f2
をそのまま実行するところからはじめました.

動画
https://youtu.be/dfQ7abCqKMk

references, その他

参考にさせていただいたページです.
他にもあったかもしれませんが…

公式のドキュメントはやはり強力でした.
http://openframeworks.cc/documentation/communication/ofSerial/#show_writeByte
https://www.arduino.cc/en/Serial/Available

serialの処理はこちらを参考にさせていただきました.
2byteのシフトの説明もこちらにかかれてあります.
基本的にはビットシフトした1byteの値に対して127(01111111)を論理積(&)をとることで後半7bitを活かす
-> 128(10000000)を足すとヘッダー(>=128)になる.
という寸法です
http://ryoizu.com/blog/2016/06/08/arduino%E3%81%A8openframeworks%E3%81%AE%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E9%80%9A%E4%BF%A1%E3%80%82/
http://d.hatena.ne.jp/kougaku-navi/20141008/p1
当初, Serial.printlnや, readBytesを利用して苦しんでいたんですが, 単数形のwriteByte, readByteでやると一気にシンプルになりました.
[0,255]を1byteで送れるし, writeByte, readByteは便利ですね.
readBytesの方が高機能なのかもしれないのですが, まぁ勉強し始めなのでおいおいということで.

安定のyoppa.org
http://yoppa.org/ma2_11/3383.html
ただし, こちらの内容はfirmataを利用されていたもののようだったので断念.
firmataも触ってみたいところですが, この記事以降の目的としてarduino megaの使用があり, firmataはmegaに対応していないようだったことが断念の利用です.
単にarduino unoを使うだけなら, firmataも便利な気がします.
http://openframeworks.cc/documentation/communication/ofArduino/#show_getPwm

改行ってどう書くんだっけ, と思い参考に
http://www9.plala.or.jp/sgwr-t/c/sec05.html

macでバックスラッシュてどう書くんだっけ, と思い参考に
http://qiita.com/miyohide/items/6cb8967282d4b2db0f61

signed char: [-127,127]
unsigned char: [0,255]
なんですね. なるほど.
http://stackoverflow.com/questions/75191/what-is-an-unsigned-char

Serial.printとbitを使った例.
今回は使用せず.
http://stackoverflow.com/questions/3031413/bitwise-operators-and-converting-an-int-to-2-bytes-and-back-again

“本題の前に”の中身につながる内容.
memset
http://www9.plala.or.jp/sgwr-t/lib/memset.html
memcpy
http://www9.plala.or.jp/sgwr-t/lib/memchr.html

c++で配列ってどう書くんだっけ, と思い参考に
http://vivi.dyndns.org/tech/cpp/array-algorithm.html

“本題の前に”の中身につながる内容 2.
あと, Kyleが答えてる!!と勝手にテンション上がってました.
https://forum.openframeworks.cc/t/reading-serial-data-from-arduino/8161

複数のデータを処理してる例
http://4009.jp/post/2016-08-16-74HC595/
非常に面白そうだったのですが, 今回は0/1ではなく256以上の解像度の複数データの処理をしたかったので断念しました.

software serial.
http://asukiaaa.blogspot.jp/2016/03/arduinosoftwareserial.html
面白そうでしたが今回はさわれず.
同時に複数のポートを使うとか, listenでスイッチするとかも後でやりたいところです.

シリアル通信速度(bps)
http://www32.atpages.jp/rs232/

本題: 注意点

ようやく本題.

以下,

  • 1 byte [0,255]のデータのやり取り (+ PWMでLEDのグラデーション制御)
  • 2 bytes [0,1023]のデータのやり取り
  • 複数の2 bytes [0,1023]のデータのやり取り (+ PWMでLEDのグラデーション制御)

の順でcodesを書いていきます.

  • 複数の1 byte [0,256]のデータのやり取り

は, 書き方として2 bytesのときと同じ方法しか思いつかなかったので書いてないです.
もっと良い処理の方法があると思うんですが, 現状では思いつかず, です.

基本的にはarduinoとmacをUSBケーブルでつないでるだけです.
arduinoのPINには何も挿しておらず, 単にserial通信をしているだけです.

bpsは早いほうがいいんですかね??
とりあえずここでは9600にしてます.
もっと早くしてる例も結構あるので, 実際に使うときは57600とか115200とかにしようかな.

ただ, oFのFrameRateは60とか120とかである程度制限したほうがいいかも?
1 byteなら

ofSetBackgroundAuto(false);
ofSetVerticalSync(false);
ofSetFrameRate(0);

とかで無制限にしても大丈夫でした.
ただ2 bytesの時?だと, 処理の途中でミスするのか, 区切りが上手く処理されないのかわからないですが変な値になることがありました(writeでも).
巨大数になったので連結しちゃってるのかなぁ.

1byteでもmac > arduinoでコケることがありました. 
ofSetVerticalSync(false)はやらない方が良さそうです. 

bpsを早くしてもoFが遅かったら意味がない(こともある)ので, そこら辺は事情に応じて調整が必要な気がします.
まぁread,write自体の処理が早く終わるからそれはそれでメリットはあるか.
ふむぅ, 悩み中です.

通信状況は
oF : コンソール
arduino: シリアルモニタ
に表示されます.

接続が上手くいかないときは, mac側のUSBを一旦抜いて挿し直すと, 通信できるようになったりします.
USBポートの接触不良が直る? or arduinoのrebootで直る? のかはわかりませんが, とりあえず現状はこれで解決します.
一度つながってからは通信中に調子が悪くなることはまだ体験してないです.

oFのmySerial.setup内の

"/dev/cu.usbmodem1421"

は, 適宜自分の環境に合わせて変えてください.

oFでerror文の書く場所をdraw内にしてるのはupdateの中においても勿論おkです.
printfなのでupdate内でもコンソールに書けます.
drawに置いているのは, 実際に画面に出す時に使おうかなぁ…という時に場所がわかりやすいかな?と思っただけです.
isValidがグローバル変数になっているのも同様の理由です.

arduino, oFで, valueがグローバル変数になっているのも同様です.
writeの際にはグローバルにする必要はないと思いますが,
先頭に書いたほうが, “実際にどの値を送ろうとしているか”がわかりやすいかな?と言う意味も込めてます.

本題: codes

1 byte [0,255]のデータのやり取り

arduino > oF

void setup(){
  Serial.begin(9600);
}

void loop(){
  Serial.write(123);
}
#include "ofApp.h"

ofSerial mySerial;
int myByte = 0;

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(60);
    mySerial.setup("/dev/cu.usbmodem1421",9600);
}

//--------------------------------------------------------------
void ofApp::update(){
    myByte = mySerial.readByte();
}

//--------------------------------------------------------------
void ofApp::draw(){
    if ( myByte == OF_SERIAL_NO_DATA )
        printf("no data was read \n");
    else if ( myByte == OF_SERIAL_ERROR )
        printf("an error occurred \n");
    else
        printf("myByte is %d \n", myByte);    
}

oF > arduino

int readByte = 0;

void setup(){
  Serial.begin(9600);
}

void loop(){
  if (Serial.available() > 0) {
    readByte = Serial.read();
    Serial.println(readByte, DEC);
  }
}
#include "ofApp.h"

ofSerial mySerial;
unsigned char myByte = 123;

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetBackgroundAuto(false);
    ofSetVerticalSync(false);
    ofSetFrameRate(0);

    mySerial.setup("/dev/cu.usbmodem1421",9600);
}

//--------------------------------------------------------------
void ofApp::update(){
}

//--------------------------------------------------------------
void ofApp::draw(){
    bool byteWasWritten = mySerial.writeByte(myByte);
    if ( !byteWasWritten )
        printf("byte was not written to serial port \n");
}

oF > arduino でLEDをグラデーションに制御

int readByte = 0;
const int OUT_PIN = 3;

void setup(){
  Serial.begin(9600);
}

void loop(){
  if (Serial.available() > 0) {
    readByte = Serial.read();
    Serial.println(readByte, DEC);
    analogWrite(OUT_PIN, readByte);
  }
}
#include "ofApp.h"

ofSerial mySerial;
unsigned char myByte = 0;
int a = +1;

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetBackgroundAuto(false);
    ofSetVerticalSync(false);
    ofSetFrameRate(0);

    mySerial.setup("/dev/cu.usbmodem1421",9600);
}

//--------------------------------------------------------------
void ofApp::update(){
    if (myByte <= 0){
        a = +1;
    } else if (myByte >= 255){
        a = -1;
    }
    myByte += a*1;
}

//--------------------------------------------------------------
void ofApp::draw(){
    bool byteWasWritten = mySerial.writeByte(myByte);
    if ( !byteWasWritten )
        printf("byte was not written to serial port");
}
IMG_3906.jpg

配線:

動画:
https://youtu.be/P7t2bUuLkBU

2 bytes [0,1023]のデータのやり取り

arduino > oF

int value = 963;

void setup(){
  Serial.begin(9600);
}

void loop(){
  int low = value & 127;
  int high = (value >> 7) & 127;
  int head = 128;
  Serial.write(head);
  Serial.write(high);
  Serial.write(low);
}
#include "ofApp.h"

ofSerial mySerial;
bool isValid = false;
int value;

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(60);
    mySerial.setup("/dev/cu.usbmodem1421",9600);
}

//--------------------------------------------------------------
void ofApp::update(){
    isValid = false;
    if (mySerial.available() >= 3) {
        int head = mySerial.readByte();
        if (head == 128){
            int high = mySerial.readByte();
            int low = mySerial.readByte();
            value = (high<<7) + low;
            if (0 <= value && value <= 1023){
                printf("value is %d \n", value);
                isValid = true;
            }
        }
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    if ( !isValid ) {
        printf("an error occurred \n");
    }
}

oF > arduino

int value = 0;
bool isValid = false;

void setup(){
  Serial.begin(9600);
}

void loop(){
  isValid = false;
  if (Serial.available() >= 3) {
    int head = Serial.read();
    if (head == 128) {
      int high = Serial.read();
      int low  = Serial.read();
      value = (high<<7) + low;
      Serial.println(value, DEC);
      if (0 <= value <= 1023) {
        isValid = true;
      }
    }
  }
}
#include "ofApp.h"

ofSerial mySerial;
int value = 963;
bool isValid = false;

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(60);
    mySerial.setup("/dev/cu.usbmodem1421",9600);
}

//--------------------------------------------------------------
void ofApp::update(){
    isValid = false;
    int head = 128;
    int high = (value >> 7) & 127;
    int low  = value & 127;
    bool byteWasWritten1 = mySerial.writeByte(head);
    bool byteWasWritten2 = mySerial.writeByte(high);
    bool byteWasWritten3 = mySerial.writeByte(low);
    if ( byteWasWritten1 && byteWasWritten2 && byteWasWritten3 ) {
        isValid = true;
        printf("write value %d \n", value);
    }
}

//--------------------------------------------------------------
void ofApp::draw(){
    if ( !isValid ) {
        printf("an error occurred \n");
    }
}

複数の2 bytes [0,1023]のデータのやり取り

arduino > oF

const int val_size = 3;
int values[val_size] = {123, 456, 789};

void setup(){
  Serial.begin(9600);
}

void loop(){
  for (int i=0; i<val_size; i++){
    int high = (values[i] >> 7) & 127;
    int low  = values[i] & 127;
    Serial.write(128+i);
    Serial.write(high);
    Serial.write(low);
  }
}
#include "ofApp.h"

ofSerial mySerial;
const int val_size = 3;
int values[val_size] = {0, 0, 0};
bool isValids[val_size] = {false, false, false};

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(60);
    mySerial.setup("/dev/cu.usbmodem1421",9600);
}

//--------------------------------------------------------------
void ofApp::update(){
    bool isValids[val_size] = {false, false, false};
    if (mySerial.available() >= 3*val_size) {
        int head = mySerial.readByte();
        for (int i=0; i<val_size; i++){
            if (head == 128+i){
                int high = mySerial.readByte();
                int low  = mySerial.readByte();
                values[i] = (high<<7) + low;
                if (0 <= values[i] && values[i] <= 1023){
                    isValids[i] = true;
                    printf("value is %d \n", values[i]);
                }
            }
        }
    }
}

oF > arduino

const int val_size = 3;
int values[val_size] = {0, 0, 0};
bool isValids[val_size] = {false, false, false};

void setup(){
  Serial.begin(9600);
}

void loop(){
  bool isValids[val_size] = {false, false, false};
  if (Serial.available() >= 3*val_size) {
    int head = Serial.read();
    for (int i=0; i<val_size; i++){
      if (head == 128+i) {
        int high = Serial.read();
        int low  = Serial.read();
        values[i] = (high<<7) + low;
        if (0 <= values[i] <= 1023) {
          Serial.println(values[i], DEC);
          isValids[i] = true;
        }
      }
    }
  }
}
#include "ofApp.h"

ofSerial mySerial;
const int val_size = 3;
int values[val_size] = {123, 456, 789};
bool isValids[val_size] = {false, false, false};

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(120);
    mySerial.setup("/dev/cu.usbmodem1421",9600);
}

//--------------------------------------------------------------
void ofApp::update(){
    bool isValids[val_size] = {false, false, false};
    for (int i=0; i<val_size; i++){
        int high = (values[i] >> 7) & 127;
        int low  = values[i] & 127;
        bool byteWasWritten1 = mySerial.writeByte(128+i);
        bool byteWasWritten2 = mySerial.writeByte(high);
        bool byteWasWritten3 = mySerial.writeByte(low);
        if ( byteWasWritten1 && byteWasWritten2 && byteWasWritten3 ) {
            printf("value is %d \n", values[i]);
            isValids[i] = true;
        } else {
            printf("an error occurred \n");
        }
    }
}

oF > arduino でLEDをグラデーションに制御

const int val_size = 3;
int values[val_size] = {0, 0, 0};
bool isValids[val_size] = {false, false, false};
const int OUT_PINs[val_size] = {3, 5, 6};

void setup(){
  Serial.begin(9600);
}

void loop(){
  bool isValids[val_size] = {false, false, false};
  if (Serial.available() >= 3*val_size) {
    int head = Serial.read();
    for (int i=0; i<val_size; i++){
      if (head == 128+i) {
        int high = Serial.read();
        int low  = Serial.read();
        values[i] = (high<<7) + low;
        if (0 <= values[i] <= 1023) {
          Serial.println(values[i], DEC);
          analogWrite(OUT_PINs[i], values[i]);
          isValids[i] = true;
        }
      }
    }
  }
}
#include "ofApp.h"

ofSerial mySerial;
const int val_size = 3;
int values[val_size] = {0, 100, 200};
int steps[val_size] = {1, 5, 10};
int coes[val_size] = {1, 1, 1};
bool isValids[val_size] = {false, false, false};

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(120);
    mySerial.setup("/dev/cu.usbmodem1421",9600);
}

//--------------------------------------------------------------
void ofApp::update(){
    for (int i=0; i<val_size; i++){
        values[i] += coes[i]*steps[i];
        if (values[i] < 0){
            values[i] = - values[i];
            coes[i] = +1;
        } else if (values[i] > 255){
            values[i] = 2*255 - values[i];
            coes[i] = -1;
        }
    }

    bool isValids[val_size] = {false, false, false};
    for (int i=0; i<val_size; i++){
        int high = (values[i] >> 7) & 127;
        int low  = values[i] & 127;
        bool byteWasWritten1 = mySerial.writeByte(128+i);
        bool byteWasWritten2 = mySerial.writeByte(high);
        bool byteWasWritten3 = mySerial.writeByte(low);
        if ( byteWasWritten1 && byteWasWritten2 && byteWasWritten3 ) {
            printf("value is %d \n", values[i]);
            isValids[i] = true;
        } else {
            printf("an error occurred \n");
        }
    }
}
IMG_3921.JPG

配線:
PWMが出力可能な3,5,6ピンを使用:
3: 赤LED
5: 青LED
6: 黃LED

動画:
https://youtu.be/DOX5agKUi7M

注意

oF > arduino Megaの時

arduino MegaだとoFからのデータをreadするのに少しコツがいるようです.
megaが複数ポートあるせいでしょうか…?
keypressed等を使って, writeするタイミングを自分で決める様にすると動きました.
理由については調べているところです.
例:

int readByte = 0;

void setup(){
  Serial.begin(9600);
}

void loop(){
  if (Serial.available() > 0) {
    readByte = Serial.read();
    Serial.println(readByte, DEC);
  }
}
#include "ofApp.h"

ofSerial mySerial;
unsigned char myByte = 123;
bool isPressed = false;

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(60);

    mySerial.setup("/dev/cu.usbmodem1421",9600);
}

//--------------------------------------------------------------
void ofApp::update(){
}

//--------------------------------------------------------------
void ofApp::draw(){
    if (isPressed) {
        bool byteWasWritten = mySerial.writeByte(myByte);
        if ( !byteWasWritten )
            printf("byte was not written to serial port \n");
    }
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    if(key=='g'){
        cout << mySerial.isInitialized() << endl;
        isPressed = true;
    }

}

終わりに

以上です.
もっと良い書き方等あれば是非教えて頂ければ幸いです!!