esp32同士でシリアル無線通信をして,無線Lチカ・光量制御をしてみる(Arduino IDE使用)

Electronics

esp32とPCを無線通信する記事は結構ありましたが,esp同士を無線通信する記事はあまり見つからなかったので,本記事にまとめてみました.

esp32とは?

esp32は,Wifi通信とBLE(Bluetooth Low Energy)通信を兼ね備えたマイクロコントローラです.Arduino環境で使用することができ,おまけにかなり安いです.私がAmazonで買ったのは2個で2,000円程度(以下 amazon リンク)でした.技適認証がちゃんと通っているのがありがたいです(安い無線モジュールは技適が通っていないことも多い).

もともと,Xbee + Arduinoで無線通信を試してみようと思っていたのですが,使っている最中に壊れてしまい,esp32を買ってみたところ非常に使いやすかったので,これを使うことにしました.Xbeeが壊れた原因はおそらく3.3V以上の電圧を印加してしまったことが原因なのではないかと思っています.ネット上の情報でファームウェア更新をしようと思いましたが,どれをやってもうまくいきませんでした.また,Xbeeは1個2,500円程度するので一度壊れるとなかなか買い直す気にはなりません…

esp32をArduino IDEで使う準備

セットアップに関しては,わかりやすくまとめてくれているサイトがあったので,こちらのサイトをベースにセットアップしました.

簡単に手順をまとめておきます.

  1. Arduino core for the ESP32ライブラリをインストール
  2. USB-UARTブリッジドライバをインストール
  3. esp32にソフトを書き込んでみる

Arduino core for the ESP32ライブラリをインストール

Arduino IDEが~/Downloads/arduino-1.8.12にインストールされている場合,以下の手順でESP32ライブラリをインストールできます.

$ mkdir -p ~/Downloads/arduino-1.8.12/hardware/espressif
$ cd ~/Downloads/arduino-1.8.12/hardware/espressif
$ git clone https://github.com/espressif/arduino-esp32 esp32
$ cd esp32/tools
$ python get.py
$ git submodule update --init --recursive

