2014年2月9日日曜日

ハンディー踏切カンカンを作る(2/3):ArduinoのPWMで和音を出力

子どものクリスマスプレゼントに踏切カンカンのおもちゃを作ってあげるという記事が素敵だったので、自分でも同様のガジェットを作ってみることにしました。

※関連記事
ハンディー踏切カンカンを作る(1/3):まずは踏切の音程を調べる
ハンディー踏切カンカンを作る(2/3):ArduinoのPWMで和音を出力
ハンディー踏切カンカンを作る(3/3):市販のおもちゃにArduinoを内蔵させて完成

前回の記事では、西武線の踏切の音がドとミの和音であることを確認しました。今回はArduinoのPWMを使って、踏切の警報音を出力してみます。

Arduinoで音を出すには

Arduinoで音を鳴らすならtone関数が便利なのですが、tone関数は単一の周波数を出すだけなので、西武線の踏切のような和音は出力できません。そこで、和音を作ったうえでPWMを使って出力させることにしました。


音を出すための回路

Arduino Uno とブレッドボードを使って下記のような回路を組んでみました。なお、スピーカーの前にローパスフィルタ(抵抗とコンデンサ)を入れていますが、無くても音は変化しません。赤色LEDも入れていますが、本記事中ではまだ点灯させていません。




(備考) ローパスフィルタの部分はとりあえず手元にあった部品を使っています。カットオフ周波数 fc = 1/(2πRC) = 72kHzですから、PWM周波数の64kHzよりも大きく、パラメーターとしてはあまり良くないようです。※このあたりは現時点ではきちんと理解できていません。また後日に学びたいと思います。


和音を用意する

ド(523Hz)とミ(659Hz)の和音(2つの周波数を重ね合わせた波形)を作るために、それぞれの波形を矩形波(on-offの2値)と仮定しました。それぞれ波長としては約1900usecと約1500usec(だいたい 5:4)です。そこで、重ね合わせの波形は7600usec(=190usec×40)を単位として繰り返すと仮定することにしました。具体的に示すと下図のようになります。この和音(うなり)を基本単位とします。
和音:2つの波形を矩形波と仮定したときの「うなり」


Arduinoのプログラムを書くにあたって、上記の和音を配列として用意しておくことにします。
int kankan[]={2,2,2,2,1,0,0,0,1,1,2,2,1,1,1,0,1,1,1,1,1,1,1,1,2,1,1,1,0,0,1,1,2,2,2,1,0,0,0,0};

PWM (analogWrite関数) を使う

上記波形は単純なon-offの2値ではないので、中間の電圧を出力させるためにPWMを使うことにしました。しかし、デフォルトではPWMの周波数が500Hzなので、出力したい音よりも周波数が低く、音が出せません。そこでPWMの周波数を64kHz(または32kHz)に上げます。そのためには、Arduinoのプログラム内でsetupの中にPWMの設定を記入します(下記)。あとはanalogWrite関数を使うだけでPWM出力を使うことができます。

int speakerPin = 6;
int kankan[]={2,2,2,2,1,0,0,0,1,1,2,2,1,1,1,0,1,1,1,1,1,1,1,1,2,1,1,1,0,0,1,1,2,2,2,1,0,0,0,0};
//delayTimeは本当は190usec(5250Hz)程度になるはずだが、音程が合わないので適当に調整した。
int delayTime = 95;

void setup(){
  //スピーカーピンを出力モードにする
  pinMode(speakerPin, OUTPUT);
  //PWMを高速化する(今回は6番ピンを使った)
  TCCR0B = 0x01;   // PWM 6 & 5 @ 64 kHz
}

void loop(){
  // 警報音
  for(int i=0; i<64; i++){
    for(int j=0; j<40; j++){
      analogWrite(speakerPin, 120*kankan[j]);
      delayMicroseconds(delayTime);
    }
  }
  // 休み
  delay(10000);
}
ただし、上の波形をそのまま出力すると「カンカン」ではなく「ビービー」と聞こえます。これは、音の強弱がついていないためです。

音の強弱(エンベロープ)をつける

