[組み込み] レジスタ制御しようぜのDMA編

# はじめに
これは以下記事の内容の一部なります。

https://qiita.com/T_ksy/items/655214cc34feacfb6d5e


# 設計
基本的な処理が決まったため、次は詳細な設計をしていきたいと思います。

:::note warn
この設計パートでは初期化処理のみ説明します。
その他の処理について大したことないため実装パートにて説明します。
:::

### DMAレジスタ制御モジュール
DMAを制御するためのモジュールになります。
今回の用途としては、内部のデータ転送制御になります。

以下が初期化制御フローになります。
![デバドラ_led制御-DMA制御フロー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/563ae48f-1349-a673-eedb-716c48175282.png)

何をしているか順に説明していきます。

#### 1\. RCCの設定
こちらの設定はもうお馴染みの内容ですね。

DMAは2つありますが、どちらも**AHB1**バスに繋がっています。
![全体ブロック図_DMAのクロック.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/9118af8b-7bfd-d998-ef44-d7541e12b48e.png)
[DS10693 P16]

今回はDNA1を使用するため**RCC_AHB1ENR**レジスタの21bit目を設定します。
![DMA_クロックレジスタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/ccd2ba10-03fc-2668-cf1e-212cf46e5dfd.png)
[RM0390 P144]


#### 2\. DMAの設定
次はDMAの設定をしていきます。

まずは以下のDMAブロック図を見てください。
![DMA概要図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/86a2d530-73d1-d95e-dd9e-9253f9677451.png)
[RM0390 P205]

内部でどのような構成になっているかを示しています。
なんかよくわかんねーってなると思いますが、とりあえず以下の認識でいいです。
ストリームとチャネルの組み合わせでデータのやり取りを行う。

この組み合わせというのが以下になります。
![DMAストリーム_チャネル組み合わせ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/7622291f-2c8c-8986-8c8d-dff12dfaeb45.png)
[RM0390 P207]

どのペリフェラルとDMAを繋げるかによって組み合わせは決まっています。
今回はUART2の送信と繋げたいため、Stream6のチャネル4になります。

DMA_CRレジスタにて先程の設定を行います。
![DMA_CRレジスタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/be05ebd0-15d2-0d26-ed3b-60cc8b6db28b.png)
[RM0390 P228]

チャネル番号が4なので、"100"を設定します。
***

次に送信元のアドレス、送信先のアドレスと長さをDMAに教える必要があります。
送信元のアドレスは**DMA_M0AR**レジスタにて設定します。
![DMA_送信元アドレスレジスタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/f4e7c56e-9352-3ae8-bd2a-198ace7ea072.png)
[RM0390 P232]

設定値は送信したいデータのアドレスを渡します。

***
送信先のアドレスは**DMA_PAR**レジスタにて設定します。
![DMA_送信先アドレスレジスタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/54d20622-0582-6990-c6bd-bd6078dba469.png)
[RM0390 P232]

設定値は送信先のデータのアドレスを渡します。

***
送信データサイズは**DMA_NDTR**レジスタにて設定します。
![DMA_送信データサイズレジスタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/79497fc2-64f3-0772-7d13-fde58c5066f8.png)

設定値は送信データのサイズを渡します。

***
次にデータの転送の向きを設定する必要があります。
なんの設定かと言うと、どこからどこへデータを送りたいかの話になります。
DMAで言うと、「メモリからどこかへ」 or 「どこからメモリへ」の2択になります。

設定するのは**DMA_CR**レジスタになります。
6-7bit目が転送モードの設定なります。
![DMA_CRレジスタ転送モード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/efee46e9-7be1-0df5-a03d-2de4d1b1a389.png)

今回はメモリに保存しているデータをUART(ペリフェラル)へ転送したいので、
Memory to Peripheralの設定になります。なので"01"を設定します。

***

次はデータの転送幅を設定します。
転送幅と言うのは、1回の転送でどのくらい送るかの設定になります。

