【PowerMeter2020】ESP-NOW1対2無線システムPgm構成<無線設定が簡単>

ESP-NOW使えそうなので、クランクトルク用の無線システム組んでみました。Lang-Ship様のサンプルプログラムにMACアドレスを書き換えてデータフォーマットに沿った送受信部分を書き込んで完成しました。
送受信タイミングエラーと時間周期精度の測定もしたので次回まとめます。

●ESP-NOWの通信設定の方法
ESP-NOW仕様書は、下記にあります。
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html

 ■ESP-NOWの通信コマンド
SlaveとMasterと名称で区別がありますが、プログラム上では立場は同じです。
Slaveの役割:Masterから送信されるデータを受信したり、Masterへ送信
Masterの役割:センサ等データを測定してSlaveへ送信

通信コマンド 使い方

プログラムで使っている関数

戻り値が構造体 esp_err_tです

イニシャライズ WIFIをESP-NOW用に設定する
3個init関数あります

esp_err_t esp_now_init(void)
peer

 

送信相手のMACアドレス登録
7個もpeer関数があります
esp_err_t

esp_now_add_peer(constesp_now_peer_info_t *peer)

送信 <手動送信>
コールバック有無は選択できます。
MACアドレスを指定するか
MACアドレスを全FF:なら全端末あてに同時送信
(ブロードキャスト)
3個send関数あります
esp_err_t

esp_now_send(const uint8_t *peer_addrconst uint8_t *data, size_t len)

受信 <割込みで自動受信>
コールバックが必須でついてます。通信の信頼性を確保するために必須だからだと思います。
受信は来るものは拒まず、相手のMACアドレスとdataを受信できます
2個しか受信関数ありません
esp_err_t

esp_now_register_recv_cb(esp_now_recv_cb_tcb)

●基本的なMACアドレスの設定
⓪受信は全て自分宛の送信は来るものは拒まず受信します。割込みで順次受信
①全部FFの場合は、自分以外の全ての端末へデータを送信します。
➁peerにaddしたMACアドレスがあればそのアドレスの端末のみへ送信します。

 

●ロードバイク パワーメーターの通信方法

 

機能
上死点タイミング同期ST ①クランクの上死点センサのタイミングを母艦M5Stackで検出して、
送信MACアドレス
FF:FF:FF:FF:FF:FFをesp_now_add_peer(esp_now_peer_t *peer)に設定してから
データバイト uint8_t data[0] data[1]に”S””T”の2文字を全端末へ
同時送信する
➁子機M5Stickは、ST文字を受信するのは割込みコールバック関数1行だけで自動受信されます。
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len)この関数内でdata[]~”ST”を認識したら回転数hasu++にして上死点毎に回転数をインクリメントしていきます
測定開始時の時間同期TR M5StackのBボタン(真ん中)を押すと母艦と右左の3個カウンタとタイマーをゼロリセットします。

測定開始時に一回やります。 data[]=”TR”を全FFで同時送信することでリセットします。プログラムは上記上死点タイミングSTと同じです。

クランクデータ受信 母艦M5Stackで受信は
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len)
の割り込み関数内で処理します。
データは、整数3個で、uint8_t  data[12]配列で渡されてきます。
data[]はASCIIではなく、バイナリーに変換して送信してます。そのため
void i_to_char(int i,uint8_t *d, int n) という整数を4バイトのバイナリに変換してdata[]の指定位置から書き込む関数を用意しました。
バイナリ受信したデータをDECへ下記式で換算して表示します。
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
Value2=data[8]+data[9]*256+data[10]*256*256+data[11]*256*256*256
必要なデータは、データカウント番号とAD変換値と回転数の3つです。受信順は、交互に左右が並んで自動的にdataに入ってきます。
データの左右区別は、MACアドレスも受信されてくるので
MACアドレスの末尾をdataとセットで記録表示します。
下記では3個分を同時受信した場合の母艦のログです。
E8:が送信先のMACアドレスの末尾
755は、データのカウンタ、8msec周期でカウントされます
6032は、millsi()タイムスタンプをいれてありますが、本番はクランクトルクのAD変換値12ビットになります。
6は、回転数で、上死点センサタイミングを受けて回転数を同期させます。
上死点同期をすることでCPU毎のクロック精度のズレを1回転毎に相殺できるようにします。
・M5Stick母艦のUSBシリアル経由TeraTermログ
この場合は3個の子機で受信した例です。同じ子機プログラムを
3個目に書き込むだけで2個が3個になります。最大15子機まで使えます
通信周期 1対1の通信だけなら4msec周期で送受信できますが
1対2になると4msecだとエラーが多いので8msecにするとエラー1%以下になります。

