【M5】M5STACK<->M5STICK間でUDP通信を行う

M5Stack・Arduino

M5STACKおよびM5STICKのボタンを押すと値をUDP経由で送信し、受信したデバイスで受信した値を表示する。

構成

プロジェクト名:Wifi_UDP_Master_M5Stack.ino/Wifi_UDP_Slave_M5StickCPlus.ino

デバイス:M5STACK、M5STICK CPLUS

プログラム

M5STACK

//--------------------------------------------
// ライブラリ
//--------------------------------------------
#include <M5Stack.h>    //ver 0.2.9
#include <WiFi.h>       
#include <WiFiClient.h>
#include <WiFiAP.h>

//双方向通信
#include <WiFiUdp.h>

//---------------------------------------------
//
//---------------------------------------------
const char *ssid = "yourAP";
const char *password = "yourpasswd";
 
const char * to_udp_address = "192.168.4.2"; //送信先のIPアドレス
const int to_udp_port = 55556;               //送信相手のポート番号
const int my_server_udp_port = 55555;        //開放する自ポート
 
WiFiUDP udp;
TaskHandle_t task_handl; 						//マルチタスクハンドル定義
uint8_t receive_data[3] ;          //
boolean  connected=false;

boolean isDisp_ok = false;                  //ディスプレイ表示フラグ
boolean isSet_send_data = false;
//
uint32_t now_time = 0;
int16_t interval = 100;                     //UDPデータ送信間隔
IPAddress myIP;
int16_t trans_count=0;
//---------------------------------------------
//wifiアクセスポイント
//---------------------------------------------
void setup_wifi() {
  WiFi.softAP(ssid, password);
  myIP = WiFi.softAPIP();


}
//---------------------------------------------
//********* core 1 task ************
//---------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(1000);
  //wifiアクセスポイント
  setup_wifi();
  //
  setupWiFiUDPserver();

  //M5Stackはデュアルコア
  //LCDディスプレイはWiFi, UDPの送受信とは別のCPU core で動かした方が遅延が少ない。
  //FreeRTOS関数で、マルチコアおよびマルチタスクで動作させます。
  //メインloop関数はcore 1のタスク。
  //LCDディスプレイ表示はcore 0のタスク。
 /*
  xTaskCreatePinnedToCore(
           タスク名,
           "タスク名",
           スタックメモリサイズ,
           NULL,
           タスク優先順位,
           タスクハンドルポインタ,
           Core ID
  );
*/
  xTaskCreatePinnedToCore(&taskDisplay, "taskDisplay", 8192, NULL, 10, &task_handl, 0);

  delay(500); //別タスクでM5.begin関数が起動するまで待つ。
  //
}
//---------------------------------------------
// ******** core 1 task *************
//UDPからのデータを受信
//UDPデータを相手先へ送信
//---------------------------------------------
void loop() {
  receiveUDP();
  sendUDP();
}
//---------------------------------------------
//******** core 0 task *************
//UDP受信して、isDisp_ok = true になったら、ディスプレイに表示。
//M5Stackのボタン操作も制御
//---------------------------------------------
void taskDisplay(void *pvParameters){
  M5.begin();
  //画面初期化
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(0, 0);

  M5.Lcd.print("AP IP address: ");
  M5.Lcd.println(myIP);
/*
  M5.Lcd.clear(WHITE);
  M5.Lcd.setTextColor(BLUE);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(55, 100);
  M5.Lcd.println("Wifi_UDP_Master");
  */
  while(true){
    M5.update(); //Update M5Stack button state
    button_action();
    // データが届いていたら表示を更新
    if(isDisp_ok){
      M5.Lcd.fillRect(0, 32, 160, 60, BLACK); 
	  M5.Lcd.setCursor(0, 32);
      M5.Lcd.setTextColor(WHITE); // 背景指定なしでもOK
      M5.Lcd.printf("1st: %c  \n", receive_data[0]);
      M5.Lcd.printf("2nd: %c  \n", receive_data[1]);
      M5.Lcd.printf("3rd: %c  \n", receive_data[2]);
      
      isDisp_ok = false; // ★ここでフラグをリセット!
    }
    delay(1);
  }
}
//---------------------------------------------
//UDPデータを受信する関数です。
//55555ポートを開放しているので、届いたデータをudp.parsePacket関数で受信して、
//ライブラリ内の受信バッファに一時保存します。
//udp.read(buf, size);
//という形式にすると、そのサイズ分だけ読込み位置が進みます。
//これは多量のデータを受信する分にはとても便利ですが、ソースコードをパッと見ただけでは意味不
/*
udp.read() の戻り値の範囲は、-1 〜 255 です。
    0 〜 255: 正常に読み取れたデータの値(1バイト分)
    -1: 読み取るデータがもう残っていない(End of File / Error)
もし戻り値が byte 型(0〜255)だと、「データとしての255」なのか「エラー(-1)」なのかを区別できません。そのため、1バイトよりも大きな型である int を使って、データとエラー状態を両方表現できるように設計されています。

int len = udp.read(packetBuffer, 255); と書いたとき:
    packetBuffer(第1引数): 届いたデータそのものが保存される「箱(配列)」です。型は char[] や uint8_t[] です。
    255(第2引数): 「最大で255バイトまで読み取ります」という制限です。
    戻り値(len): 「実際に何バイト読み取って、bufferに書き込んだか」 という数字(int型)が返ってきます。

int packetSize = udp.parsePacket(); // 受信データがあるか確認
if (packetSize) {
    char packetBuffer[255]; 
    // udp.readの戻り値を変数「len」に格納
    int len = udp.read(packetBuffer, 255); 
    
    if (len > 0) {
        packetBuffer[len] = 0; // 文字列として扱うために終端を追加
    }
    Serial.printf("受信サイズ: %d バイト\n", len);
}
*/
//---------------------------------------------
void receiveUDP(){
  if(!isDisp_ok){ // 表示待ち状態でない場合のみ処理
    if(connected){
      int packetSize = udp.parsePacket();
      
      // 3バイト('A','B','C')届いているか確認
      if(packetSize >= 3){
        // 1バイトずつ読み出す
        receive_data[0] = udp.read(); // 'A' (65) が入る
        receive_data[1] = udp.read(); // 'B' (66) が入る
        receive_data[2] = udp.read(); // 'C' (67) が入る

        // LCDに表示
           
        // 処理完了フラグを立てる
        isDisp_ok = true;
      }
    }
  }
}
//---------------------------------------------
//udp.beginPacket関数で、UDP送信バッファ領域を1460byte確保します。
//このbeginPacket関数は相手の送信先のIPアドレスとUDPポートを指定すること。
//送信バッファにはwrite関数で順番に貯め込みます。
//udp.endPacket関数で実際にデータを一気に送信します。
//バッファは1460byte以上になると捨てられますので注意です。
 //---------------------------------------------
