2016年7月9日土曜日

ミツトヨのデジマチック出力をArduinoで受信してみる

電池入れ替えたんだけど動かないというミツトヨのデジタルインジケータを借りてきたんだけど、原因は前の電池が液漏れしていた?ので端子の接触不良だけだったという落ち…
ミツトヨの製品ってノギスもそうだけど電池の入れるところめっちゃ金ピカ。多分メッキがしっかりしているのかな?
なので今回は普通にアルコールつけた綿棒とか爪楊枝で傷をつけないようにゴシゴシしただけでピッカピカに!治ってしまった。電池もSR44っていうピッカピカのやつ使ってる。さすが高級な計測器。

ネタが無くなってしまったので最初電池と間違って開けた蓋に出力が出ていたので調べてみると専用のケーブルとオプションでボタンを押すと測定値をエクセルに入力したりできるらしい。

この出力ポート、デジマチック出力と言うみたいだけど海外だとSPC Outputって呼ばれている?ようでArduinoでデータを取得してる例があったので試してみた。しかもスケッチまで公開してくれている!データ的には4bitずつのnibbleで数値はBCD、符号と小数点の位置が含まれてる感じっぽい。

デジマチック出力は同期式シリアル通信のようでClock、Dataとデータ要求のREQとGNDの4本線が出てる。コネクタが5ピンなのはホールドユニット用かな?REQは電池に100kΩ程度でプルアップされていて、ClockとDataはオープンコレクタらしい。REQをGNDに落とすために2SC1815などのNPNトランジスタを使用し、ClockとDataは内蔵プルアップでも行けそうだったので、Arduinoのピン設定でINPUT_PULLUPにしてみた。

純正ケーブルだと10Pinのピンヘッダ用出力なんだけど、今回はお試しだったので端子形状に合わせたスポンジ10x5mmを突っ込んでそこにArduino用のピンヘッダケーブルを無理やり差し込んでつないだ。(良い子は真似しないように)
ちなみに純正ケーブルでスイッチ付きのやつは10pinのところの4番Pinに出てるみたいで、片方はGNDに落ちてるらしい。つまりこのスイッチを使いたければこいつもINPUT_PULLUPにしておいて、LOWになっていたら押されてる判定ができる。

上記サイトのコードをコピペしてもうまく動かなかったのでちょっと修正。
#define REQ 5
#define DAT 2
#define CLK 3

void setup() {
  Serial.begin(115200);
  pinMode(REQ, OUTPUT);
  pinMode(DAT, INPUT_PULLUP);
  pinMode(CLK, INPUT_PULLUP);
  digitalWrite(REQ, LOW);
}

void loop() {
  byte mydata[14];
  digitalWrite(REQ, HIGH);
  for (uint8_t i = 0; i < 13; i++ ) {
    uint8_t k = 0;
    for (uint8_t j = 0; j < 4; j++) {
      while ( digitalRead(CLK) == LOW) { // CLK HIGHが来るまで待つ
      }
      while ( digitalRead(CLK) == HIGH) { // CLK LOWが来るまで待つ
      }
      bitWrite(k, j, (digitalRead(DAT) & 0x1)); // DATピンを読んで順番に格納(最初に読んだのがMSB)
    }
    mydata[i] = k;
  }
  digitalWrite(REQ, LOW);

  uint8_t sign = mydata[4];//符号
  uint8_t decimal = mydata[11];//小数点の位置
  //uint8_t units = mydata[12];

  //char buf[7];
  //for (int lp = 0; lp < 6; lp++) {
  //  buf[lp] = mydata[lp + 5] + '0';
  //}
  //buf[6] = 0;

  char buf[9];
  if ((mydata[0] == 0xF) && (mydata[1] == 0xF)) {
    for (uint8_t lp = 0; lp < 6; lp++) {
      buf[lp] = mydata[lp + 5] + '0';
    }
    buf[6] = 0;
  } else if ((mydata[0] == 0x8) && (mydata[1] == 0xF)) {
    //デジマチック2フォーマット(8桁対応
    for (uint8_t lp = 0; lp < 2; lp++) {
      buf[lp] = mydata[lp + 2] + '0';
    }
    for (uint8_t lp = 0; lp < 6; lp++) {
      buf[lp + 2] = mydata[lp + 5] + '0';
    }
    buf[8] = 0;
  }
  float num = ((atol(buf) / pow(10, decimal)) * ((sign - 4) / 4));
  Serial.println(num);
}
とりあえずこんな感じでArduino UNOで動いた。割り込みを使った例もあったけどそこらへんは用途による?とりあえず修正ついでに小数点の位置と符号にも対応しておいた。

