工作と競馬2

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

SPRESENSEでマルチコアを使用したプログラムを試しに作る

概要

SPRESENSEでマルチコアを使用したプログラムの実装を試し、動作を確認した。




背景と目的

ある目的で、SPRESENSEでマルチコアを使用したプログラムの実装をする必要が出た。そこで、まずは簡単なサンプルをもとに自分なりに理解しながら、実装し動作を確認してみる。



詳細

0. 参考資料

1. 作成するプログラムの概要

  • Arduino IDE
  • SPRESENSE Arduino SDK
  • SPRESENSE Arduino SDKのサンプルにある Examples > MultiCore MP > Message > MessageHelloスケッチを参考
  • サブコア1からメッセージとして以下の構造体データを引き渡す

実践的なデータとして、構造体をやり取りできたほうが良いので、MessageHelloを基にした。

struct MyPacket {
  volatile int status; /* 0:ready, 1:busy, 互いに書き込みをするためvolatile */
  char message[MSGLEN];
};

2. メインコアのプログラム

ポイントをまとめると、

  • setupにて、MP.begin(N)としてSubCoreNの起動を行う
  • MP.RecvTimeoutで受信モード設定
  • loopにて、MyPacket型構造体のポインタを定義し、MP.Recvで受けとる
  • 受け取ったら、statusを0にし、受け取り完了=> ready状態をSubCore1に伝える
#include <MP.h>

#define MSGLEN      64
#define MY_MSGID    10

struct MyPacket {
  volatile int status; /* 0:ready, 1:busy, 互いに書き込みをするためvolatile */
  char message[MSGLEN];
};

void setup() {

  Serial.begin(115200);
  while (!Serial);

  // サブコアの起動
  int ret = MP.begin(1);
  if (ret < 0) {
    printf("MP.begin(%d) error = %d\n", 1, ret);
  }

  // 受信モードの設定: MP_RECV_POLLING=データの受信をポーリングするモード
  // Recv() を呼び出したときに受信データが無かった場合はすぐに抜けます。このモードでは受信待ちに入ることはありません。
  MP.RecvTimeout(MP_RECV_POLLING);

  int usedMem, freeMem, largestFreeMem;
  MP.GetMemoryInfo(usedMem, freeMem, largestFreeMem);
  MPLog("Used:%4d [KB] / Free:%4d [KB] (Largest:%4d [KB])\n",
        usedMem / 1024, freeMem / 1024, largestFreeMem / 1024);
}

void loop() {
  int8_t   msgid;
  MyPacket *packet; // サブコア側のMyPacketのポインタを受け取る

  // サブコアからメッセージを受け取る
  if (MP.Recv(&msgid, &packet, 1) > 0) {
    printf("id=%d, message=%s\n", msgid, packet->message);
    packet->status = 0; // status -> ready
  }
}

3. サブコア1のプログラム

  • 各種マクロ定義とMyPacket構造体は同様に参照する必要があるのでメインコアと同様に定義
  • setupで、MP.begin()でメインコアに起動完了を伝える。なお、後述の書き込み対象Coreを設定しないと、MP.begin()はコンパイルエラーになる。※beginの引数が必要と怒られてしまう。
  • loopで、char配列messageに文字列データを格納し、MP.Sendにてメインコアに送信
#include <MP.h>

#define MSGLEN      64
#define MY_MSGID    10

struct MyPacket {
  volatile int status; /* 0:ready, 1:busy, 互いに書き込みをするためvolatile */
  char message[MSGLEN];
};

MyPacket packet; // やり取りしたい対象のデータ, グローバルで宣言

void setup() {
  memset(&packet, 0, sizeof(packet));
  MP.begin(); // Tools > CoreでSubCoreを選ぶと、引数なしのbeginがコンパイルエラーにならなくなる
}

void loop() {

  static int count = 0;

  // メインコアでpacket.status -> readyに書き換えられたら実行する
  if (packet.status == 0) {
    // メッセージ作成
    packet.status = 1; // status -> busy
    snprintf(packet.message, MSGLEN, "[%s] Hello %d", "Sub1", count++);

    // メインコアにメッセージ送信
    int ret = MP.Send(MY_MSGID, &packet);
    if (ret < 0) {
      printf("MP.Send error = %d\n", ret);
    }
  }

  delay(500);
}

4. 書き込み

メインコアは、通常通り書き込みすればよい。 サブコアは、IDEツールバーからTools > Coreで、SubCore1を選択。

5. 動作確認

以下のように、シリアルターミナルにメインコア側で受け取ったメッセージが出力された。

というわけで、無事動作確認OK。

[Main] Used: 896 [KB] / Free: 640 [KB] (Largest: 640 [KB])
id=10, message=[Sub1] Hello 0
id=10, message=[Sub1] Hello 1
id=10, message=[Sub1] Hello 2
id=10, message=[Sub1] Hello 3
id=10, message=[Sub1] Hello 4
id=10, message=[Sub1] Hello 5
id=10, message=[Sub1] Hello 6
id=10, message=[Sub1] Hello 7
id=10, message=[Sub1] Hello 8
id=10, message=[Sub1] Hello 9



まとめと今後の課題

SPRESENSEで、マルチコアのプログラムの実装方法の基本が整理できた。実装予定のプログラムでは、より複雑なデータをやり取りする必要があるが、今回の経験を役立てて完成させたい。