つれづれなる備忘録

日々の発見をあるがままに綴る

TinkercadによるArduinoシミュレーション7 ~ Arduino同士のシリアル通信応用

前回のArduino間のシリアル通信について今回はもう少し掘り下げて紹介してみようと思う。

1. 複数バイトのデータを送受信する場合

 255以上の整数や小数を送る場合やRGBのように3つ1組のデータをシリアル通信で送受信する場合に相当する。簡単な方法として1バイトのデータをいくつかまとめてパケットとして送信するようにすればよい。詳しい方法はArduinoでバイナリ送受信のシリアル通信をするときのパケットの構造 - Qiitaにあるが、その中の「複数の0-255を送りたい」の部分を利用したTinkercadでのシミュレーションを紹介したいと思う。下の構成ではTinkercadのRGB LED(LEDの各脚に電圧を加えることでRGBの混色を発光するLED)に定電圧電源を用いてそれぞれ独立に電圧を加えたときの、電圧値をアナログピンで読み取りシリアル通信で別のArduinoへ送信するというものである。RGB LEDはTinkercadコンポーネント-すべてに切り替えると選択することができる。LEDのそれぞれの脚は左からR,GND,B,Gとなっているが、下図のリード線の配色と一致させている。アナログピンへの入力はA0からA2までRBGの順で接続している。またLEDには保護抵抗300Ωを入れている。

3色LEDのRGB値を送信する構成
3色LEDのRGB値を送信する構成
Arduino1で各色の電圧(0-244)を読み取り送信し、Arduino2で送信された電圧値をRGBに分けてシリアルモニタに表示するスケッチを以下に示す。

//Arduino1:アナログピンの電圧を読み取り、Arduino2へ送信
void setup()
{ 
  Serial.begin(9600);
}

void send()
{
  uint8_t ra=analogRead(A0)/4; //順次各アナログピンから電圧(0-255)を読み出す
  uint8_t ga=analogRead(A1)/4;
  uint8_t ba=analogRead(A2)/4;
  uint8_t r=constrain(ra,0,254); // 255が入力されたら254を返す
  uint8_t b=constrain(ga,0,254);
  uint8_t g=constrain(ba,0,254);
  
  Serial.write(255); //ヘッダーデータ'255'を送信
  Serial.write(r); //ヘッダーに続いてR,G,Bの1バイトデータを送信
  Serial.write(g);
  Serial.write(b);
}
void loop()
{
  send();
  delay(1000); //適当な休止時間を設定する
}

Arduino1では関数send()を定義してloop関数内で呼び出す。前回と同様にアナログピンの入力をanalogRead(A0)で読み出すが、4で割ることで1024→256へ変換して1バイトのデータにする。 次に255をデータ読み取り開始のヘッダーとして扱うため、RGB自体のデータは255を除外するためcontrain(x,a,b)を使用する。この関数の引数xは入力で、a以上b以下の範囲ではxはそのまま、xがaより小さいときはa、bより大きいときはbが返る。上のスケッチでは例えばconstrain(ra,0,254)としているのでraが255のときには254となり、あとはraの値がそのまま返されることになる。同様の処理をアナログピンA1,A2で実行し、RGB LEDに入力されている3色の電圧(0-254)をuint8_t型の変数r,b,gに格納する。次に255,r,g,bの順に1バイトづつ送信することで複数バイトのデータを送信する。

//Arduino2:Arduino1からデータを受信し、RGB値をモニタ出力する
void setup()
{
  Serial.begin(9600);
}
void read()
{
  if(Serial.available()){  //受信データがあるかどうか
    uint8_t data=Serial.read(); // 1バイト読み込み変数dataに格納
    if(data==0xFF){  // dataが255ならば以下の処理
          uint8_t r=Serial.read(); // R,G,B順での1バイトづつデータを受信
          uint8_t g=Serial.read(); // 
          uint8_t b=Serial.read();
          Serial.print("R:"); //シリアルモニタに表示
          Serial.print(r);
          Serial.print("G:");
          Serial.print(g);
          Serial.print("B:");
          Serial.println(b);
    }
  }
  delay(1000); //適当な休止時間を設定
}
void loop()
{
  read();
}

Arduino2ではread()関数を定義し、loop関数内で呼び出す。読み出し側のスケッチも前回同様Serial.available()で受信したデータのバイト数を取得して1バイト以上であれば、データを受信したとして処理を進める。まずSerial.read()で1バイトだけデータを読み込んだときにヘッダーとして割り当てていた255(HEX:0xFF)と一致したときに有効なパケットデータを取得したとして、さらにRGBを順次1バイトづつ読み込みシリアルモニタに表示する。RGB LEDに接続する定電圧電源のノブを動かすとLEDの色が変わると同時に、シリアルモニタのRGB値が変化するのが確認できると思う。

