OGIMOノート

6歳の娘と、4歳の息子(脳性麻痺)を持った父親エンジニアの備忘録。自作の電子工作おもちゃ/リハビリ器具の製作記録、勉強記録を残していきます

【電子工作(Arduino)】歩くと効果音の鳴るメロディ靴

製作動機

肢体不自由な息子、いつか歩ける事を夢見てリハビリなどで歩行練習を行うも、歩く事になかなか興味を持ってくれず停滞中。成長加速のため、何とか歩く行為に興味を持ってもらえないかという事で、何か歩く楽しみを見いだせるものを作ってやろうというのがきっかけ。
アイディアとしては、歩くとピコピコ音が鳴る靴。しかし、あの音だと息子は興味なさそうなので、息子が好きそうな音に変えたい!
例えば、マリオのジャンプの音とか、びよーんっていうコミカル音とか。

製作ターゲット(要求仕様的なもの)

足踏みに合わせて楽しい効果音が鳴る子供靴

  • 靴裏にしっかり体重が乗らないと音が鳴らない事
  • 効果音は自由な音源を選べる。
  • 右足と左足で効果音は変える。
  • うまく左右交互に歩ければ成功音を鳴せるとBetter

完成品

 f:id:motokiinfinity8:20180108011804j:plain:w400

完成品の使用動画
youtu.be

材料

Arduino uno or  Adafruit Pro Trinket - 5V 16MHz

f:id:motokiinfinity8:20180108002050j:plain:w200
まずラズパイかArduinoマイコンかで迷うところですが、
AC入り後の起動の速さと足踏み時の圧力閾値を微調整したかった理由でArduinoマイコンを選択。
ラピッドプログラム時にはUno、筐体に組み込む際は小型版のAdafruit Pro Trinket を使用します
(ピン互換性ありのため、我が家では重宝しています)

感圧センサ円形  SFE-SEN-09375× 2

f:id:motokiinfinity8:20180107233759j:plain:w200
靴に体重が乗っている事を検知するための感圧センサー。スイッチサイエンスで購入可。
100g~10kgまでの測量が可能。下記サイトによれば、無負荷で100kΩ、1kg過重で約1kΩ との事なので、1kΩ抵抗との分圧比でアナログ電圧値を検知すればよさそう
Force Sensitive Resistor Hookup Guide - learn.sparkfun.com

SDカードスロット HiLetgo Micro SD TFカードメモリシールドモジュール

f:id:motokiinfinity8:20180108003229j:plain:w200
音源(wavファイル)格納用のストレージ(Arduinoマイコンの内蔵RAMが32kBしかないため)
ArduinoマイコンとはSPIで接続。モジュールは上記メーカーに限らなくてもよいが、使うならば5V/3.3Vレベルシフタ回路を搭載している方がラク

 

スピーカーアンプ PAM8403 + スピーカ

f:id:motokiinfinity8:20180108003943j:plain:w200
Arduino出力端子を直接スピーカにつなげるだけでは音量不足のため、子供に気づかれる程度のボリューム確保のためのアンプ。
スピーカは壊れたおもちゃのスピーカを使用。再生する音声ファイルのゲインさえ大きくしておけば
それなりの音量で出力可能。ただし、スピーカ後ろに響かせるための空間確保は必要。

  

ハードウェア部

ざっくりした回路図は以下の通り。
f:id:motokiinfinity8:20180108004951j:plain
子供用靴は別途購入して感圧センサーを靴裏(かかと側)に埋め込む。

f:id:motokiinfinity8:20180108011320j:plainf:id:motokiinfinity8:20180108011243j:plain
靴(感圧センサー)以外はBOXタイプの外装に収めておく。スピーカ音はBOX側から鳴るイメージ。
靴とBOXとは有線接続(これが一番簡単なので)。
この線材は100均ショップで売ってるモノラルイヤホン(3m)を切って使用しました。
モノラルジャックで靴とBOX接続を取り外しできるアイディアは持ち運び/交換の観点で便利。

 f:id:motokiinfinity8:20180108011804j:plain:w400

f:id:motokiinfinity8:20180108011954j:plainf:id:motokiinfinity8:20180108012033j:plain
最終的な完成イメージ。

 

ソフトウェア部

・SDカードからwavファイル読み込み

こちらのサイトを参考にさせて頂きました。
hello-world.blog.so-net.ne.jp
1bit Readの低速度による遅延回避のためダブルバッファにして、片側を再生中に、もう片側をSD Readするという技を使っていて大変勉強になりました。
ただし、SDカード読込みの遅さが上記サイトと異なり異音が発生したため、バッファサイズは256Byteにしています。
wavファイルの形式は、32kHz / 8 bit / Stereo にしています。

音源の作り方としては、

  1. 無料サイト/Youtubeなどか効果音を抜き出してwav形式で保存
  2. Sound Engine などの音声波形編集ツールを使用して形式変換をする。

