FIFO APIについて
昨日書いたブログを手違いで消してしまったようなので、もう一度書いてみます。
DevkitProのFIFO APIに音を鳴らすFIFOSound機能があります。NDSではARM7が音を鳴らす機能を受け持っているので、ARM9からARM7にデータを渡して鳴らす指示を与える必要があります。そこで、ARM9とARM7の通信に使われるのが、FIFO APIです。
FIFO APIには、Sound以外にも、タッチペンやwifiと言ったARM7で行われる機能の操作に使うことができるAPIが用意されています。
サウンドAPIのバグ
FIFO Soundの通信にバグ(仕様?)があって、長い曲の再生ができずに途中で終わってしまう現象が発生していました。原因はARM9からARM7にデータを渡すときに、曲のサイズを渡す変数の定義がu16(undigned short)で定義されていたからです。下記、dataSizeがu16になっています。
typedef struct FifoMessage { u16 type; union { struct { u16 type; const void* data; u16 loopPoint; u16 dataSize; u16 freq; u8 volume; u8 pan; bool loop; u8 format; } SoundPlay; struct{ u16 freq; u8 dutyCycle; u8 volume; u8 pan; } SoundPsg; struct{ void* buffer; u32 bufferLength; u16 freq; u8 format; } MicRecord; struct{ void* buffer; u32 length; } MicBufferFull; struct{ touchPosition touch; u16 keys; } SystemInput; }; } ALIGN(4) FifoMessage;
これが原因で、再生が途中で終わってしまうのですね・・・。ということで、これを修正するために、FIFO APIを自作することにしました。と言っても、変更点はu16→u32への一箇所です。はい、他は、丸々コピーです。
FIFO通信の仕組み
DevkitProのFIFO通信は簡単に自作モジュールを追加できるようになっています。FIFO通信に関してほとんど何も考えなくていいように設計されており、FIFOユーザ割り込み用チャネルが用意されています。
/*! \file fifocommon.h \brief low level FIFO API. */ typedef enum { FIFO_PM = 0, //パワーマネージメント FIFO_SOUND = 1, //サウンド FIFO_SYSTEM = 2, //システム(キー、タッチ、時間等) FIFO_MAXMOD = 3, //maxmod(音源ドライバ) FIFO_DSWIFI = 4, //wifi用 FIFO_RSDV_01 = 5, //何かな、ReSerVeDの略?いや、RSDVだし・・・。 FIFO_RSVD_02 = 6, //何かな、ReSerVeDの略? FIFO_RSVD_03 = 7, //何かな、ReSerVeDの略? FIFO_USER_01 = 8, //以下、ユーザ用 FIFO_USER_02 = 9, FIFO_USER_03 = 10, FIFO_USER_04 = 11, FIFO_USER_05 = 12, FIFO_USER_06 = 13, FIFO_USER_07 = 14, FIFO_USER_08 = 15, } FifoChannels;
上記のような感じです。
なので、FIFO_USER_xxを使えば、独自のFIFOモジュールを追加することができます。
今回は、サウンドモジュールを焼き直し版になりますが、独自モジュールとして組み込むことにしました。(後々考えれば、堂々とFIFO_SOUNDチャネルを使っても良かったですね)
共有RAM領域を使ったメッセージのやり取り
FIFO通信では、ARM9とARM7のデータのやり取りに共有RAM領域を使います。ここに、Message構造体として宣言したものを書き込むことになります。
ただし、この辺の仕組みはDevkitProに用意されたFIFO通信がすべて行うので、独自モジュールを作る側では何も気にする必要はありません。独自モジュール側でメッセージ構造体を用意するだけです。今回はサウンドのメッセージ構造体と丸々同じものを作りました。そして、dataSizeの変数定義とloopPointの定義をそれぞれu32(unsigned long)に変更しました。
typedef struct FifoMessage2 { u16 type; union { struct { u16 type; const void* data; u32 loopPoint; u32 dataSize; u16 freq; u8 volume; u8 pan; bool loop; u8 format; } SoundPlay; struct{ u16 freq; u8 dutyCycle; u8 volume; u8 pan; } SoundPsg; struct{ void* buffer; u32 bufferLength; u16 freq; u8 format; } MicRecord; struct{ void* buffer; u32 length; } MicBufferFull; struct{ touchPosition touch; u16 keys; } SystemInput; }; } ALIGN(4) FifoMessage2;
この構造体をARM9とARM7の両方で定義して、それらを使ってプログラムを作ればいいだけです。また、メッセージ構造体以外にも、1データでいいのなら32ビットの数値を渡すこともできます。FIFO通信ではどちらを使ってもいいですし両方使ってもいいです。通信は当然、受け取り/受け渡しの両方ができます。
ARM9側のプログラム
ARM9側では、FIFO通信の初期化をする必要は特に無く、通信データをARM7に投げるプログラムを書けばいいだけです。この投げるときの割り込みチャネルにFIFO_USER_xxを使えばいいのです。
今回は、FIFO_USER_05番を使うことにしました。
下記にARM9側のプログラムの一例を示します。sound.cの丸々コピーです。u32に変更したのと、FifoMessage2を使っています。
int soundPlaySample2(const void* data, SoundFormat format, u32 dataSize, u16 freq, u8 volume, u8 pan, bool loop, u32 loopPoint){ FifoMessage2 msg; msg.type = SOUND_PLAY_MESSAGE; msg.SoundPlay.data = data; msg.SoundPlay.freq = freq; msg.SoundPlay.volume = volume; msg.SoundPlay.pan = pan; msg.SoundPlay.loop = loop; msg.SoundPlay.format = format; msg.SoundPlay.loopPoint = loopPoint; msg.SoundPlay.dataSize = dataSize >> 2; fifoSendDatamsg(FIFO_USER_05, sizeof(msg), (u8*)&msg); while(!fifoCheckValue32(FIFO_USER_05)); return (int)fifoGetValue32(FIFO_USER_05); }
msg構造体にデータを書き込んで、fifoSendDatamsg()でARM7に送信しています。そして、ARM7が32ビット数値を返して来るのをfifoCheckValue32()で待って、それをfifoGetValue32()で取得しています。この場合は、鳴らした音源のチャネルが戻り値として返ってきます。
ARM7側のプログラム
ARM7側ではARM9側から入った割り込みの処理を行うプログラムを書きます。必要なものは、お決まりのInitialize処理とそれぞれの処理プログラムだけです。
お決まりのInitialize処理プログラムは下記です。
//--------------------------------------------------------------------------------- void installSoundFIFO2(void) { //--------------------------------------------------------------------------------- fifoSetDatamsgHandler(FIFO_USER_05, soundDataHandler2, 0); fifoSetValue32Handler(FIFO_USER_05, soundCommandHandler2, 0); }
fifoSetDatamsgHandler(FIFO_USER_05, soundDataHandler2, 0);は、ARM9側からfifoSendDatamsg()命令で割り込みが入ったときに実行するプログラムの定義です。この場合は、soundDataHandler2()が実行されます。
fifoSetValue32Handler(FIFO_USER_05, soundCommandHandler2, 0);は、ARM9側からfifoSendValue32()命令で割り込みが入った場合に実行するプログラムの定義です。この場合は、soundCommandHandler2()が実行されます。
soundDataHandler2()のプログラムが下記です。
//--------------------------------------------------------------------------------- void soundDataHandler2(int bytes, void *user_data) { //--------------------------------------------------------------------------------- int channel = -1; FifoMessage2 msg; fifoGetDatamsg(FIFO_USER_05, bytes, (u8*)&msg); if(msg.type == SOUND_PLAY_MESSAGE) { channel = getFreeChannel2(); if(channel >= 0) { SCHANNEL_SOURCE(channel) = (u32)msg.SoundPlay.data; SCHANNEL_REPEAT_POINT(channel) = (u32)msg.SoundPlay.loopPoint; SCHANNEL_LENGTH(channel) = msg.SoundPlay.dataSize; SCHANNEL_TIMER(channel) = SOUND_FREQ( msg.SoundPlay.freq ); SCHANNEL_CR(channel) = SCHANNEL_ENABLE | SOUND_VOL(msg.SoundPlay.volume) | SOUND_PAN(msg.SoundPlay.pan) | (msg.SoundPlay.format << 29) | (msg.SoundPlay.loop ? SOUND_REPEAT : SOUND_ONE_SHOT); } } else if(msg.type == SOUND_PSG_MESSAGE) { channel = getFreePSGChannel2(); if(channel >= 0) { SCHANNEL_CR(channel) = SCHANNEL_ENABLE | msg.SoundPsg.volume | SOUND_PAN(msg.SoundPsg.pan) | (3 << 29) | (msg.SoundPsg.dutyCycle << 24); SCHANNEL_TIMER(channel) = SOUND_FREQ( (u16)msg.SoundPsg.freq ); } } else if(msg.type == SOUND_NOISE_MESSAGE) { channel = getFreeNoiseChannel2(); if(channel >= 0) { SCHANNEL_CR(channel) = SCHANNEL_ENABLE | msg.SoundPsg.volume | SOUND_PAN(msg.SoundPsg.pan) | (3 << 29); SCHANNEL_TIMER(channel) = SOUND_FREQ( (u16)msg.SoundPsg.freq ); } } else if(msg.type == MIC_RECORD_MESSAGE) { micStartRecording(msg.MicRecord.buffer, msg.MicRecord.bufferLength, (u16)msg.MicRecord.freq, 1, msg.MicRecord.format, micSwapHandler2); channel = 17; } fifoSendValue32(FIFO_USER_05, (u32)channel); }
処理後に、fifoSendValue32()でchannelを送っています。
また、soundCommandHandler2()のプログラムが下記です。
//--------------------------------------------------------------------------------- void soundCommandHandler2(u32 command, void* userdata) { //--------------------------------------------------------------------------------- int cmd = (command ) & 0x00F00000; int data = command & 0xFFFF; int channel = (command >> 16) & 0xF; switch(cmd) { case SOUND_MASTER_ENABLE: enableSound2(); break; case SOUND_MASTER_DISABLE: disableSound2(); break; case SOUND_SET_VOLUME: SCHANNEL_CR(channel) &= ~0xFF; SCHANNEL_CR(channel) |= data; break; case SOUND_SET_PAN: SCHANNEL_CR(channel) &= ~SOUND_PAN(0xFF); SCHANNEL_CR(channel) |= SOUND_PAN(data); break; case SOUND_SET_FREQ: SCHANNEL_TIMER(channel) = SOUND_FREQ( (u16)data ); break; case SOUND_SET_WAVEDUTY: SCHANNEL_CR(channel) &= ~(7 << 24); SCHANNEL_CR(channel) |= (data) << 24; break; case SOUND_KILL: SCHANNEL_CR(channel) &= ~SCHANNEL_ENABLE; break; case SOUND_PAUSE: SCHANNEL_CR(channel) &= ~SCHANNEL_ENABLE; break; case SOUND_RESUME: SCHANNEL_CR(channel) |= SCHANNEL_ENABLE; break; case MIC_STOP: micStopRecording(); break; default: break; } }
詳細はプログラムを見てください。
ARM7側のmain()のプログラム
main()には、初期化命令のinstallSoundFIFO2()を書くだけです。
以下がmain()です。
//************************************************************************ // ARM7側の処理 2010.1.19 //************************************************************************ #include <nds.h> #include <dswifi7.h> #include "FIFO_USER_05/audio2.h" //--------------------------------------------------------------------------------- void VcountHandler() { //--------------------------------------------------------------------------------- inputGetAndSend(); } //--------------------------------------------------------------------------------- void VblankHandler(void) { //--------------------------------------------------------------------------------- //Wifi_Update(); } //--------------------------------------------------------------------------------- int main() //--------------------------------------------------------------------------------- { irqInit(); //irqの初期化 fifoInit(); //fifoの初期化 readUserSettings(); //ファームからユーザセッティングを読込む initClockIRQ(); //RTCのirqへのトラッキング開始 SetYtrigger(80); // installWifiFIFO(); //Wifiのfifoの初期化と開始 installSystemFIFO(); //システム関連のfifo初期化と開始 installSoundFIFO2(); //サウンドのfifoの初期化と開始 //irqの設定 irqSet(IRQ_VCOUNT, VcountHandler); irqSet(IRQ_VBLANK, VblankHandler); //irq割り込みの開始 irqEnable( IRQ_VBLANK | IRQ_VCOUNT | IRQ_NETWORK); // Keep the ARM7 mostly idle while (1) swiWaitForVBlank(); return( 0 ); }
このように、DevkitProのFIFO APIは、簡単に独自拡張ができます。既にあるFIFO APIについても気に入らないところがあれば、変更するのもいいかもしれませんね。プログラムソースの公開はhttp://www.geocities.jp/momoonga/で行っています。
最後にですが、これらの説明はソースから独自に解釈した情報ですので、すべて未保証です。
本当にやりたいことリスト
(ブログの終わりにやりたいことを書いておきたいと思います)
- 求職活動・・・このブログで興味を持った人一声かけてください。m(_ _)m
- Androidプログラム
- Web系のプログラム