nanopbによりマイコンでProtocol Buffersを使用する方法
記事の概要
Protocol Buffersとは何か?
データ構造をGoogleの策定した形式に従ってシリアライズしたものをProtocol Buffersと呼びます。
このProtocol Buffersを使う利点は、様々な機器間で送受信するデータを共通のデータ構造で扱えることです。
例えば、サーバとアプリ、アプリとマイコン、マイコンとマイコンでそれぞれ別のデータ構造を用いて通信していては管理が煩雑になります。
共通のデータ構造を持ち、共通の形式でシリアライズされたProtocol Buffersを使えば、間違いが起きにくいというわけです。
Protocol Buffersの意義について、詳しくは以下の記事をご参照ください。
今さらProtocol Buffersと、手に馴染む道具の話
環境
本記事では以下の環境で作業しています。
本記事では以下の開発環境の構築方法については解説しません。
マイコンはSTMでも、ルネサスでも、他のどれでも同様に使えます。
- OS
- Windows 10
- Python3
- anaconda3
- マイコン
- Nordic nRF52832(ARM系)
nanopbのダウンロード
マイコンに組み込める小サイズなライブラリnanopbを使用します。
以下をダウンロードしてください。
protoファイルの作成
データ構造を定義します。これは自分の作成するアプリケーションの仕様に合わせて自由に作成できます。
今回は以下のmy_test.proptを作成しました。
3つのデータ構造体です。
これらは長さやid、そしてデータ配列から成ります。
syntax = "proto3";
import "nanopb.proto";
message TestOne {
int32 length = 1;
bytes first_data = 2 [(nanopb).max_size = 5, (nanopb).fixed_length = true];
}
message TestTwo {
int32 length = 1;
bytes second_data = 2 [(nanopb).max_size = 8, (nanopb).fixed_length = true];
}
message TestThree {
int32 length = 1;
int64 id = 2;
bytes third_data = 3 [(nanopb).max_size = 16, (nanopb).fixed_length = true];
}
ここでデータ配列はあらかじめサイズを設定しました。 (nanopb).max_size
で配列のサイズを、 (nanopb).fixed_length
でサイズは固定であることを指定してあります。
他のデータ型の設定方法の詳細は以下をご参照ください。
https://jpa.kapsi.fi/nanopb/docs/concepts.html#data-types
また、protoファイルにおいて (nanopb)
を使用するためには、以下の例のように import "nanopb.proto";
が必要になります。
これがないと、 Option "(nanopb)" unknown.
というエラーメッセージが表示されます。
コンパイル
マイコンに組み込むファイルを自動生成します。
そのために、まずは以下のツールをインストールしてください。
python -m pip install protobuf grpcio-tools
anaconda3のコマンドプロンプトにおいて python ../../generator/nanopb_generator.py YOUR_FILE.proto
を実行します。
階層の場所とファイル名は各位の環境に合わせて修正ください。
(base) D:\nanopb\examples\my_test>python ../../generator/nanopb_generator.py my_test.proto
Writing to my_test.pb.h and my_test.pb.c
成功すれば、*.pb.h と *.pb.c が生成されます。
マイコンに組み込む
今回はNordicのnRF52382を例に説明しますが、手順は他のマイコンでも同様です。
上で自動生成されたファイルmy_test.pb.h と my_test.pb.c、およびnanopbの階層にあるファイル、pb.h、pb_common.c、pb_common.h、pb_decode.c、pb_decode.h、pb_encode.c、pb_encode.hを自分のマイコン開発環境にコピーします。
今回は適当なサンプルプロジェクトの中に放り込んでみました。
エンコーダ
送信データをシリアライズします。
nanopbのexampleフォルダにあるサンプルプログラムのsimple.cを参照して以下を作成しました。
送受信するメッセージのデータは message1_buffer[]
に格納します。
メッセージのデータサイズは message1_length
に格納します。
uint8_t message1_buffer[128];
size_t message1_length;
bool result;
/* Encode our message */
{
/* Allocate space on the stack to store the message data. */
TestOne message1 = TestOne_init_zero;
TestTwo message2 = TestTwo_init_zero;
TestThree message3 = TestThree_init_zero;
/* Create a stream that will write to our buffer. */
pb_ostream_t stream1 = pb_ostream_from_buffer(message1_buffer, sizeof(message1_buffer));
/* Fill in the data */
uint8_t sample_data1[5] = "Hello";
message1.length = 11;
for(int i=0; i<5; i++)
{
message1.first_data[i] = sample_data1[i];
}
/* Now we are ready to encode the message! */
result = pb_encode(&stream1, TestOne_fields, &message1);
message1_length = stream1.bytes_written;
/* Then just check for any errors.. */
if (!result)
{
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream1));
return 1;
}
}
メッセージ実体の作成
まずはデータを格納する実体を作成します。
TestOne message1 = TestOne_init_zero;
TestTwo message2 = TestTwo_init_zero;
TestThree message3 = TestThree_init_zero;
データ型は自動生成したmy_test.pb.hに定義されています。
my_test.proptで設定した通りのデータ構造体が作成されているはずです。
/* Struct definitions */
typedef struct _TestOne {
int32_t length;
pb_byte_t first_data[5];
} TestOne;
typedef struct _TestThree {
int32_t length;
int64_t id;
pb_byte_t third_data[16];
} TestThree;
typedef struct _TestTwo {
int32_t length;
pb_byte_t second_data[8];
} TestTwo;
初期化定数の *_init_zero
は自動生成したmy_test.pb.hに作成されています。
0で初期化する以外にも、初期値を設定する*__init_default
も用意されています。
/* Initializer values for message structs */
#define TestOne_init_default {0, {0}}
#define TestTwo_init_default {0, {0}}
#define TestThree_init_default {0, 0, {0}}
#define TestOne_init_zero {0, {0}}
#define TestTwo_init_zero {0, {0}}
#define TestThree_init_zero {0, 0, {0}}
送信用stereamの作成
stremを作成します。
message1_buffer[]を引数に設定することで、これにシリアライズしたデータが格納されるようになります。
/* Create a stream that will write to our buffer. */
pb_ostream_t stream1 = pb_ostream_from_buffer(message1_buffer, sizeof(message1_buffer));
送信データの準備
送信データを用意します。
/* Fill in the data */
uint8_t sample_data1[5] = "Hello";
message1.length = 11;
for(int i=0; i<5; i++)
{
message1.first_data[i] = sample_data1[i];
}
送信データのシリアライズ
stream を用いてmessage1_buffer[]にシリアライズした送信データを格納します。
/* Now we are ready to encode the message! */
result = pb_encode(&stream1, TestOne_fields, &message1);
message1_length = stream1.bytes_written;
以下のようにpb_encode
の実行前後を比較することで、message1_buffer[]にシリアライズされたデータが格納されていることが分かります。
デコーダ
受信したデータをデシリアライズします。
このサンプルプログラムでは、先にエンコードしたデータを使用していますが、実際には送信元から受信したシリアライズ・データをmessage1_buffer[]に格納します。
nanopbのexampleフォルダにあるサンプルプログラムのsimple.cを参照して以下を作成しました。
{
/* Allocate space for the decoded message. */
TestOne message1 = TestOne_init_zero;
TestTwo message2 = TestTwo_init_zero;
TestThree message3 = TestThree_init_zero;
/* Create a stream that reads from the buffer. */
pb_istream_t stream1 = pb_istream_from_buffer(message1_buffer, message1_length);
/* Now we are ready to decode the message. */
result = pb_decode(&stream1, TestOne_fields, &message1);
/* Check for errors... */
if (!result)
{
printf("Decoding failed: %s\n", PB_GET_ERROR(&stream1));
return 1;
}
}
メッセージ実体の作成
まずはデータを格納する実体を作成します。
これはエンコーダと同様なので説明は省略します。
受信用straemの作成
streamを作成します。
受信データを格納したmessage1_buffer[]をstreamに渡すことで、デシリアライズの準備をします。
/* Create a stream that reads from the buffer. */
pb_istream_t stream1 = pb_istream_from_buffer(message1_buffer, message1_length);
受信データのデシリアライズ
streamを介して受信データを構造体に代入します。
/* Now we are ready to decode the message. */
result = pb_decode(&stream1, TestOne_fields, &message1);
自動的に構造体の定義通りにデータが格納されるので、バグが減らせて、コード作成も容易になります。
以下のようにpb_decode
の実行前後を比較することで、構造体のmessage1にデシリアライズされたデータが自動的に格納されていることが分かります。
今回は構造体を1つしか試しませんでしたが、他の作成した2つについても同様のことができます。
まとめ
以上のようにnanopbを使用することで、簡単にProtocol Buffersをマイコンに導入できることが分かりました。
IoTが盛んになり様々な機器やサーバ同士を連結するようになったことで、送受信データの管理が複雑化しています。
データ管理を容易にするProtocol Buffersは、今後に使用する機会が増えていくと思われます。
Author And Source
この問題について(nanopbによりマイコンでProtocol Buffersを使用する方法), 我々は、より多くの情報をここで見つけました https://qiita.com/Kosuke_Matsui/items/878b4f511366675d9428著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .