nRF SDK:EasyDMAってなんだ・・・？

お、2回連続でnRF SDKネタじゃん！(笑)
# PPIと並んで省電力の要
らしいです。CPUを使わないってのはそんなにも消費電力を下げられるものなのでしょうか・・・。発売されてかなり経過していますし、僕自身もなんだかんだでもう3年くらいはこのNordicシリーズと格闘しておりますが、今さらnRF52シリーズの最大のウリがPPIとEasyDMAであるというのを知りました。
## でもさぁ・・・
これは言い訳なんですが新しいCPUを全部知るのってとても難しいですよ。なにせ最初は動かすだけで精一杯ですから。で、ある程度まで習熟するとようやくこういう目玉機能の存在に気が付く、みたいな。

:::note
余談ですがそれまではCPUといえばFPGAのソフトウェアCPU(NIOSとか)を触っていました。
はるか昔にSH2とかMC68000とか触っていたので全く知見がないわけでもないです。
:::
# で、Easy(DMA)ってなんだよ
まあ一般的にEasyと付くようなのは「簡単に」「お手軽に」という意味で付けられることが多いですね。DMAってだいたいDMAC(Direct Memory Access Controller)がいて、そいつがCPUを介さずにメモリ転送するというパターンでして、だいたいDMACを動かすのがめんどくさいんですよね、知らんけど。

:::note
最近のCPU事情は知りません(笑)
:::

## EasyDMAの使い方を調べるにも・・・
これがまた調べても調べても全然出てきません。正確にはDevzoneでいくつかヒットはするのですが、情報が古くて最近のSDK事情に全然あっていなさそうです。
## ソースコードから解析する
探しても埒が明かなさそうなので、EasyDMAをどのように起動してどうすれば動かせるのかをSDKのソースコード側から追ってみることにしました。だってどう考えてもその方が早いですから。
### ソースコード
EasyDMAはどうやらnrfx_spim.cの中にあるようです。
```nrfx_spim.c
static nrfx_err_t spim_xfer(NRF_SPIM_Type               * p_spim,
                            spim_control_block_t        * p_cb,
                            nrfx_spim_xfer_desc_t const * p_xfer_desc,
                            uint32_t                      flags)
{
    nrfx_err_t err_code;
    // EasyDMA requires that transfer buffers are placed in Data RAM region;
    // signal error if they are not.
    if ((p_xfer_desc->p_tx_buffer != NULL && !nrfx_is_in_ram(p_xfer_desc->p_tx_buffer)) ||
        (p_xfer_desc->p_rx_buffer != NULL && !nrfx_is_in_ram(p_xfer_desc->p_rx_buffer)))
    {
        p_cb->transfer_in_progress = false;
        err_code = NRFX_ERROR_INVALID_ADDR;
        NRFX_LOG_WARNING("Function: %s, error code: %s.",
                         __func__,
                         NRFX_LOG_ERROR_STRING_GET(err_code));
        return err_code;
    }

#if NRFX_CHECK(NRFX_SPIM_NRF52_ANOMALY_109_WORKAROUND_ENABLED)
    p_cb->tx_length = 0;
    p_cb->rx_length = 0;
#endif

    nrf_spim_tx_buffer_set(p_spim, p_xfer_desc->p_tx_buffer, p_xfer_desc->tx_length);
    nrf_spim_rx_buffer_set(p_spim, p_xfer_desc->p_rx_buffer, p_xfer_desc->rx_length);

#if NRFX_CHECK(NRFX_SPIM3_NRF52840_ANOMALY_198_WORKAROUND_ENABLED)
    if (p_spim == NRF_SPIM3)
    {
        anomaly_198_enable(p_xfer_desc->p_tx_buffer, p_xfer_desc->tx_length);
    }
#endif

    nrf_spim_event_clear(p_spim, NRF_SPIM_EVENT_END);

    spim_list_enable_handle(p_spim, flags);

    if (!(flags & NRFX_SPIM_FLAG_HOLD_XFER))
    {
        nrf_spim_task_trigger(p_spim, NRF_SPIM_TASK_START);
    }
#if NRFX_CHECK(NRFX_SPIM_NRF52_ANOMALY_109_WORKAROUND_ENABLED)
    if (flags & NRFX_SPIM_FLAG_HOLD_XFER)
    {
        nrf_spim_event_clear(p_spim, NRF_SPIM_EVENT_STARTED);
        p_cb->tx_length = p_xfer_desc->tx_length;
        p_cb->rx_length = p_xfer_desc->rx_length;
        nrf_spim_tx_buffer_set(p_spim, p_xfer_desc->p_tx_buffer, 0);
        nrf_spim_rx_buffer_set(p_spim, p_xfer_desc->p_rx_buffer, 0);
        nrf_spim_int_enable(p_spim, NRF_SPIM_INT_STARTED_MASK);
    }
#endif

    if (!p_cb->handler)
    {
        while (!nrf_spim_event_check(p_spim, NRF_SPIM_EVENT_END)){}

#if NRFX_CHECK(NRFX_SPIM3_NRF52840_ANOMALY_198_WORKAROUND_ENABLED)
        if (p_spim == NRF_SPIM3)
        {
            anomaly_198_disable();
        }
#endif
        if (p_cb->ss_pin != NRFX_SPIM_PIN_NOT_USED)
        {
#if NRFX_CHECK(NRFX_SPIM_EXTENDED_ENABLED)
            if (!p_cb->use_hw_ss)
#endif
            {
                if (p_cb->ss_active_high)
                {
                    nrf_gpio_pin_clear(p_cb->ss_pin);
                }
                else
                {
                    nrf_gpio_pin_set(p_cb->ss_pin);
                }
            }
        }
    }
    else
    {
        spim_int_enable(p_spim, !(flags & NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER));
    }
    err_code = NRFX_SUCCESS;
    NRFX_LOG_INFO("Function: %s, error code: %s.", __func__, NRFX_LOG_ERROR_STRING_GET(err_code));
    return err_code;
}
```
このあたりがどうやらそんな感じです。
```
    nrf_spim_tx_buffer_set(p_spim, p_xfer_desc->p_tx_buffer, p_xfer_desc->tx_length);
    nrf_spim_rx_buffer_set(p_spim, p_xfer_desc->p_rx_buffer, p_xfer_desc->rx_length);
```
ちなみに調べてみると、nrfx_spim_xferをコールしてSPIMを駆動させると必ずここに来るので、普通にSPIMを使うだけでEasyDMAが勝手に機能しているようです。なんだよ全然知らなかったよ！
つまり僕は知らないうちにEasyDMAを使っていたということなんですね。

:::note
TWIも同じくTWIMとして使うことで自動的にEasyDMAが使われます。
:::

# この先が鬼門
でもこれだけでは省電力にならないのですよ。なるかも知らんけど。
これを先日のPPIと組み合わせて完全にCPU非介入でSPIMペリフェラルを動かさないと意味がありません。
が・・・残念ながら現在調べている限りではそこまで分かりませんでした。また次回！(笑)