【PowerMeter2020】M5StackのESP-NOWで1対2無線送受信実験<Pgmが簡単>

1対1の送受信では、4msec周期で通信エラー率0.03%程度とXbeeに比べ一桁上の信頼性があるESP-NOWですがパワーメーターでは左右から2個同時に母艦へ送信してきますので、その場合の通信エラー率がどうなるか実験してみました。

ここ2年のパワーメーター開発では、左右で4個のXbeeを使って受信していたのですが、ESP-NOWだとM5StackBasic母艦1個に2個のM5StickCのデータを同時受信できるので、抜群のパフォーマンスを得られました。「システムのサイズ半分+コスト1/3+開発手間数か月」お得です。
2020年からは無線はESP-NOW中心で使っていきます。
Xbeeは性能とコストが悪いので、遅くて電池寿命が必須な用途で使います。
BlueToothもスマホと通信する場合だけ使うようにします。
※2021年7月追記 ESP-NOW使い初めて1年経過して、ESP-NOWは1パケット250byte制限があるので、大容量高速無線通信が必要になったのでUDPに挑戦してます触り初めですが、UDPシンプルで超速いですが、アンテナ状態など無線アナログばらつきをくらいそうです。

【M5】UDPを初めていじってみた<めっちゃ速い>

●ESP-NOWは1対多端末通信が得意
こちらのQIITAの
記事のサンプルPgmで1対2通信できました。作者様に感謝です。
https://qiita.com/nnn112358/items/da9762c4651c521af2c8

ESP-NOWについては、固定ページで後日まとめますが、やりながら、学習する開発方式なので、試行錯誤しながらESP-NOWを体験学習します。
ここの記事でサンプルプログラムが2個ありますが、呼称が真逆なので注意です。
■SLAVE:一般的にはセンサ側の端末なのですが、ESP-NOWでは、母艦側をSLAVEと呼びます。つまりデータを受信するマイコン側で今回はM5StackBasic1個使ってます。

■MASTER:一般的には母艦側の端末なのですが、ESP-NOWでは、センサ側をMASTERと呼びます。ですのでMASTERは複数台あります。今回はM5StickCを2個使ってます。

■同期
無線で双方向同期しようと思ったのですが、サンプルプログラムが一方通行用なので
SLAVE(M5StackBasic)は、受信専用、
MASTER(M5StickC)は、送信専用
なので、送受信のタイミングを計測するために、GPIO線でM5StackBasicとM5StickC2個を接続して、1秒毎に4msecのLOW信号をいれて同期確認しました。

 

●送信速度エラー率測定=>端末数だけ遅くしないといけない
1対1では4msec周期で余裕でしたが、1対2の場合はそうはいかないだろうということで、4msecと8msecの2水準で 送信エラー率を測定しました。30分間サンプリングを
回した結果です。
結果1:

M5Stick2個でも個体差があってMACアドレスCCのものが5%もエラーが発生してます。MACアドレスE8のものだと0.5%ですが、1対1で4msec0.03%だったので、明かに、1対2では通信エラー率が悪化してます。

結果2:

8msecと遅くすると、エラー率は、CCが0.044% E8が0.07%と1対1の場合に近い桁まで落ちてくるので、使えるレベルとなります。


 

●1対2での同期遅延測定
上記結果より8msecに周期を決めて、母艦M5Stackから同期信号を1秒毎にM5StickC2個にGPIO線でいれて、M5StickCがピン割り込みで、送信カウントをゼロリセットするようにしてタイミングの遅延を測定しました。

オシロで60分ほど測定してましたが、黄色のエッジから水色の送信タイミングの遅延時間は8msec以内におさまっているようでした、しかし、毎回コロコロと変動しますので、やはり、各CPUのクロックとかアナログノイズなどで周期変動が発生していることがみえました。

母艦側とセンサ側データ受信

このデータを60分サンプリングした結果から1秒毎の同期割込み信号でのタイムスタンプエラーをカウントしてエラー率を計算した結果
・8msec周期なので125回目にリセットがはいって126回目がゼロカウントにならないといけないが、クロック誤差などで、125回目でなく124回とか126回目の割り込みが入る場合が何回か発生するので、それがエラーとなる。
①周期誤差
M5StickE8が1%誤差、M5StickCCが0.17%誤差と個体差が大きい、上記ピン割り込みが
無しの場合のエラー率が0.0x%だったので桁違いに悪化することが判った。
◆これは重大なことです。ピン割り込みが1秒に一回程度はいっただけで、一桁エラー率が大きくなるということは、ハードのちょっとした変動で大きくサンプリング周期が変動する可能性があります。個体差と電圧差ノイズでデータが化ける可能性があると理解しました。外部水晶クロック無しで、1回転リセット方式でタイミング合わせができるかを実験しているのですが、実走行で環境が悪化した時にデータ周期が大きく化けるリスクがあるので外部クロックでの送信実験もすることにします。

