#author("2023-07-27T11:52:23+09:00;2023-07-27T11:51:22+09:00","","")
#author("2023-07-27T11:54:27+09:00;2023-07-27T11:51:22+09:00","","")
* DMA [#na3db11a]
DMA(Direct Memory Access)はCPUを介さず、高速にメモリ間のデータ転送を行うハードウェア機能です。GBAのDMAでは0~3の4つのチャンネルがあり、それぞれ特徴が異なります。違いはほとんどありませんけれど、詳しくはGBATEKを参照してください。

** DMA0 [#ac431e85]
最も優先度が高く、転送する時間が厳密である必要があるときに使います。内部メモリから内部メモリへの転送のみ可能です。HBlank DMAにオススメです。

** DMA1とDMA2 [#tfa5d935]
DMA0の次に優先度が高く、サウンド再生時のFIFOにデータを転送する時に使われることが多いです。内部メモリ・外部メモリから内部メモリへの転送が可能です。

** DMA3 [#qf421623]
最も優先度が低いDMA。転送先・転送元ともに内部メモリ・外部メモリのいずれでも指定できます。最も汎用的に使えるのが特徴です。

** I/Oアドレス [#t1200194]
 #define REG_DMA0SAD	*(vu32*)(REG_BASE + 0x0b0)
 #define REG_DMA0DAD	*(vu32*)(REG_BASE + 0x0b4)
 #define REG_DMA0CNT	*(vu32*)(REG_BASE + 0x0b8)
 
 #define REG_DMA1SAD	*(vu32*)(REG_BASE + 0x0bc)
 #define REG_DMA1DAD	*(vu32*)(REG_BASE + 0x0c0)
 #define REG_DMA1CNT	*(vu32*)(REG_BASE + 0x0c4)
 
 #define REG_DMA2SAD	*(vu32*)(REG_BASE + 0x0c8)
 #define REG_DMA2DAD	*(vu32*)(REG_BASE + 0x0cc)
 #define REG_DMA2CNT	*(vu32*)(REG_BASE + 0x0d0)
 
 #define REG_DMA3SAD	*(vu32*)(REG_BASE + 0x0d4)
 #define REG_DMA3DAD	*(vu32*)(REG_BASE + 0x0d8)
 #define REG_DMA3CNT	*(vu32*)(REG_BASE + 0x0dc)

それぞれ各3づつI/Oアドレスを持ちます。

| REG_DMxSAD         | Source Addressの略で、転送元のアドレスを指定します。      |
| REG_DMxDAD         | Destination Addressの略で、転送先のアドレスを指定します。 |
| REG_DMxCNT(下位) | 転送するサイズ(16 or 32bit)を指定します。                 |
| REG_DMxCNT(上位) | DMA転送の設定をします。                                   |

** REG_DMxSAD、REG_DMxDAD [#w39232d1]
転送元、転送先のアドレスの最後の桁に注意してください。正しいアドレス値でないと失敗します。エミュレータで成功する場合もありますが実機でハマるので注意してください。DMA転送に関わるすべての変数についてはALIGNマクロをつけておく事をおすすめします。

- 16bit転送の場合、''0,2,4,6,8,a,c,e''
- 32bit転送の場合、''0,4,8,c''

 #define ALIGN(m)	__attribute__((aligned (m)))
 
 u16 dataBuf[64] ALIGN(4);
 u16 buf[SCREEN_CX][SCREEN_CY] ALIGN(4);

** REG_DMxCNT_L [#tee63cd2]
転送したいバイト数を登録します。ただし転送サイズが32ビットの場合は4(バイト)で、16ビットの場合は2であらかじめ割っておく必要があります。

** REG_DMxCNT_H [#hc166121]
 #define DMA_DST_INC		(0<<21)
 #define DMA_DST_DEC		(1<<21)
 #define DMA_DST_FIXED		(2<<21) // 固定
 #define DMA_DST_RELOAD		(3<<21)
 
 #define DMA_SRC_INC		(0<<23)
 #define DMA_SRC_DEC		(1<<23)
 #define DMA_SRC_FIXED		(2<<23) // 固定

転送アドレスをどう変化させるかの設定です。FIXEDはアドレスを固定し同じアドレスのデータを転送するようになります。0クリアなどに便利ですね。DMA_DST_RELOADは転送ごとに増加しすべての転送が終わった時に最初のアドレスに戻します。

 #define DMA_REPEAT		(1<<25)

一度のDMA転送が終了したあとに次回のDMA転送を繰り返し行うか行わないかの設定です。サウンド再生用FIFOへの転送や、フレームごとの転送を行う場合はONにします。

 #define DMA_IRQ			(1<<30)

DMAの転送終了時に割り込みを発生させるかの設定です。

 #define DMA_ENABLE		(1<<31)

DMAが有効になります。

 #define DMA_IMMEDIATE		(0<<28)
 #define DMA_VBLANK		(1<<28)
 #define DMA_HBLANK		(2<<28)
 #define DMA_SPECIAL		(3<<28)

DMA_ENABLE設定後の、転送開始のタイミングです。DMA_IMMEDIATEはDMA_ENABLEが設定されるとすぐに転送を開始します。VBLANKは1秒間に60回行われる画面の更新時に転送を開始します。HBLANKは画面の更新時の横1列の描画時に転送を開始します。SPECIALは、DMA1CNT_Hのみサウンド機能のFIFO要求があった時にDMA転送を開始します。またSPECIALは、DMA3CNT_Hのみビットマップモードで次のフレームの内容を毎回転送したい場合に使います。詳しくは不明です。

 #define GAMEPAK_DRQ		(1<<27)

ゲームパックROMからの要求があった時に転送を開始します。こちらも詳しくは不明です。

** Mode3で画像をDMA転送例 [#v19c3a29]
 #include "lib/gba.h"
 #include "res.h"
  
 //---------------------------------------------------------------------------
 void WaitForVsync(void)
 {
 	while(*(vu16*)0x4000006 >= 160) {};
 	while(*(vu16*)0x4000006 <  160) {};
 }
 //---------------------------------------------------------------------------
 int main(void)
 {
 	SetMode(MODE_3 | BG2_ENABLE);
 
 	REG_DMA3SAD = (u32)&imageBitmap;
 	REG_DMA3DAD = (u32)VRAM;
 	REG_DMA3CNT = (u32)(240*160) | (DMA_SRC_INC | DMA_DST_INC | DMA16 | DMA_ENABLE);
 
 	for(;;)
 	{
 	    WaitForVsync();
 	}
 }

*** 動作画面 [#u5d582b2]
#ref(1.png,nolink)

** 2023/07/27追記 [#f82216a5]
DMAは割り込み中にのみ使用してください。[[Tonc:https://www.coranac.com/tonc/text/dma.htm]]で警告していました。CPUを停止させて割り込みがダメになるとのこと。BIOSのCpuFaseSetか、DMA予約配列を作って割り込み中に一気に転送するなどの処置が必要かもしれません。
DMAは割り込み中にのみ使用してください。[[Tonc:https://www.coranac.com/tonc/text/dma.htm]]で警告していました。CPUを停止させて割り込みがダメになるとのこと。BIOSのCpuFaseSetか、DMA予約配列を作って割り込み中に一気に転送するなどの処置が必要です。上記のサンプル例はやってはいけない例ということで・・・。

- 14.3.2. DMA; don't wear it out
 DMA is fast, there's no question about that. It can be up to ten times as fast as array copies. However, think twice about using it for every copy.
 While it is fast, it doesn't quite blow every other transfer routine out of the water.
 
 CpuFastSet() comes within 10% of it for copies and is actually 10% faster for fills.
 The speed gain isn't that big a deal. Another problem is that it stops the CPU, which can screw up interrupts, causing seemingly random bugs.
 It does have its specific uses, usually in conjunction with timers or interrupts, but for general copies, you might consider other things as well.
 
 CpuFastSet() is a good routine, but tonclib also comes with memcpy16()/32() and memset16()/32() routines that are safer than that, and less restrictions.
 They are assembly routines, though, so you'll need to know how to assemble or use libraries. 

** 履歴 [#mdc53abc]
- 2023/07/27
- 2023/04/26
- 2007/09/05

トップ   差分 履歴 リロード   一覧 検索 最終更新   ヘルプ   最終更新のRSS