パイピコでシリアル通信

# パイピコでシリアル通信
arduinoではシリアル通信の関数があるので悩みませんでしたが、パイピコではシリアル通信についての関数はありません。

こちらのブログを参考にLinuxとパイピコでシリアル経由によりテキストを送受信できることを確かめました。

https://ysin1128.hatenablog.com/entry/2021/08/25/143519


# パイピコ側のプログラム


``` c
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "class/cdc/cdc_device.h"

#define MAX_LENGTH 256



int
main ()
{
  char getDAT[MAX_LENGTH];	// 受信したデータ用のバッファ

  stdio_init_all ();		// 標準入出力の初期化

  while (true)
    {
      int count = tud_cdc_available ();	// USB CDCバッファにデータがあるか確認

      if (count > 0)
	{
	  if (count > MAX_LENGTH - 1)
	    count = MAX_LENGTH - 1;	// ヌル文字のスペースを確保

	  tud_cdc_read (getDAT, count);	// USB CDCからデータをバッファに読み込む
	  getDAT[count] = '\0';	// 文字列の終端にヌル文字を設定

	  printf ("Received: %s\n", getDAT);	// 受信した文字列を表示
	  tud_cdc_write_flush ();	// 送信バッファにあるデータを送信し、バッファを空にする
	}
    }

  return 0;
}
```
先のブログ内のプログラムを1文字送受信から、文字列送受信に改変したものです。
ブログ内にもあるようにCMakeList.txtに標準出力をUSB側にする設定が必要です。

``` c
# Enable stdio over USB and UART
pico_enable_stdio_usb(USB_Serial 1)
pico_enable_stdio_uart(USB_Serial 0)
```

上記の設定がない場合はコンパイル時に必要なファイル(#include "class/cdc/cdc_device.h")が見つけられずmake時にエラーになるようです。

# コマンドラインでの確認
下記のコマンド(picocom)によりパイピコとメッセージを送受信できます。先のブログ内でもシリアル通信の設定について変更が必要とありますのでDTRサーキットとRTSサーキットのオプションが必要です。キーボードから入力された文字を表示します。

```
sudo picocom -b 115200 /dev/ttyACM0 --omap delbs  --raise-rts --raise-dtr
```
コマンドの実行結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683949/583f993f-7e2f-c391-5e5a-12d5b1e28e69.png)


# Linux側のプログラムからの送受信

プログラムから連続してUSB Serial Testの文字列を送信し、パイピコから受信します。
シリアル通信の設定についてコマンドラインからの送受信と同じようにDTRとRTSを設定しています。


