この記事は2020年11月17日に開催された 【オンライン】IoT縛りの勉強会! IoTLT vol.69 @Youtube で発表した内容をベースにしています。
はじめに
UXデザイナーの どたてつや です。
UXデザイナーの どたてつや です。
普段はUI設計やUX開発などの仕事の傍ら、趣味でプロトタイプなどを作ったりしています。
先日、Eテキスタイルを使ったインプットモジュール「nüno」をnanbwrks さんと作りました。
nünoの最新バージョンはver.2ですが、
今回はこのnüno ver.2を使用してしゃべるぬいぐるみの服を作ってみました。
しゃべぐるみ
GRデザインコンテスト2017に応募した
「しゃべぐるみ」という作品を過去に作成していたのですが、
このときは音声再生のためにスピーカーをぬいぐるみのリュックに搭載していました。
M5Stack Core2を使えば、スピーカーを内蔵しているためnüno ver.2とつなぐだけで接続できるため、
改めて作ってみることにしました。
ハードウェアの構成
シンプルですね
nünoはGROVE端子に対応しているのでGROVEケーブルで接続するだけで作成できます。
Arduino
Arduinoのプログラムは下記です。
また、 nünoではMTCH6102という静電タッチセンサをつかっているので、
プログラムと同じ階層にこちらから借りてきた
- 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];
という部分があるのですが、ここで外部テキストファイル化した音声データを読み込んでいます。
こちらは下記のページを参考にさせていただきました
おおまかな流れとしては
- Audacityをインストール
- Audacityでモノラルデータに
- Audacityで8000Hzに変換
- オーディオ書き出しでraw形式のデータに変換
- コマンドラインでテキストデータに変換&クリップボードにコピー
- Arduinoのプログラムファイル同じディレクトリに配置
という形で読み込めるようにしました。
できたもの
こんな感じで、触ると喋ってくれます。
— elevendots (@elevendots1) December 15, 2020
今後は、もう少し精度を上げていきたいと思います!