実際の踏切の警報音は、音の強弱(エンベロープ)があります。西武線の踏切音を録音して解析した結果、音の強弱は、(1)増大、(2)一定、(3)減衰、(4)ゆるやかに減衰、といったおおまかに4区間に分けられるようです(下記)。
踏切の警報音:音の強弱(エンベロープ)の様子

Arduinoのプログラムで上記の強弱(エンベロープ)を再現すると、下記のようになります。
//(これより上の部分は先ほどと同じ)

void loop(){
  //区間(1):増大
  for(int i=0; i<6; i++){
    for(int j=0; j<40; j++){
      analogWrite(speakerPin, (18*i+12)*kankan[j]);
      delayMicroseconds(delayTime);
    }
  }
  //区間(2):一定
  for(int i=0; i<10; i++){
    for(int j=0; j<40; j++){
      analogWrite(speakerPin, 120*kankan[j]);
      delayMicroseconds(delayTime);
    }
  }
  //区間(3):減衰
  for(int i=0; i<8; i++){
    for(int j=0; j<40; j++){
      analogWrite(speakerPin, (120-10*i)*kankan[j]);
      delayMicroseconds(delayTime);
    }
  }
  //区間(4):ゆるやかに減衰
  for(int i=0; i<40; i++){
    for(int j=0; j<40; j++){
      analogWrite(speakerPin, (40-i)*kankan[j]);
      delayMicroseconds(delayTime);
    }
  }
  // 休み
  delay(10000);
}
音の出力してみるとわかるのですが、エンベロープによって音の聞こえ方が全く変わってくるということを実感できます。


ふりかえり:本当に狙った出力になっているのか

ここでやったことをオシロスコープで確認します。オシロスコープはTektronixのTBS1022を使いました。まず、全体の波形は下記になります。黄色は6番ピンのPWM出力をそのまま表示、緑色はローパスフィルタを通過したあとの電圧変化です。緑色のほうに注目すると、音の強弱(エンベロープ)が確認できます。
「カン」の音全体 (横軸は1マスが50msec)

次に、拡大します。和音の基本単位が見えてきます。
音の立ち上がり部分 (横軸は1マスが10msec)

さらに拡大します。和音の基本単位がはっきり見えてきました。もともと設定した配列に対応していることがわかります。
「うなり」の基本単位 (横軸は1マスが10msec)

さらに拡大します。PWM(黄色)と出力波形(緑色)の対応が見えてきます。
PWMによる出力電圧変化 (横軸は1マスが100usec)

まとめ

  • 和音の「うなり」1つぶんを基本単位とした。
  • PWMを使った。ただし、PWMは高速化する必要がある。
  • 基本単位を元に、音の強弱(エンベロープ)をつけた。
この手順で、ある程度出したかった音が出せました。

最後に

先の記事のはじめの部分で
LED 2個を交互に点灯して「踏切チカチカ」にするのはビギナー過ぎるのですが、音をならして「踏切カンカン」にしようとすると、とたんにレベルが上がるような気がします。
と指摘されていたのですが、全くその通りだと思いました。これだけ苦労して踏切の「カンカン」という音を出せただけ、ですからね。

2 件のコメント:

  1. Hello, I'm Taiwanese, sorry for that I'm using English because I can't understand 日本語 very well.

    I'm trying to make 踏切's sound with my Arduino.
    I want to make sound of Taiwanese "平交道" like.
    (like 南海電車's sound)

    I want to ask what is kankan[] this array mean?
    Or what did you count that these values?

    Thanks.

    返信削除
    返信
    1. Thanks for watching my page!
      I'm very interested in Taiwanese "平交道". I hope your project will succeed.

      As you wrote, the array kankan[] means Japanese 踏切's sound.
      The sound is a combination of two notes C5 and E5.
      So, I had to make a superposition of waves C5 (523Hz) and E5 (659Hz).

      The wavelength of C5 is about 1900usec and E5 is about 1500usec.
      The ratio of those two wavelength is 5 to 4,
      That is like below:

      C5: 11111000001111100000....
      E5: 11110000111100001111....

      Then I added those two waves. That is kankan[].

      For your information, the real waveform is a sin wave.
      But, I made each wave as a square wave to reduce data size.

      削除