➁割り込み遅延測定
オシロで8msec以内でおさまっているのを確認してますが、エラー率を測定しました
母艦のタイミングエッジに対して、リセットされて1か0でカウントスタートするので
1,0が73-75%で、1カウントずれが25% その他が2%ほど発生してます。
これが1秒内の誤差ですので、連続して数十分も測定すれば、累積誤差で数秒ずれていくはずですので回転毎にリセットをいれて累積して積算しないような処理をしないと精度がでないことを確認できました。

 

●以後
  外部水晶クロックなしで、1対2通信の同期精度を維持できるかぎりぎりの状況であることが判りましたので外部水晶クロックをM5Stick2個の取り付けた場合の測定もやっておいたほうがリスク回避できるのでやってみます。使用したプログラムは下記の備忘録しておきます。
SLAVE(M5StackBasic用)

//=======Multi_Slave_ESP-NOW_8msec_M5Stack-Slave_rev01=======
// 2020.5.3 Shinshu-Makers
//==========================================================
/**
ESPNOW – Basic communication – Slave
Date: 26th September 2017
Author: Arvind Ravulavaru <https://github.com/arvindr21>
Purpose: ESPNow Communication between a Master ESP32 and multiple ESP32 Slaves
Description: This sketch consists of the code for the Slave module.
Resources: (A bit outdated)
a. https://espressif.com/sites/default/files/documentation/esp-now_user_guide_en.pdf
b. http://www.esploradores.com/practica-6-conexion-esp-now/<< This Device Slave >>Flow: Master
Step 1 : ESPNow Init on Master and set it in STA mode
Step 2 : Start scanning for Slave ESP32 (we have added a prefix of `slave` to the SSID of slave for an easy setup)
Step 3 : Once found, add Slave as peer
Step 4 : Register for send callback
Step 5 : Start Transmitting data from Master to Slave(s)Flow: Slave
Step 1 : ESPNow Init on Slave
Step 2 : Update the SSID of Slave with a prefix of `slave`
Step 3 : Set Slave in AP mode
Step 4 : Register for receive callback and wait for data
Step 5 : Once data arrives, print it in the serial monitorNote: Master and Slave have been defined to easily understand the setup.
Based on the ESPNOW API, there is no concept of Master and Slave.
Any devices can act as master or salve.
*/
#include<M5Stack.h>
#include <esp_now.h>
#include <WiFi.h>#define CHANNEL 1
int RTC_Pin=5;//GP5
int NTP_Pin=26;//GP26
int j=0;
int value0,value1;
int t,t_1;
uint8_t data[3];// ttigger send data
// Init ESP Now with fallback
void InitESPNow() {
WiFi.disconnect();
if (esp_now_init() == ESP_OK) {
Serial.println(“ESPNow Init Success”);
}
else {
Serial.println(“ESPNow Init Failed”);
// Retry InitESPNow, add a counte and then restart?
// InitESPNow();
// or Simply Restart
ESP.restart();
}
}// config AP SSID
void configDeviceAP() {
String Prefix = “Slave:”;
String Mac = WiFi.macAddress();
String SSID = Prefix + Mac;
String Password = “123456789”;
bool result = WiFi.softAP(SSID.c_str(), Password.c_str(), CHANNEL, 0);
if (!result) {
Serial.println(“AP Config failed.”);
} else {
Serial.println(“AP Config Success. Broadcasting with AP: ” + String(SSID));
}
}void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
char macStr[18];
snprintf(macStr, sizeof(macStr), “%02x:%02x:%02x:%02x:%02x:%02x”,
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
//Serial.print(“Last Packet Recv from: “);
value0=data[0]+data[1]*256+data[2]*256*256+data[3]*256*256*256;
value1=data[4]+data[5]*256+data[6]*256*256+data[7]*256*256*256;
//Serial.print(“M5SackRcv:”);
//Serial.print(macStr);
if(value0<=1)
{
digitalWrite(RTC_Pin,0);//5pin Fall
delay(2);
digitalWrite(RTC_Pin,1);//5pin Rise
}
Serial.print(mac_addr[5],HEX);
Serial.print(“:”);
Serial.print(j);
Serial.print(“,”);
Serial.print(value0);
Serial.print(“,”);
Serial.print(value1);
Serial.println(“”);}
void setup() {
Serial.begin(115200);
pinMode(RTC_Pin, OUTPUT);
pinMode(NTP_Pin, OUTPUT);
Serial.println(“ESPNow/Basic/Slave Example”);
//Set device in AP mode to begin with
WiFi.mode(WIFI_AP);
// configure device AP mode
configDeviceAP();
// This is the mac address of the Slave in AP Mode
Serial.print(“AP MAC: “); Serial.println(WiFi.softAPmacAddress());
// Init ESPNow with a fallback logic
InitESPNow();
// Once ESPNow is successfully Init, we will register for recv CB to
// get recv packer info.

// callback when data is recv from Master
esp_now_register_recv_cb(OnDataRecv);

t_1=0;
}

