2014年1月27日月曜日

STM32F1 DMA mem2mem

STM32F1のDMAを使ってメモリ間コピーをしてみました

uint8_t Buff1[1024];
uint8_t Buff2[1024];
int i;

for (i = 0; i < sizeof(i); i++) { Buff1[i] = i & 0xFF; }
memset(Buff2, 0, sizeof(Buff2));

Buff1からBuff2へコピーします
コピーが成功したかどうかはBuff2の内容を表示することにより確認できます
すべてのバッファが0の場合はコピー失敗 連続した値が表示されたら成功です

DMA転送の方法

まずクロックを有効にします
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
Periph1やPeriph2ではないので注意が必要です
(1や2にDMAが無いのでDMAはデフォルトでクロックが有効 と思い込んで悩みました)

つぎにDMAの初期化をします
DMA_InitTypeDef DMA;

DMA.DMA_PeripheralBaseAddr = (uint32_t)Buff1;
DMA.DMA_MemoryBaseAddr = (uint32_t)Buff2;
DMA.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA.DMA_BufferSize = sizeof(Buff2);
DMA.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
DMA.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA.DMA_Mode = DMA_Mode_Normal;
DMA.DMA_Priority = DMA_Priority_VeryHigh;
DMA.DMA_M2M = DMA_M2M_Enable;

DMA_Init(DMA1_Channel1, &DMA);

PeripheralBaseAddrにペリフェラルのメモリアドレスを
MemoryBaseAddrにRAMのメモリアドレスを設定します
ペリフェラルとメモリは便宜上の名前のため、メモリ間転送の場合はどちらに設定しても構いません
双方のアドレスはuint32_t型で、何らかのポインタでは無いので注意してください(キャスト無しでポインタを突っ込むと警告が出ます)

次にDIRですが、これはどちらが転送先か を設定します
DMA_DIR_PeripheralDSTで転送先がペリフェラルになります
DMA_DIR_PeripheralSRCで転送元がペリフェラルになります
今回は転送元のBuff1をペリフェラルに 転送先のBuff2をメモリに設定したので
ペリフェラル→メモリ となりDMA_DIR_PeripheralSRCとしてあります

BufferSizeは転送する要素数です メモリサイズではないので注意してください
ザックリ説明すると sizeof(Buff) ではなく sizeof(Buff) / sizeof(Buff[0]) の方です

PeripheralIncとMemoryIncはインクリメントするか否かです
今回は両方共RAMバッファなのでインクリメントする設定です

PeripheralDataSizeとMemoryDataSizeは配列のメモリサイズです
8bit 16bit 32bitでそれぞれByte HalfWord Wordです

ModeはNormalとCircularがあり、Circularの場合はメモリ転送を繰り返します
これはおそらくADCからどんどんメモリを読む場合などに便利な機能だと思います

Priorityは優先度です
ソフトウェアで設定できる優先度は下からLow Medium High VeryHighの4段階です
ソフトウェアで設定できる優先度以外にハードウェア出固定された優先度があり、これは同じソフトウェア優先度の転送が複数有る場合は番号の小さいチャネルが優先されます
例えばDMA1Channel1がMedium DMA1C2がHigh DMA1C3がHighの場合
DMA1C2→DMA1C2→DMA1C1の順に処理されます(おそらく
DMA2が有効の場合は どっかで読んだ気がしましたが忘れました
(RM0008とかあのあたりに書いてあります)

最後にM2Mですが
これはメモリ間転送モードの場合はEnable 非メモリ間はDisableです

構造体の初期化が完了したらDMA_Initで初期化します
この際に設定するチャネルを指定子ます


そして
 DMA_Cmd(DMA1_Channel1, ENABLE);
で転送を開始します

残りのデータ量はDMA_GetCurrDataCounter(DMA1_Channel1)で確認できます
転送が完了するまで待つ場合は
while (DMA_GetCurrDataCounter(DMA1_Channel1));
のようになります
DMAを使うとメモリ転送中に他の処理をすることができるので、whileで待つのは無駄に思えますが
CPUで転送するよりDMAのほうが早い場合があります


次はペリフェラルの転送を試してみたいと思います
これができればSPI転送などが高速化できます

0 件のコメント:

コメントを投稿