``` c
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>

#define SERIAL_PORT "/dev/ttyACM0"	/* シリアルインターフェースに対応するデバイスファイル */
#define BUFFER_SIZE 256

int
main (int argc, char *argv[])
{
  char buf[BUFFER_SIZE];	/* バッファ */

  int fd;			/* ファイルディスクリプタ */
  struct termios oldtio, newtio;	/* シリアル通信設定 */

  fd = open (SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);	/* デバイスをオープンする */
  if (fd < 0)
    {
      perror ("Error opening serial port");
      return -1;
    }

  // 現在のシリアルポートの設定を待避させる
  tcgetattr (fd, &oldtio);
  newtio = oldtio;

  // ポートの設定を変更
  newtio.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
  newtio.c_iflag = IGNPAR;	// パリティエラーを無視
  newtio.c_oflag = 0;		// 出力モードを無効
  newtio.c_lflag &= ~ECHO;	// エコーバックを無効にする

  // フロー制御を設定しない場合
  newtio.c_cflag &= CRTSCTS;	// RTS/CTSのハードウェアフロー制御を無効化

  // シリアルポートの設定を適用
  if (tcsetattr (fd, TCSANOW, &newtio) != 0)
    {
      perror ("Error setting serial port attributes");
      close (fd);
      return -1;
    }

  // DTRをアサート（通常はデバイスの電源供給やリセットに使う）
  int status = TIOCM_DTR;
  ioctl (fd, TIOCMBIS, &status);

  // RTSをアサート（デバイスにデータ送信準備ができていることを通知）
  status = TIOCM_RTS;
  ioctl (fd, TIOCMBIS, &status);

  // 受信バッファをクリアする
  tcflush (fd, TCIFLUSH);


  for (;;)
    {
      // データ送信
      write (fd, "USB Serial Test", sizeof ("USB Serial Test") - 1);
      usleep (800);
      // 受信部分の改良
      int bytes_read = 0;
      int total_bytes = 0;

      // バッファをクリア
      memset (buf, 0, sizeof (buf));

      while (1)
	{
	  // 読み込み可能なバイト数を確認
	  int available = 0;
	  ioctl (fd, FIONREAD, &available);

	  // 読み込むバイト数が0ならループを抜ける
	  if (available == 0)
	    break;

	  // 受信バッファにデータがあれば読み込む
	  bytes_read = read (fd, buf + total_bytes, available);
	  if (bytes_read <= 0)
	    {
	      if (bytes_read == -1)
		perror ("Read error");
	      break;
	    }
	  total_bytes += bytes_read;

	  // バッファが一杯ならそれ以上は受け付けない
	  if (total_bytes >= BUFFER_SIZE - 1)
	    {
	      buf[BUFFER_SIZE - 1] = '\0';	// 文字列の終端を保証
	      break;
	    }

	  // 改行文字が見つかった場合、終端と見なす（オプション）
	  //if (strchr(buf, '\n') != NULL) {
	  //  buf[total_bytes] = '\0'; // 文字列の終端を保証
	  //  break;
	  //}
	}

      // 受信したデータを表示
      if (total_bytes > 0)
	{
	  buf[total_bytes] = '\0';	// 文字列の終端を保証
	  printf ("> %s", buf);
	}

      usleep (1500);		// 次のループまで少し待つ
    }

  tcsetattr (fd, TCSANOW, &oldtio);	/* ポートの設定を元に戻す */
  close (fd);			/* デバイスのクローズ */

  return 0;
}
```
連続して送受信します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/683949/81c62acd-3e36-ebcd-6c89-c0936e9fe9d4.png)


# USBによるシリアル通信ってどういう仕組み？


仕組みがよくわからないのでAIに聞いてみました。以下はAIによる回答です。

```
USB１．１の下ではUSBのパケットに変換されること無くシリアル通信しているということ？

回答
いいえ、その理解は誤解があります。Raspberry Pi PicoはUSB 1.1 Full Speedを使用してシリアル通信を行いますが、その過程では以下のような変換が行われます：

UARTからUSBへの変換: PicoのUART（シリアル）データは、まずPicoの内部でUSBパケットに変換されます。この変換は、PicoのRP2040マイクロコントローラ内のUSBデバイスコントローラが行います。つまり、UARTのシリアルデータは直接USBケーブルに送信されるわけではなく、一度USBパケットの形式に変換されます。
USBプロトコル: このUSBパケットは、USBプロトコルに従って構築され、USBバスを通じて送信されます。これはUSB 1.1 Full Speedの規格に従った通信であり、USBケーブルを通じてPCに送られます。
PC側の変換: PC側では、USBデバイスとして認識されたPicoからのデータを受信し、それを仮想COMポートドライバを使ってシリアルデータに戻します。つまり、シリアルデータはUSBパケットとして送信され、PC側でまたシリアル形式に変換されます。

つまり、Picoのシリアル通信はUSB 1.1の下で行われる際には、必ずUSBパケットへの変換を経由します。これは、物理的なUART信号をUSBケーブルを通じて伝送するために必要なプロセスです。したがって、「USBのパケットに変換されることなく」というのはこの文脈では正しくありません。シリアルデータはUSBパケットに変換され、USBプロトコルに従って送信されます。
```