その際に、ファイル内の音量ゲインを確認して、小さければゲインを上げておく

 ソースは以下

#include <SPI.h>
#include <SD.h>
#define BUF_SIZE 256 // バッファ・サイズ

File dataFile;
const int speaker_pin = 3;

volatile uint8_t // グローバル変数
 buf[2][BUF_SIZE], // バッファ
 buf_page, // バッファ・ページ
 buf_flg; // バッファ読み込みフラグ
volatile uint16_t buf_index; // バッファ位置
volatile uint16_t read_size[2]; // バッファ読み込みサイズ
 
void play() {
 pinMode(speaker_pin, OUTPUT);
 DDRD |= B00001000; // PD3 (OC2B) (Arduino D3)
 TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); // 8bit高速PWM
 TCCR2B = _BV(CS20); // 分周なし
 OCR2A = 255;

 // *** 周期
 // *** 1count=0.0625us、 0.0625*256 -> 16us -> 62.5kHz
 buf_index = 44; buf_page = 0; buf_flg = 1; // パラメータ設定 (44byteはヘッダ) 
 read_size[buf_page] = dataFile.read( (uint8_t*)buf[buf_page], BUF_SIZE );
 TIMSK2 |= _BV(TOIE2);
 while(TIMSK2 & _BV(TOIE2)) {
  if(buf_flg) { // データ読み込み指令のフラグが立ったら読み込む
   read_size[buf_page ^ 1] = dataFile.read( (uint8_t*)buf[buf_page ^ 1], BUF_SIZE );
   buf_flg = 0; // 読み込んだらフラグを下ろす
  }
 }
 OCR2B = 0;
 dataFile.close();
}

ISR(TIMER2_OVF_vect) {
 OCR2B = buf[buf_page][buf_index++]; // データをPWMとして出力
 if(buf_index == read_size[buf_page]) { // 現在のバッファの最後まで来たら...
  if(buf_index != BUF_SIZE) TIMSK2 &= ~_BV(TOIE2); // ファイルの最後なら,TOIE2をクリア
  buf_index = 0; buf_page ^= 1; buf_flg = 1; // バッファを切り替え
 }
}


・感圧センサーからの踏み込み検知

シンプルにAruduinoマイコンのAnalogreadを使用。
チャタリング防止のため、足圧ON→OFF、OFF→ONの判定閾値を変えたくらいの工夫。
(閾値は子供の体重や姿勢に応じて微調整が必要)

const int l_sense_pin = 0;
const int r_sense_pin = 1
int l_foot = 0;       // l:左足で地面を踏んでいる
int r_foot = 0;       // l:右足で地面を踏んでいる
int sound_flag = 0;   // 1: 左足踏んだ際の音声、2:右足踏んだ際の音声
int l_foot_cnt = 0;
int r_foot_cnt = 0; 

#define FOOT_ON  180   // 足で地面を踏んでいると判断する閾値
#define FOOT_OFF  64    // 足を地面から話していると判断する閾値

void loop() {
  // put your main code here, to run repeatedly:
  l_sense_val = analogRead(l_sense_pin);
  r_sense_val = analogRead(r_sense_pin);

  // 左足踏み時に閾値を超えた場合
  if(l_foot==0 && l_sense_val>FOOT_ON ){
      l_foot = 1;
      sound_flag = 1;
      l_foot_cnt++;
  }
  if(l_foot==1 && l_sense_val<FOOT_OFF){
       l_foot = 0;
  }
  // 右足踏み時に閾値を超えた場合
  if(r_foot==0 && r_sense_val>FOOT_ON ){
      r_foot = 1;
      sound_flag = 2;
      r_foot_cnt++;
  }
  if(r_foot==1 && r_sense_val<FOOT_OFF){
    r_foot = 0;
  }

  if( sound_flag >0){
       // L5回+R5回の場合、成功音
      if(l_foot_cnt==5 && r_foot_cnt == 5){
        dataFile = SD.open("succeed.wav");
        l_foot_cnt=0;
        r_foot_cnt=0;
      // 左足踏み音
      }else if(sound_flag == 1){
        dataFile = SD.open("left.wav");
      // 右足踏み音
      }else if(sound_flag == 2){
        dataFile = SD.open("right.wav");
      }
      play();
      sound_flag = 0;
   }
  delay(50);
}


 

使用結果

動画は割愛。
最初は靴だけを鳴らせて様子を見せると「んっ?なんやなんや?」って興味を持って
「お、成功か!?」と思ったのですが、
いざ装着してもらうと、、、今いち反応なし。
今いち「歩く=音が鳴る」が繋がらないみたいで、頭にクエスチョンマークが飛んでいる模様。
しまいには、うじゃり出して終了。。。
撃沈。。。

…これに懲りず、引き続き頑張ります。。。