void sendUDP(){
  if(isSet_send_data){
    udp.beginPacket(to_udp_address, to_udp_port);
    
    // printを使うと数値が自動的に「文字」として送信されます
    udp.print(trans_count);     // 例:0 なら "0" が送られる
    udp.print(trans_count + 1); // 例:1 なら "1" が送られる
    udp.print(trans_count + 2); // 例:2 なら "2" が送られる

    udp.endPacket();
  // デバッグ用にシリアルに出力して、本当に増えているか確認
    Serial.printf("Sent UDP: %d, %d, %d\n", trans_count%10, (trans_count+1)%10, (trans_count+2)%10);  
    trans_count++;
    // 0-9の範囲でループさせたい場合は以下を追加
    if(trans_count > 7) trans_count = 0; 
    
    isSet_send_data = false;
  }
}
//---------------------------------------------
//M5Stackをアクセスポイントおよびルーターにする。
//SoftAPモードで起動し、UDPサーバーとしてポートを開放して
//待ち受け状態にします。
//--------------------------------------------- 
void setupWiFiUDPserver(){
  Serial.println("Setting up SoftAP...");
  WiFi.disconnect(true, true); // 以前の設定をクリア
  delay(100);

  // SoftAPとして起動
  if(WiFi.softAP(ssid, password)) {
    Serial.println("SoftAP started!");
  } else {
    Serial.println("SoftAP failed to start.");
    return;
  }

  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);

  // UDP待ち受け開始(ポート番号のみ指定が標準的です)
  if(udp.begin(my_server_udp_port)) {
    Serial.printf("UDP Server started at port %d\n", my_server_udp_port);
    connected = true;  // 通信準備完了!
  } else {
    Serial.println("UDP begin failed.");
  }

  delay(1000);
}
 
