AquesTalk pico LSI を M5Stack の I2C, UART, SPI で動かす 

# 1. はじめに
　音声合成用LSI「AquesTalk pico LSI」を M5Stack に接続して動かしました。ESP32 をプロセッサに持つ M5Stack の場合、音声合成ライブラリ AquesTalk-ESP32 を使用すれば、わざわざ LSI を繋ぐ必要はありません。しかしながら I2C, UART, SPI の 3 種類のインタフェースで制御可能という点に惹かれ、試してみることにしました。 
　作成したコード全体は、GitHub[^11][^12] にあります。製作したプリント基板を委託販売[^13][^14][^15]しています。

- I2C(Inter-Integrated Circuit)
- UART(Universal Asynchronous Receiver/Transmitter)
- SPI(Serial Peripheral Interface)

[^11]: [botanicfields/PCB-MBUS-AquesTalk-pico-LSI](https://github.com/botanicfields/PCB-MBUS-AquesTalk-pico-LSI)
[^12]: [botanicfields/PCB-Grove-AquesTalk-pico-LSI](https://github.com/botanicfields/PCB-Grove-AquesTalk-pico-LSI)
[^13]: [M5Stack用 AquesTalk pico LSI モジュール基板](https://www.switch-science.com/catalog/7624/)
[^14]: [AquesTalk pico LSI用Grove(M5)接続基板](https://www.switch-science.com/catalog/7625/)
[^15]: [ダイオードマトリクススイッチ16基板](https://www.switch-science.com/catalog/7626/)


# 2. AquesTalk pico LSI
　AquesTalk pico LSI[^21]は、株式会社アクエスト[^22]の製品です。Atmel ATmega328(P)[^23] に音声合成エンジンが書き込んであります。製品が 2 種 (ATP3011[^24], ATP3012[^25]) があります。音質、クロック、ピン配置、最大ボーレート等の違いはありますが I2C, UART, SPI の仕様は同じです。

[^21]: [音声合成LSI 「AquesTalk pico LSI」](https://www.a-quest.com/products/aquestalkpicolsi.html)
[^22]: [株式会社アクエスト](https://www.a-quest.com/index.html)
[^23]: [MICROCHIP ATMEGA328](https://www.microchip.com/en-us/product/ATmega328)
[^24]: [データシート ATP3011XX](https://www.a-quest.com/archive/manual/atp3011_datasheet.pdf)
[^25]: [データシート ATP3012XX](https://www.a-quest.com/archive/manual/atp3012_datasheet.pdf)

# 3. M5Stack
　M5Stack[^31] は ESP32[^32] をベースとする IoT コントローラです。今回、開発環境に Aruduino-IDE[^33] を使用しています。ハードウェア機能として実装されている UART, I2C, SPI は、標準装備ライブラリで使用できます。GPIO への信号はデフォルトの割り当てを使います。

[^31]: [M5Stack](https://m5stack.com/)
[^32]: [Espressif ESP32](https://www.espressif.com/en/products/socs/esp32)
[^33]: [Arduino Software](https://www.arduino.cc/en/software)

## 3.1 I2C のデフォルト割り当て (M5Stack Basic)

|名称|SDA|SCL|用途|備考|
|:-:|:-:|:-:|:-:|:-:|
|Wire|GPIO21|GPIO22|内蔵デバイス・Grove-A|今回利用|
|Wire1|未指定|未指定| - | |

- GPIO(General Purpose Input/Output)
- SDA(Serial DAta)
- SCL(Serial CLock)

## 3.2 UART のデフォルト割り当て (M5Stack Basic)

|名称|RX|TX|用途|備考|
|:-:|:-:|:-:|:-:|:-:|
|Serial|GPIO3|GPIO1|USB-C| |
|Serial2|GPIO16|GPIO17| - |今回利用|

- RX(Receive)
- TX(Transmit)

## 3.3 SPI のデフォルト割り当て (M5Stack Basic)

|名称|SCLK|MISO|MOSI|SS|用途|備考|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|SPI|GPIO6|GPIO7|GPIO8|GPIO11|内蔵FLASH(GPIO6-11)| |
|HSPI|GPIO14|GPIO12|GPIO13|GPIO15| - | |
|VSPI|GPIO18|GPIO19|GPIO23|GPIO14|内蔵LCD| |
|VSPI|GPIO18|GPIO19|GPIO23|​GPIO4|内蔵TF| |
|VSPI|GPIO18|GPIO19|GPIO23|​GPIO5|（VSPIのデフォルト）|今回利用|

- SCLK(Serial CLocK)
- MISO(Mater In Slave Out)
- MOSI(Master Out Slave In)
- SS(Slave Select)
- LCD(Liquid Crystal Display)
- TF(TransFlash)

# 4. ライブラリ
　AquesTalk pico LSI とのインタフェースを、I2C, UART, SPI に固有な部分と、共通部分に分けてコーディングします。共通部分を基底クラスとし、固有部分を派生クラスとします。メインプログラムからは、インタフェースを選んだ上で派生クラスをインクルードします。

## 4.1 基底クラス BF_AquesTalkPico.h
1. virtual int Send(const char* msg) = 0
　AquesTalk pico LSI に文字列 msg を送信します。送信の様子をシリアルモニタに出力します。純粋仮想関数として定義し、使用するインタフェースに合わせて派生クラスで実装します。

2. virtual size_t Recv(char* res, size_t res_size) = 0
　AquesTalk pico LSI から文字列を受信します。受信完了は、'>' または '*' で判断します。純粋仮想関数として定義し、使用するインタフェースに合わせて派生クラスで実装します。

3. virtual bool Busy() = 0
　発声中など AquesTalk pico LSI が処理中の場合trueを応答します。false の場合発声が終了したと判断できます。純粋仮想関数として定義し、使用するインタフェースに合わせて派生クラスで実装します。I2C, SPI ではおのずとポーリングができますが、UART では Send() による明示的なポーリングが必要です。

4. int ShowRes(int res_length_to_show = 1)
　AquesTalk pico LSI からの応答をシリアルモニタに出力します。res_length_to_show に 2 を渡すと、応答が 1 文字以下の場合にシリアルモニタへの出力を抑止でき、I2C、SPI のポーリングによる応答が正常 ('>') の場合のシリアルモニタへの出力を省略できます。

5. int DumpEeprom()
　AquesTalk pico LSI の EEPROM (アドレス 0x000-0x3FF) の値をシリアルモニタに出力します。

6. int WriteEeprom(int address, int data)
　AquesTalk pico LSI の EEPROM にデータを書き込みます。

7. int WritePresetMsg(const char* msg[], int num_of_msg)
　AquesTalk pico LSI の EEPROM にプリセットメッセージを書き込みます。書き込みの様子をシリアルモニタに出力します。

8. int WriteSerialSpeed(int serial_speed)
　AquesTalk pico LSI (ATP3012) のシリアル通信速度を書き換えます。EEPROM への書き換えであり、UART でなくても更新できます。

9. int WriteI2cAddress(int i2c_address)
　AquesTalk pico LSI の I2C アドレスを書き換えます。EEPROM への書き換えであり、I2C でなくても更新できます。

```cpp:BF_AquesTalkPico.h
#pragma once

class AquesTalkPico {
 public:
  AquesTalkPico();
  ~AquesTalkPico();

  virtual int    Send(const char* msg) = 0;
  virtual size_t Recv(char* res, size_t res_size) = 0;
  virtual bool   Busy() = 0;

  int ShowRes(int res_length_to_show = 1);
  int DumpEeprom();
  int WriteEeprom(int address, int data);
  int WritePresetMsg(const char* msg[], int num_of_msg);
  int WriteSerialSpeed(int serial_speed);
  int WriteI2cAddress(int i2c_address);

 private:
  char HexChar(int n);
};
```

## 4.2 派生クラス BF_AquesTalkPicoWire.h
　AquesTalk pico LSI と I2C でインタフェースする派生クラスです。

1. int AquesTalkPicoWire::Begin(TwoWire &wire, int i2c_address = 0x2e);
　AquesTalk pico LSI を接続する I2C と I2C アドレスを指定します。I2C では、アドレスが異なる複数のデバイスを 1 つのバスに接続できます。M5Stack の内蔵デバイスや Grove-A ポートに接続したデバイスと、AquesTalk pico LSI のアドレス 0x2E が重複しない場合、Wire をそのまま使用できます。Begin() で Wire を受けとって利用します。

2. int AquesTalkPicoWire::Send(const char* msg);
　Arudino UNO などでは I2C に 32 バイトの制限がありますが、ESP32 では 127byte まで送れます。AuesTalk pico LSI は AVR ですが、I2C で 32 バイト以上の受信ができる模様です。コーディングが楽です。

3. size_t AquesTalkPicoWire::Recv(char* res, size_t res_size);
　エラーコードなど、AquesTalk pico LSI のレスポンスは不定長と言えます。Wire.requestFrom() は受信バイト数を明示する必要があります。そこで、1 バイトずつ受信しバッファに追加していく様にしました。区切り記号 '>' または '*' を受信したら Recv() の結果として返します。

4. bool AquesTalkPicoWire::Busy()
　AquesTalk pico LSI から読み出しを行い、区切り記号 '>' 1文字のみを受信できた場合に false を返します。それ以外は動作中を示す true を返します。

```cpp:BF_AquesTalkPicoWire.h
#pragma once
#include "BF_AquesTalkPico.h"
#include <Wire.h>

class AquesTalkPicoWire : public AquesTalkPico {
 public:
  AquesTalkPicoWire();
  ~AquesTalkPicoWire();

  // 0x2e: defaut/safe-mode i2c address of AquesTalk pico LSI
  int    Begin(TwoWire &wire, int i2c_address = 0x2e);
  int    Send(const char* msg);
  size_t Recv(char* res, size_t res_size);

 private:
  TwoWire* m_wire;
  int      m_i2c_address;
};

int AquesTalkPicoWire::Begin(TwoWire &wire, int i2c_address)
{
  delay(80);  // 80ms: reset process of AquesTalk pico LSI
  m_wire = &wire;
  m_i2c_address = i2c_address;
  return 0;
}

int AquesTalkPicoWire::Send(const char* msg)
{
  m_wire->beginTransmission(m_i2c_address);
  m_wire->write(msg);
  return m_wire->endTransmission();
}

size_t AquesTalkPicoWire::Recv(char* res, size_t res_size)
{
  int i = 0;
  while (i < res_size - 1) {
    m_wire->requestFrom(m_i2c_address, 1);
    if (m_wire->available()) {
      char recv_data = m_wire->read();
      res[i++] = recv_data;
      if (recv_data == '>' || recv_data == '*')
        break;
    }
    else
      break;
  }
  res[i] = '\0';
  return strlen(res);
}

bool AquesTalkPicoWire::Busy()
{
  char res[10];
  int res_length = Recv(res, sizeof(res));
  if (res_length >= 2) {
    Serial.printf("[AquesTalk Wire] Receive:%s\n", res);
    return true;
  }
  if (res[0] != '>')
    return true;
  return false;
}
```

## 4.3 派生クラス BF_AquesTalkPicoSerial.h
AquesTalk pico LSI と UART でインタフェースする派生クラスです。

1. int AquesTalkPicoSerial::Begin(Stream &stream)
　AquesTalk pico LSI を接続する UART (Serial2) を指定します。

2. int AquesTalkPicoSerial::Send(const char* msg);
　UART における送信処理は、大変シンプルです。

3. size_t AquesTalkPicoSerial::Recv(char* res, size_t res_size);
　UART からのレスポンスは Serial2.available() を監視して受信することができます。AquesTalk pico LSI は、最大 5 文字を返します。ボーレートが 9600bps の場合 5ms かかります。受信スピードの影響を減らすため受信バッファを用意し Serial2.available() 検出毎に追加する様にしました。区切り記号である '>' または '*' を受信したら Recv() の受信データとします。

4. bool AquesTalkPicoSerial::Busy()
　AquesTalk pico LSI にポーリングを行います。'\r' を送信し、区切り記号 '>' 1文字のみを受信できた場合に false を返します。それ以外は動作中を示す true を返します。なんらかの受信があるまで待機する作りです。

```cpp:BF_AquesTalkPicoSerial.h
#pragma once
#include "BF_AquesTalkPico.h"

class AquesTalkPicoSerial : public AquesTalkPico {
 public:
  AquesTalkPicoSerial();
  ~AquesTalkPicoSerial();
  int    Begin(Stream &stream);
  int    Send(const char* msg);
  size_t Recv(char* res, size_t res_size);

 private:
  Stream*   m_stream;
  const int m_recv_size = 10;
  char*     m_recv;
  int       m_recv_count;
};

int AquesTalkPicoSerial::Begin(Stream &stream)
{
  delay(80);  // 80ms: reset process of AquesTalk-Pico
  m_stream = &stream;
  m_recv_count = 0;
  return 0;
}

int AquesTalkPicoSerial::Send(const char* msg)
{
  m_stream->write(msg);
  return 0;
}

size_t AquesTalkPicoSerial::Recv(char* res, size_t res_size)
{
  while (m_recv_count <  m_recv_size - 1) {
    if (m_stream->available()) {
      char recv_data = m_stream->read();
      m_recv[m_recv_count++] = recv_data;
      if (recv_data == '>' || recv_data == '*')
        break;
    }
    else
      return 0;
  }
  if (m_recv_count > res_size - 1)
    m_recv_count = res_size - 1;
  for (int i = 0; i < m_recv_count; ++i)
    res[i] = m_recv[i];
  res[m_recv_count] = '\0';
  m_recv_count = 0;
  return strlen(res);
}

bool AquesTalkPicoSerial::Busy()
{
  Send("\r");
  char res[10];
  int res_length(0);
  do {
    res_length = Recv(res, sizeof(res));
  } while (res_length == 0);
  if (res_length >= 2) {
    Serial.printf("[AquesTalk Serial] Receive:%s\n", res);
    return true;
  }
  if (res[0] != '>')
    return true;
  return false;
}
```

## 4.4 派生クラス BF_AquesTalkPicoSpi.h
　AquesTalk pico LSI と SPI でインタフェースする派生クラスです。

1. int AquesTalkPicoSpi::Begin(SPIClass &spi, int ss)
　SPI バスは、SS 信号を分けることによって共有できます。Begin() で SPI バスのインスタンス（vspi）と SS（slave select）ピンの GPIO 番号を受け取って使用します。

2. int AquesTalkPicoSpi::Send(const char* msg);
　AquestTalk pico LSI では SPI において 1 バイト毎に 20μs の間隔が必要です。transfer() で 1 バイト転送する毎に delayMicroseconds(20) で間隔を確保しています。

3. size_t AquesTalkPicoSpi::Recv(char* res, size_t res_size);
　vspi.transfer() で 1 バイト転送する毎にバッファに追加していきます。区切り記号 '>' または '*' を受信したら aqtp.Recv() の結果として返します。AquestTalk pico LSI では SPI において 1 バイト毎に 20μs の間隔が必要です。transfer() で 1 バイト転送する毎に delayMicroseconds(20) で間隔を確保しています。

4. bool AquesTalkPicoSpi::Busy()
　AquesTalk pico LSI から読み出しを行い、区切り記号 '>' 1文字のみを受信できた場合に false を返します。それ以外は動作中を示す true を返します。

```cpp:BF_AquesTalkPicoSpi.h
#pragma once
#include "BF_AquesTalkPico.h"
#include <SPI.h>

class AquesTalkPicoSpi : public AquesTalkPico {
 public:
  AquesTalkPicoSpi();
  ~AquesTalkPicoSpi();
  int    Begin(SPIClass &spi, int ss);
  int    Send(const char* msg);
  size_t Recv(char* res, size_t res_size);

 private:
  SPIClass* m_spi;
  int       m_ss;
};

int AquesTalkPicoSpi::Begin(SPIClass &spi, int ss)
{
  delay(80);  // 80ms: reset process of AquesTalk-Pico
  m_spi = &spi;
  m_ss = ss;
  pinMode(m_ss, OUTPUT);
  digitalWrite(m_ss, HIGH);
  return 0;
}

int AquesTalkPicoSpi::Send(const char* msg)
{
  int i = 0;
  m_spi->beginTransaction(SPISettings());
  digitalWrite(m_ss, LOW);
  while (msg[i] != '\0') {
    m_spi->transfer(msg[i++]);
    delayMicroseconds(20);
  }
  digitalWrite(m_ss, HIGH);
  m_spi->endTransaction();
  return 0;
}

size_t AquesTalkPicoSpi::Recv(char* res, size_t res_size)
{
  int i = 0;
  m_spi->beginTransaction(SPISettings());
  digitalWrite(m_ss, LOW);
  while (i < res_size - 1) {
    char recv_data = m_spi->transfer(0xff);
    delayMicroseconds(20);
    res[i++] = recv_data;
    if (recv_data == '>' || recv_data == '*')
      break;
  }
  digitalWrite(m_ss, HIGH);
  m_spi->endTransaction();
  res[i] = '\0';
  return strlen(res);
}

bool AquesTalkPicoSpi::Busy()
{
  char res[10];
  int res_length = Recv(res, sizeof(res));
  if (res_length >= 2) {
    Serial.printf("[AquesTalk Spi] Receive:%s\n", res);
    return true;
  }
  if (res[0] != '>')
    return true;
  return false;
}
```

# 5. メインプログラム
  I2C, UART, SPI の 3 種類作成しますが、初期化部分が異なるのみで、処理の内容は同じです。

## 5.1 #include
　AquesTalk pico LSI とインタフェースする派生クラスで I2C, UART, SPI のいずれかを読み込み、インスタンス aqtp を宣言します。SPI については、インスタンス vspi の宣言も必要です。

``` cpp:BF-034.ino
#include <M5Stack.h>

// in the case of I2C
#include "BF_AquesTalkPicoWire.h"
AquesTalkPicoWire aqtp;

// In the case of Serial
#include "BF_AquesTalkPicoSerial.h"
AquesTalkPicoSerial aqtp;

// in the case of SPI
#include "BF_AquesTalkPicoSpi.h"
AquesTalkPicoSpi aqtp;
SPIClass vspi(VSPI);
const int vspi_ss = 5;
```

## 5.2 setup()

1. Begin() 
　インタフェース情報を渡して初期化します。
　UART については Serial2.begin() でボーレートの設定が必要です。
　SPI についてはインタフェース自体の初期化 vspi.begin() が必要です。

2. SLEEP と UART 速度設定
　SLEEP 信号と GPIO を接続している場合、SLEEP 信号を適切に制御する必要があります。
　AquesTalk pico LSI は、ATP3011 の場合、UART 速度を自動設定します。その仕組みは、リセット解除直後または SLEEP 解除直後に文字 '?' を受信して測定します。コントローラ側としては、リセット解除時点または SLEEP 解除時点で送信信号をハイレベルに安定させる必要があります。
　M5Stack のリセット信号 EN を AquesTalk pico LSI のリセットに接続した場合、リセット解除時の条件を満足させることは難しいと考えられます。このため GPIO13 を Aquestalk pico LSI の SLEEP に接続し、aqtp.Begin() で一旦 SLEEP させてこれを解除し、指定の 80ms の経過を待って '?' を送信しています。
　AquesTalk pico LSI のピン PMOD(1,0) = (HIGH, LOW) に設定して「セーフモード」にすると、ボーレートは 9600bps 固定になり、SLEEP の手順は不要です。しかし最大 76,800bps（ATP3011）のスピードをあきらめることになります。作成した M-BUS モジュール基板の場合、38,400bps まで動作を確認できました。

3. EEPROM の書き込み、読み出し
　UART のスピード（ATP3012）、I2C のアドレスの設定、プリセットメッセージの書き込み、EEPROM データのダンプなどを指定できます。

4. バージョン、チャイム
AquesTalk pico LSI のバージョンを表示し、チャイム 2 種を鳴らして、準備完了を知らせます。

``` cpp:BF-034.ino
void setup()
{
  M5.begin(lcd_enable, sd_enable, serial_enable, i2c_enable);

  // in the case of I2C
  aqtp.Begin(Wire);          // default or safe mode

  // In the case of Serial
  Serial2.begin(9600);  // safe mode
  aqtp.Begin(Serial2);

  // in the case of SPI
  vspi.begin();
  aqtp.Begin(vspi, vspi_ss);

  // designate "true" if sleep pin is connected
  if (true /*false*/) {
    const int aqtp_sleep_pin(13);  // GPIO13 for sleep pin of m-bux module
    pinMode(aqtp_sleep_pin, OUTPUT);
    digitalWrite(aqtp_sleep_pin, HIGH);

    // designate "true" to set speed automatic for ATP3011 & not safe mode
    if (/*true*/ false) {
      digitalWrite(aqtp_sleep_pin, LOW);
      delay(500);
      digitalWrite(aqtp_sleep_pin, HIGH);
      delay(80);
      aqtp.Send("?");
      for (int i = 0; i < 10; ++i) {
        aqtp.ShowRes();
        delay(200);
      }
    }
  }

  // option: set serial-speed into EEPROM of ATP3012
  aqtp.WriteSerialSpeed( 38400);
  for (int i = 0; i < 10; ++i) {
    aqtp.ShowRes();
    delay(200);
  }

  // option: write i2c address into EEPROM
  aqtp.WriteI2cAddress(0x2E);  // change i2c address to customize

  // option: write preset message into EEPROM
  aqtp.WritePresetMsg(preset_msg, sizeof(preset_msg)/sizeof(preset_msg[0]));

  // option: dump EEPROM to the serial monitor
  aqtp.DumpEeprom();

  aqtp.Send("#V\r");  // read version
  for (int i = 0; i < 10; ++i) {
    aqtp.ShowRes();
    delay(200);
  }
  aqtp.Send("#J\r");  // chime sound J
  for (int i = 0; i < 10; ++i) {
    aqtp.ShowRes();
    delay(200);
  }
  aqtp.Send("#K\r");  // chime sound K
  for (int i = 0; i < 10; ++i) {
    aqtp.ShowRes();
    delay(200);
  }
}
```

## 5.3 loop()
　ループの中で、AquesTalk pico LSI からのレスポンスを aqtp.Recv() で受信してシリアルモニタに出力します。I2C, SPI では、ポーリングを行います。ポーリング間隔を 10ms 以上にする必要があります。
　M5Stack のボタンが押されたら、Busy() で AquesTalk pico LSI が発声中か否かを判断し、それに応じて aqtp.Send() でコマンドを送信します。発声を中断するには '$' を送信します。

| M5 | ボタン | 停止中 | 発声中 |
|:-:|:-:|:-:|:-:|
|M5Stack| A   | 1つ前のpreset_msgを発声 | 発声を中断して1つ前のpreset_msgを発声 |
|M5Stack| B   | 現在のpreset_msgを発声 | 発声を中断して停止 |
|M5Stack| C   | 1つ後のpreset_msgから連続して発声 | 発声を中断して1つ後のpreset_msgから連続して発生 |

``` cpp:BF-035.ino
void loop()
{
  M5.update();
  if (M5.BtnA.wasReleased()) {
    if (aqtp.Busy()) {
      aqtp.Send("$");  // Abort
    }
    play_command = play_previous;
  }
  if (M5.BtnB.wasReleased()) {
    if (aqtp.Busy()) {
      aqtp.Send("$");  // Abort
      play_command = play_stop;
    }
    else {
      play_command = play_current;
    }
  }
  if (M5.BtnC.wasReleased()) {
    if (aqtp.Busy()) {
      aqtp.Send("$");  // Abort
    }
    play_command = play_forward;
  }

  int num_of_msg = sizeof(preset_msg)/sizeof(preset_msg[0]);
  switch (play_command) {
    case play_current:
      if (!aqtp.Busy()) {
        aqtp.Send(preset_msg[msg_selected]);
        play_command = play_stop;
      }
      break;
    case play_previous:
      if (!aqtp.Busy()) {
        if (--msg_selected < 0)
          msg_selected = num_of_msg - 1;
        aqtp.Send(preset_msg[msg_selected]);
        play_command = play_stop;
      }
      break;
    case play_forward:
      if (!aqtp.Busy()) {
        if (++msg_selected >= num_of_msg)
          msg_selected = 0;
        delay(500);
        aqtp.Send(preset_msg[msg_selected]);
      }
      break;
    default:
        play_command = play_stop;
      break;
  }

  aqtp.ShowRes(2);

  delay(loop_ms + loop_last_ms - millis());
  loop_last_ms = millis();
}
```

# 6. 波形観測
　各インタフェースの波形を観測しました。

## 6.1 I2C 波形（正常）
- Ch-1（黄色）:SCL
- Ch-2（水色）:SDA

![DS1Z_QuickPrint5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378410/c2d092de-6de0-e283-6977-b64b7785ac68.png)

## 6.2 UART Send 波形（正常）
- Ch-1（黄色）: M5Stack --> AquesTalk pico LSI
- Ch-2（水色）: M5Stack <-- AquesTalk pico LSI

![DS1Z_QuickPrint13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378410/d0ed9696-c75d-6a7c-6700-6feef92798f2.png)

## 6.3 UART Send-Receive 波形（正常）

![DS1Z_QuickPrint10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378410/1db69fb0-3457-a424-b358-a25d5376aebe.png)

## 6.4 SPI 波形（正常）
- Ch-1（黄色）:SS
- Ch-2（水色）:SCLK
- Ch-3（紫色）:MOSI
- Ch-4（青色）:MISO

　"oyasum.." の各バイトの送出に対して '>' で応答しています。発声も正常です。
![DS1Z_QuickPrint3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378410/25dc40de-4722-b679-666c-17c2d388c64d.png)

## 6.5 SPI 波形（正常・拡大）
　各バイトの間隔を拡大しています。23.2μs あります。
![DS1Z_QuickPrint1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378410/3383a617-120e-b555-302e-80669539bb1b.png)

## 6.6 SPI 波形（わざと異常にした例）
　各バイトの間隔をわざとゼロにした例です。'>' の応答は最初のみでした。発声も異常となりました。
![DS1Z_QuickPrint4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/378410/46a604ae-4e95-2486-8f4a-e9dd137b4094.png)

# 7. おわりに
　I2C は、GPIO を新たに消費せずシンプルかつ高速度で使えます。UART は専用の GPIO が必要となるほか、ボーレートの設定に面倒な点があります。SPI は一般には転送レートで有利ですが 20μs の間隔の影響でそれほどでもありません。また、GPIO としては SS の追加のみで済みますが、SPI を共有する LCD や TF Card への影響も懸念されます。

## GPIO の条件

|インターフェース|専用GPIO|共用GPIO|備考|
|:-:|:-:|:-:|:-:|
|I2C | - |2 (SDA, SCL)|  |
|UART|2 (RX, TX)| - |  |
|SPI |1 (SS)|3 (SCLK, MISO, MOSI)|LCD/TFへの影響の懸念あり|

## 転送レートの概算

|インターフェース|速度|1 バイトの所要時間|転送レート|備考|
|:-:|:-:|:-:|:-:|:-:|
|I2C |100kHz|    90μs（実測）|11kB/s|M5Stackデフォルト|
|I2C |400kHz|    23μs（推定）|44kB/s|上限|
|UART|9600bps| 1040μs（実測）|0.9kB/s|セーフモード|
|UART|76800bps| 130μs（推定）| 7kB/s|ATP3011上限(*1)|
|UART|115200bps| 87μs（推定）|11kB/s|ATP3012上限(*2)|
|SPI |1MHz（バイト間隔 20μs）|31μs（実測）|32kB/s|上限|

(*1) 作成した M-BUS モジュール基板では、38,400BPS の動作を確認できました。
(*2) 作成した M-BUS モジュール基板では、76,800BPS の動作を確認できました。