Arduino LeonardoでUSBキーボードとして認識させれば純正のデバイスみたいにボタンを押したら数値を入力してくれるようなデバイスが作れそう…

追記:
スケッチをデジマチック d2(8桁対応)に一部変更しました。(未検証)

10 件のコメント:

  1. 始めまして、こちらを参考にさせていただき、デジマチックをArduinoで受けようとしている、プログラム初心者です。

    私の場合はデジタルインジケータではなく測長ユニットの信号を受けようと思っており、取り急ぎnWorld様のスケッチで動かしてみて、何とか通信を出来ている状況までたどり着けました。(感謝いたします)
    ですが、バイナリでビットの取りこぼしが判明し、手探りで何とかやろうとしてますが、どうも上手くいきません。

    どうもデジマチックは非同期シリアルの様で、SerialBeginに要因があるのかな?と悩んでいる状況です。

    最後のコメントに「割り込みを使った例もあったけどそこらへんは用途による?」とありますが、どこに情報があるかご教示いただけないでしょうか?
    恐れ入りますが、よろしくお願い申し上げます。

    返信削除
    返信
    1. コメントありがとうございます!

      プログラムの説明をしていなかったので念のため簡単な説明をしますと、digitalWrite(REQ, HIGH);でREQをHIGHにすると(トランジスタにより測定器側のREQはLOWになり)デジマチック測定器からデータが流れてきます。
      デジマチックはCLK(クロック)とDAT(データ)の2つの出力を使った同期式シリアル通信で、CLKがLOWになった瞬間のDATピンの状態を見てデータを受信しています。
      規定の長さのデータの受信が終わったらdigitalWrite(REQ,LOW);でREQピンをLOWにして次回のデータ送信要求に備えます。
      最後にデータを整形してシリアルコンソールに流してます。

      ちなみにSerial.begin(115200);はパソコンとの通信用のシリアルポートの初期化なのでデジマチック通信用ポートとはあまり関係がないと思います。

      割り込みを使った例はArduinoのフォーラムで見かけた気がします…
      https://forum.arduino.cc/t/mitutoyo-digimatic-spc/78312/4

      割り込み処理を使っていないスケッチですと、データの受信中は何もできなくなるので、他に色々処理をしないといけないものを作るときには割り込み処理を使ったスケッチのほうが有効だと思います。

      ちなみになのですが、デジマチックは6桁分の数字データを送信できますが、デジマチックd2になると8桁までの数字データを送信できるようです。当時のプログラムではd2までは対応してないので、もし測定器側が8桁出力に対応で8桁出力設定になっている場合、一旦6桁出力設定に変更してみると良いかもしれません。
      よろしくお願いします。

      削除
  2. nWorld様

    お世話になります、ご回答ありがとうございます。
    当方も確認と返信が大変遅くなり失礼いたしました。。

    また仰る通り、質問文は「非同期シリアル」は「同期シリアル」の誤記でした。同期シリアルで相違ございません。

    フォーラムのご紹介、ありがとうございます。
    コピペでは動かせませんでした。。
    読み砕いて通信をとれるまでにかなり時間がかかりそうですが、細々と頑張ってみます(精神論ですみません)!

    なるほどですね、恐らくd2ではないと想像しておりますが、
    念のためメーカにも問合せてみました。

    長い時間をかけてトライしてみます。

    返信削除
    返信
    1. 返信ありがとうございます!

      こちらも色々調べてみたのですが、まずは通信だけ確認だけであれば割り込みを使わない方法を最初に試すのが良さそうですね。後に他の処理を入れた時に処理負荷に応じて検討されたほうが良いかと思います。

      データがどこまで取れているのか確認するためにSerial.printなどで変数の中身をシリアルコンソールに吐き出して確認してみると良いかもしれないです。

      念のため上のサンプルスケッチをd2対応のものにアップデートしてみましたのでもしd2のデータが来ているようでしたらお試しいただければと思います。(40行目と45行目で普通のデジマチックかd2かを判別するようにしてみました。)

      うまく通信できることを願っています。

      削除
    2. nWorld様

      お世話になります、先日ご教示いただいたモカです。
      このPGMを書き込んだところ、バイナリで綺麗に取込めました!
       https://forum.arduino.cc/t/mitutoyo-digimatic-spc/78312/53

      心より感謝申し上げます!

      あとは、以下のデータ整理が今後の課題となりそうです。

      ・Bitデータが下位から出力される
       (12.35 なら 1000 100 1100 1010)
       0:0
       1:1000
       2:100
       3:1100
       4:10
       5:1010
       6:110
       7:1110
       8:1
       9:1001

      ちょっと悩んでみます~。

      削除
    3. なるほど、バイナリでデータが読めるようになったのですね!

      もしかしてDATピンの論理が反転しているのでしょうか?
      出てきたデータを反転してやるとBCDで整数に直せそうです。
      1000 0100 1100 1010 → 0001 0010 0011 0101 → 0x1 0x2 0x3 0x5

      NOT(~)をつかってビットリーバスしてやって上のスケッチの最後の方のように整数に直してみるともしかしたら行けるかもしれないです?

      削除
    4. nWorld様

      >出てきたデータを反転してやるとBCDで整数に直せそうです。
      >1000 0100 1100 1010 → 0001 0010 0011 0101 → 0x1 0x2 0x3 0x5

      まさしくビットリバースでした。
      ミツトヨに問い合わせてみたところ、デジマチックのデータフォーマットのビットリバースは仕様だそうです。

      お教えいいただいたフォーラムでもビットリバースの対応がかなり熱く議論されており、この記事をアレンジしてようやく正しい値を出力される様になりました。
      https://forum.arduino.cc/t/mitutoyo-digimatic-spc/78312/88

      ここのビットリバースの対応は、アドバイスいただいた方法ではなく、
       1000なら1
       0100なら2 を出力
       :
      という方法でした。

      他の方々も試行錯誤されておりますが、もう少しスマートな方法がないか…

      まずは、おかげ様様で「デジマチックをHEXで正しく表示できる」をクリアできました!

      心より御礼申し上げます。

      削除
    5. やはりビットリーバスですね。すみません、この前言っていたNOTはビット反転でリバースと勘違いしていました。意外とビットリーバスは面倒そうですね…

      フォーラムの方では決め打ちで変換しているようですが、少しだけスマートそうな方法を見つけました。
      https://forum.arduino.cc/t/quickly-reversing-a-byte/115529/4

      これを使ってテストプログラムを作ってました。

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

      void loop() {
      uint8_t bcd = B1100;
      Serial.print("Data:");
      Serial.println(bcd, BIN);
      Serial.print("Reverse:");
      Serial.println(flipByte(bcd) >> 4, BIN);
      Serial.print("BCD:");
      Serial.println(flipByte(bcd) >> 4, HEX);
      delay(1000);
      }

      byte flipByte(byte c)
      {
      c = ((c >> 1) & 0x55) | ((c << 1) & 0xAA);
      c = ((c >> 2) & 0x33) | ((c << 2) & 0xCC);
      c = (c >> 4) | (c << 4) ;
      return c;
      }

      これで1100を3にすることができました。

      削除
    6. nWorld 様

      情報提供、検証までしていただき、ありがとうございます。
      しかもビットリバースのテストコードまで! (TmT

      こちらも 0~F までリバースしてくれるのを確認できました。

      決め打ち変換を、こちらに置き換えて調整すれば使えそうです。
      が、置き換えられる頭が…

      一旦別件を進めますので期間は開いてしまいますが、
      折角ここまでご提供いただいたので…日を改めて頑張ってみます。
      (未定ですが進展ありましたら、改めて報告に上がります。)

      大変助かりました、貴重なお時間を割いてご支援いただき、
      誠にありがとうございました!!

      削除
    7. いえいえ、
      無事動作することを願っております!

      削除