ESP32-WROOM-32EでUDP通信する

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

ESP32-WROOM-32EはWiFiを使用してUDP通信を行うことができます。UDP通信ライブラリを使用してUDPサーバー及びUDPクライアントを実装し、ブロードキャスト及びユニキャストの動作確認を行いました。

ESP32-WROOM-32Eを3台使用します。2台をサーバーとして使用し、1台をクライアントとして使用します。クライアントにBME280モジュールを実装して温湿度気圧のデータを取得してサーバーにデータを送信します。

ESP32-WROOM-32E開発ボード(秋月電子)を使用しArduino IDEで開発を行います。また、温湿度気圧センサーのAE-BME280(秋月電子)を使用しています。アクセスポイントにはWZR-HP-G300NH(バッファロー:生産中止)を使用しています。

ESP32-WROOM-32E(以下ではESP32とする)を使って動作確認したことをまとめています。

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

UDPライブラリを使用する

ESP32のUDP通信の全体構成
ESP32のUDP通信の全体構成

ESP32のWiFiを使用してデータを送信すると標準ではTCP/IPによる通信になります。UDP通信を行う場合はUDP通信ライブラリを実装する必要があります。

ESP32-WROOM-32Eを3台サーバーモード(以下サーバーESP32とします。)で起動してアクセスポイントに接続します。クライアントESP32はUDPクライアントとしてサーバーESP32にUDPデータを送信します。

UDPの特徴は全体に一斉送信するブロードキャスト(マルチキャスト)通信です。ブロードキャストは接続しているすべての端末にデータを送信します。マルチキャストは接続しているネットワークのクラス全体の端末にデータを送信します。

校内放送がブロードキャストでクラス担任がクラスの生徒に連絡するのがマルチキャストのイメージです。

複数の端末にデータを送信するブロードキャスト(マルチキャスト)に対して1対1の端末でデータを送信することをユニキャストと言います。相手の接続状態を確認とパケットの整合性を確認するTCP通信が多く使用される傾向があります。

ユニキャストでは①サーバーと②サーバーのそれぞれにクライアントESP32から温湿度気圧データを含むパケットを送信しサーバーからの応答を確認します。ブロードキャストではクライアントESP32のパケットに対して①と②が同時に応答するかを確認します。

UDPライブラリはESP32 Dev Module用のスケッチ例にESP32 Async UDPが準備されています。本記事ではスケッチ例の「AsyncUDPClient」及び「AsyncUDPServer」を流用して動作確認を行っています。

UDPライブラリのインクルードからを初期化及び関数の使用方法を説明します。

スポンサーリンク

ライブラリの準備と初期化

#include "AsyncUDP.h"

unsigned int localPort = 10002;      // local port to listen on
AsyncUDP Udp;

void setup() {

  if(Udp.listen(localPort )) {
      Udp.onPacket([](AsyncUDPPacket packet){

     //UDPパケットの受信処理
      ledCnt(packet.data());//受信後の処理を行う関数を追加
      packet.printf("LED%dON",num);//reply to the client
      });
  }
}

UDP通信ライブラリを使用するためAsyncUDP.hをインクルードします。非同期通信で受信と送信を管理するライブラリです。

AsyncUDPクラスの変数としてUdpをインスタンス化(Udpオブジェクト)します。Udpオブジェクトのlisten()関数でUDP通信を行うポートを指定します。ポートを指定しない場合はIPが自局のものであればすべて受け付けます。

自局宛の不要なパケットを処理する頻度を減らすことができるためサーバーとクライアントで共通したポートを指定することが有効です。

UdpオブジェクトのonPacket()関数はパケットを受信した時に遷移させる関数を登録します。登録する関数はAsyncUDPPacketクラスのコンストラクターです。コンストラクターの引数がAsyncUDPPacketクラスの変数になっており引数が戻り値になるため引数からUDPパケットの情報を取得することができます。

パケットを受信するとコンストラクターの関数がコールさせるためコンストラクターの関数内に処理を追加する必要があります。例では自作の関数ledCnt()を追加しています。

PR:スキマ時間を有効に!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!

UDPデータと準備と送信

IPAddress server(192, 168, 11, 10);//サーバーのIP
char txpacket[64];

sprintf(&txpacket[0], "Temperature: %3.2f *C Humidity: %3.0f %% Pressure: %4.2f hPa", bme280Meas[BME280_TEMP] , bme280Meas[BME280_HUMI], bme280Meas[BME280_PRES]);
//ユニキャストで送信
Udp.writeTo((uint8_t*)&txpacket[0],sizeof(txpacket),server,localPort);
//ブロードキャストで送信
Udp.broadcastTo((uint8_t*)&txpacket[0],sizeof(txpacket),localPort);

UdpオブジェクトのwriteTo()関数を使用するとUDPパケットを宛先に指定して送信することができます。第1引数に送信するデータを格納しているアドレスを指定します。第2引数に送信するデータのサイズを指定します。

第3引数に送信相手のIPアドレスを指定します。第4引数に送信相手のポートを指定します。

例ではIPAddressクラスでインスタンス化したserverのIPアドレスを指定し、サーバーのポートに10002を指定しています。

ブロードキャスト通信を行う場合はUdpオブジェクトのbroadcastTo()関数を使用します。第1引数に送信するデータを格納しているアドレスを指定します。第2引数に送信するデータのサイズを指定します。第3引数に相手先のポートを指定します。

connect()関数で相手先に接続してwrite()関数で1バイトずつ送信する方法もありますがタイミングによってはパケットが分割されてしまうためsprintf()関数で温湿度気圧のデータを含めた文字列を生成し、writeTo()関数(BroadcastTo()関数)で同一のパケットで送信できるようにしています。

PR:テックジム:プログラミングの「書けるが先で、理解が後」を体験しよう!

BME280モジュールからデータを取得

Arduino IDEのライブラリマネージャの検索欄にbme280を入力するとライブラリの候補が表示されます。候補の中からGrove-Barometer Sensor BME280 by Seeed Studioをインストールします。使用例は以下の通りです

#include "Seeed_BME280.h"

BME280 bme280;

void setup() {

  Serial.begin(115200);
  if(!bme280.init()){
    Serial.println("Device error!");
  }
}

void loop() {
  float press;

  Serial.print("Temperature = ");
  bme280Meas[BME280_TEMP] = bme280.getTemperature();//温度
  Serial.print(bme280Meas[BME280_TEMP]);
  Serial.println(" C");

  Serial.print("Humidity = ");
  bme280Meas[BME280_HUMI] = bme280.getHumidity();//湿度
  Serial.print(bme280Meas[BME280_HUMI]);
  Serial.println(" %");

  press = bme280.getPressure();//気圧
  Serial.print("Pressure = ");
  bme280Meas[BME280_PRES] = press / 100.0F;
  Serial.print(bme280Meas[BME280_PRES]);
  Serial.println(" hPa");
  delay(2000);
}

Seeed_BME280.hをインクルードします。BME280クラスのbme280をインスタンス化(bme280オブジェクト)して使用します。

bme280オブジェクトのinit()関数でBME280の初期化を行います。BME280ライブラリのスレーブアドレスはデフォルトで0x76になっているため、AE-BME280のスレーブアドレスを0x76で使用する必要があります。

getTmeperature()関数は温度情報を取得します。getHumidity()関数は湿度情報を取得します。getPressure()関数は気圧情報を取得します。気圧の単位はPaなのでhPaで表示する場合は100で割った値に換算する必要があります。

スポンサーリンク

動作確認

ESP32のUDPライブラリ動作確認用の回路図
ESP32のUDPライブラリ動作確認用の回路図

クライアントESP32は電源を入れるとBME280の測定が開始し、UDP通信でサーバーに温湿度気圧のデータを含めたパケットを送信します。

クライアントESP32の33ピンをGNDに接続すると①サーバーESP32が送信先になり、32ピンをGNDに接続すると②サーバーESP32が送信先になります。それ以外はブロードキャスト通信になります。

サーバーESP32はIPを切り替えるため2台目の5ピンをGNDに接続します。サーバーESP32はクライアントからUDPパケットを受信するとシリアルモニターにパケット情報を表示し、LED1ON(LED2ON)の文字列のパケットで応答します。

クライアントESP32はサーバーESP32からUDPパケットを受信するとLED1(LED2)を点灯して通知します。①サーバーESP32の場合はLED1が点灯し、②サーバーESP32の場合はLED2が点灯します。

クライアントESP32から受信したUDPパケットを確認するため①サーバーESP32をシリアルモニターで確認します。電源投入後はアクセスポイントが動作開始するまで数秒間待機します。WiFi接続が確立するとサーバーとして動作開始します。

サーバーESP32のシリアルモニターの確認
サーバーESP32のシリアルモニターの確認

クライアントESP32からUDPパケットを受信するとUDPパケットの種別及び送信先のIPとポートを表示します。Data:以降はパケットの内容を表示しています。クライアントESP32が送信したパケットデータ表示できています。

サーバーESP32はUDPパケットを受信するとクライアントESP32にUDPパケットで応答します。①サーバーESP32の場合はLED1ON、②サーバーESP32の場合はLED2ONの文字列で応答します。クライアントESP32のシリアルモニターを確認します。

クライアントESP32のシリアルモニターの結果(ユニキャスト)
クライアントESP32のシリアルモニターの結果(ユニキャスト)