設定するのはこちらも**DMA_CR**レジスタになります。
11-12it目がペリフェラルのデータ幅で、13-14bit目がメモリのデータ幅になります。
![DMA_CRレジスタ転送幅.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/96fb0b26-9cf7-a2c1-5ec7-8f99e399ca87.png)

今回は1byteづつ送りたいの、"00"を設定します。

***

次にメモリアドレスの増分設定をします。
これはなにかと言うと、先程設定したDMAの送信元アドレスを増分するかどうかの設定になります。
例えば連続したデータ(配列など)を送りたい場合、この設定をしないと自動でアドレスをインクリメントしてくれません。インクリメントしないとないにが起こるかと言うと、**常に同じアドレスの値のみをサイズ分送信する**ことになります。

設定するレジスタは先程から登場している**DMA_CR**レジスタの10bit目(MINC)になります。

![DMA_CRレジスタメモリ増分.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/fdb6c4a8-2e4d-9e75-4e94-e27fd19560c2.png)

自動で増分したいので、"1"を設定します。

***

まだ続きますよ。次はDMAの動作モードの設定をします。
FIFOモードとダイレクトモードという2つのモードが存在します。
なにが違うのかと言うと、シンプルにFIFOを使用するかしないかの違いになります。
FIFOを使用するメリットとしては、大量のデータをFIFOに貯めて一気に転送することができるなどのメリットがあります。今回の用途ではFIFOを使用するメリットがないためダイレクトモードを使用します。

設定するレジスタは**DMA_FCR**レジスタになります。
![DMA_FIFO転送モード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/d2030e09-7790-6a05-fabb-4caefa872f31.png)
[RM0390 P233]

2bit目がその設定になります。"0"を設定すればダイレクトモードになります。

またこのレジスタにはもう一つ設定するbitがあります。
0-1bitの設定になります。これはFIFOの閾値を設定するレジスタになります。
でも、今回ダイレクトモードだからFIFO使わないよね？って思っていると思いますが、
ダイレクトモード時は"0"を設定してと記載があるので設定しましょう。

以上で初期化の設計パートは終わりです。と言いたいところですが、
まだ設定していない箇所がありますよね。そう、割り込みです。
では設定していきます。

DMA_CRレジスタとDMA_FCRレジスタに割り込みの設定が含まれています。
DMA_CR
1bit目：ダイレクトモードエラー割り込み
2bit目：エラー割り込みのイネーブル
3bit目：ハーフ転送割り込み
4bit目：転送完了割り込み

DMA_FCT
7bit目：FIFエラー割り込み

使わないものもありますが、これらに"1"を設定してください。
その後、GPIOやUARTの時同様、NVICの設定をします。

# 実装

先ほどの設計を踏まえて実装していきます。

### init_dma1:初期化処理
```c
void init_dma1(void)
{
    RCC_TypeDef *pRCC;
    pRCC = RCC;

    DMA_Stream_TypeDef *pSTREAM6;
    pSTREAM6 = DMA1_Stream6;

    USART_TypeDef *pUART2;
    pUART2 = USART2;

    // DMA1のクロックを有効化
    pRCC->AHB1ENR |= (1 << 21);

    // DMA1の設定
    // チャンネル(4)の選択

    // UART2の送信をDMA1に接続
    pSTREAM6->CR &= ~(0x7 << 25);  // clear
    pSTREAM6->CR |= (0x4 << 25);

    // 送信元のアドレスを設定(メモリ)
    pSTREAM6->M0AR = (uint32_t)dma_data1;

    // 送信先のアドレスを設定(ペリフェラル)
    pSTREAM6->PAR = (uint32_t)&pUART2->DR;

    // 送信データの長さを設定
    uint32_t len   = sizeof(dma_data1);
    pSTREAM6->NDTR = len;

    // データの転送モードを設定 (M2M, M2P, P2M)
    pSTREAM6->CR |= (0x1 << 6);  // M2P

    // データ幅を設定
    pSTREAM6->CR &= ~(0x3 << 13);  // 8bit(memory)
    pSTREAM6->CR &= ~(0x3 << 11);  // 8bit(peripheral)

    // メモリアドレスの増分設定
    pSTREAM6->CR |= (0x1 << 10);  // メモリアドレスの増分あり

    // モードの設定 (ダイレクトモード、FIFOモード)
    pSTREAM6->FCR |= (0x1 << 2);  // ダイレクトモード

    // FIFOの閾値の設定
    pSTREAM6->FCR &= ~(0x3 << 0);
    pSTREAM6->FCR |= (0x3 << 0);

    // 循環モードの設定(オプション)
    // →デフォルトを使用するので設定不要
    // バースト転送の設定(オプション)
    // →デフォルトを使用するので設定不要
    // ストリームの優先度の設定(オプション)
    // →デフォルトを使用するので設定不要
}
```
設計時に説明したので特に追記はないです。