void loop() {
// Chill
j++; //2msecx500kai=1sec
t=millis();
if(t-t_1>1000)
{
j=0;
t_1=t;
digitalWrite(NTP_Pin,0);//26pin Fall
delay(4);
digitalWrite(NTP_Pin,1);//26pin Rise
Serial.println(“==========Triggered==========”);
}
delay(2);
}

MASTER(M5StickC2個とも同じプログラム)

//===================MultiMaster_ESP_Now_8msec_M5StickC-Masters_rev01========================================================
//
// 2020.5.3 Shinshu-Makers
//
//=====================================================================================================
#include <M5StickC.h>
#include <esp_now.h>
#include <WiFi.h>
//********************Genaral Parameters************************************************************
int i=0;
int j=0;
int pushN=0;
int value0,value1;
uint8_t ti,ti_1;
int RTC_Pin=0;//GP0
int NTP_Pin=26;//GP26
int t;
int goZero=0;
int sTime=0;
uint8_t data[8];
//+++++++++++++++i_to_char+++++++++++++++++
// i=IntegerValueData,*d=Array pointer, n=Array start No
void i_to_char(int i,uint8_t *d, int n)
{
t=millis();
d[n] = i & 0x000000ff;
d[n+1] = (i & 0x0000ff00) >> 8;
d[n+2] = (i & 0x00ff0000) >> 16;
d[n+3] = (i & 0xff000000) >> 24;
}
//++++++++++++++++++++++++++++++++++++++++++
//=====================================ESP NOW Sub ================================================
esp_now_peer_info_t slave;// 送信コールバック
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
char macStr[18];
snprintf(macStr, sizeof(macStr), “%02X:%02X:%02X:%02X:%02X:%02X”,
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);}// 受信コールバック
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len)
{
char macStr[18];
pushN++;
/*
Serial.print(“M5StickRcved:”);
for (i=0;i<data_len;i++)
{
Serial.print(data[i],HEX);
Serial.print(“,”);
}
Serial.println(“”);
//j=0;//reset Data couter
*/
}
void zeroR()
{
j=0;
Serial.println(“=========zeroR_j===========”);
}//============================================ESP-NOW Sub end==========================================================================================
void setup() {
M5.begin();
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setRotation(3);
M5.Lcd.print(“ESP-NOW Test\n”);
//========= Ext Signal Pin====
//pinMode(RTC_Pin, OUTPUT);
//pinMode(NTP_Pin, OUTPUT);
//========Interrupt Pin========
pinMode(26, INPUT_PULLUP);
attachInterrupt(26, zeroR, FALLING);
//========= ESP-NOW初期化=============
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() == ESP_OK) {
Serial.println(“ESPNow Init Success”);
M5.Lcd.print(“ESPNow Init Success\n”);
} else {
Serial.println(“ESPNow Init Failed”);
M5.Lcd.print(“ESPNow Init Failed\n”);
ESP.restart();
}// マルチキャスト用Slave登録
memset(&slave, 0, sizeof(slave));
for (int i = 0; i < 6; ++i) {
slave.peer_addr[i] = (uint8_t)0xff;
}
esp_err_t addStatus = esp_now_add_peer(&slave);
if (addStatus == ESP_OK) {
// Pair success
//Serial.println(“Pair success”);
}// ESP-NOWコールバック登録
esp_now_register_send_cb(OnDataSent);
esp_now_register_recv_cb(OnDataRecv);
}void ESP_error(esp_err_t result)
{
Serial.print(“Send Status: “);
if (result == ESP_OK) {
Serial.println(“Success”);
} else if (result == ESP_ERR_ESPNOW_NOT_INIT) {
Serial.println(“ESPNOW not Init.”);
} else if (result == ESP_ERR_ESPNOW_ARG) {
Serial.println(“Invalid Argument”);
} else if (result == ESP_ERR_ESPNOW_INTERNAL) {
Serial.println(“Internal Error”);
} else if (result == ESP_ERR_ESPNOW_NO_MEM) {
Serial.println(“ESP_ERR_ESPNOW_NO_MEM”);
} else if (result == ESP_ERR_ESPNOW_NOT_FOUND) {
Serial.println(“Peer not found.”);
} else {
Serial.println(“Not sure what happened”);
}
j=0;
}
//============================================ESP-NOW END=========================================
void loop() {M5.update();
j++;

t=millis();
//Serial.println(t);

// if( pushN%2==1 )// 8msec data send
// {
digitalWrite(NTP_Pin,0);

i_to_char(j,data,0);
i_to_char(t,data,4);

esp_err_t result = esp_now_send(slave.peer_addr, data, sizeof(data));
//ESP_error(result);
//====CHECK Send Data==========================================
value0=data[0]+data[1]*256+data[2]*256*256+data[3]*256*256*256;
value1=data[4]+data[5]*256+data[6]*256*256+data[7]*256*256*256;
Serial.print(value0);
Serial.print(“,”);
Serial.println(value1);

digitalWrite(NTP_Pin,1);
// }
delay(8);
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です