クライアントESP32からユニキャストで①サーバーESP32にパケットを送信すると「192.168.11.10」からLED1ONの応答でLED1が点灯することが確認できました。また②サーバーESP32にパケットを送信すると「192.168.11.20」からLED2ONの応答でLED2が点灯することが確認できました。

クライアントESP32のシリアルモニターの結果(ブロードキャスト)
クライアントESP32のシリアルモニターの結果(ブロードキャスト)

クライアントESP32からブロードキャストのパケットを送信すると2つのサーバーが同時に応答し、LED1とLED2が同時に点灯することが確認できました。

PR:次の一手があなたの未来を決める! アビリティクラウドーフリーランスを目指すあなたに向けたエンジニアのマッチングサービス。大手のエンドクライアントメインの企業が多く、業務内容も規模が大きいのが特徴

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

クライアントESP32のソースコード:

#include <Seeed_BME280.h>
#include <WiFi.h>
#include "AsyncUDP.h"

#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIM_MEASURE 500
#define TIM_LED_ON 300
#define PIN_DI1 32
#define PIN_DI2 33
#define PIN_DO1 25
#define PIN_DO2 26
#define BUF_SZ 64

const char *ssid = "xxxxxxxxxxxx"; //アクセスポイントのSSID
const char *pass = "yyyyyyyyyyyyy"; //password
unsigned int localPort = 10002; // local port to listen on

typedef enum{
  BME280_TEMP = 0,
  BME280_HUMI,
  BME280_PRES,
  //BME280_ALTI,       
  BME280_MAX  
}BME280_TYPE;

/* 変数宣言 */
IPAddress ip(192, 168, 11, 2);//ESP32のIP
IPAddress server(192, 168, 11, 10);//サーバーのIP
IPAddress server2(192, 168, 11, 20);//サーバーのIP
const IPAddress subnet(255,255,255,0); //サブネットマスク
AsyncUDP Udp;
uint32_t beforetimCnt = millis();
int16_t timMeasure;
int16_t timled1 = TIME_OFF;
int16_t timled2 = TIME_OFF;
int16_t waitcnt;
uint8_t packetBuffer[BUF_SZ];
char txpacket[BUF_SZ];
BME280 bme280;
float bme280Meas[BME280_MAX];

/* プロトタイプ宣言 */
void mainTimer(void);
void mainApp(void);
void ledCnt(uint8_t *buf);

void setup() {

  Serial.begin(115200);
  pinMode(PIN_DI1, INPUT_PULLUP);
  pinMode(PIN_DI2, INPUT_PULLUP);
  pinMode(PIN_DO1, OUTPUT);
  pinMode(PIN_DO2, OUTPUT);

  if(!bme280.init()){
    Serial.println("Device error!");
  }

  WiFi.begin(ssid,pass);
  WiFi.config(ip,ip,subnet);

  while( WiFi.status() != WL_CONNECTED ){
    delay(1000);
    Serial.print(".");
    if( ++waitcnt >= 60 ){
      esp_restart();
    }
    Serial.print(waitcnt);
  }
  Serial.println("");

  if(Udp.listen(10002)) {
    Serial.print("UDP Listening on IP: ");
    Serial.println(WiFi.localIP());
      Udp.onPacket([](AsyncUDPPacket packet){
      Serial.print("UDP Packet Type: ");
      Serial.print(packet.isBroadcast()?"Broadcast":packet.isMulticast()?"Multicast":"Unicast");
      Serial.print("From: ");
      Serial.print(packet.remoteIP());
      Serial.print(":");
      Serial.print(packet.remotePort());
      Serial.print(" Length: ");
      Serial.println(packet.length());
      Serial.print("Data: ");
      Serial.write(packet.data(), packet.length());
      Serial.println();
      ledCnt(packet.data());
      });
  }
  timMeasure = TIM_MEASURE;
}

void loop() {

  mainApp();
  mainTimer();
}

/* タイマ管理 */
void mainTimer(void){

  if ( millis() - beforetimCnt > BASE_CNT ){
    beforetimCnt = millis();

    if( timMeasure > TIME_UP ){
      --timMeasure;
    }

    if( timled1 > TIME_UP ){
      --timled1;
    }

    if( timled2 > TIME_UP ){
      --timled2;
    }
  }
}

