Arduino Unoで複数のSoftwareSerialのインスタンスを使用したらうまくできなかった

# はじめに
複数のSoftwareSerialを使用した際、片方のデータ受け取りの処理しか実行されない問題が起きました。調べた結果、解決方法は各インスタンスを使用する直前にlisten()を入れてあげることでした。SoftwareSerialは同時にデータを受け取ることができるインスタンスは一つだけで適宜選択してあげる必要があり、その他のデータは破棄されるようです。

# 背景
Arduino Unoに複数のモジュールを載せてそれぞれをシリアル通信で制御しようとしました。SerialはPCにプリントデバックとして接続するのに使用しました。２種類の通信モジュールにはそれぞれSoftwareSerialのインスタンスを作成し使用しました。しかしこの２つのSoftwareSerialがどうにもうまくいかなかったのです。片方のインスタンスしか使用できませんでした。結論としてこの問題は解決しました。内容も簡単な話でしたが、知らないとハマりやすそうだったので今回記事にしてみようと考えました。


# 説明
問題、解決方法、原因及び注意点について記述しています。

## 説明で使用するインスタンス
説明及びコード例の中で使うSerial及びSoftwareSerialのインスタンスです。

|インスタンス|通信先|
|:-:|:-:|
|Serial|PC|
|softwareSerial_1|モジュールA|
|softwareSerial_2|モジュールB|


## 問題のコード
問題のコードは次の通りです。
だいぶ省略して書いてしまいましたが、イメージとしてはこんな感じです。

```
#include <SoftwareSerial.h>
// モジュールA用
SoftwareSerial softwareSerial_1(2, 3);
// モジュールB用
SoftwareSerial softwareSerial_2(4, 5);

void setup() {
  Serial.begin(115200);
  softwareSerial_1.begin(9600);
  softwareSerial_2.begin(9600);
}

void loop() {
  Serial.println("loop start")
  delay(1000);
  while(softwareSerial_1.available()){
    Serial.println("syori 1")
    // 処理1
  }
  delay(1000);
  while(softwareSerial_2.available()){
    Serial.println("syori 2")
    // 処理2
  }
}
```

出力結果
```
loop start
sryori 2
```

## 問題
これの何が問題かというと、処理1と処理2とのうち処理2しか動きませんでした。
処理1は実行されませんでした。

## 解決方法

各SoftwareSerialのインスタンスがそれぞれのメソッドを使用する直前にそれぞれのインスタンスの.listen()を入れてあげます。

実例のコードは次の通りです。
loop以外は同じです。

```
void loop() {
  Serial.println("loop start")
  softwareSerial_1.listen(); // 追加
  delay(1000);
  while(softwareSerial_1.available()){
    Serial.println("syori 1");
    // 処理1
  }
  softwareSerial_2.listen(); // 追加
  delay(1000);
  while(softwareSerial_2.available()){
    Serial.println("syori 2");
    // 処理2
  }
}
```

出力結果
```
loop start
syori 1
syori 2
```

## 原因
SoftwareSerialは複数のインスタンスを作成しても同時に使用することはできず、一つのインスタンスしかデータを受け取ることができないからです。

## 公式の説明

Arduino公式のSoftwareSerail libraryのDocumentationに書いてありました。

https://docs.arduino.cc/learn/built-in-libraries/software-serial

問題については冒頭の"Limitations of This Library"に
"It cannot transmit and receive data at the same time.
If using multiple software serial ports, only one can receive data at a time."
と書いてありました。

解決策については"listen()"の部分に
"Enables the selected SoftwareSerial object to listen. Only one SoftwareSerial object can listen at a time; data that arrives for other ports will be discarded. "
と書いてありました。

ざっくり和訳すると、
「SoftwareSerialは一つのオブジェクトしか同時にデータを受け取れず、他のものは破棄されます。listen()を使用すると受け取るオブジェクトを選択することができます。」
になるかと思います。

## データを受け取るオブジェクトの選択はbegin()でも起こる

実は上記のコードで最初に処理１が実行されなかったのはこれが原因です。

`softwareSerial_1.begin(9600);`でsoftwareSerial_1が選択された後に`softwareSerial_2.begin(9600);`でsoftwareSerial_2が選択されたため、続く`while(softwareSerial_1.available()){`ではsoftwareSerial_1のデータは破棄されてしまいました。

例えば以下のようなコードがあったとします。

```
void setup() {
  Serial.begin(115200);
  softwareSerial_1.begin(9600);
  softwareSerial_2.begin(9600);

  Serial.println("test start")
  softwareSerial_1.println("test module1 start");
　　　　delay(1000);
  while(softwareSerial_1.available()){ 
    //　モジュールAのテスト
  }
  softwareSerial_2.println("test module2");
　　　　delay(1000);
  while(softwareSerial_2.available()){ 
    //　モジュールBのテスト
  }
}
```

この場合、モジュールAのテストは実行されません。
データを受け取るオブジェクトの選択はbegin()でもできるため、listen()を使わなくてもbegin()の位置を変えるだけで解消できます。

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

  softwareSerial_1.begin(9600);
  Serial.println("test start")
  softwareSerial_1.println("test module1 start");
  delay(1000);
  while(softwareSerial_1.available()){ 
    //　モジュールAのテスト
  }
  
  softwareSerial_2.begin(9600);
  softwareSerial_2.println("test module2");
　　　　delay(1000);
  while(softwareSerial_2.available()){ 
    //　モジュールBのテスト
  }
}
```

コードの書き方が関わってくる問題なのでこの方法を使うかはお任せします。
「可読性の観点からlisten()を使って使用するオブジェクトを明示的に表現しておくのが良い。」や「一行でも削減したい！」などあるかと思います。

## 注意点
今回のコード例では書きませんでしたが、使用するモジュールによってはlistern()とavailable()との間にdelay()が必要なことがあります。今回はとりあえずで1000msとしました。適切な時間は様々かと思います。使用するモジュールの取り扱い説明書やドキュメントをよく読んでください。

またSoftwareSerialの通信速度についてですが、早過ぎるとうまくデータの受け取りができない場合があります。今回のコードでは9600bpsを使用していますがもっと遅く1200bpsにするなどの方法もあります。これについても考慮しておく必要があります。

# さいごに
今回の問題は、「複数のSoftwareSerialを使用した際、片方のデータ受け取りの処理しか実行されない」でした。解決方法は「各インスタンスを使用する直前にlisten()を入れてあげる」でした。原因は「同時にデータを受け取ることができるインスタンスは一つだけで適宜選択してあげる必要があり、その他のデータは破棄されるため」でした。

# 参考

公式が複数のSoftwareSerialを使用する場合のサンプルスケッチを公開しています。
https://docs.arduino.cc/tutorials/communication/TwoPortReceive


# あとがき
調べてみればたいした問題ではなかったのですが、筆者のようなArduino初心者はほぼ必ずと言っていいほど躓きそうな問題です。この記事が少しでも役に立てれば嬉しいです。また筆者はこの記事が初投稿になります。何か問題などありましたらご指摘ください。