0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spresenseのコア間アドレス通信のポインタ型を間違えた話

Posted at

SONYのマイコンであるSpresenseではMultiCoreライブラリ(MPライブラリ)を使うことでマルチコアプログラミングができます.

ただし,直接データの値をやりとりできるのはuint_32tのスカラー値のみなので,例えば配列変数を送りたいケースではアドレスを送受信する必要があります.

このアドレス送受信を私が実装する上で,ポインタの記述で悩んだ点があったので記事にしておきます.
間違いや不適切な点があればお知らせください.

ドキュメントの内容

Spresense Arduino IDEのマニュアルを読むと,アドレス送受信の項を読むと関数の定義は以下のようになっています.

  • 送信関数:int Send(int8_t msgid, void *msgaddr, int subid)
  • 受信関数:int Recv(int8_t *msgid, void *msgaddr, int subid)

Send関数の第二引数とRecv関数の第二引数がそのまま対応しているようにも見えますが,ここが以下のように書き間違えたポイントでした.

間違った書き方

#include <MP.h>

#ifndef SUBCORE // maincore
double a[2] = {1.5, 2.0};
void setup() {
  MP.begin(1);
  MP.RecvTimeout(MP_RECV_BLOCKING);
  delay(300);
  MP.Send(0, &a, 1);
}

void loop() { }
#endif

#if (SUBCORE == 1)
double* b;

void setup() {
  MP.begin();
  MP.RecvTimeout(MP_RECV_BLOCKING);
  uint8_t msgid_ = 255;
  MP.Recv(&msgid_, b);
}

void loop() { 
  MPLog("%lf, %lf\n", b[0], b[1]);
  delay(1000);
  }
#endif

メインコアで宣言したdouble a[2]のアドレスをサブコア1に送り,double* bに格納して表示したいプログラムです.

サブコア1,メインコアを選択してそれぞれ書き込みます
コンパイルは問題なく通りますが,シリアルモニタの出力を見ると正しく値を受け取れていないことがわかります.

[Sub1] 0.000000, 0.000000

正しい書き方

#include <MP.h>

#ifndef SUBCORE // maincore
double a[2] = {1.5, 2.0};
void setup() {
  MP.begin(1);
  MP.RecvTimeout(MP_RECV_BLOCKING);
  delay(300);
  MP.Send(0, &a, 1);
}

void loop() { }
#endif

#if (SUBCORE == 1)
double* b;

void setup() {
  MP.begin();
  MP.RecvTimeout(MP_RECV_BLOCKING);
  uint8_t msgid_ = 255;
  //MP.Recv(&msgid_, b);
  MP.Recv(&msgid_, &b);     // bそのものではなくポインタを渡すように修正
}

void loop() { 
  MPLog("%lf, %lf\n", b[0], b[1]);
  delay(1000);
  }
#endif

シリアルモニタの出力結果は以下の通りです.

[Sub1] 1.500000, 2.000000

正しく値を受け取れていることが分かります.

何がおかしいのか

最初間違えたコードでは,Recv関数の第二引数としてdouble *型を与えているので,ポインタではあるためコンパイルは問題なく通ります.

しかし,通信がアドレスを送受信していることを考えると,Recv関数の引数にはアドレスを格納する変数のポインタを与える必要があります.

Recv関数の第二引数は,アドレスをどこに格納すればいいかを聞いているのです.
したがって先のプログラムでは,アドレスを格納する変数であるdouble* bの場所を&bとして教えてやる必要があります.

画像1.png

Sendでは変数のアドレス(ポインタ)を渡せばいいのに対して,Recvではポインタのアドレス(ポインタのポインタ)を渡すという点で,注意が必要です.

応用

特定のコアの特定のメッセージIDからのみアドレスを受け取る関数を作りたいときは以下のように書くことができます.
メインコア側の送信プログラムは同じなので省略しています.

#if (SUBCORE == 1)
double* b;

void receiveWithID(uint8_t msgid_, double** pointer_){
  uint8_t msgid_receive_;
  double* addr_;
  while(1){
    MP.Recv(&msgid_receive_, &addr_);
    if (msgid_receive_==msgid_){
      *pointer_ = addr_;
      break;
    }
  }
}

void setup() {
  MP.begin();
  MP.RecvTimeout(MP_RECV_BLOCKING);
  const uint8_t REQUIRED_MSG_ID = 0;
  receiveWithID(REQUIRED_MSG_ID, &b);
}

void loop() { 
  MPLog("%lf, %lf\n", b[0], b[1]);
  delay(1000);
  }
#endif

終わりに

最初から公式のサンプルコードであるMessegeHelloを参考にすべきでした…

ここから発展してstd::vectorをコア間でやりとりしようとして失敗したときの話はこちら

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?