/* メイン処理関数 */
void mainApp(void){

  if( timMeasure == TIME_UP ){
    timMeasure = TIM_MEASURE;
    float press;
    bool flg;
    Serial.print("Temperature = ");
    bme280Meas[BME280_TEMP] = bme280.getTemperature();
    Serial.print(bme280Meas[BME280_TEMP]);
    Serial.println(" C");

    Serial.print("Humidity = ");
    bme280Meas[BME280_HUMI] = bme280.getHumidity();
    Serial.print(bme280Meas[BME280_HUMI]);
    Serial.println(" %");

    press = bme280.getPressure();
    Serial.print("Pressure = ");
    bme280Meas[BME280_PRES] = press / 100.0F;
    Serial.print(bme280Meas[BME280_PRES]);
    Serial.println(" hPa");

    sprintf(&txpacket[0], "Temperature: %3.2f *C Humidity: %3.0f %% Pressure: %4.2f hPa"
           , bme280Meas[BME280_TEMP] , bme280Meas[BME280_HUMI], bme280Meas[BME280_PRES]);

    if( digitalRead(PIN_DI1) == HIGH && digitalRead(PIN_DI2) == LOW ){
      Udp.writeTo((uint8_t*)&txpacket[0],sizeof(txpacket),server,localPort);
    }
    else if( digitalRead(PIN_DI1) == LOW && digitalRead(PIN_DI2) == HIGH ){
      Udp.writeTo((uint8_t*)&txpacket[0],sizeof(txpacket),server2,localPort);
    }
    else{
      Udp.broadcastTo((uint8_t*)&txpacket[0],sizeof(txpacket),localPort);
    }
  }
  
  if( timled1 == TIME_UP){
    timled1 = TIME_OFF;
    digitalWrite(PIN_DO1, LOW);
  }

  if( timled2 == TIME_UP){
    timled2 = TIME_OFF;
    digitalWrite(PIN_DO2, LOW);
  }
}

/* UDP受信後LED操作 */
void ledCnt(uint8_t *buf){

  for(uint16_t i=0; i < BUF_SZ; i++ ){
    packetBuffer[i] = *buf;
    buf++;
  }

  if( packetBuffer[0] =='L' && packetBuffer[1] =='E' && 
      packetBuffer[2] =='D' ){
      
    if(packetBuffer[3] =='1'){
      digitalWrite(PIN_DO1, HIGH);
      timled1 = TIM_LED_ON;
    }
    else if(packetBuffer[3] =='2'){
      digitalWrite(PIN_DO2, HIGH);
      timled2 = TIM_LED_ON;
    }
    else{
      digitalWrite(PIN_DO1, LOW);
      digitalWrite(PIN_DO2, LOW);
    }
  }
}

サーバーESP32のソースコード:

#include <WiFi.h>
#include "AsyncUDP.h"

#define PIN_DI 5

const char *ssid = "xxxxxxxxxxxx"; //アクセスポイントのSSID
const char *pass = "yyyyyyyyyyyyy"; //password
const IPAddress ip(192,168,11,10); //IPアドレス
const IPAddress ip2(192,168,11,20); //IPアドレス
const IPAddress subnet(255,255,255,0); //サブネットマスク
  
/* 変数宣言 */
WiFiServer server(10001);
AsyncUDP udp;
int16_t waitcnt;
int8_t num;

/* プロトタイプ宣言 */
void mainApp(void);

void setup() {

  Serial.begin(115200);
  pinMode(PIN_DI,INPUT_PULLUP);

  WiFi.begin(ssid,pass);

  if(digitalRead(PIN_DI)){
    WiFi.config(ip,ip,subnet);
    num = 1;
  }
  else{
    WiFi.config(ip2,ip2,subnet);
    num = 2;
  }

  while( WiFi.status() != WL_CONNECTED ){
    delay(1000);
    Serial.print(".");
    if( ++waitcnt >= 60 ){
      esp_restart();
    }
    Serial.print(waitcnt);
  }

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin(); //サーバーの開始

  if(udp.listen(10002)) {
    Serial.print("UDP Listening on IP: ");
    Serial.println(WiFi.localIP());
    udp.onPacket([](AsyncUDPPacket packet) {
      Serial.print("UDP Packet Type: ");
      Serial.println(packet.isBroadcast()?"Broadcast":packet.isMulticast()?"Multicast":"Unicast");
      Serial.print("From: ");
      Serial.print(packet.remoteIP());
      Serial.print(":");
      Serial.print(packet.remotePort());
      Serial.print(", To: ");
      Serial.print(packet.localIP());
      Serial.print(":");
      Serial.print(packet.localPort());
      Serial.print(" Length: ");
      Serial.println(packet.length());
      Serial.print("Data: ");
      Serial.write(packet.data(), packet.length());
      Serial.println();
      packet.printf("LED%dON",num);//reply to the client
    });
  }
}

void loop() {

  mainApp();//udp通信では使用しない
}

/* メイン処理関数 */
void mainApp(void){
  
 WiFiClient client = server.available();   // listen for incoming clients

  if (client) {                             // if you get a client,
    Serial.println("New Client.");           // print a message out the serial port
    //String currentLine = "";
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
      }
    }

    client.stop();
    Serial.println("Client Disconnected.");
  }
}

サーバーESP32のアドレスは5ピンをGNDに接続すると「192.168.11.20」に切り替えることができます。

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

広告

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました