子どものクリスマスプレゼントに踏切カンカンのおもちゃを作ってあげるという記事が素敵だったので、自分でも同様のガジェットを作ってみることにしました。
※関連記事
ハンディー踏切カンカンを作る(1/3):まずは踏切の音程を調べる
ハンディー踏切カンカンを作る(2/3):ArduinoのPWMで和音を出力
ハンディー踏切カンカンを作る(3/3):市販のおもちゃにArduinoを内蔵させて完成
前回の記事では、西武線の踏切の音がドとミの和音であることを確認しました。今回はArduinoのPWMを使って、踏切の警報音を出力してみます。
※関連記事
ハンディー踏切カンカンを作る(1/3):まずは踏切の音程を調べる
ハンディー踏切カンカンを作る(2/3):ArduinoのPWMで和音を出力
ハンディー踏切カンカンを作る(3/3):市販のおもちゃにArduinoを内蔵させて完成
前回の記事では、西武線の踏切の音がドとミの和音であることを確認しました。今回はArduinoのPWMを使って、踏切の警報音を出力してみます。
Arduinoで音を出すには
音を出すための回路
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); }
ただし、上の波形をそのまま出力すると「カンカン」ではなく「ビービー」と聞こえます。これは、音の強弱がついていないためです。
音の強弱(エンベロープ)をつける
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個を交互に点灯して「踏切チカチカ」にするのはビギナー過ぎるのですが、音をならして「踏切カンカン」にしようとすると、とたんにレベルが上がるような気がします。と指摘されていたのですが、全くその通りだと思いました。これだけ苦労して踏切の「カンカン」という音を出せただけ、ですからね。
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.
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.