最後の手順を忘れると「ヘッダが見つかりません.」と言われてしまうのでご注意を.(参考

USB-UARTブリッジドライバをインストール

このサイトによると,esp32を認識するドライバがない場合は,インストールする必要がありそうです.OSに合ったものをインストールしてください.

ubuntu 18.04を使っている場合は,この方法でプリインされているモジュールをロードできると思います.(参考

sudo modprobe cp210x

ここで私は少しつまりました.ドライバのインストール自体は成功したようですが,なぜかUSBが認識されません.私の場合はubuntu 18.04を使っていますが,kernelのバージョンを変更したらUSBが認識されるようになりました.kernelは以下バージョンを使用しています.(参考まで)

4.15.0-101-generic

esp32にソフトを書き込んでみる

esp32に実際にソフトを書き込むときは,以下のようにArduino IDEを設定して書き込みました.

⬛ 備考
Flash Frequencyは40MHzでないと書き込めないと書いてあるサイトもありましたが,私の場合は80MHzで書き込めました.あと,書き込み中にesp32のBOOTボタンを押さないと書き込みに失敗するという記事もありましたが,私の環境ではBOOTボタンを押す必要はありませんでした.esp32の場合,普通のArduinoより書き込みに少し時間がかかるので多少辛抱が必要です(^^);

BLE通信を使った無線Lチカ

esp32を使ったBLE無線シリアル通信のやり方はこちらのサイトでかなりわかりやすく書いてあったので,非常に参考になりました.本記事ではBLE通信ができているかどうかを確かめるために,無線でLチカしてみます.下記は,送信側のesp32に接続されているスイッチが押されると,受信側のLEDが点灯するサンプルです.

⬛ 手順

  1. 受信側のBluetoothのMACアドレスを調べる.
  2. 配線する.
  3. 送信側と受信側それぞれに対応するプログラムを書き込む.

⬛ 必要なもの

  • esp32 x 2
  • LED x 1
  • 抵抗 x 1

ソースコード

まず,1の手順からですが,以下のプログラムを受信側のesp32に書き込んで,シリアルプロットで結果を見てください.

void setup(void) {
  Serial.begin(115200);
  Serial.println("-----------------");
  uint8_t macBT[6];
  esp_read_mac(macBT, ESP_MAC_BT);
  Serial.printf("%02X:%02X:%02X:%02X:%02X:%02X\r\n", macBT[0], macBT[1], macBT[2], macBT[3], macBT[4], macBT[5]);
}

void loop() {
  delay(1000);
}

私のesp32の場合,24:62:AB:DC:30:5Eと表示されていたので,このMACアドレスを送信側のコードに埋め込みます.

つぎに,受信側・送信側に以下のプログラムを書き込みます.

⬛ 送信側

#include "BluetoothSerial.h"

BluetoothSerial SerialBT;

String MACadd = "24:62:AB:DC:30:5E";
uint8_t address[6]  = {0x24, 0x62, 0xAB, 0xDC, 0x30, 0x5E};
bool connected;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32test", true); 
  Serial.println("The device started in master mode, make sure remote BT device is on!");
  
  // connect(address) is fast (upto 10 secs max), connect(name) is slow (upto 30 secs max) as it needs
  // to resolve name to address first, but it allows to connect to different devices with the same name.
  // Set CoreDebugLevel to Info to view devices bluetooth address and device names
  connected = SerialBT.connect(address);
  
  if(connected) {
    Serial.println("Connected Succesfully!");
  } else {
    while(!SerialBT.connected(10000)) {
      Serial.println("Failed to connect. Make sure remote device is available and in range, then restart app."); 
    }
  }
  // disconnect() may take upto 10 secs max
  if (SerialBT.disconnect()) {
    Serial.println("Disconnected Succesfully!");
  }
  // this would reconnect to the name(will use address, if resolved) or address used with connect(name/address).
  SerialBT.connect();

  pinMode( 2, OUTPUT);
  pinMode(14, INPUT_PULLUP);

  // Sign for the end of bluetooth setup.
  for (int i=0; i<3; i++) {
    digitalWrite(2, HIGH);
    delay(50);
    digitalWrite(2, LOW);
    delay(50);
  }
}

void loop() {
  if (digitalRead(14) == LOW) {
    Serial.println("LED is ON.");
    SerialBT.write('T');
  }
  delay(20);
}

⬛ 受信側

#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32test"); //Bluetooth device name
  Serial.println("The device started, now you can pair it with bluetooth!");
  pinMode( 2, OUTPUT);
  pinMode(14, OUTPUT);

  // Sign for the end of bluetooth setup.
  for (int i=0; i<3; i++) {
    digitalWrite(2, HIGH);
    delay(50);
    digitalWrite(2, LOW);
    delay(50);
  }
}

void loop() {
  if (SerialBT.available()) {
    if (SerialBT.read() == 'T') {
        digitalWrite(14, HIGH);
    }
  }
  delay(20);
  digitalWrite(14, LOW);
}

配線

⬛ 送信側

GPIO14 -> スイッチ -> GND

⬛ 受信側

GPIO14 -> LEDアノード -> LEDカソード -> 抵抗(220Ω) -> GND

これで目的の挙動になると思うので試してみてください!

応用編:ジョイスティックでLEDの明るさを調節する

応用編として,ジョイスティックによって受信側のLEDの明るさを調節するということもやってみました.

[必要なもの]

  • esp32 x 2
  • LED x 2
  • 抵抗 x 2
  • ジョイスティック x 1

ソースコード

⬛ 送信側

  • 4byteの送信データsend_dataを準備する
  • send_data[0]に文字列’T’を格納する
  • ジョイスティックから入力されたアナログデータを0〜255までの値に変換し,send_data[1]とsend_data[2]に格納する
  • send_data[1]とsend_data[2]からchecksumを計算し,send_data[3]に格納する
  • send_dataを受信側に送る

checksumとは送信側と受信側でデータが食い違っていないかを調べるデータです.これにより,データの改ざんやパケットロス等を検知します.

ちなみに,以下のようにSerial.print()の関数を使うと処理に時間がかかってしまい,20msではデータが送信できなくなってしまいます.Serial.print()はデバッグのとき以外は使わないほうが無難ですね.

