Spresense サブコア利用でのソフトシリアル通信例

## ■概要

シリアル通信を使う方は多いと思いますが、ピンを他機能で使用したいためにリソース上HWのUARTでなく、SWシリアルを使用したいと思っている方が少なからずいます。

SWシリアルはCPUで処理するために負荷の影響を受けやすいので処理タイミングを考えたりやデータ量によって都度チューニングが発生したり何かと手間になります。そこで、メイン処理でシリアル通信との処理バランスをあまり気にすることないように、サブコアでSWシリアルを専用に動作させるコードを端的に紹介します。

## ■環境

以下の役割で、2台のSpresenseメイン基板を使用します。
対向機としてPCや他のシリアルを使う場合は、1台で構いませんが、ここでは2台の使用で説明します。
・1台をデータの送り側：sender
・もう一台をデータの受け側：receiver

接続はメイン基板のD20とD21を使用します。
それぞれをオス・オスのジャンパーワイヤー等で結線してください。
以下で説明しているプログラムでは、送信、受信での役割を逆にしてますので、ストレート接続で構いません。
また、電位差を合わせるためお互いのGNDも結線してください。

|ピン|Receiver役割|Sender役割|
|:-:|:-:|:-:|
|D20|RX|TX|
|D21|TX|RX|
|GND|GND|GND|

参考情報：
[メイン基板ピン情報]("https://developer.sony.com/ja/spresense/development-guides/introduction_ja.html#_spresense_%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9C%E3%83%BC%E3%83%89")

## ■プログラム

###  １. Receiverメインコアプログラム

まず、SWシリアルのreceiver側のメインプログラムを以下に示します。
```c
#ifdef SUBCORE
#error "Please select main core!"
#endif

#include <MP.h>

#define MSG_ID_ADDR   10
#define MSG_ID_ADDROK 11

int subcore = 1;

static uint8_t *addr = NULL;
static struct swsdata *sdata_s;

void setup()
{
  int ret = 0;
  void *raddr;
  int8_t msgid;

  /* Boot SubCore */
  MP.begin(subcore);
  
  /* Send shared memory address to SubCore */
  msgid = MSG_ID_ADDR;
  MP.Send(msgid, addr, subcore);

  /* Receive from SubCore */
  while(1){
    ret = MP.Recv(&msgid, &raddr, subcore);
    if(ret == MSG_ID_ADDROK)
      {
        break;
      }
  }
  sleep(1);
}

void loop()
{
}
```

ここでの役割は、Subcoreの起動用にメッセージをメインコアに送り、Subcoreからの応答（`MSG_ID_ADDROK`）を待ち受けるまで、繰り返しメッセージを送るものです。応答がありましたらsetup関数での処理を終え、あとは空のloop関数のの中に入り何もしません。

:::note warn
　コードの書き込み時は、ArduinoIDEの`Tools -> Core`でMainCoreを選択してください。
:::

