Release 2017.10.26

(日本語) シリアルモニタから数値を送る

Sorry, this entry is only available in 日本語. For the sake of viewer convenience, the content is shown below in the alternative language. You may click the link to switch the active language.

ArduinoIDEにあるシリアルモニタは、Arduinoの挙動を確認するのに便利です。それに加えて、ウインドウ上部にある入力フィールドから、Arduino本体へ直接情報を送ることも出来ます。ただし、基本的にキャラクター型の1バイトずつ、つまり「1文字」でしか送れません。もし、数桁に及ぶ「数値」を投げたいなら、ちょっとした工夫が要ります。そのため、それほど使用頻度は高くありません(自分的に)。

そこで、シリアルモニタから簡単に大きい数値を送れるスケッチを考えてみました。順を追って説明していきますが、「理屈はいならい」という方は最後のまとめスケッチをどうぞ。

シリアルモニタからの送信

シリアルモニタにはデータを入れるフィールドがあり、文字を打ち込んで「送信」すると、シリアル通信を使ってArduinoにそのデータを送ることが出来ます。

送信に使う関数

シリアル通信の関数は色々用意されていて、普段良く使うものにSerial.beginとかSerial.printなんてありますが、Arduino側が受信するためには以下の関数を使います。

Serial.available()

シリアル通信を有効(Serial.begin())にすると、裏で「受信したらキャッシュする」というタスクを常時行うようになります。その蓄積したデータ数を返してきます。つまり、受け取ったバイトデータの個数を教えてくれます。最大で64個(バイト)までキャッシュできるようです。また、受信がなければ0が返ってくるので、受信有無の確認にも使われます。

Serial.read()

受信してキャッシュしているデータを1バイトずつ吐き出します。呼び出すごとにそのキャッシュはクリアされるので、Serial.available()の返り値も減っていきます。

 

Serial.available()で有無を確認したら(0個以上だったら)、Serial.read()で順次そのキャッシュ(受信したバイトデータ)を引き出していく、と言う形で使っていきます。

送信時の問題

これらを踏まえて、まず、受信したデータをそのままシリアルモニタに返すスケッチを書きます。

void setup() {
  Serial.begin(38400);
  Serial.println("watch Serial");
}

void loop() {
  if (Serial.available())
  {
    Serial.println(Serial.read());
  }
}

シリアルモニタの入力フィールドから数字を打ち込んで「送信」すれば、モニタに反応が返ってきます。

しかし、適当に数字を送信しても、違う数値しか表示されません。あるいは、aとかgとかアルファベットを打ち込んでも同様です。

