効果音のような短い音源であれば、SDカードやシールド等を使用しなくても、音源をソースコードに埋め込んで、圧電スピーカから出力することができます。プログラムサイズが容量オーバーしないように、音質を落とす必要がありますが、利用できる機会は多いと思います。
WAV音源を準備する
まずは音源を準備する必要があります。ネットで購入するのが、一番お手軽だと思います。国内サイトだとAudioStock、海外サイトだとAudioJungleがメジャーです。先日、AudioJungleで効果音を2ドルで購入しました。支払いはPayPalで行いました。購入前に音を再生することができますので、色々聴き比べて選ぶことができます。購入したファイルはMP3形式だと思うので、フリーソフト等でWAV形式に変換してください。このあたりの情報は、検索するとたくさん出てきます。
WAV音源からデータ配列に変換する
RAW形式に変換
音声データをArduinoのプログラムに埋め込む必要があるため、多くの場合はステレオからモノラルに変換したり、音質(ビットレート)を落として、データサイズを小さくする必要があります。これは、Audacityというフリーソフトで出来ます。手順は次のとおりです。
- AudacityでWAVファイルを開いて、ステレオからモノラルに変換する。
[トラック] -> [Mix] -> [Mix Stereo down to Mono] - ビットレートを8000Hzにする。
画面左下の[Project Rate]で8000を選択する。 - ファイル形式を指定して出力する。
[ファイル] -> [Expoprt] -> [選択したオーディオの書き出し] をクリック
ファイルの種類:その他の非圧縮ファイル
ヘッダ:RAW(header-less)
エンコーディング:Unsigned 8-bit PCM
データ配列に変換
作成したRAW形式のファイルをArduinoで取り扱えるデータ配列に変換します。これは、「放課後マイコンクラブ」の「PROGMEM作蔵さん」というHTMLスクリプトを使用すると簡単です。 「PROGMEM作蔵さん」 で変換すると、「 const uint8_t bindata[] PROGMEM = { … };」という形式で出力されます。 他の方法としては、「 xxd -i 」というLinuxコマンドでもできるようですが、Windowsを使用されている方は環境を整えることが難しそうなので、先述の方法をお勧めします。
プログラム
音源ヘッダファイル
先述の方法で出力したデータをヘッダファイル(*.h)で保存します。メインプログラムと分離しておくことで、コードの管理がしやすくなります。PROGMEMはFlashメモリにデータを格納する記述です。プログラムを実行するためのメモリはSRAMですが、容量が2kBしかありません。そこで、比較的容量の大きいFlashメモリ(32kB)に音源を保管します。必要なときに、必要な分だけSRAMに読み込みます。
// file name : OnYourMarks.raw // file size : 10505 bytes const uint8_t soundOnYourMarks[] PROGMEM = { 0x80,0x7f,0x80,0x80,0x7f,0x80,0x7f,0x80,0x80,0x7f,0x7f,0x7f,0x80,0x80,0x7f,0x7f, 0x80,0x7f,0x80,0x7f,0x80,0x80,0x7f,0x7f,0x7f,0x80,0x80,0x7f,0x7f,0x80,0x80,0x7f, ・・・・・・・・・・・・(省略)・・・・・・・・・・・・ 0x7f,0x80,0x80,0x7f,0x80,0x80,0x7f,0x80,0x7f,0x7f,0x80,0x7f,0x80,0x7f,0x7f,0x80, 0x7f,0x80,0x7f,0x7f,0x80,0x7f,0x80,0x7f,0x80 };
メインプログラム
次にメインのプログラムです。今回は、3つの音源を使用しました。それぞれ冒頭のincludeで読み込みます。圧電スピーカは3番ピン(PWM)で出力します。以下の記述は今まで見たこと無い方が多いと思いますが、これはATmega328Pのデータシートを見ると記載があります。
TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
TCCR2B = _BV(CS20);
TCCR2A/TCCR2Bは、タイマ/カウンタ2制御レジスタです。「_BV」は、ビットセットで、カッコ内のビットを1にセットします。すなわち、下記のCOM2B1、WGM21、WGM20を1にセットします。
WGM2(波形生成種別)を2’b11にセットすることで、高速PWM動作モードになります。そして、COM2B(比較B出力選択)を2’b10にセットすることで、タイマとOCR2Bとの非反転比較をOC2Bピン(ArduinoのD3ピン)へ出力します。CS2(クロック選択)ビットは、3’b001なので分周無しです。
次に、「OCR2B = pgm_read_byte_near(&soundArray[i]);」について説明します。 pgm_read_byte_near は、Flashメモリから1バイトのデータをSRAMに読み出します。引数はアドレスです。ヘッダファイルでPROGMEMで音源をメモリに入れているので、そのアドレスを引き渡します。OCR2B(タイマ/カウンタ2比較B値)は、継続的にTCNT2( タイマ/カウンタ2計数値値)と比較される8ビットの値です。
そして、「delayMicroseconds(125);」についてですが、音源データを作成する際に、8000Hzを選択しました。なので、1秒÷8000=125マイクロ秒のdelayを設けます。
//www.stemship.com //2020.01.03 #include "On-Your-Marks.h" #include "Get-Set.h" #include "Go.h" const int speakerPin = 3; //PWM void setup() { pinMode(speakerPin, OUTPUT); TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); TCCR2B = _BV(CS20); } void loop() { playAudio(soundOnYourMarks, sizeof soundOnYourMarks / sizeof soundOnYourMarks[0]); delay(5000); playAudio(soundGetSet, sizeof soundGetSet / sizeof soundGetSet[0]); delay(3000); playAudio(soundGo, sizeof soundGo / sizeof soundGo[0]); delay(5000); } void playAudio(const uint8_t* soundArray, unsigned int soundArrayLength) { for (int i = 0; i < soundArrayLength; i++) { OCR2B = pgm_read_byte_near(&soundArray[i]); delayMicroseconds(125); } }
回路
スピーカのプラス端子を3番ピンに接続し、マイナス端子をGNDに接続します。
まとめ
ファイルサイズを小さくするために、音質をかなり落としましたが、意外とまともな音を再生できました。効果音や短い音声であれば、十分使える方法だと思います。ATmega328Pのレジスタ設定について、いろいろ書きましたが、なかなか理解することは難しいと思います。まずは、ソースコードをコピーして、とにかく動く状態を確認し、そこからソースコードを自分で少し変えながら理解を深めていくのが良いと思います。
コメントを残す