Serial.print("X-axis: ");
Serial.println(left_x_val);

⬛ 受信側

  • SerialBT.readBytes(recv_data, 4)で4byteのデータを読む
  • 最初のデータが’T’になっていることを確認する
  • checksumのデータが誤っていないかを確認する
  • recv_data[1]とrecv_data[2]の値でLEDの明るさを調節する

recv_data[1]で緑色のLEDを,recv_data[2]で青色のLEDを制御しています.

一般的にArduinoでLEDの明るさを調節する場合,analogWriteで調節すると思いますがesp32には,この関数がありません.そこで,ledcWriteという関数を使います.ledcWriteに関しては,このサイトを参考にしました.

analog入出力はできるポートとできないポートがあるのでその点も注意が必要です.たとえば,私のesp32開発ボードでは,GPIO2はビルトインLEDに使われていたので,このポートはanalog入力に使うことができません.ポートの対応はこのサイトを見ました.

以下,ソースコードです.

⬛ 送信側

#include "BluetoothSerial.h"
#define BUILTIN_LED 2
#define LEFT_X_PIN 12
#define LEFT_Y_PIN 13

BluetoothSerial SerialBT;

String MACadd = "24:62:AB:DC:30:5E";
uint8_t address[6]  = {0x24, 0x62, 0xAB, 0xDC, 0x30, 0x5E};
bool connected;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32test", true); 
  Serial.println("The device started in master mode, make sure remote BT device is on!");
  
  // connect(address) is fast (upto 10 secs max), connect(name) is slow (upto 30 secs max) as it needs
  // to resolve name to address first, but it allows to connect to different devices with the same name.
  // Set CoreDebugLevel to Info to view devices bluetooth address and device names
  connected = SerialBT.connect(address);
  
  if(connected) {
    Serial.println("Connected Succesfully!");
  } else {
    while(!SerialBT.connected(10000)) {
      Serial.println("Failed to connect. Make sure remote device is available and in range, then restart app."); 
    }
  }
  // disconnect() may take upto 10 secs max
  if (SerialBT.disconnect()) {
    Serial.println("Disconnected Succesfully!");
  }
  // this would reconnect to the name(will use address, if resolved) or address used with connect(name/address).
  SerialBT.connect();

  pinMode(BUILTIN_LED, OUTPUT);

  // Sign for the end of bluetooth setup.
  for (int i=0; i<3; i++) {
    digitalWrite(BUILTIN_LED, HIGH);
    delay(50);
    digitalWrite(BUILTIN_LED, LOW);
    delay(50);
  }
}

uint8_t calculate_checksum(uint8_t *data) {
  uint8_t checksum = 0;
  checksum |= 0b11110000 & data[1];
  checksum |= 0b00001111 & data[2];

  return checksum;
}

void loop() {
  int left_x_val = 0;
  int left_y_val = 0;
  uint8_t send_data[4];
  
  left_x_val  = analogRead (LEFT_X_PIN) >> 4;
  //Serial.print("X-axis: ");
  //Serial.println(left_x_val);
  
  left_y_val  = analogRead (LEFT_Y_PIN) >> 4;
  //Serial.print("Y-axis: ");
  //Serial.println(left_y_val);

  send_data[0] = 'T';
  send_data[1] = left_x_val;
  send_data[2] = left_y_val;
  send_data[3] = calculate_checksum(send_data);
  SerialBT.write(send_data, 4);

  delay(20);
}

⬛ 受信側

#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

BluetoothSerial SerialBT;

#define BUILTIN_LED 2
#define LED_PIN_GR 14
#define LED_PIN_BL 15

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32test"); //Bluetooth device name
  Serial.println("The device started, now you can pair it with bluetooth!");
  
  pinMode(BUILTIN_LED, OUTPUT);
  pinMode(LED_PIN_GR, OUTPUT);
  pinMode(LED_PIN_BL, OUTPUT);

  // Sign for the end of bluetooth setup.
  for (int i=0; i<3; i++) {
    digitalWrite(BUILTIN_LED, HIGH);
    delay(50);
    digitalWrite(BUILTIN_LED, LOW);
    delay(50);
  }

  ledcSetup(0,12800,8);
  ledcAttachPin(LED_PIN_GR, 0);
  ledcSetup(1,12800,8);
  ledcAttachPin(LED_PIN_BL, 1);
}