### config_int_dma1:DMA割り込み設定処理

```c
void config_int_dma1(void)
{
    DMA_Stream_TypeDef *pSTREAM6;
    pSTREAM6 = DMA1_Stream6;

    // ハーフ転送割り込みの有効化(HTIE)
    pSTREAM6->CR |= (0x1 << 3);

    // 転送完了割り込みの有効化(HCIE)
    pSTREAM6->CR |= (0x1 << 4);

    // エラー割り込みの有効化(エラーが発生した場合のみ)(IEIE)
    pSTREAM6->CR |= (0x1 << 2);

    // FIFOエラー割り込みの有効化(オプション)(FEIE)
    pSTREAM6->FCR |= (0x1 << 7);

    // ダイレクトモードエラー割り込みの有効化(オプション)(DMEIE)
    pSTREAM6->CR |= (0x1 << 1);

    // IRQハンドラの有効化
    NVIC_EnableIRQ(DMA1_Stream6_IRQn);
}
```
概要：DMAの割り込み設定を行う
処理内容：設計時に説明した内容の処理を順に行う

### DMA1_Stream6_IRQHandler:割り込みハンドラ
以下に割り込みハンドラも記載します。
※こちらは「stm32f4xx_it.c」ファイルに記載しています。
```c
#define is_HT()  DMA1->HISR & (1 << 20)
#define is_FT()  DMA1->HISR & (1 << 21)
#define is_TE()  DMA1->HISR & (1 << 19)
#define is_FE()  DMA1->HISR & (1 << 16)
#define is_DME() DMA1->HISR & (1 << 18)

void DMA1_Stream6_IRQHandler(void)
{
    if (is_HT()) {
        DMA1->HIFCR |= (1 << 20);
    } else if (is_FT()) {
        DMA1->HIFCR |= (1 << 21);
        FT_callback();
    } else if (is_TE()) {
        DMA1->HIFCR |= (1 << 19);
    } else if (is_FE()) {
        DMA1->HIFCR |= (1 << 16);
    } else if (is_DME()) {
        DMA1->HIFCR |= (1 << 18);
    } else {
        ;
    }
}
```
概要：DMAの割り込み処理を行う
処理内容：各割り込みによって処理を実行する(今回は転送完了のみサポートとしている)


### FT_callback:コールバック関数
```c
void FT_callback(void)
{
    USART_TypeDef *pUART2;
    pUART2 = USART2;

    DMA_Stream_TypeDef *pSTREAM6;
    pSTREAM6 = DMA1_Stream6;

    // 送信完了後の処理
    pUART2->CR3 &= ~(1 << 7);

    enable_dma1_steam6();
}

void enable_dma1_steam6(void)
{
    DMA_Stream_TypeDef *pSTREAM6;
    pSTREAM6 = DMA1_Stream6;

    // DMAの有効化
    pSTREAM6->CR |= (0x1 << 0);
}
```
概要：DMAの転送完了割り込み処理を行う
処理内容：
以下のようなことをやっています。
1\. UART_CR3レジスタの送信フラグをOFFにする
2\. DMAのイネーブルフラグをONにする

# 終わりに
これにてDMAのレジスタ制御モジュール作成は完了となります。