こんばんは、Reveです。
前回に引き続き、WAVファイルの「ダヨーン」音声をArduinoで再生する電子工作を取り上げてみます。
【デモ動画】
前と同じ動画ですが、復習のため(相変わらず、音量は小さいですorz)
【音声処理にはなにが必要か】
ダヨーンの再生について、前回はハードウェアを中心に見てきましたが、今回はソフトウェアを中心に考えてみます。
今回は音声処理だけなので、まずは音の再生をどうするかを見ていきます。
(左はアナログでの音信号、右がデジタルでの音信号)
元々、アナログ信号をデジタルで完全に再現するのは原理的に無理なのですが、オーディオ機器では音の信号を時間ごとに区切ってその時間での音レベルを記憶(サンプリング)し、その時間ごとに音のデータを出力することで音を再現しています。
サンプリング数を増やして細かく区切ることで、人間の耳では判別できないくらいのレベルで音を再現することができます(いろいろと端折って説明してますが)。
なので、音の再生までにやることは大まかにいうと3つになります。
・音を時系列毎のデータ(音量)としてファイルに記録
・ファイルから音のデータを時系列順で読み込む
・順番に音のデータを再生する
一つ目の音データの記録については、あらかじめ決められた音声ファイル(WAV)を使うのですが、Arduinoの処理能力を考えてあまり精度の高いデータは使えません。
処理速度は後述のタイマー割り込みで決まるのですが、Arduino(Pro Mini)のタイマーが最大でも31.25kHz(1秒間に31250回実行される)で、32マイクロ秒以内に処理を完了しないといけないので、音声ファイルは32kHz(8bit モノラル)のものを利用しました。
既存の音声ファイルを変換する場合は、SoundEngineというフリーソフトがおすすめです。
二つ目の音データの読み込みはSDカードから行いますが、単純に音データを1バイトずつ読み込むのでは処理が間に合わなくなるため、バッファを用意してまとめて読み込んでいます(参考サイトを参照)。
最後の音データ再生については、単にPWM機能(ArduinoのanalogWrite)を使うという話ですが、今回はマイコンのPWM機能を直接プログラム内で打ち込むことになります。
【Arduinoの設定】
プログラムを実装する前に、まずArduino(マイコン)のどんな機能を使うか確認してみましょう。
Arduinoはそれだけで様々な機能を使える万能ツールですが、今回は以下の機能を使います。
・タイマー割り込み
・PWM
・SPI(SDカードの読込)
この中でSPIはライブラリを使うため、直接ユーザーが設定することはありません。
問題は上の二つで、まずタイマー割り込みは「一定の時間でメインの処理に割り込ませる処理」で、割り込ませる時間の間隔や呼び出す関数(イベント)の登録などをしますが、デフォルトでついてくるライブラリの中にはありません(え…)。
タイマー割り込みのライブラリもあるのですが、なるべく簡潔に短い時間で割り込ませるようにするため、今回はプログラム内でマイコンの設定を実装しました(最近はタイマー割り込み用のライブラリも増えてきたので、時間があれば調べてみようかと)。
また、PWMは「疑似的に電圧のアナログ出力」をさせる機能で、こちらはArduinoで元から使えます(analogWrite関数)が、こちらも処理を少しでも早くしたいので関数は使っていません。
(ちなみに、これもタイマー割り込みを利用して実装されています)
これらの機能を設定するためには、レジスタと呼ばれる記憶領域に値を代入していかないといけません。
使いたい機能に対応するレジスタの値を読み取る、あるいは書き込むことで必要な機能を有効にしていきます。
(実はマイコン本来の使い方をするのですが、この辺りは経験が無いと少しわかりづらいかも。逆に言うと、Arduinoはそのあたりの過程をすっ飛ばしてプログラムが書けることがラピッドプロトタイピングツールとして普及した要因の一つです)
【プログラムの実装】
ではいよいよ、肝心のプログラムに入っていきます。
早速、プログラム全体を見てみます(参考サイトのプログラムと)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
; title="wavplay_promini1.ino"]#include <SD.h> //バッファサイズ #define BUF_SIZE 384 volatile uint8_t // グローバル変数 buf[2][BUF_SIZE], // バッファ buf_page; // バッファ・ページ volatile boolean buf_flg; // バッファ読み込みフラグ volatile uint16_t buf_index; // バッファ位置 volatile uint16_t read_size[2]; // バッファ読み込みサイズ void setup() { // put your setup code here, to run once: pinMode(10, OUTPUT); //SDライブラリ使用時の約束 while(!SD.begin(10)); //ライブラリとSDカードを初期化 //PWM初期化 DDRD |= B00001000; //PD3(OC2B):Arduino D3 TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); //8bit高速PWM TCCR2B = _BV(CS20); //分周なし } void loop() { // put your main code here, to run repeatedly: //再生前の準備 File dataFile; if(!(dataFile = SD.open("dayon2.wav"))) return; //error opening wavefile buf_index = 44; buf_page = 0; buf_flg = true; // パラメータ設定 (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 = false; // 読み込んだらフラグを下ろす } } //再生終了後の処理 OCR2B = 0; dataFile.close(); delay(1000); } //タイマー割り込みのイベント ISR(TIMER2_OVF_vect) { OCR2B = buf[buf_page][buf_index++]; // データをPWMとして出力 if(buf_index == read_size[buf_page]) { // 現在のバッファの最後まで来たら... if(buf_index != BUF_SIZE) TIMSK2 &= &tilda;_BV(TOIE2); // ファイルの最後なら,TOIE2をクリア buf_index = 0; buf_page ˆ= 1; buf_flg = true; // バッファを切り替え } } |
【次回】
では、プログラム詳細についてArduinoの各機能から見ていきたいところですが、これ以上はまた長くなってしまいそうなので、
この先は次の記事ということで…お許しくださいorz
次の記事では、どこのレジスタをいじったかなど見ていきたいと思います。
【参考】
(放課後マイコンクラブ様)
SDカードのWAVファイル再生する。 [Arduino]
コメント