« Syntax Highlighter 3.0の使用方法 | トップページ | mbedでリニア温度センサーを使う »

mbedでDMAの実験

8-bitマイコンのArduinoでは味わえない32-bitマイコンの醍醐味(?)としてDMAがあります。NXP提供のCMSIS DMAライブラリのデモコードをmbedに移植して実験をしてみました。


実験の内容

  1. SRAM→SRAM,  Flash ROM→SRAM間で32 wordのDMAを起動
  2. DMA起動後、メインルーチンはループカウンタのインクリメントを行うだけの無限ループに入る。
  3. DMA完了割り込みルーチン内で再度DMAを起動することで、DMAによるメモリコピーとループが平行して動く(筈)
  4. DMAが1000回動いた時点でプログラムを止めて、起動からの経過時間とループカウンタ値を表示
  5. DMAのソースがSRAMとFlash ROMの場合の差分をチェック

ポイントは5項でありまして、図に示す通り、①のSRAM→SRAM間DMAでは、DMAと命令フェッチでメモリアクセスが競合しないと考えられます。一方、②のROM→SRAMのDMAではROMアクセスが競合するためループ処理が①に比べてまわらないというのが期待値です。

LPC1764_DMA

実験コード

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」カテゴリの記事

コメント

この記事へのコメントは終了しました。

2018年10月
  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      
無料ブログはココログ