●プログラム備忘録
①SLAVE(母艦M5StackBasic用)

//=====================LangShip ESP-NOW1=================-

#include <M5Stack.h>
#include <esp_now.h>
#include <WiFi.h>

esp_now_peer_info_t slave;
int value0,value1,value2;
int i,j;
int t,t_1,toff,tzero;
uint8_t data[2]={};
//+++++++++++++++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;
}
//++++++++++++++++++++++++++++++++++++++++++

// 送信コールバック
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]);
//Serial.print(“Last Packet Sent to: “);
//Serial.println(macStr);
//Serial.print(“Last Packet Send Status: “);
//Serial.println(status == ESP_NOW_SEND_SUCCESS ? “Delivery Success” : “Delivery Fail”);

}

// 受信コールバック
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]);
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;
value2=data[8]+data[9]*256+data[10]*256*256+data[11]*256*256*256;
//Serial.print(macStr);
Serial.print(mac_addr[5],HEX);
Serial.print(“:”);
Serial.print(value0);
Serial.print(“,”);
Serial.print(value1);
Serial.print(“,”);
Serial.print(value2);
Serial.println(“”);

/*
//Serial.printf(“Last Packet Recv from: %s\n”, macStr);
Serial.print(macStr);
Serial.print(“,”);
Serial.print(data_len);
for ( int i = 0 ; i < data_len ; i++ ) {
Serial.print(data[i]);
Serial.print(” “);
}
Serial.println(“”);
*/
}

void setup() {
M5.begin();
toff=0;
tzero=0;
// ESP-NOW初期化
WiFi.mode(WIFI_STA);
Serial.println(WiFi.macAddress());
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;
//}
// マルチキャスト用Slave登録
// M5Stick COM7=xx:xx:xx:xx:xx:E8
// M5Stick COM13=yy:yy:yy:yy:yy:CC
// M5StackBasic COM9=zz:zz:zz:zz:zz:DC
// ============M5StickC COM13 CC ======================
slave.peer_addr[0]=(uint8_t)0xff;
slave.peer_addr[1]=(uint8_t)0xff;
slave.peer_addr[2]=(uint8_t)0xff;
slave.peer_addr[3]=(uint8_t)0xff;
slave.peer_addr[4]=(uint8_t)0xff;
slave.peer_addr[5]=(uint8_t)0xff;
//===============================
esp_err_t addStatus = esp_now_add_peer(&slave);

if (addStatus == ESP_OK) {
// Pair success
Serial.println(“Pair success:CC”);
}
j=0;
// ESP-NOWコールバック登録
esp_now_register_send_cb(OnDataSent);
esp_now_register_recv_cb(OnDataRecv);
}

void loop() {
M5.update();
if(toff==1){tzero=millis();toff=0;}
t=millis()-tzero;

if(t-t_1>1000)
{
t_1=t;
data[0] = 0x73;//”S”
data[1] = 0x74;//”T”
esp_err_t result = esp_now_send(slave.peer_addr, data, sizeof(data));
Serial.print(“[ST]”);
Serial.println(t);
}

// Aボタンを押したらM5StackPower Reset
if ( M5.BtnA.wasPressed() ) {
Serial.println(“[PR]”);
M5.Power.reset();
}
//Bボタンを押したらタイマーリセット “T””R”
if(M5.BtnB.wasPressed()){
data[0] = 0x54;//”T”
data[1] = 0x52;//”R”
esp_err_t result = esp_now_send(slave.peer_addr, data, sizeof(data));
Serial.println(“[TR]”);
toff=1;
t_1=0;

}
//Cボタンを押したらM5Stack電源オフ
if(M5.BtnC.wasPressed()){
data[0] = 0x4F;//”O”
data[1] = 0x46;//”F”
esp_err_t result = esp_now_send(slave.peer_addr, data, sizeof(data));
Serial.println(“[OF]”);
M5.powerOFF();

}

}

 

