2020年5月8日金曜日

PSoC5LPでDMA転送をする

しばらくぶりです。
世間も世間で大変なことになっておりますが、私も色々と大変なことになってなってきてしまいました。
おし!!PSoCに逃げ込むぞ!!

今回はPSoC5LPでDMA転送をしようと思います。
DMA転送なんじゃいなって言うところに触れる前にペリフェラルへのデータのアクセスについてちょっとおさらい。
 通常のペリフェラルのデータレジスタにアクセスする場合、CPUがペリフェラルのレジスタ番号を使ってデータを取ってきていました。端的に言ってしまえばCPUがレジスタ番号の0x0000番地にあるデータを取得するとかですね。また「ペリフェラル側でデータの用意出来たら、CPUにお知らせしてね。そうしたらCPUがペリフェラルのデータレジスタにアクセスするよ。」っていう割り込み処理(IRQ)を使ったデータレジスタへのアクセスがあります。どちらともペリフェラルからデータを取得する際に「CPUがデータを取得する」という事が発生します。
 「CPUがデータを取得する」というのは時として難しいケースもあります。例えばADCからの連続データを取得したいけど、表示や計算にもCPUパワーを使いたい時。ほかにもありますねSPI通信で大容量のデータをセンサーやモジュールに流し込みたい時等々。こういうケースは結構あります。そこで「CPUを使わないでデータの移し替えが出来ないかしら…。」って登場するのがDMA転送です。

DMA転送についてを触れようと思うと、話が膨らみすぎるのでポイントを絞って進めようと思います。
① DMAコントローラーの行えることについて
② DMAコントローラーが何を手がかりデータを転送しているかについて
③ DMAコントローラーはどのタイミングでデータの転送をするかについて
の3つで考えておくと良いと思います。あえてCPUとペリフェラル間の通信バスについては触れないです。優先順位や通信バスの規格の話になってややこしいので。

① DMAコントローラーの行えることについて
DMAコントローラーの行えることは4つあります。どれもデータを送ったり、受け取ったりすることです。
・メモリー(flashも含め)からペリフェラルにデータを送る。
・ペリフェラルにあるデータをメモリーへ送る。
・メモリーからメモリーへデータを送る(移す)。
・ペリフェラルからペリフェラルへデータを送る。

② DMAコントローラーは何を手がかりにしてデータの転送をしているか
これは3つあるのですがちょっと複雑です。
・ペリフェラルやメモリーに割り当てられているアドレス
・送るデータの総数
・1個当たりのデータ長さ
これらを頼りにDMAコントローラーはデータ転送をします。
これだけだとピンと来ないと思うので簡単に例を挙げようと思います。


ペリフェラル(送り元)からメモリー(送り先)にDMAが転送するとします。
送るデータの総数は32byte、1個当たりのデータは1byteとします。
送り先のメモリーはuint8_tで32byte分の配列になっていてます。(送り先のメモリーは配列じゃないと送れないです。)
DMAコントローラーはペリフェラルのアドレスは分かっているので、ペリフェラルから1個目のデータを取得します。DMAコントローラーは取得したデータをメモリーへ1個目格納します。格納が終わったらまたDMAコントローラーはペリフェラルからデータを取得してメモリーへ格納するわけですがちょっと問題ですね。メモリーのアドレスが分かっている1個目の配列にはもうデータが入っています。そこでDMAコントローラーは転送したデータの個数を数えることで次に格納するメモリーのアドレスを特定することが出来ます。
メモリー側の配列の1番目のアドレスが0x0001であれば、カウンタの値と合わせて2番目は0x0002、3番目のアドレス0x0003というようになります。

③ DMAコントローラーはどのタイミングでデータの取得を行うか
DMAコントローラーにデータの取得を伝えることを「DMA request」といいます。
このDMA requestはデータの送り元がDMAコントローラーに伝えることでDMA転送が開始されます。ペリフェラルでは内部割込みのピンがDMA requestを操作したり、メモリ間での転送はCPUがDMA requestを操作することもあります。
DMAコントローラーへDMA requestを伝えるタイミングはケースバイケースなのです。例を挙げておくと、通信系のペリフェラルで受信時にペリフェラル側のデータが溜まった時にDMA requestを行います。送信時ではペリフェラル側の送るデータが無い時にDMA requestを行います。

ここからは実際にPSoC5LPでDMA転送を行います。
今回はUARTの受信をDMA転送でSRAMに移すことをします。
コンポーネントの接続は次の通りです。


DMA requestは次の通りです。
UARTの場合Level edge設定じゃないとDMA requestがかかりません。


プログラムはFreeRTOSが入った状態で書きました。
現状のコードだとデバッガを当てないと通信テストが出来ないです。

void vGPStask()
{
    uint8_t gpsbuff[32];
   
    /* Defines for DMA_rx */
    #define DMA_rx_BYTES_PER_BURST 1 //1回のデータの長さ(byte単位)
    #define DMA_rx_REQUEST_PER_BURST 1 //request毎に転送
    #define DMA_rx_SRC_BASE (CYDEV_PERIPH_BASE) //転送元
    #define DMA_rx_DST_BASE (CYDEV_SRAM_BASE) //転送先
   
    /* Variable declarations for DMA_rx */
    /* Move these variable declarations to the top of the function */
    uint8 DMA_rx_Chan;
    uint8 DMA_rx_TD[1];
   
    /* DMA Configuration for DMA_rx */
    DMA_rx_Chan = DMA_rx_DmaInitialize(DMA_rx_BYTES_PER_BURST, DMA_rx_REQUEST_PER_BURST,
        HI16(DMA_rx_SRC_BASE), HI16(DMA_rx_DST_BASE));
    DMA_rx_TD[0] = CyDmaTdAllocate();
    CyDmaTdSetConfiguration(DMA_rx_TD[0], 32, DMA_rx_TD[0], CY_DMA_TD_INC_DST_ADR);//DMAのデータのカウントと送る設定
    CyDmaTdSetAddress(DMA_rx_TD[0], LO16((uint32)UART_1_RXDATA_PTR), LO16((uint32)gpsbuff));// 転送元と転送先のアドレス
    CyDmaChSetInitialTd(DMA_rx_Chan, DMA_rx_TD[0]);   
    CyDmaChEnable(DMA_rx_Chan, 1);//DMAの有効化
    UART_1_Start();//UARTの有効化

    for(;;)
    {
        vTaskDelay(250);
    }
}

転送テストを行った結果
tera tarmから文字で「123」と入力しました。
バッファの先頭から1(0x31)、2(0x32)、3(0x33)が入っています。

注意事項
PSoC5LPのDMAは最大で8chまでです。複数のDMAを使う場合はDMA同士の優先順位を決める必要があります。またDMAのカウンタは4095なのでそれを超えるデータの量を取り扱う場合はDMAの設定を組み替える必要があります。DMAと割り込みを組み合わせるケースもあります。DMAのコードジェネレータ(DMA Wizard)の使い方や詳細はアプリケーションマニュアルAN52705 - PSoC® 3 and PSoC 5LP - Getting Started with DMAを読んでください。

0 件のコメント:

コメントを投稿