NXPのARMプロセッサのDMAを理解したい
LPC1768/LPC1769を使っていて、是非使いたい機能の中にDMAがあります。しかし、GPDMAとあるように汎用DMAになると特定のペリフェラルに特化していないDMAとなるため、はじめてNXPマイコンを使う人に取っては設定項目の多さ、初期化手順の混沌といった問題にぶつかります。
以前製作したMTM06の時に展示したロスレスオーディオデコーダではユーザRAMに数kBのバッファをソフトウェアで用意し、割り込みを使用してI2Sペリフェラルにデータを渡していました。8ワード分のバッファがあるにせよ44.1kHzステレオ、16bitの音声ですのでそれなりのオーバーヘッドが生じます。DMAを使えば割り込みのオーバーヘッドが無くなりはしないものの大きく低減できますので、是非使いたいところ*1です。
ところがいざこの準備をしようとするとsirius506氏も仰る通りかなり大変です。【注:PDFです】ユーザーマニュアルのI2SとGPDMAの項目に精通していなければなりません。
id:suikan 氏が最近TOPPERS/ASPによるTalkThroughプログラムをリリースされました。こちらも参考にしながら、DMAを実装していこうと思いました。
しかし、mbed IDE上でレジスタを直接*2叩くとおかしな挙動をする事があるようです*3。mbedライブラリはCMSISレイヤの上にある高位なライブラリですので、mbedライブラリに存在しないLPC17xxのペリフェラルを操作する際にはなるべくCMSIS越しに操作した方が良いのかもしれません。NXPが用意しているCMSISドライバライブラリを参考にします。
I2S DMAでは音声データを途切れる事無くDMAに渡すためLLIを使用します。今回は勉強のためI2Sの機能を省いたLLIのテストコードを使用しました。
このサンプルもGPDMAフォルダにある(Link_list)ので流用しました。
このサンプルはワンショットLLIとなっているため、LLIがリングバッファを形成するように(無限ループになるように)書き換えます。
/* Init GPDMA link list */ DMA_LLI_Struct[0].SrcAddr = (uint32_t)&DMASrc_Buffer1; DMA_LLI_Struct[0].DstAddr = (uint32_t)&DMADest_Buffer; DMA_LLI_Struct[0].NextLLI = (uint32_t)&DMA_LLI_Struct[1]; DMA_LLI_Struct[0].Control = (DMA_SIZE/2) | (2<<18) //source width 32 bit | (2<<21) //dest. width 32 bit | (1<<26) //source increment | (1<<27) | (1<<31) //trigger interrupt <- これを追記 ; DMA_LLI_Struct[1].SrcAddr = (uint32_t)&DMASrc_Buffer2; DMA_LLI_Struct[1].DstAddr = ((uint32_t)&DMADest_Buffer) + (DMA_SIZE/2)*4; DMA_LLI_Struct[1].NextLLI = (uint32_t)&DMA_LLI_Struct[0]; // <- これを追記 DMA_LLI_Struct[1].Control = (DMA_SIZE/2) | (2<<18) //source width 32 bit | (2<<21) //dest. width 32 bit | (1<<26) //source increment | (1<<27) | (1<<31) //trigger interrupt <- これを追記 ;
こちらをコンパイルしたものをmbed.org上にuploadしてあります。Buffer1,2それぞれデータを送出した後にTC割り込みを発生させます。確認のためprintfで"hogehoge"とUART0越しに喋ります。
参考:todotani氏のブログPS3とLinux、電子工作も::mbedでDMAの実験