参考：[MultiCore MP ライブラリプログラミング情報](https://developer.sony.com/ja/spresense/development-guides/arduino_developer_guide_ja.html#_mp_library)

### ２. Receiverサブコアプログラム

まず、SWシリアルのreceiver側のメインプログラムを以下に示します。
```c
#if (SUBCORE != 1)
#error "Please select subcore1!"
#endif

#include <MP.h>
#include <SoftwareSerial.h>

#define SWSERIAL_BAUDRATE  115200
#define UART_BAUDRATE      115200

#define MSG_ID_ADDR   10
#define MSG_ID_ADDROK 11

SoftwareSerial mySerial(20, 21); /* RX, TX */

static void *addr;
static struct swsdata *sdata;
    
void setup()
{
  int8_t msgid;
  int  ret = 0;

  addr = NULL;

  Serial.begin(UART_BAUDRATE);
  while (!Serial) {
    ;
  }
  /* Initialize sw serial */
  mySerial.begin(SWSERIAL_BAUDRATE);

  MP.begin();
  MP.RecvTimeout(MP_RECV_BLOCKING);
 
  /* Receive address of a shared memory from MainCore */
  while(1){
    ret = MP.Recv(&msgid, &addr);
    if(ret == MSG_ID_ADDR)
      {
        MPLog("MSG_ID_ADDR received\n");
        msgid = MSG_ID_ADDROK;
        if(addr != NULL)
          {
             /* to do：get data when using a share memory */ 
          }
        MP.Send(msgid, addr);      
        MPLog("MSG_ID_ADDROK sent\n");
        break;
      }
  }
}

void loop()
{
  /* Pass a message to HW serial directly for output. */
  if (mySerial.available()) Serial.write(mySerial.read());  
}
```

サブコア側では、メインコア側からメッセージ(`MSG_ID_ADDR`)が送られたら、`MSG_ID_ADDROK`のメッセージを送り返して、loop関数の処理に入ります。
```
if (mySerial.available()) Serial.write(mySerial.read());
```
ここでは、SWシリアルにメッセージがあれば、`mySerial.read()`でデータをリードしたものを、`Serial.write`でHWシリアルを通じてデータをターミナルに表示します。

:::note warn
　コードの書き込み時は、ArduinoIDEの`Tools -> Core`でSubCore1を選択してください。
:::

### ３. Senderプログラム

データを送信する側のサンプルプログラムは以下となります。
少しシンプルでない感がありますが、ご了承ください。

```c
#include <SoftwareSerial.h>

#define RESTART_CYCLE       (60 * 5)  /* check coutner */
#define SELF_UART_BAUDRATE   115200
#define TGT_SERIAL_BAUDRATE  115200

#define MAXCNT 10000

SoftwareSerial mySerial(21, 20); // RX, TX

char *data;
const char *str ="20231225 Merry Christmas and Happy New Year! counter:";

static unsigned long time1;
static unsigned long num = 1;
static double sum = 0;  

void setup() {

  /* Reserve a message area */
  data = (char *)malloc(strlen(str)+10);
   
  /* Set Serial baudrate. */
  Serial.begin(SELF_UART_BAUDRATE);
 
  /* Initialize sw serial */
  mySerial.begin(TGT_SERIAL_BAUDRATE);

  /* Wait HW initialization done. */
  sleep(3);

  /* Turn off all LED:Setup done. */
  ledOff(PIN_LED0);
  ledOff(PIN_LED1);
  ledOff(PIN_LED2);
  ledOff(PIN_LED3);

  /* set start time */
  time1 = micros();
}

/* Toggle status of LED every when this called */
void togLED(int *val){
  if(*val==0){
    ledOn(PIN_LED0);
    *val = 1;
  }else{
    ledOff(PIN_LED0);
    *val = 0;    
  }  
}

void loop()
{
  static int LoopCount = 0;
  static int toggle = 0;
  static int cnt = 0;
  unsigned long time2;
  unsigned int bytes;
  double bps;
  long delta;
  long mean;
  int addlen;
      
  togLED(&toggle);

  if(cnt>=MAXCNT) cnt = 0;
  addlen = 7;　/* 5(character of cnt) + 1(\r) + 1(\0) */
  snprintf(data, strlen(str)+addlen, "%s%05d\r",str, cnt++);

  /* Output data */
  mySerial.print(data);

  /* Check loop count. */
  LoopCount++;
  if (LoopCount >= RESTART_CYCLE)
  {
    /* Turn off/on LED0 */
    togLED(&toggle);

    /* get current time */
    time2 = micros();

    /* calculation a bps(bit per sec) */
    delta = abs((signed long)(time2 - time1));
    bytes = (strlen(str)+addlen);
    bps = (bytes*8*RESTART_CYCLE/(double)delta)*1000000;

    /* for average */
    sum += bps;

    /* show difference time and bps, average of bps */
    printf("num:%d time diff :%d bps:%.1f mean;%.1f \n",num, delta, bps, (double)(sum/num));

    /* reset a start time for next */
    time1 = time2;
    
    if (num>=LONG_MAX){
      printf("reset num, sum\n");
      num = 0;
      sum = 0;
    }
    else{
      num++;
    }
    
    LoopCount = 0;
  }
}
```

:::note warn
　コードの書き込み時は、ArduinoIDEの`Tools -> Core`でMainCoreを選択してください。
:::

## ■実行結果例

以下に実行結果例を示します。

Receiver側　サブコアのメッセージ起動時
```
[Sub1] MSG_ID_ADDR received
[Sub1] MSG_ID_ADDROK sent
20231225 Merry Christmas and Happy New Year! counter:00000
20231225 Merry Christmas and Happy New Year! counter:00001
20231225 Merry Christmas and Happy New Year! counter:00002
20231225 Merry Christmas and Happy New Year! counter:00003
20231225 Merry Christmas and Happy New Year! counter:00004
20231225 Merry Christmas and Happy New Year! counter:00005
・
・
・
```

Sender側　メッセージ
```
num:1 time diff :1563058 bps:92127.1 mean;92127.1
num:2 time diff :1563496 bps:92101.3 mean;92114.2
num:3 time diff :1563516 bps:92100.1 mean;92109.5
num:4 time diff :1563710 bps:92088.7 mean;92104.3
num:5 time diff :1563759 bps:92085.8 mean;92100.6
・
・
・
```

ここでの例のように、`bps:92083.3`にデータの転送率（bps）が表示され、`mean;92085.1`にはbpsの平均値が表示されます。

この実際の転送速度は、送信側の1回の送信でのメッセージ量にも関係しています。また、今回のサンプルの例では送信で扱うデータ量に少しゆとりがある状態なので、プログラムで設定している`Baudrate値：115200`に対しての実質的な最大転送速度を表してはいないと思います。あくまでも参考値としてとらえてください。