LoginSignup
4
2

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-08-29

1. はじめに

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

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

2. AquesTalk pico LSI

 AquesTalk pico LSI6は、株式会社アクエスト7の製品です。Atmel ATmega328(P)8 に音声合成エンジンが書き込んであります。製品が 2 種 (ATP30119, ATP301210) があります。音質、クロック、ピン配置、最大ボーレート等の違いはありますが I2C, UART, SPI の仕様は同じです。

3. M5Stack

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

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 でなくても更新できます。

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 を返します。

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 を返します。なんらかの受信があるまで待機する作りです。

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 を返します。

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 の宣言も必要です。

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 種を鳴らして、準備完了を知らせます。

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から連続して発生
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

6.2 UART Send 波形(正常)

  • Ch-1(黄色): M5Stack --> AquesTalk pico LSI
  • Ch-2(水色): M5Stack <-- AquesTalk pico LSI

DS1Z_QuickPrint13.png

6.3 UART Send-Receive 波形(正常)

DS1Z_QuickPrint10.png

6.4 SPI 波形(正常)

  • Ch-1(黄色):SS
  • Ch-2(水色):SCLK
  • Ch-3(紫色):MOSI
  • Ch-4(青色):MISO

 "oyasum.." の各バイトの送出に対して '>' で応答しています。発声も正常です。
DS1Z_QuickPrint3.png

6.5 SPI 波形(正常・拡大)

 各バイトの間隔を拡大しています。23.2μs あります。
DS1Z_QuickPrint1.png

6.6 SPI 波形(わざと異常にした例)

 各バイトの間隔をわざとゼロにした例です。'>' の応答は最初のみでした。発声も異常となりました。
DS1Z_QuickPrint4.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 の動作を確認できました。

  1. botanicfields/PCB-MBUS-AquesTalk-pico-LSI

  2. botanicfields/PCB-Grove-AquesTalk-pico-LSI

  3. M5Stack用 AquesTalk pico LSI モジュール基板

  4. AquesTalk pico LSI用Grove(M5)接続基板

  5. ダイオードマトリクススイッチ16基板

  6. 音声合成LSI 「AquesTalk pico LSI」

  7. 株式会社アクエスト

  8. MICROCHIP ATMEGA328

  9. データシート ATP3011XX

  10. データシート ATP3012XX

  11. M5Stack

  12. Espressif ESP32

  13. Arduino Software

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2