この原因には2つの問題が絡んでいます。

  • シリアルモニタ側からの送信はChar型文字(ASCII文字コード
  • 1バイトサイズずつ処理してしまっている

送ったデータとは違う数字が出るのは「型」が違うからなので、とりあえず、そこだけちゃんと表示されるようにします。

    Serial.println((char)Serial.read());

上手くいったいるように見えますが、ASCII文字コードの「数値」をキャラ型として表示変換しているだけです。また、ひとまとめの数値や文字列を送ったつもりでも、1バイトずつの処理になっているので、数行に分かれてしまったりします。

本来なら、しかるべきデータ型でやりとりするのが正解なんでしょうけど、この入力フィールドからの送信データを、受信側(Arduino)で整理・補正して「数値」として受け取れるスケッチにしちゃおう、というのが今回の狙いです。

受信データをまとめる

まず、バラバラで受け取ってしまうデータをまとめていきます。

1.配列でひとまとめに

数桁(複数の数字)を一つの値として認識するために、配列へまとめてしまい、一気に処理します。

最初に、受信データサイズを「data_size」に収め、その個数分、配列変数を用意。データサイズが0以上なら、一気に「buf[]」へ格納し、表示。

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

void loop() {
  byte data_size = Serial.available();

  if (data_size > 0)
  {
    byte buf[data_size];

    Serial.print("data size:");
    Serial.println(data_size);

    for (byte i = 0 ; i < data_size ; i++)
    {
      buf[i] = Serial.read();
      Serial.print((char)buf[i]);
      Serial.print("  ");
    }
    Serial.println();
  }
}

が、これじゃ1個ずつの受信と変わりません。

1バイト受け取った時点で「deta_size > 0」が成立するため、結局、受け取った先から処理されてしまうからです。

2.データが貯まるまで待機

そこで、受信の確認があったところで、受信データが貯まるまで少し待ちます。単純に20ms(ミリ秒)ほどディレイを置きます。

void loop() {
  byte data_size = Serial.available();

  if (data_size > 0)
  {
    delay(20);
    byte buf[data_size];

    Serial.print("data size:");
    Serial.println(data_size);

なんとなく、まとまって表示されるようになりましたが、とりこぼしが出てしまいます。

「123456」と送信してみると

3.待機後、再度データ受信量を更新

上の失敗は、待機時間中に貯まった分を考慮に入れていないためです。そこで、delay後に再度、「data_size」の更新をします。

void loop() {
  byte data_size = Serial.available();

  if (data_size > 0)
  {
    delay(20);
    data_size = Serial.available();
    byte buf[data_size];

    Serial.print("data size:");
    Serial.println(data_size);

上手く表示されるようになりました。

ポイントは「一回受信を確認したら、一定時間待って再確認」ということです。

文字から数値へ

TWE-LITEからの受信や、u8glibの解説でも書きましたが、キャラ型の数字は、簡単に実数値へ変換できます。

Serial.read() – ‘0’

これを利用して、受信データを数値に変換してから「buf[]」に代入します。

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

void loop() {

  if (Serial.available() > 0)
  {
    delay(20);
    byte data_size = Serial.available();
    byte buf[data_size];

    Serial.print("data size:");
    Serial.println(data_size);

    for (byte i = 0 ; i < data_size ; i++)
    {
      buf[i] = Serial.read() - '0';
      Serial.print(buf[i]);
      Serial.print("  ");
    }
    Serial.println();
  }
}

まだ中身はバラバラですが、数桁の値を1括りの配列へまとめることは出来るようになりました。

データ整理

ここからは配列内の数字をどうまとめていくかになります。

配列をまとめる

配列に収めている1桁ずつの数値を、桁を合わせて足していき、1つの変数へまとめます。

例えば、「456」という3桁の数値を投げた場合、data_sizeは3となり、配列3個の変数が出来、1桁ごとに代入されていきます。

数値 4 5 6
組み込まれる配列 buf [0] buf [1] buf [2]

1回ごとに10倍する変数を組み込んでやれば、for文で簡単に桁数を合わせることが出来るんですが…

long recv_data;
long dub = 1;

for (byte i = 0 ; i < data_size ; i++)
{
  recv_data += buf[i] * dub;
  dub *= 10;
}

これだと「654」になってしまいます。逆の配列から桁合わせしていくために、

recv_data += buf[(data_size - 1) - i] * dub;

とします。ちなみに-1が入るのは「0」からカウントが始まるからです。

Forのi 参照する配列 配列の中身 桁(dub) 計算
0 buf [ 3 – 1 – 0 ] = buf [2] buf [2] = 6 1 6 * 1 = 6
1 buf [ 3 – 1 – 1 ] = buf [1] buf [1] = 5 1*10 = 10 5 * 10 = 50
2 buf [ 3 – 1 – 2 ] = buf [0] buf [0] = 4 10*10 = 100 4 * 100 = 400
      合計 456

ややこしい話です。

マイナスも

ついでにマイナスも判別できるようにします。先頭に‘ – ’の文字があれば、それを配列の足し算から省きつつ、最後に-1をかけて負の数値になるようにします。

long recv_data;
bool minus = 0;

for (byte i = 0 ; i < data_size ; i++)
{
  buf[i] = Serial.read();
  if (buf[0] == '-') minus = 1;
  else buf[i] -= '0';
}

long dub = 1;

for (byte i = 0 ; i < (data_size - minus) ; i++)
{
  recv_data += buf[(data_size - 1) - i] * dub;
  dub *= 10;
}

if (minus) recv_data *= -1;

まとめスケッチ

以上をまとめ、諸々を関数化するとこんな感じです。

// SKETCH : send value from text field of Serial Monitor
// 2017.10.25 jumbleat.com

void setup() {
  Serial.begin(38400);
  Serial.println("Send value from Serial Monitor");
}

void loop() {

  if (KICK_SERIAL())
  {
    long val;
    val = SERIAL_VAL();

    Serial.print("original value:");
    for (byte i = 0 ; i < 10 ; i++)
    {
      Serial.println(val);
      val -= 1;
      delay(100);
    }
    Serial.println();
  }
}


bool KICK_SERIAL() {
  bool flag = false;
  if (Serial.available() > 0)
  {
    flag = true;
    delay(20);
  }
  return flag;
}

long SERIAL_VAL() {

  byte data_size = Serial.available();

  byte buf[data_size], degree = 1;
  long recv_data = 0, dub = 1;
  bool minus = 0;

  for (byte i = 0 ; i < data_size ; i++)
  {
    buf[i] = Serial.read();

    if (buf[i] >= '0' && buf[i] <= '9') buf[i] -= '0';
    else {
      if (buf[0] == '-') minus = 1;
      else degree = 0;
    }
  }
  if (degree == 1) degree = data_size - minus;

  for (byte i = 0 ; i < degree ; i++)
  {
    recv_data += buf[(data_size - 1) - i] * dub;
    dub *= 10;
  }
  if (minus) recv_data *= -1;

  return recv_data;
}

KICK_SERIAL()関数で受信の有無を確認したら、数値にして返してくれるSERIAL_VAL()関数を呼び、適当な変数に代入。

こんな感じで使います。loop内のサンプルでは、数値を受け取ったら、1ずつ減算した値を連続表示するようにしました。

ただし、「文字」を受ける事は考慮に入れていないので、そうしたい場合は別個そういうプログラムを作らないといけません。あしからず。

*SERIAL_VAL()関数内は、「‘ – ’は先頭に」「数字のみ」、というルールを無視したら、0を返すようにしました。

リアルタイムで連続的に数値制御をしたいなら、ロータリーエンコーダや、可変抵抗を使ったほうが断然手軽です。でも、こんな感じでちょっとした数値変更を手軽にArduinoへ送れる点では結構利用価値があるのでは、と。

参考リンク

Leave a Reply

Your email address will not be published. Required fields are marked *

CAPTCHA


This site uses Akismet to reduce spam. Learn how your comment data is processed.