uint8_t calculate_checksum(uint8_t *data) {
  uint8_t checksum = 0;
  checksum |= 0b11110000 & data[1];
  checksum |= 0b00001111 & data[2];

  return checksum;
}

void led_control(uint8_t *data) {
  ledcWrite(0, data[1]);
  ledcWrite(1, data[2]);
}

void data_decode() {
  uint8_t recv_data[4];
  if (SerialBT.available()) {
    SerialBT.readBytes(recv_data, 4);
    
    if (recv_data[0] != 'T') {
      Serial.print("Receive error!");
      return;
    }

    if (recv_data[3] != calculate_checksum(recv_data)) {
      Serial.print("Decode error!");
      return;
    }
    
    led_control(recv_data);
  }
  
  return;
}

void loop() {
  data_decode();
  delay(20);
}

配線

⬛ 送信側

esp32ジョイスティック
GPIO12VRX
GPIO13VRY
3V3+5V
GNDGND

⬛ 受信側

esp32LED
GPIO14緑色のLED
GPIO15青色のLED

動画

esp32の無線通信機能を使って、LEDの光量制御を行う

ジョイスティックを左上に倒すと両方ともLEDが消灯し,右下に倒すと両方ともLEDが点灯しています.

参考サイト

コメント

  1. サイトカイン says:

    こんにちは 
    ESP32同士で無線通信をしたいと思っていたので、大変参考にさせていただきました。
    ありがとうございます。
    主様のプログラムを参考にスイッチとLEDを1つずつ増やし、それぞれ else if を使ってプログラムしたところ増設したLEDがうまく点灯しませんでした。
    もしよければ、解決方法を教えていただけないでしょうか?

    • サイトカインさん

      コメントありがとうございます.管理人です.

      以下の原因が考えられるかなと思いました.
      ①スイッチ,LEDに使用したピンがそれぞれ入力ポート,出力ポートとして使えるピンではない
      ②setup() で,受信機側と送信機側の pinMode の設定をしていない

      ①に関しては,以下のサイトを参考にすると良いと思います.たとえば GPIO34 は input only なので出力ポートにはできない等,考慮してポートを選んでみてください.
      https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
      他にも,GPIO2 は connected to on-board LED でオンボードの LED につながっているため,LED や スイッチを外部からつなげても入出力ポートになりえないということも考慮に入れないといけないです.

      あと,おそらく直接の原因ではないと思いますが,else if ではなく if を使った方がいい気がします.
      else if だと1つ目スイッチが押されたとき,2つ目のスイッチを押しても受信機側の LED が点灯しないかと思います.

      • サイトカイン says:

        管理人様

        管理人様のおっしゃっているように、私も入出力ポートが対応していないのではないかと思ったのでポートを変更してみたり、部品をとりかえてみました。また、setup()内についても確認してみましたが、やはり改善しませんでした。
        また、else ifをifにしても変わりませんでした。
        他の人に聞いてみたところ、受信バッファが空になっているのではないかといわれましたが、改善点がわかりません。

  2. サイトカイン様

    管理人です.すいません.返信おくれました.

    たとえば仮に,受信側のコードが以下のようにしていたとしたら確かに片方だけ光らないような気がしました.
    if (SerialBT.available()) {
    if (SerialBT.read() == ‘T’) { // step 1
    digitalWrite(14, HIGH);
    }
    if (SerialBT.read() == ‘T’) { // step 2
    digitalWrite(15, HIGH); // 別のポート
    }
    }
    (step 1で読み出されたときに,バッファが空になってしまっているため.)

    たとえば,受信側のコードを以下のようにしてみるか(同時にLEDが光ります),応用編のコードを参考に複数のバッファを読み出すように改変する必要があるかと思います.
    if (SerialBT.available()) {
    if (SerialBT.read() == ‘T’) { // step 1
    digitalWrite(14, HIGH)
    digitalWrite(15, HIGH); // 別のポート
    }
    }