M5Stack x 導電布でしゃべるぬいぐるみ（の服）をつくる

この記事は2020年11月17日に開催された[ 【オンライン】IoT縛りの勉強会! IoTLT vol.69 @Youtube](https://iotlt.connpass.com/event/192582/) で発表した内容をベースにしています。

# はじめに
UXデザイナーの どたてつや です。

UXデザイナーの どたてつや です。

普段はUI設計やUX開発などの仕事の傍ら、趣味でプロトタイプなどを作ったりしています。
先日、Eテキスタイルを使ったインプットモジュール「[nüno](https://github.com/tendots/nuno)」を[nanbwrks](https://qiita.com/nanbuwks) さんと作りました。
nünoの最新バージョンはver.2ですが、
今回はこのnüno ver.2を使用してしゃべるぬいぐるみの服を作ってみました。

# しゃべぐるみ
[GRデザインコンテスト2017](http://gadget.renesas.com/ja/contest/2017/index.html)に応募した
「[しゃべぐるみ](https://www.youtube.com/watch?v=gYOCoP9HWos)」という作品を過去に作成していたのですが、
このときは音声再生のためにスピーカーをぬいぐるみのリュックに搭載していました。

M5Stack Core2を使えば、スピーカーを内蔵しているためnüno ver.2とつなぐだけで接続できるため、
改めて作ってみることにしました。


# ハードウェアの構成
![ScreenShot 2020-12-15 19.49.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242072/93a21ade-3d9b-1581-d84b-edf44763a53a.png)

### シンプルですね
nünoはGROVE端子に対応しているのでGROVEケーブルで接続するだけで作成できます。

# Arduino

Arduinoのプログラムは下記です。
また、 nünoではMTCH6102という静電タッチセンサをつかっているので、
プログラムと同じ階層に[こちら](https://github.com/theapi/touch_timer)から借りてきた
- MTCH6102.h
- MTCH6102.cpp
を利用しています

```
#include <M5Core2.h>
#include <Arduino.h>
#include <Wire.h>
#include "MTCH6102.h"
#include <driver/i2s.h>


#define ADDR 0x25
#define ScreenWidth 320
#define ScreenHeight 240

//Voice
#define CONFIG_I2S_BCK_PIN 12
#define CONFIG_I2S_LRCK_PIN 0
#define CONFIG_I2S_DATA_PIN 2
#define CONFIG_I2S_DATA_IN_PIN 34

#define Speak_I2S_NUMBER I2S_NUM_0

#define MODE_MIC 0
#define MODE_SPK 1
#define DATA_SIZE 1024

extern const unsigned char nani[13890];
extern const unsigned char kusuguttai[28406];
extern const unsigned char konnichiwa[14922];
extern const unsigned char doushitano[15824];

//Voide end

MTCH6102 mtch = MTCH6102();

const int len = 8;//感知ポイント数
int nuno_mode = 1;

void setup() {

  // Initialize the M5Stack object
  M5.begin();
  M5.Lcd.fillScreen(TFT_BLACK);
  Serial.begin(115200);

  //Wire.begin();
  mtch.begin(ADDR);
  delay(100);
  mtch.writeRegister(MTCH6102_NUMBEROFXCHANNELS, 0x07);
  mtch.writeRegister(MTCH6102_NUMBEROFYCHANNELS, 0x03);//最低3点必要なため
  mtch.writeRegister(MTCH6102_MODE, MTCH6102_MODE_FULL);
  mtch.writeRegister(MTCH6102_HORIZONTALSWIPEDISTANCE, 0x04);
  mtch.writeRegister(MTCH6102_MINSWIPEVELOCITY, 0x02);
  mtch.writeRegister(MTCH6102_TAPDISTANCE, 0x02);
  mtch.writeRegister(MTCH6102_BASEPOSFILTER, 0x00);
  mtch.writeRegister(MTCH6102_BASENEGAFILTER, 0x00);

  mtch.writeRegister(MTCH6102_CMD, 0x20);
  delay(500);

  //------Voice
  SpeakInit();
  delay(100);
}


bool sound = 0;
void loop() {
  M5.update();
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setCursor(0, 70);
  byte data;
  int sensVals[len];
  for (int i = 0; i < len; i++) {
    data = mtch.readRegister(MTCH6102_SENSORVALUE_RX0 + i);
    sensVals[i] = data;
    M5.Lcd.fillRect(30 + (i * 35), ScreenHeight - 20, 30, 10, TFT_BLACK);
    M5.Lcd.setCursor(30 + (i * 35), ScreenHeight - 20);
    M5.Lcd.print(data);
  }

  if (nuno_mode == 1) {
    data = mtch.readRegister(MTCH6102_GESTURESTATE);
    if (data != 0) {
      //Serial.println(data, HEX);
      if (data == 0x41) {
        Serial.println("swipeLeft");
        if(sound) {DingDong("kusuguttai");}
      }
      if (data == 0x61) {
        Serial.println("swipeRight");
        if(sound) {DingDong("doushitano");}
      }
      if (data == 0x10) {
        Serial.println("click");
        if(sound) {DingDong("nani");}
      }
      if (data == 0x20) {
        Serial.println("double click");
        if(sound) {DingDong("konnichiwa");}
      }
    }
  }

  M5.Lcd.setCursor(0, 70);

  //背景ライン
  for (int i = 0; i < len; i++) {
    M5.Lcd.drawLine((i + 1) * 35, ScreenHeight - 40, (i + 1) * 35, 0, 0x0000cc);
  }
  for (int i = 1; i < 11; i++) {
    M5.Lcd.drawLine(0, i * 20, ScreenWidth, i * 20, 0x0000cc);
  }
  //グラフ線の描画
  for (int i = 0; i < len + 1; i++) {
    float prev = 0;
    float current = 0;
    if (i == 0) {
      prev = 0;
    } else {
      prev = sensVals[i - 1];
    }
    if (i == len) {
      current = 0;
    } else {
      current = sensVals[i];
    }
    M5.Lcd.drawLine(i * 35, 200 - (prev / 255) * 200, ((i + 1) * 35), 200 - (current / 255) * 200, TFT_WHITE);
  }

  delay(100);
}


//-------Voice

bool InitI2SSpeakOrMic(int mode)
{
  esp_err_t err = ESP_OK;

  i2s_driver_uninstall(Speak_I2S_NUMBER);
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER),
    .sample_rate = 8000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB
    .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 2,
    .dma_buf_len = 128,
  };
  i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
  i2s_config.use_apll = false;
  i2s_config.tx_desc_auto_clear = true;
  
  err += i2s_driver_install(Speak_I2S_NUMBER, &i2s_config, 0, NULL);
  i2s_pin_config_t tx_pin_config;

  tx_pin_config.bck_io_num = CONFIG_I2S_BCK_PIN;
  tx_pin_config.ws_io_num = CONFIG_I2S_LRCK_PIN;
  tx_pin_config.data_out_num = CONFIG_I2S_DATA_PIN;
  tx_pin_config.data_in_num = CONFIG_I2S_DATA_IN_PIN;
  err += i2s_set_pin(Speak_I2S_NUMBER, &tx_pin_config);
  err += i2s_set_clk(Speak_I2S_NUMBER, 8000, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);

  return true;
}

void SpeakInit(void) {
  M5.Axp.SetSpkEnable(true);
  InitI2SSpeakOrMic(MODE_SPK);
}

void DingDong(String str)
{
  size_t bytes_written = 0;
  if(str == "nani"){
    i2s_write(Speak_I2S_NUMBER, nani, 13890, &bytes_written, portMAX_DELAY);
  }
  if(str == "kusuguttai"){
    i2s_write(Speak_I2S_NUMBER, kusuguttai, 28406, &bytes_written, portMAX_DELAY);
  }
  if(str == "konnichiwa"){
     i2s_write(Speak_I2S_NUMBER, konnichiwa, 14922, &bytes_written, portMAX_DELAY);
  }
  if(str == "doushitano"){
    i2s_write(Speak_I2S_NUMBER, doushitano, 15824, &bytes_written, portMAX_DELAY);
  }
}

//--------Voice end
```


# 音声変換

「しゃべるぬいぐるみ」なので当然音声データが必要です。
今回はArduinoのプログラム前半に

```
extern const unsigned char kusuguttai[28406];
extern const unsigned char konnichiwa[14922];
extern const unsigned char doushitano[15824];
```
という部分があるのですが、ここで外部テキストファイル化した音声データを読み込んでいます。
こちらは下記のページを参考にさせていただきました

https://nn-hokuson.hatenablog.com/entry/2017/09/01/092945

おおまかな流れとしては

- Audacityをインストール
- Audacityでモノラルデータに
- Audacityで8000Hzに変換
- オーディオ書き出しでraw形式のデータに変換
- コマンドラインでテキストデータに変換＆クリップボードにコピー
- Arduinoのプログラムファイル同じディレクトリに配置


という形で読み込めるようにしました。



# できたもの

こんな感じで、触ると喋ってくれます。

<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/MI4lYMhGlS">pic.twitter.com/MI4lYMhGlS</a></p>&mdash; elevendots (@elevendots1) <a href="https://twitter.com/elevendots1/status/1338802304649023489?ref_src=twsrc%5Etfw">December 15, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

今後は、もう少し精度を上げていきたいと思います！