mbedでDMAの実験
8-bitマイコンのArduinoでは味わえない32-bitマイコンの醍醐味(?)としてDMAがあります。NXP提供のCMSIS DMAライブラリのデモコードをmbedに移植して実験をしてみました。
実験の内容
- SRAM→SRAM, Flash ROM→SRAM間で32 wordのDMAを起動
- DMA起動後、メインルーチンはループカウンタのインクリメントを行うだけの無限ループに入る。
- DMA完了割り込みルーチン内で再度DMAを起動することで、DMAによるメモリコピーとループが平行して動く(筈)
- DMAが1000回動いた時点でプログラムを止めて、起動からの経過時間とループカウンタ値を表示
- DMAのソースがSRAMとFlash ROMの場合の差分をチェック
ポイントは5項でありまして、図に示す通り、①のSRAM→SRAM間DMAでは、DMAと命令フェッチでメモリアクセスが競合しないと考えられます。一方、②のROM→SRAMのDMAではROMアクセスが競合するためループ処理が①に比べてまわらないというのが期待値です。
DMA完了の割り込み処理ルーチンとメインルーチンを以下に示します。コード全体は、mbed.orgにポストしてあります。
static uint32_t DMASrc_Buffer[DMA_SIZE]= { 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10, 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20, 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30, 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40, 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10, 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20, 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30, 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40 }; uint32_t DMADest_Buffer[DMA_SIZE]; /*-------------------------MAIN FUNCTION------------------------------*/ extern "C" void DMA_IRQHandler (void); // extern "C" required for mbed /*----------------- INTERRUPT SERVICE ROUTINES --------------------------*/ /*********************************************************************//** * @brief GPDMA interrupt handler sub-routine * @param[in] None * @return None **********************************************************************/ void DMA_IRQHandler (void) { // check GPDMA interrupt on channel 0 if (GPDMA_IntGetStatus(GPDMA_STAT_INT, 0)) { //check interrupt status on channel 0 // Check counter terminal status if(GPDMA_IntGetStatus(GPDMA_STAT_INTTC, 0)) { // Clear terminate counter Interrupt pending GPDMA_ClearIntPending (GPDMA_STATCLR_INTTC, 0); if (++TC_count < DMA_CYCLES) { // Setup channel with given parameter GPDMA_Setup(&GPDMACfg); // Run DMA again GPDMA_ChannelCmd(0, ENABLE); return; } else { /* DMA run predetermined cycles */ int elapsedTime = GetTickCount(); Buffer_Verify(); pc.printf("%s", compl_menu); pc.printf("DMA %d cycles, %d loop executions\n\r", TC_count, loop); pc.printf("Elapsed time %d ms\n\r\n\r", elapsedTime); while(1); // Halt program } } if (GPDMA_IntGetStatus(GPDMA_STAT_INTERR, 0)){ // Clear error counter Interrupt pending GPDMA_ClearIntPending (GPDMA_STATCLR_INTERR, 0); pc.printf("DMA Error detected.\n\r"); while(1); // Halt program } } } /*-------------------------MAIN FUNCTION--------------------------------*/ /*********************************************************************//** * @brief c_entry: Main program body * @param[in] None * @return int **********************************************************************/ int c_entry(void) { pc.baud(9600); // print welcome screen pc.printf("%s", menu); /* Disable GPDMA interrupt */ NVIC_DisableIRQ(DMA_IRQn); /* preemption = 1, sub-priority = 1 */ NVIC_SetPriority(DMA_IRQn, ((0x01<<3)|0x01)); /* Initialize GPDMA controller */ GPDMA_Init(); // Setup GPDMA channel -------------------------------- // channel 0 (highest priority GPDMACfg.ChannelNum = 0; // Source memory GPDMACfg.SrcMemAddr = (uint32_t)DMASrc_Buffer; // Destination memory GPDMACfg.DstMemAddr = (uint32_t)DMADest_Buffer; // Transfer size GPDMACfg.TransferSize = DMA_SIZE; // Transfer width GPDMACfg.TransferWidth = GPDMA_WIDTH_WORD; // Transfer type GPDMACfg.TransferType = GPDMA_TRANSFERTYPE_M2M; // Source connection - unused GPDMACfg.SrcConn = 0; // Destination connection - unused GPDMACfg.DstConn = 0; // Linker List Item - unused GPDMACfg.DMALLI = 0; // Setup channel with given parameter GPDMA_Setup(&GPDMACfg); /* Enable GPDMA interrupt */ NVIC_EnableIRQ(DMA_IRQn); pc.printf("Start transfer...\n\r"); GetTickCount_Start(); // Enable GPDMA channel 0 GPDMA_ChannelCmd(0, ENABLE); /* Wait for GPDMA processing complete */ while (1) { loop++; } return 1; }
DMAを何回も起動しながら、平行して117行目のloopが何回まわるかを確認します。CMSISライブラリのexampleからの変更点は以下です:
- 割り込みハンドラー(17行目)のプロトタイプ宣言にextern "C"をつける。これがないとハングしてしまう
- コンソールへの文字出力はmbedライブラリのprintf関数に変更
- 実行時間の計測は1ms周期のSysTick割り込みを使用
実行結果と考察
1行目のDMASrc_Bufferをstatic宣言するとSRAM上に領域が確保され①のSRAM→SRAM DMAになります。1行目をconst宣言するとFlash ROM上に領域が確保され②になります。
①:SRAM→SRAM
DMA 1000 cycles, 22962 loop executions
Elapsed time 5 ms
②Flash ROM→SRAM
DMA 1000 cycles, 7985 loop executions
Elapsed time 5 ms
上記より、期待通り①の方がループの実行回数が多いことが分かります。実行時間は①②とも5msで共通のため、DMA 1回(32 wordの転送)に要する時間は同じで、DMA期間中にDMACとMCUでバスの使用権を融通し合ういわゆるサイクルスチールモードになっていると思われます。
①ではDMACがSRAMアクセス中でもMCUは独立してループ処理の命令フェッチを進めることができ、DMACがバスマスタ権を譲ってくれた隙にloopカウンターの更新値をSRAMに書き戻せるのに対して、②はDMACがROMアクセス中は命令フェッチが止まるためループ回数が低下すると思われます。
おまけ
試しに以下のコードで、DMAと同様のコピーをmemcpyライブラリを使って行いました。
#include "mbed.h" #include "GetTickCount/GetTickCount.h" #define LOOP 1000 #define BUF_SIZE 32 Serial pc(USBTX, USBRX); // tx, rx /* ---- Source data to be copied ---- */ // Allocated to Flash ROM const uint8_t __align(8) array1[BUF_SIZE] = { 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10, 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20, 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30, 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40, 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10, 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20, 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30, 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40 }; // Allocated to SRAM static uint8_t __align(8) array2[BUF_SIZE] = { 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10, 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20, 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30, 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40, 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10, 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20, 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30, 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40 }; /* * My Memcpy function * If source and destination data is aligned at DWROD, performe copy by 8 byts */ void Memcpy(void *dst, const void *src, size_t len) { if ( (uint32_t)dst % sizeof(uint64_t) == 0 && (uint32_t)src % sizeof(uint64_t) == 0 && len % sizeof(uint64_t) == 0 ) { uint32_t *d = (uint32_t*)dst; uint32_t *s = (uint32_t*)src; uint32_t *end = (uint32_t*)(s + len/sizeof(uint64_t)); while (s != end) { *(d++) = *(s++); // Copy first 4 bytes *(d++) = *(s++); // Copy second 4 bytes } } else { uint8_t *d = (uint8_t*)dst; uint8_t *s = (uint8_t*)src; uint8_t *end = (uint8_t*)(s + len); while (s != end) { *(d++) = *(s++); // Copy each byte } } } int main() { unsigned int startTime, endTime; int i,len; char __align(8) array3[BUF_SIZE * sizeof(uint32_t)]; pc.baud(9600); // set baud rate len = BUF_SIZE * sizeof(uint32_t); pc.printf("Data Length: %d\n", len); GetTickCount_Start(); /* ---- Flash ROM to SRAM copy ----- */ pc.printf("Memory copy Flash->SRAM using memcpy lib %d bytes.\n", LOOP*len); startTime = GetTickCount(); for (i = 0; i < LOOP; i++) { memcpy(array3, array1, len); } endTime = GetTickCount(); pc.printf("Elapsed time: %d ms.\n\n", endTime - startTime); pc.printf("Memory copy Flash->SRAM using My Memcpy %d bytes.\n", LOOP*len); startTime = GetTickCount(); for (i = 0; i < LOOP; i++) { Memcpy(array3, array1, len); } endTime = GetTickCount(); pc.printf("Elapsed time: %d ms.\n\n", endTime - startTime); /* ---- SRAM to SRAM copy ----- */ pc.printf("Memory copy SRAM->SRAM using memcpy lib %d bytes.\n", LOOP*len); startTime = GetTickCount(); for (i = 0; i < LOOP; i++) { memcpy(array3, array2, len); } endTime = GetTickCount(); pc.printf("Elapsed time: %d ms.\n\n", endTime - startTime); pc.printf("Memory copy SRAM->SRAM using My Memcpy %d bytes.\n", LOOP*len); startTime = GetTickCount(); for (i = 0; i < LOOP; i++) { Memcpy(array3, array2, len); } endTime = GetTickCount(); pc.printf("Elapsed time: %d ms.\n\n", endTime - startTime); }
なんと、SRAM→SRAM、Flash ROM→SRAM共に、1msで終了。loop処理を行っていないという差分はありますが、ひょっとして単なるmemoryコピーならDMAを使うよりMCUでやった方が早いのか?
DMACの設定にSoftware Burst Requestというヤツがあり、こいつを使うとDMACがバスを占有するバーストモードとなり、DMA時間を短縮できるのではと思ったのですが、動かし方が分からず断念でした。
参考情報
« Syntax Highlighter 3.0の使用方法 | トップページ | mbedでリニア温度センサーを使う »
「mbed」カテゴリの記事
- mbed TY51822r3でmbed OSを使う(2016.04.16)
- mbed OSでLチカ(2015.11.22)
- 各種mbedのベンチマークテスト(2014.08.31)
- iPhoneからmbedをBluetooth LE (BTLE)で制御する(2013.02.11)
- Debug printf用の可変長引数マクロ(2011.04.29)
この記事へのコメントは終了しました。
コメント