➁MASTER(左右子機 M5StickC用)

#include <M5StickC.h>
#include <esp_now.h>
#include <WiFi.h>esp_now_peer_info_t slave;
uint8_t data[12];//12byte 4*value0,value1,value2
int i=0;
int j=0;
int value0,value1,value2;
uint8_t ti,ti_1;
int toff=0;
int tzero=0;
int t=0;
int hasu=0;
//+++++++++++++++i_to_char+++++++++++++++++
// i=IntegerValueData,*d=Array pointer, n=Array start No
void i_to_char(int i,uint8_t *d, int n)
{
d[n] = i & 0x000000ff;
d[n+1] = (i & 0x0000ff00) >> 8;
d[n+2] = (i & 0x00ff0000) >> 16;
d[n+3] = (i & 0xff000000) >> 24;
}
//++++++++++++++++++++++++++++++++++++++++++// 送信コールバック
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]);
//Serial.print(“Last Packet Sent to: “);
//Serial.println(macStr);
//Serial.print(“Last Packet Send Status: “);
//Serial.println(status == ESP_NOW_SEND_SUCCESS ? “Delivery Success” : “Delivery Fail”);
}// 受信コールバック
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]);if(data[0]==0x73 && data[1]==0x74)//when data=”ST” TimingSTART
{
hasu++;
delay(1);
}
if(data[0]==0x54 && data[1]==0x52)//when data=”TR” TimerReset
{
toff=1;
hasu=0;
j=0;
}
/*Serial.print( macStr);
//Serial.print(“,”);
//Serial.print(data_len);
for ( int i = 0 ; i < data_len ; i++ ) {
Serial.print(data[i]);
Serial.print(” “);
}
Serial.println(“”);
*/
}

void setup() {
M5.begin();
// 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登録
// M5Stick COM7=xx:xx:xx:xx:xx:E8
// M5Stick COM13=yy:yy:yy:yy:yy:CC
// M5StackBasic COM9=zz:zz:zz:zz:zz:DC

memset(&slave, 0, sizeof(slave));
//============M5Stack Slave AP Mac======================
slave.peer_addr[0]=(uint8_t)0xzz;
slave.peer_addr[1]=(uint8_t)0xzz;
slave.peer_addr[2]=(uint8_t)0xzz;
slave.peer_addr[3]=(uint8_t)0xzz;
slave.peer_addr[4]=(uint8_t)0xzz;
slave.peer_addr[5]=(uint8_t)0xDC;
//===============================
// 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 loop() {
M5.update();
j++;
if(toff==1){tzero=millis();toff=0;}//Timer Reset
t=millis()-tzero;
/*
Serial.print(“Value=”);
Serial.print(j);
Serial.print(“,”);
Serial.println(t);
*/
i_to_char(j,data,0);
i_to_char(t,data,4);
i_to_char(hasu,data,8);

esp_err_t result = esp_now_send(slave.peer_addr, data, sizeof(data));
//ESP_error(result);
//====CHECK Send Data==========================================
value0=(int)(data[0]+data[1]*256+data[2]*256*256+data[3]*256*256*256);
value1=(int)(data[4]+data[5]*256+data[6]*256*256+data[7]*256*256*256);
value2=(int)(data[8]+data[9]*256+data[10]*256*256+data[11]*256*256*256);

Serial.print(value0);//j counter
Serial.print(“,”);
Serial.print(value1);//data millis
Serial.print(“,”);
Serial.println(value2);//flag

delay(8);

/*
// ボタンを押したら送信
if ( M5.BtnA.wasPressed() ) {
uint8_t data[2] = {123, 234};
esp_err_t result = esp_now_send(slave.peer_addr, data, sizeof(data));
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”);
}
}
*/

}

 

●以後
30分以上連続運転してデータをログして、送受信による周期エラー
上死点タイミング同期の誤差エラーを測定して使えるか検証します。

コメントを残す

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