ESP32とNXPマイコンFRDM-K64Fをbluetoothでつなげて、ESP32で測定したセンサ信号をK64F側でbluetooth経由で受信させてみました。
K64F側はbluetoothモジュールとして、RN42を使いました。使い方は下の記事にまとめてあります。
RN42-Bluetoothモジュールを使ってみた | ぼくのマイコン開発のメモ (tekuteku-embedded.xyz)
要するに、SPPというプロファイルを使って、データをシリアル通信するわけですが、信号自体をどういうフォーマットにして、送受信するかちょっと迷いました。
というのも、測定するチャンネルも複数で、かつ2バイトデータになる予定なので、チャンネル間で、データのはじめと終わりをうまくK64Fに伝えないとまずいのと、データがなんのセンサのものなのか識別子的なものを用意しとかないと、データを混濁してしまうからです。
google先生によれば、SLIPとかCOBSとかいう通信フォーマットがあるらしく、うまくすれば、使えそうなことは分かりました。
ただ、今回は、センサのチャンネル数も複数といっても固定だし、各チャンネルでデータサイズも固定だし、ちょっと冗長感は否めませんでした。とくにCOBSの理論も難しそうだし。
なんで、とりあえず、JSONフォーマットでこんな感じに信号を定義したらどうかなっと思って実際にやってみました。
ArduinoIDEにJSONパーサのライブラリがあるので、結構簡単に実現できました。
ただ、これは、数値データも文字データとして送信するので、はやいサンプリングでたくさん測定したいよ、みたいな目的には、たぶん向いてません。
この記事でわかること
分かることと言っても、先人の方々の成果をいい所どりしているだけですが。
- ESP32からSPPでBluetoothの信号を出す方法
- Arduino IDEでJSONパーサを使う方法
- FRDM-64FでJSONから値を取得する方法
ESP32からBluetoothでシリアル通信する方法
これは、他のサイトで色々紹介されてますね。こんなのとか。このサイトはかなりEPS32の記事が豊富なので、参考になります。(英語ですが…)
ESP32のライブラリには、SerialToSerialBTというのがあるので、これをベースにします。
Loop関数内のコードはこんな感じになってて、コマンドーを使えば、シリアル通信と全く同じような感じで、Bluetoothの信号の送受信ができちゃいます。
if (Serial.available()) {
SerialBT.write(Serial.read());
}
// ↑がシリアルモニタで入力したデータを送るコマンド
if (SerialBT.available()) {
Serial.write(SerialBT.read());
}
// ↑がBluetoothで受信したシリアルをシリアルモニタに表示するコマンド
実際PCとBluetoothで通信してみます。ESP32をPCとつないで、ペアリングします。下のBluetoothとその他のデバイスの設定をクリック。
デバイスを追加するをクリックして、ESP32testという名前のデバイスを探します。
発見。
右側のその他のBluetoothオプションという所をクリックすると下の画面がでます。ESP32SPPと表示があるポートをおぼえときます。COM19。
Tera Termを起動して、COM19に繋ぎます。ボーレートは115200bpsにします。
下のような感じで接続テストをします。左がPC、右がESP32です。
ArduinoJsonを使う
ESP32に入ってきたセンサ情報をJSONに変換するには、パーサーを実装しないといけないですが、ありました。
ライブラリマネージャ経由でインストールします。一番うえの💛のやつね。
とりあえず、サンプルとして、センサのかわりに乱数を発生させて、信号を3チャンネル分、JSONフォーマットで吐き出します。レイアウトはこんな感じ。
サンプルコードは下です。適当ですが、配列の定義の仕方とかは、参考になると思うので、のせておきます。
#include "BluetoothSerial.h"
#include "ArduinoJson.h" //これがJSONパーサ用のライブラリ
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif
BluetoothSerial SerialBT;
const int capacity = JSON_OBJECT_SIZE(6);
StaticJsonDocument<capacity>doc;
void setup() {
doc["TYPE"]="DATA";
doc["ID"]=1;
JsonArray Value = doc.createNestedArray("Value");
Value.add(1.0);
Value.add(2.0);
Value.add(3.0);
Serial.begin(115200);
SerialBT.begin("ESP32test"); //Bluetooth device name
Serial.println("The device started, now you can pair it with bluetooth!");
}
void loop() {
float x = 1.0f*(float)random(0,255)/255;
float y = 2.0f*(float)random(0,255)/255;
float z = 3.0f*(float)random(0,255)/255;
doc["Value"][0]=x;
doc["Value"][1]=y;
doc["Value"][2]=z;
String postmessage;
serializeJson(doc,postmessage);
SerialBT.print(postmessage);
delay(1000);
}
下が実行例。マイコンが受け取る用なので、改行とかはしてません。
これで、ESP32側は準備おk。
RN42の設定。
通信では、FRDM-K64F(RN42)をマスターにします。ペアリングの相手は限定したかったので、手持ちのEPS32のマックアドレスを指定する形をとりたいと思います。(マックアドレスの確認方法は、ググるとでてきました)
モードの設定を下のモードからAuto-Connect Master Modeに設定することにしました。
このモードだと、指定したMACアドレスに一致するデバイスに自動でペアリングしてくれます。アドレスの指定はSRコマンドーを使います。
Tera Termつかって、こんな感じに。
$$$ => SM, 3 => SR, <MACアドレス>
FRDM-K64F側の処理
gummoniさんという方がつくったライブラリを流用させてもらいました。見た目シンプルだったので、実装しやすいと思って。
ライブラリは、gummo_jsonとrefrectionという二つのコードに分かれていて、gummo_jsonはノータッチで、refrectionに自分の受信したいJSONのフォーマットを定義すればいいみたいです。
マイコンに実際に実装したときにやったことを書いときます。
メインコードでやったこと
マイコン側はRN42からUARTで信号を受けます。
実際には、{ “TYPE”:”DATA”,”ID”:1,”VALUE”:[1.0,2.0,3.0] } みたいなのが来ます。プログラムでは、 { から } まで受信できたときにフラグをたてて、json_deserializeという関数を呼ぶようにします。
この関数で、JSON->構造体に変換できるみたいです。
//JSON パーサー
#include "gummo_json.h"
#include "JSON.h"
// json format の定義
json_format_1 json_f1;
// 実際の変換
json_deserialize(&json_f1, &struct_types[My_Format_ID],msg);
ここで、構造体はjson_format_1という型で、これは自分のフォーマットに合わせて、reflection.hに定義します。
関数の二番目の引数のstruct_typesは、JSONのフォーマットに合わせて、フィールド情報が各要素毎に格納されている配列になってます。これもreflection.cで自分のデータに合わせて、定義しました。
最後のmsgはUART信号で受信した実際の文字列になってます。実際に変換したい文字列がはいってます。
要するに、json_f1の構造体に値をいれて戻してね、そして、フィールドの詳細はreflection.cで定義したものに従ってるよ、ちなみに受信した文字列はmsgよ、これを変換してくれ。という感じに関数に引数をわたしてます。
refrection.hでやったこと
#define JSON_FORMAT_1_ID_LENGTH (1)
#define JSON_FORMAT_1_DATA_LENGTH (3)
#define JSON_FOMRAT_1_MSG_LENGTH (16)
typedef struct
{
char TYPE[JSON_FOMRAT_1_MSG_LENGTH ];
int SensorID;
float Value[JSON_FORMAT_1_DATA_LENGTH];
} json_format_1;
好きな場所にオリジナルフォーマットをこんな感じで追加しました。
typedef enum
{
TYPE_ID_TEST1_DATA = 0,
TYPE_ID_TEST2_DATA = 1,
TYPE_ID_WIMU_Format1 =2 //これを追加した。
} TYPE_ID_USR;
あと、フォーマットの識別子として、使っているenumにオリジナルフォーマット番号を追加しました。
refrection.cでやったこと
各要素毎(TYPE, ID, Value)のフィールド情報をstruct_field という構造体に配列で定義しました。FIELD_INFOというマクロに最初に定義した構造体、キー名、値の型(gummo_json.hで定義されている)、データサイズを渡せば、いい感じに構造体に値を渡してくれる仕様でした。
static const json_format_1 f1;
const struct_field json_format_1_fields[]=
{FIELD_INFO(f1,TYPE,TYPE_ID_STRING,JSON_FOMRAT_1_MSG_LENGTH),
FIELD_INFO(f1,ID,TYPE_ID_INT,JSON_FORMAT_1_ID_LENGTH),
FIELD_INFO(f1,Value,TYPE_ID_FLOAT,JSON_FORMAT_1_DATA_LENGTH),
FIELD_END()
};
最後に構造体のタイプを下のように登録しました。
const struct_type struct_types[] =
{
TYPE_INFO(test1_data_fields, tmp1),
TYPE_INFO(test2_data_fields, tmp1.data[0] ),
TYPE_INFO(json_format_1_fields,f1), //追加
TYPE_END()
};
以上で、作業はおしまい。これで、ESP32のスケッチで乱数を設定した箇所にセンサ信号をいれれば、K64Fが信号を無線で受信できるはずです。
ちなみに、マイナス値の信号がはいってくるときにfloat型にうまく変換できていなかったので、下の関数の箇所の if文の条件式に(‘-‘,==*dst)を追加しました。
static bool json_capture_numeric(json_parse_context* context)
{
char* dst = context->tmp;
for (*dst = *context->msg; ; *(++dst) = *(++context->msg))
{
if (('0' <= *dst && *dst <= '9') || ('x' == *dst) ||
('.' == *dst) || ('a' <= *dst && *dst <= 'f') ||
('-' ==*dst)|| ('A' <= *dst && *dst <= 'F'))
continue;
*dst = '\0';
struct_set_value(context->pval, context->pfield->type,context->tmp);
return ('\0' != *context->msg);
}
}
json万歳🙌