うごくものをつくる

個人的な学習ノート

基礎技術メモ

ESP-NOWによる無線通信の使い方(応用編)

ESP-NOW機能を使った無線通信の基本的なやり方について、送信編と受信編の2本に分けて記事を書いた。

ESP-NOWによる無線通信の使い方(送信編)

ESP-NOWによる無線通信の使い方(受信編)

ここでは、ESP-NOWによるデータの送受信について、応用的なやり方を記載する。

応用1 8ビット以上の数値を送る。

送信編の記事に書いたとおり、基本的にESP-NOWによるデータ送信はuint8_t型の変数を格納したリストしか送ることができない。すなわち、符号なし8ビットで表現できる0~255までの値しか送れないということだ。

試しに「300」という数値をESP-NOWで送ってみよう。結果はこうなる。

シリアルコンソールの表示

このように、300という数値を送ったにもかかわらず、「44」という数値を受け取ってしまった。

これは、300という数値は2進数で表すと100101100なのだが、これをuint8_t型変数に代入してしまうと8ビットしか入らないために最上位(9桁目)の1が入らず、00101100のみが格納されてしまう。00101100は10進数で言うと44であるため、受信側は44を受け取る。

(※厳密に言うと、先ほど「300という数値を送ったにもかかわらず」と書いたが、送信する時点で送信用の配列要素に300を代入した時点で既に最上位の数値が落ちてしまっているため、そもそも送信機は300を送ることができておらず、44を送信してしまっている。受信機はそれを正しく受け取り、結果は44だと言っているだけである。)

255よりも大きな数値を送信するため、ここでは「数値を8ビットごとに分割して送り、受信側で結合する」という作戦を取る。

今回送る値は、61644とする。これを2進数で表すと1111000011001100という16桁(16ビット)で記述される。この16ビット整数を、上8桁、下8桁に分割したものを格納するため、uint8_t型変数を2個用意する。なお、ここでは16ビット整数を例にして説明するが、同じやり方で32ビットなどのもっと大きな整数も送信できる。

uint16_t bigNum = 61644;//送信したい大きな値(16ビット)
uint8_t num_MSB;//送りたいbigNumの上位8ビットを格納するための変数
uint8_t num_LSB;//送りたいbigNumの下位8ビットを格納するための変数

まずは下位8ビットを、num_LSBに格納しよう。やり方はとっても簡単。ただ普通に代入するだけで良い。

num_LSB = bigNum;//下位8ビットは普通に代入

変数bigNumは16ビットなので16桁の2進数であるが、それを代入する受け入れ先のnum_LSBは8ビットしか格納できないため、何もしなくてもただ代入するだけで下位8ビットのみを抽出して代入するような動作となる。

16ビット変数を8ビット変数に代入したときの挙動

上位8ビットを代入するためには、まず16ビット変数を右に8つビットシフトさせる。その後に代入してやれば、上位8ビットを抽出して代入ができる。

bigNum = bigNum >> 8;//右に8桁ビットシフト
num_MSB = bigNum;//ビットシフト後の変数の下位8桁(もとの変数の上位8桁)を代入
ビットシフトしてから代入して、上位8ビットを抽出する。

これで大きな桁数の変数を8ビットごとに分割してuint8_t型変数に代入できたので、これを送信する。受信側では、複数のuint8_t型変数を結合して、元々送りたかった大きな桁数の数値を再現する必要がある。

ここから先は受信機側での処理である。今は16ビット変数を送ろうとしているので、受信側にもそれを格納するためのuint16_t型変数を用意しておく(ここではRecvNumという名前で変数を用意した)。

あとは先程の8ビット分割と逆のような手順によって数値を結合する。つまり、まず上位8桁を16ビット変数の下位8桁に代入したあと、左に8ビットシフトさせて上位8桁とし、空いた下位8桁に受信した下位8ビットを足し合わせれば、元々送りたかった16ビットが完成する。

uint16_t RecvNum; //16ビット整数格納用の変数

RecvNum = data[0];//16ビット変数の下位8桁に、送信したい値の上位8桁を代入
RecvNum = RecvNum << 8;//左に8桁ビットシフトして、下位8桁を上位8桁に送る
RecvNum = RecvNum + data[1];//送信したい値の下位8桁を、16ビット変数の下位8桁に加算する。

ここで、受信データとして受け取った配列であるdataの0番目の要素が上位8桁(先程のnum_MSBに相当)で、1番目の要素が下位8桁(num_LSB)である。

こうして結合した結果、どのような数値になったかチェックしてみよう。

コンソール出力

240というのは上位8桁の11110000を10進数にしたもので、204は下位8桁の11001100を10進数にしたものである。ちゃんと上位・下位8桁に分割して送受信できていることがわかる。これらを上記のように結合して16ビット変数にしたものを10進数で表すと61644となっており、今回送りたかった大きな値がちゃんと送受信できている。

応用2 文字列の送信

数値だけではなく文字列を送りたいということもあるだろう。この場合、文字列を各文字に分割してuint8_t型で表される数値に変換した後に送信し、受信側でそれを一文字ずつ復元して繋げるというやり方となる。

まずは送信側の処理について。今回送りたい文字は”abcdef”とする。char型変数を格納する配列として用意する。また、これらの文字をuint8_t型に変換して格納するための配列も用意する。

char moji[] = "abcdef";
uint8_t mojiData[sizeof(moji)];

配列mojiを用意するとき、わざわざ要素数を指定せず、カッコ[]の中は何も書かずに直接文字列代入で宣言してやると楽。勝手に必要な要素数をカウントして配列を作ってくれる。なお、今回の文字列は6文字だから要素数は6個かと思いきや実際には文字数より1多い要素数の配列になる。なぜなら、char文字列の最後には文字列終了を表す空文字が入るからである。

というわけで、格納用のuint8_t型配列も6 + 1の7個の要素数を持ったものを用意する。sizeofを使って、配列mojiのサイズ(つまり要素数)と同じ要素数を持った配列として宣言してやれば良い。

さて、このchar型配列の各要素をuint8_tに代入するわけだが、char型変数はuint8_tと同じく8ビットの変数なので、先程のように桁を分割・結合する必要はない。そのまま代入してやるとよい。

for(int i=0;i < sizeof(moji);i++)
  {
    mojiData[i] = moji[i];
  }

複数の文字からなる文字列mojiを1文字ずつ抜き取って8ビット変数として配列mojiDataに入れていっている。ただそれだけ。あとはこの配列mojiDataをESP-NOWで送信する。

次に、受信機側の処理。受け取った配列の各要素を抜き取って、char型配列に一つずつ入れていく。ちょうど送信時と逆の手順のようなものだ。データ受信時に呼び出される関数をまるごと以下で紹介する。

void dataRecv(const uint8_t *addr, const uint8_t *data, int dataSize)//データ受信時に呼び出される関数
{
  char recvMoji[dataSize];//受け取ったデータと同じ要素数の、文字列格納用配列を作る
  for(int i=0;i<dataSize;i++) //1文字ずつ抜き取って配列に格納
  {
    recvMoji[i] = data[i];
  }

  Serial.print("Received char is, ");//受け取った文字列をシリアル出力(確認用)
  Serial.println(recvMoji);
}

これを、データ受信時に呼び出される関数内にて実行する。

コンソール出力

送りたかった文字列”abcdef”をちゃんと送れていることがわかる。

3,まとめ

ESP-NOWは8ビットの整数しか送ることができないのでなんの工夫もしないと0~255までの値しか送ることができないが、本記事の方法によりもっと大きな値を送ったり、文字列を送信することができるので、実用上これらができたらだいたい事足りると思う。

返信する

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