2. シリアルポートを増やす場合

 Arduino Unoの場合シリアル通信はデジタルピンの0(Rx)と1(Tx)を用いるが、接続するデバイスが複数になるなど複数のシリアルポートが必要な場合、ソフトウェアシリアルを用いると増やすことができる。ただし、0,1ピンでのハードウェアシリアルに対して通信速度・安定性に関してソフトウェアシリアルの方が劣るようなので*1データ量の多い通信や応答の速さが必要な場面に関しては不向きと思われる。ソフトウェアシリアルを利用するには、最初に#include <SoftwareSerial.h>とタイプするかTinkercadのライブラリボタンからSoftwareSerialのところ"含める"をクリックすることで#include <SoftwareSerial.h>が挿入される。なおライブラリボタンをクリックしたときに表示されるものがTinkercadでサポートされるライブラリとなる。

Tinkercad上からライブラリを選択
Tinkercad上からライブラリを選択
今回はソフトウェアシリアルを用いてArduino1とArduino2の10ピンをRx,11ピンをTxに設定し、Arduino1でのカウンタ値をArduino2へ送信し、Arduino2のシリアルモニタに表示するスケッチを示す。なおArduino1の10ピンとArduino2の11ピンを接続する。(GND同士も接続しておく)

//Arduino1 count値を生成し送信
#include <SoftwareSerial.h>

SoftwareSerial Peri(10,11); //Rx, Txをソフトウェアシリアルで定義
int BR=4800;
int count=0;
void setup(){
 Serial.begin(BR);
 Peri.begin(BR);
}
void loop(){
  count++;
  Peri.write(count);// count値を送信
  delay(500);
}
//Arduino2 count値を受信し、モニタに表示
#include <SoftwareSerial.h>

SoftwareSerial Cent(10,11); //Rx, Txをソフトウェアシリアルで定義
int BR=4800;
int count=0;
void setup(){
  Serial.begin(BR);
  Cent.begin(BR);
}
void loop(){
  if(Cent.available()){
    int val=Cent.read(); //11ピンからデータ読み出し
    Serial.println(val); //シリアルモニタに表示
  }
}

まず送信側はペリフェラル(Peripheral)としてSoftwareSerial Peri(10,11);とすることで10ピンをRx,11ピンをTxとして定義し、またPeriというオブジェクトを生成することができる。あとはシリアル通信のときと同様の関数を使用することができる。受信側も同様にセントラル(Central)としてSoftwareSerial Cent(10,11);により10ピンをRx,11ピンをTxとして定義し、またCentというオブジェクトを生成する。シリアルモニタへの表示はSerial.println();を使用する。Tinkercadのシミュレーション上はボーレート(変数BR)を4800bpsにすると正常にカウントアップされていくが、例えば9600bpsにすると数値が1からスタートしなかったりカウント自体が失敗する。この点がソフトウェアシリアルにすると高い速度で不安定になることを表していると考えられる。(Tinkercadのシミュレーションがどこまで実機を反映しているか不明なので、この点は実機検証が必要)なおソフトウェアシリアルを用いて複数のソフトウェアシリアルポート(Rx,Tx)を設定する場合、時間的に同時に通信させることはできず.listen()で切り替える必要がある。

3. 双方向通信

今まで取り上げた例を応用してArduino同士の双方向通信の例を示したい。具体的にはArduino1ではカウントアップをしていき、Arduino2から'g'(シリアルモニタの窓からユーザが入力)というコマンドを受信したら、その時のカウント値をArduino2へ送信し、Arduino2は受信したArduino1のカウント値をシリアルモニタに表示するというもの。Arduino1とArduino2の間の送受信はソフトウェアシリアルにより10ピンと11ピンを用いる。接続はArduino1の10ピンとArduino2の11ピン、Arduino1の11ピンとArduino2の10ピンを接続する。(図参照)

Arduino双方向通信
Arduino双方向通信
スケッチは以下のようにする。いままでのスケッチを応用しているが、文字"g"をArduino1に送信するところは.write()ではなく.print()を用いる。

// Arduino1: 'g'を受信したらcount値を送信
#include <SoftwareSerial.h>

SoftwareSerial Peri(10,11); //Rx, Tx
int BR=4800;
int count=0;
void setup(){
 Serial.begin(BR);
 Peri.begin(BR);
}
void loop(){
  count++;
  if (Peri.available()){ //Centralからの入力
    char a=Peri.read();
    if(a=='g'){ //Centralから'g'が入力されたら、count現在値を返す
        Peri.write(count);
       }
  }
  delay(500);
}
// Arduino2: Arduino1のcount現在値を表示
#include <SoftwareSerial.h>

SoftwareSerial Cent(10,11); //Rx, Tx
int BR=4800;
int count=0;
void setup(){
  Serial.begin(BR);
  Cent.begin(BR);
}
void loop(){
  
  if(Serial.available()){
    char com=Serial.read(); //シリアルモニタからの入力読み込み
      if(com=='g'){
        Cent.print("g");//'g'が入力されたら'g'を送信
        delay(100);//受信までの待ち時間
        if(Cent.available()){ //Peripheralからのデータ受信
            int val=Cent.read();
            Serial.println(val);//Peripheralのcount値表示
        }
    }
  }
  else{}
}

4. まとめ

 Arduino同士のシリアル通信の応用として複数バイト送信、ソフトウェアシリアル、ソフトウェアシリアルを用いた双方向通信を取り上げた。簡素化している部分はあるが、事例としてはだいぶ実用に近づいてきていると思う。