にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

無理な時に押すボタンをつくった

最近いろいろと無理なので無理な時に押すためのボタンをつくった。Arduino Unoを使って押すとむーりぃーって言うもの。

へぇボタンじゃねーか。

ハードウェア

Arduino Uno (R3)を使った。

全体図こんな感じ。
f:id:nixeneko:20161223184726p:plain

電源

Arduinoへの電源供給様に電池6Vを3端子レギュレータで5Vに降圧してArduinoのUSB端子につなげている。
普通にUSBで電源につなぐので問題ない。

ボタン

ボタンは100円ショップで売っている押すとオン・オフが切り替わるプッシュライトのスイッチを改造した。

上のサイトを参考に、スイッチ内部でカチカチと引っかかる部品を取り除き、押している間だけスイッチが切り替わるようにする。また、押している間通電するようにしたかったので元の配線から配線を付け替えている。
さらに、ライト部分も生かしたかったのでそこにもリード線をつけて外に引っ張りだした。

ライトを開けるとこんな感じ。
f:id:nixeneko:20161223185855p:plain

配線

こんな感じの回路図になると思う。入力ピンに繋がれる部分は、ボタンが押されてないときはGNDに、押されてる間は+5Vになる。
f:id:nixeneko:20161223182431p:plain

実装時には、あまり良くないけどプルダウン抵抗は省略した(要するにプッシュライトのLED用抵抗がプルダウン抵抗の役目を果たすことになる)が、一応問題なく動いているようである。
f:id:nixeneko:20161223191024p:plain

ソフトウェア

音声の出力については

を参考にした。

音声を用意する

今回はArduinoフラッシュメモリ(32kB)に書きこむので、データのサイズをメモリに乗る程度に小さくしないといけない。という訳でメモリぎりぎりのサイズになるようサンプリング周波数を調整した。

  • サンプリング周波数13800Hz
  • モノラル
  • 符号なし8bit
  • ヘッダなし

の音声ファイルをAudacityを使って用意した。音量は割れない程度に大きめにしておいた。
f:id:nixeneko:20161220003235p:plain
ファイル名はmuri13k8.rawとした。


次に、Arduinoで読み込めるように、.hファイルに8ビット符号なし整数の配列として書き出す。
適当なpythonスクリプトconv.pyを書いて変換する。

with open('muri13k8.raw', 'rb') as f:
    data = f.read()
print('const unsigned char muri[] PROGMEM = {')
for x in data[:-1]:
    print('{},'.format(x), end="")
print('{}}};'.format(data[-1]))

この出力をmuri.hとして保存し、スケッチのフォルダに突っ込んでおく。

python conv.py > muri.h

muri.hの内容はこんな感じになる。

const unsigned char muri[] PROGMEM = {
126,125,125,125,125,125,125,125,125,125,125,125,(以下略)
};

Arduinoコード

Arduinoのプログラムを書く。Arduino IDEのバージョンは1.6.11。

#include <avr/pgmspace.h>
#include "muri.h"
#define ARRAYSIZE(x)  (sizeof(x) / sizeof((x)[0]))

#define OUT_PIN 3
#define BUTTON 19

int i = 0;       // 音声の再生位置
int lastval = 0; // ボタン状態レジスタ

void setup()
{
  pinMode(BUTTON, INPUT);
  pinMode(OUT_PIN, OUTPUT); 

  // http://qiita.com/kinu/items/6cd5da0415e31834e7da から
  //   これを設定するとすごい速さで動くようになるらしい
  // Non-inverting fast PWM mode on Pin 3.
  // COM2B1:0 ==  10: Non-inverting mode
  // WGM22:0  == 011: Fast PWM mode, 256 cycle (16MHz / 256 == 62.5kHz)
  // CS2:0    == 001: No prescaler (runs at maximum rate, 62.5kHz)
  TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  TCCR2B = _BV(CS20);

  i = ARRAYSIZE(muri); // 再生位置を最後に指定し自動再生されなくする
}

void loop()
{
    if(i < ARRAYSIZE(muri)){
      OCR2B = pgm_read_byte_near(&muri[i]); // 音声データ書き込み
      i++;
    }
    lastval = (lastval << 1) | digitalRead(BUTTON); // チャタリング回避
    if(lastval == 0x7FFF)            // ボタンが押されたら
      i=0;                           // 再生位置を0に

    delayMicroseconds(72);   // 1000000microsec / 13800Hz
}
  • setup()関数内でマイコンが早い周波数で動くように設定している。
  • 単純にloop()関数内で音声の周期(72μs)ごとに音データを書き込んでるだけで、ボタンが押されると音声データの配列のインデックスを0に更新することで最初から再生されるようにし、ボタンを連打できるようにしている。
  • チャタリング回避を行っている。具体的には、digitalReadの値がボタンが押されていないときに0、押されているときに1となるのを利用して、0のあと15回1が続いた場合、信号が安定したとみなして音の再生を行う。ボタンの数値のログを保管するのにはシフトレジスタを使い、毎回レジスタを1ビット左シフトしてLSBにdigitalReadの値を入れていく。シフトレジスタ(lastval)の値が0b0111 1111 1111 1111となったときにボタンが押されたときの操作を実行する(再生位置を0に更新する)。これによりチャタリングが抑制されボタンを離した時に音が再生されることがほぼなくなった。

最後に

むーりぃー
f:id:nixeneko:20161228020555p:plain