//---------------------------------------------
//ボタン操作関数です。
//--------------------------------------------- 
void button_action(){
  //左ボタン(Botton A)
  if (M5.BtnA.wasReleased()) {
    isSet_send_data = true;
  }
  //中央ボタン(Botton B) 
  else if (M5.BtnB.wasReleased()) {
    isSet_send_data = true;
  } 
  //右ボタン(Botton C) 
  else if (M5.BtnC.wasReleased()) {
    isSet_send_data = true;
  } 
}

M5STICK CPLUS

//============================================
//
//  Device:M5Stick-CPLUS
//
//  参考サイト
//  wifi双方向通信
//  https://www.mgo-tec.com/blog-entry-udp-wifi-m5stack.html/3
//
//  wifiアクセスポイント
//   M5StackでWifiをAPとSTAとで使い分ける
//  https://qiita.com/nnn112358/items/fbb4f24505c6ef8082cd
//=
//============================================
//--------------------------------------------
// ライブラリ
//--------------------------------------------
//#include <M5StickC.h>
#include <M5StickCPlus.h>
#include <WiFi.h>
#include <WiFiUdp.h>
 
const char* ssid = "yourAP"; 
const char* password = "yourpasswd"; 
const char * to_udp_address = "192.168.4.1"; 	//送信先のIPアドレス
const int to_udp_port = 55555; 					//送信相手のポート番号
const int my_server_udp_port = 55556; 			//開放する自ポート
 
WiFiUDP udp;
TaskHandle_t task_handl; 						//マルチタスクハンドル定義
uint8_t receive_data[3] ;          //受信図形の座標位置

boolean connected = false;

boolean isDisp_ok = false; //ディスプレイ表示フラグ
boolean isSet_send_data = false;

uint32_t now_time = 0;
int16_t interval = 100; //UDPデータ送信間隔
int16_t trans_count=0;
//********* core 1 task ***************
//====================================================
//  初期化処理
//====================================================
void setup(){
  Serial.begin(115200);
  delay(1000);
  connectToWiFi();
  while(!connected){
    delay(1);
  }
  xTaskCreatePinnedToCore(&taskDisplay, "taskDisplay", 8192, NULL, 10, &task_handl, 0);
  delay(500); //別タスクでM5.begin関数が起動するまで待つ。
}
//---------------------------------------------
// ******** core 1 task *************
//UDPからのデータを受信
//UDPデータを相手先へ送信
//---------------------------------------------
void loop() {
  receiveUDP();
  sendUDP();
}
//******** core 0 task *************
void taskDisplay(void *pvParameters){
  M5.begin();
// 画面を縦持ち(ボタンが下)にする場合は 0、横持ちにする場合は 1 または 3
    M5.Lcd.setRotation(3); 
    
    M5.Lcd.fillScreen(BLACK); // clear()より確実に画面をリフレッシュ
    M5.Lcd.setTextSize(2);    // StickCは画面が小さいのでサイズ2くらいが見やすいです
while(true){
    M5.update(); //Update M5Stack button state
    button_action();
    // データが届いていたら表示を更新
    if(isDisp_ok){
M5.Lcd.fillRect(0, 32, 160, 60, BLACK); 

M5.Lcd.setCursor(0, 32);
M5.Lcd.setTextColor(WHITE); // 背景指定なしでもOK
      M5.Lcd.printf("1st: %c  \n", receive_data[0]);
      M5.Lcd.printf("2nd: %c  \n", receive_data[1]);
      M5.Lcd.printf("3rd: %c  \n", receive_data[2]);
      
      isDisp_ok = false; // ★ここでフラグをリセット!
    }
    delay(1);
  }
}
//---------------------------------------------
//UDPデータを受信する関数です。
//55555ポートを開放しているので、届いたデータをudp.parsePacket関数で受信して、
//ライブラリ内の受信バッファに一時保存します。
//udp.read(buf, size);
//という形式にすると、そのサイズ分だけ読込み位置が進みます。
//これは多量のデータを受信する分にはとても便利ですが、ソースコードをパッと見ただけでは意味不
/*
udp.read() の戻り値の範囲は、-1 〜 255 です。
    0 〜 255: 正常に読み取れたデータの値(1バイト分)
    -1: 読み取るデータがもう残っていない(End of File / Error)
もし戻り値が byte 型(0〜255)だと、「データとしての255」なのか「エラー(-1)」なのかを区別できません。そのため、1バイトよりも大きな型である int を使って、データとエラー状態を両方表現できるように設計されています。

int len = udp.read(packetBuffer, 255); と書いたとき:
    packetBuffer(第1引数): 届いたデータそのものが保存される「箱(配列)」です。型は char[] や uint8_t[] です。
    255(第2引数): 「最大で255バイトまで読み取ります」という制限です。
    戻り値(len): 「実際に何バイト読み取って、bufferに書き込んだか」 という数字(int型)が返ってきます。

int packetSize = udp.parsePacket(); // 受信データがあるか確認
if (packetSize) {
    char packetBuffer[255]; 
    // udp.readの戻り値を変数「len」に格納
    int len = udp.read(packetBuffer, 255); 
    
    if (len > 0) {
        packetBuffer[len] = 0; // 文字列として扱うために終端を追加
    }
    Serial.printf("受信サイズ: %d バイト\n", len);
}
*/
//---------------------------------------------
void receiveUDP(){

  if(!isDisp_ok){ // 表示待ち状態でない場合のみ処理
   if(connected){
      int packetSize = udp.parsePacket();
   

      // 3バイト('A','B','C')届いているか確認
      if(packetSize >= 3){
        // 1バイトずつ読み出す
        receive_data[0] = udp.read(); // 'A' (65) が入る
        receive_data[1] = udp.read(); // 'B' (66) が入る
        receive_data[2] = udp.read(); // 'C' (67) が入る

        // 処理完了フラグを立てる
        isDisp_ok = true;
      }
    }
  }
}
//---------------------------------------------
//udp.beginPacket関数で、UDP送信バッファ領域を1460byte確保します。
//このbeginPacket関数は相手の送信先のIPアドレスとUDPポートを指定すること。
//送信バッファにはwrite関数で順番に貯め込みます。
//udp.endPacket関数で実際にデータを一気に送信します。
//バッファは1460byte以上になると捨てられますので注意です。
 //---------------------------------------------
