産業用ネットワークRS-422にSpresenseを接続してみました

Next Step社製の **「Sony Spresense 用 RS-422/485 AddOnボード」** をVisual Studio CodeベースのSPRESENSE開発環境「SPRESENSE SDK」から利用してみました。RS-422/RS-485は産業向けネットワークとして人気の高いネットワークで、差動信号を用いた伝送により長距離を、ノイズの影響を少なく通信できる特徴を備えています。RS-485は半二重通信、RS-422は全二重通信です。

RS-422/485に対応したPLC自体は珍しくなく、Raspberry Pi由来のものもありますが、 **SPRESENSEをRS-422/485に対応させることにより、長寿命・低消費電力・ファンレス・コンパクトなPLCを実現することができます。**

![DSC01453.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2595184/5aa4f139-a04a-3da0-ec78-26ec86277643.jpeg)

### 今回購入したデバイス

https://nextstep.official.ec/items/38434310

### 関連するコミット

https://github.com/shino-ixy/spresense/commit/6190ebd545c499fbbf29a80e9fd501bb9c0a2c36

### 配線方法

「Sony Spresense 用 RS-422/485 AddOnボード」ボード背面のハンダ付けが必要です。

- MASTER側とSLAVE側で以下のジャンパ設定をします
 - JP1:2-3をショート：I2S端子を制御に利用
 - JP2:2-3をショート：I2S端子を制御に利用
 - JP3:終端抵抗 = 有効（クローズ）
 - JP4：RS-422（オープン）：全二重通信
 - JP5：RS-422（オープン）：全二重通信

![DSC01456.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2595184/dc5d0ea7-ad2d-15e6-bb77-df081de8994a.jpeg)

また、RS-422を利用するので、
MASTERのAとSLAVEのY、MASTERのBとSLAVEのZ、
MASTERのYとSLAVEのA、MASTERのZとSLAVEのBをケーブルで接続してください。

![DSC01455.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2595184/3a339c4c-5b2e-0d05-4d73-481bcb2f7ab1.jpeg)


# ビルド方法

SDKコンフィグを開き「Examples」にある「rs422」を選択してください。RS-422/RS-485の受信・送信制御にI2SのピンあるいはEMMCのピンを利用します。同居するアプリケーションにご注意ください。

![スクリーンショット 2022-09-25 14.16.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2595184/24dea694-1e0a-7d6d-7766-b21ccc004de8.png)

![スクリーンショット 2022-09-25 14.17.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2595184/67e52c89-cd2a-71d2-658a-00eb555ee4c4.png)

![スクリーンショット 2022-09-25 14.17.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2595184/4bab38d1-d219-6462-812d-46721c423629.png)

# プログラムの構造

NextStep社製のボードではRS-422/RS-485がSPRESENSE Main Board上のUART2と接続されています。SPRESENSE SDKでは、NuttXというRTOS（リアルタイムOS）が、ハードウェアを制御してくれますが、本環境ではこれらのポートは「/dev/ttyS2」というファイルで表現されます。このファイルをopenしてUART通信を行い、RS-422/RS-485の信号を生成します。

### UART通信ポートの初期化

ttyS2はUART通信用に以下のソースコードで初期化します。

```c
  struct termios tio;

  /* Open and Setup RS485_TTY_NAME */

  comm_tty = open(RS485_TTY_NAME, O_RDWR | O_NOCTTY);
  ret = errno;
  if (ret != 0)
    {
      printf("could not open %s\n", RS485_TTY_NAME);
      return -1;
    }

  tcgetattr(comm_tty, &tio);

  tio.c_cflag |= CREAD;   /* Enable receive */
  tio.c_cflag |= CLOCAL;  /* Local line, no modem control */
  tio.c_cflag &= ~CSIZE;  /* Clean the bits */
  tio.c_cflag |= CS8;     /* Data bit 8bit */
  tio.c_cflag &= ~CSTOPB; /* Stop bit 1bit */
  tio.c_cflag &= ~PARENB; /* Paritiy none */
  cfsetspeed(&tio, RS485_TTY_SPEED);

  tio.c_iflag = 0;
  tio.c_oflag = 0;
  tio.c_lflag = 0;
  tio.c_cc[VTIME] = 0;
  tio.c_cc[VMIN] = 1;

  tcflush(comm_tty, TCIOFLUSH);
  tcsetattr(comm_tty, TCSANOW, &tio);

  fcntl(comm_tty, F_SETFL, O_NONBLOCK);
```

### データの受信

UARTはいつデータが来るかわからないため、ファイルディスクリプタに対してpoll関数で待ちます。また、すべてのデータが一度に届くとは限らないため、要求長データが揃うまでループしてデータを繋げていきます。

```c
  int ret;
  struct pollfd fds[1];
  int nfds = 1;
  int done = 0;
  fds[0].fd = comm_tty;
  fds[0].events = POLLIN | POLLRDNORM;

  while (remain > 0)
    {
      ret = poll(fds, nfds, 500);
      while ((ret = poll(fds, nfds, 0)) > 0)
        {
          if (ret > 0)
            {
              if ((fds[0].revents & (POLLIN | POLLRDNORM)) != 0)
                {
                  ret = read(comm_tty, pbuf + offset, remain);
                  offset += ret;
                  remain -= ret;
                  done += ret;
                }
            }
        }
    }
```

### データの送信

送信は簡単です。write関数で書き込み、すべてのデータが送出完了するまでループします。

```c
  int done = 0;
  while (remain > 0)
    {
      ret = write(comm_tty, pbuf + offset, remain);
      offset += ret;
      remain -= ret;
      done += ret;
    }
```

以上のコードを組み合わせて、プログラムを作りました。正確には、追加でコマンドパーサーなどを実装しており、MASTER側で生成するメッセージの先頭４文字（ヌル文字含む）を変えることにより、SLAVE側の挙動を切り替えられるようにしました。


### プログラムの利用方法

Slave側を先に起動し、NuttShell(RTOS:NuttXのシェル）上で「rs422 -s」を実行、スレーブを起動します。次にMaster側に移動し、同じくNuttShell上で「rs422 -m」を実行、マスターを開始すると、マスターからコマンドがスレーブへ送出され、下記のシーケンスの後、マスターが終了します。スレーブは、初期状態に戻ります。

 - ini
   - マスターから"ini"コマンドを送出
   - スレーブ側でバッファを確保、初期化
   - 完了をACK応答

 - dat
   - マスターから"dat"コマンドを送出
   - スレーブ側のバッファをマスターへ送出
   - マスター側でバッファ内容をチェック

 - fin
   - マスターから"fin"コマンドを送出
   - スレーブ側でバッファを解放
   - 完了をACK応答

### おわりに

ノイズに強い有線接続の伝送線が準備できました。
色々なアプリケーションに利用できそうですね。