void sendUDP(){
  if(isSet_send_data){
    udp.beginPacket(to_udp_address, to_udp_port);
    
    // printを使うと数値が自動的に「文字」として送信されます
    udp.print(trans_count);     // 例:0 なら "0" が送られる
    udp.print(trans_count + 1); // 例:1 なら "1" が送られる
    udp.print(trans_count + 2); // 例:2 なら "2" が送られる

    udp.endPacket();
  // デバッグ用にシリアルに出力して、本当に増えているか確認
    Serial.printf("Sent UDP: %d, %d, %d\n", trans_count%10, (trans_count+1)%10, (trans_count+2)%10);  
    trans_count++;
    // 0-9の範囲でループさせたい場合は以下を追加
    if(trans_count > 7) trans_count = 0; 
    
    isSet_send_data = false;
  }
}
 
//---------------------------------------------
//---------------------------------------------
void connectToWiFi(){
  Serial.println("Connecting to WiFi network: " + String(ssid));
  WiFi.disconnect(true, true);
  delay(1000);
  WiFi.onEvent(WiFiEvent);
  WiFi.begin(ssid, password);
  Serial.println("Waiting for WIFI connection...");
}
 //---------------------------------------------
//---------------------------------------------

void WiFiEvent(WiFiEvent_t event){
  IPAddress myIP = WiFi.localIP();
  switch(event) {
    case SYSTEM_EVENT_STA_GOT_IP:
      Serial.println("WiFi connected!");
      Serial.print("My IP address: ");
      Serial.println(myIP);
      //udp.begin関数は自サーバーの待ち受けポート開放する関数である
      udp.begin(myIP, my_server_udp_port);
      delay(1000);
      connected = true;
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      Serial.println("WiFi lost connection");
      connected = false;
      break;
    default:
      break;
  }
}
//---------------------------------------------
//ボタン操作関数です。
//--------------------------------------------- 
void button_action(){
  if (M5.BtnA.wasReleased()) {
    isSet_send_data = true;
  }
    if (M5.BtnB.wasReleased()) {
    isSet_send_data = true;
  }
}

コメント