nRF SDK:PPIでTWIMを駆動する

最近nRF SDKでのPPIネタが多いですが、ちょっと必要に駆られたものでして・・・もちろん仕事ですよ(笑)
# TWIM・・・SPIMと同じでしょ？
そんな風に考えていた時期が俺にもありました・・・(by グラップラー刃牙)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/78053c21-74f4-bbd1-3910-cdf4fb53effd.png)
いや、でも本当にそう思っていました。ペリフェラルをSPIMからTWIMに変更するだけで動くと思っていましたよ、ええ。だからわざわざ記事にする必要もないと思っていたのです。
# 実際にやってみると・・・
ソースコードはSPIMの時とほとんど一緒なので省略します。必要な方はSPIMの記事を参照してください。

https://qiita.com/yoshihiro0709/items/069ea9d11156de6aaaa5

この記事を読んでいる時点でTWIMとかSPIMが何のことか分からないということはないと思いますが、SPIMと違ってCSの制御は不要なのでPPIは一つで大丈夫です。
ふう、この程度なら楽勝じゃん！ということでいざ動かしてみると・・・よし、ちゃんとコールバック返ってきたぜ・・・あれ？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/9ceb4284-6cad-dfdd-a18c-0e5f9e3dcdbd.png)
なぜ1回しか表示されないのだろう・・・タイマーで何度もPPI経由で動かしているはずなんだけど。当たり前ですが何回再起動してみても変わりません。
実はPPIではTWIM動かせないのか・・・？全く分からないのでオシロスコープで見てみることに。
![PPI_TWIM0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/bb2529a0-25cc-d65b-49d0-d4cfe0885acb.png)

:::note
SPIMのサンプルでは1秒間隔でしたが、ここでは何回も繰り返していることを1画面に収めるためにタイマースパンを短くしています。
:::

当たり前だけど繰り返して送信しているよね、うん。この時点ですでになんか気になるところはあるんだけどさ・・・なんで待機期間中のSCLがLなんだろうとか(はい、答えです(笑))

:::note
SCLが待機期間にLなのは違和感があると思いつつも開始時に持ち上がるのでOKだと思っていました
:::

![PPI_TWIM1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/8d38fb93-d336-b6ff-9135-af7bf86673eb.png)
波形を拡大してみると・・・あれ、ストップコンディションがないのですが！ないのですが！！！
その後、よくよく調べてみると最初の1回目だけはあるんですよね。なるほど、これが1回しかコールバックが呼び出されない原因ですね。
## ところが・・・
どうすればこの状態を回避できるのかが全く分かりません。いったいどこで何を設定すればよいのだろうか・・・。とりあえず考えたのは「NACKだから止まってしまっているのではないか」という点でした。いや、だってPPIでTWIMを動かすだけなんだから別にデバイスなんかなくなっていいじゃん・・・ダメなの？(笑)
しぶしぶ(笑)ですが手元にあるI2Cデバイス(ICM-20948)を引っ張り出してきて評価ボードにジャンパーで接続してACKが返ってくるような構成にしました。が・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/6eea921f-1ccb-6eae-baa2-2226b16d0e81.png)
やっぱりダメでした。1回しか動きません。オシロスコープで波形を見てみても![ICM_NG.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/33bb9e9d-3966-62e4-0eb0-0ffab1e529f0.png)
やっぱりストップコンディションはありません。
(ちなみに送信しているコマンドの内容は適当です(笑)がそこは関係ないです)

:::note warn
最後にまとめますが、設定が正しくてもNACKになるとストップコンディションの制御は止まります。正直、バグなんじゃないかと思っています(笑)
ですので、ここでデバイスを接続したのは実は正解だったのです。
:::

仕方がない(？)ので例によってソースコード側から探ることにしてみました。追いかけたのはnrfx_twim.cです。
```nrfx_twim.c
__STATIC_INLINE nrfx_err_t twim_xfer(twim_control_block_t        * p_cb,
                                     NRF_TWIM_Type               * p_twim,
                                     nrfx_twim_xfer_desc_t const * p_xfer_desc,
                                     uint32_t                      flags)
{
    nrfx_err_t err_code = NRFX_SUCCESS;
    nrf_twim_task_t  start_task = NRF_TWIM_TASK_STARTTX;
    p_cb->error = false;

    if (!nrfx_is_in_ram(p_xfer_desc->p_primary_buf))
    {
        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;
    }
    /* Block TWI interrupts to ensure that function is not interrupted by TWI interrupt. */
    nrf_twim_int_disable(p_twim, NRF_TWIM_ALL_INTS_MASK);
    if (p_cb->busy)
    {
        nrf_twim_int_enable(p_twim, p_cb->int_mask);
        err_code = NRFX_ERROR_BUSY;
        NRFX_LOG_WARNING("Function: %s, error code: %s.",
                         __func__,
                         NRFX_LOG_ERROR_STRING_GET(err_code));
        return err_code;
    }
    else
    {
        p_cb->busy = ((NRFX_TWIM_FLAG_NO_XFER_EVT_HANDLER & flags) ||
                      (NRFX_TWIM_FLAG_REPEATED_XFER & flags)) ? false: true;
    }

    p_cb->xfer_desc = *p_xfer_desc;
    p_cb->repeated = (flags & NRFX_TWIM_FLAG_REPEATED_XFER) ? true : false;
    p_cb->flags = flags;
    nrf_twim_address_set(p_twim, p_xfer_desc->address);

    nrf_twim_event_clear(p_twim, NRF_TWIM_EVENT_STOPPED);
    nrf_twim_event_clear(p_twim, NRF_TWIM_EVENT_ERROR);
    nrf_twim_event_clear(p_twim, NRF_TWIM_EVENT_LASTTX);
    nrf_twim_event_clear(p_twim, NRF_TWIM_EVENT_SUSPENDED);

    twim_list_enable_handle(p_twim, flags);
    switch (p_xfer_desc->type)
    {
    case NRFX_TWIM_XFER_TXTX:
        NRFX_ASSERT(!(flags & NRFX_TWIM_FLAG_REPEATED_XFER));
        NRFX_ASSERT(!(flags & NRFX_TWIM_FLAG_HOLD_XFER));
        NRFX_ASSERT(!(flags & NRFX_TWIM_FLAG_NO_XFER_EVT_HANDLER));
        if (!nrfx_is_in_ram(p_xfer_desc->p_secondary_buf))
        {
            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;
        }
        nrf_twim_shorts_set(p_twim, NRF_TWIM_SHORT_LASTTX_SUSPEND_MASK);
        nrf_twim_tx_buffer_set(p_twim, p_xfer_desc->p_primary_buf, p_xfer_desc->primary_length);
        nrf_twim_event_clear(p_twim, NRF_TWIM_EVENT_TXSTARTED);
        nrf_twim_task_trigger(p_twim, NRF_TWIM_TASK_RESUME);
        nrf_twim_task_trigger(p_twim, NRF_TWIM_TASK_STARTTX);
        while (!nrf_twim_event_check(p_twim, NRF_TWIM_EVENT_TXSTARTED))
        {}
        NRFX_LOG_DEBUG("TWIM: Event: %s.", EVT_TO_STR_TWIM(NRF_TWIM_EVENT_TXSTARTED));
        nrf_twim_event_clear(p_twim, NRF_TWIM_EVENT_TXSTARTED);
        nrf_twim_tx_buffer_set(p_twim, p_xfer_desc->p_secondary_buf, p_xfer_desc->secondary_length);
        p_cb->int_mask = NRF_TWIM_INT_SUSPENDED_MASK;
        break;
    case NRFX_TWIM_XFER_TXRX:
        nrf_twim_tx_buffer_set(p_twim, p_xfer_desc->p_primary_buf, p_xfer_desc->primary_length);
        if (!nrfx_is_in_ram(p_xfer_desc->p_secondary_buf))
        {
            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;
        }
        nrf_twim_rx_buffer_set(p_twim, p_xfer_desc->p_secondary_buf, p_xfer_desc->secondary_length);
        nrf_twim_shorts_set(p_twim, NRF_TWIM_SHORT_LASTTX_STARTRX_MASK |
                                    NRF_TWIM_SHORT_LASTRX_STOP_MASK);
        p_cb->int_mask = NRF_TWIM_INT_STOPPED_MASK;
        nrf_twim_task_trigger(p_twim, NRF_TWIM_TASK_RESUME);
        break;
    case NRFX_TWIM_XFER_TX:
        nrf_twim_tx_buffer_set(p_twim, p_xfer_desc->p_primary_buf, p_xfer_desc->primary_length);
        if (NRFX_TWIM_FLAG_TX_NO_STOP & flags)
        {
            nrf_twim_shorts_set(p_twim, NRF_TWIM_SHORT_LASTTX_SUSPEND_MASK);
            p_cb->int_mask = NRF_TWIM_INT_SUSPENDED_MASK;
        }
        else
        {
            nrf_twim_shorts_set(p_twim, NRF_TWIM_SHORT_LASTTX_STOP_MASK);
            p_cb->int_mask = NRF_TWIM_INT_STOPPED_MASK;
        }
        nrf_twim_task_trigger(p_twim, NRF_TWIM_TASK_RESUME);
        break;
    case NRFX_TWIM_XFER_RX:
        nrf_twim_rx_buffer_set(p_twim, p_xfer_desc->p_primary_buf, p_xfer_desc->primary_length);
        nrf_twim_shorts_set(p_twim, NRF_TWIM_SHORT_LASTRX_STOP_MASK);
        p_cb->int_mask = NRF_TWIM_INT_STOPPED_MASK;
        start_task = NRF_TWIM_TASK_STARTRX;
        nrf_twim_task_trigger(p_twim, NRF_TWIM_TASK_RESUME);
        break;
    default:
        err_code = NRFX_ERROR_INVALID_PARAM;
        break;
    }

    if (!(flags & NRFX_TWIM_FLAG_HOLD_XFER) && (p_xfer_desc->type != NRFX_TWIM_XFER_TXTX))
    {
        nrf_twim_task_trigger(p_twim, start_task);
    }

    if (p_cb->handler)
    {
        if (flags & NRFX_TWIM_FLAG_NO_XFER_EVT_HANDLER)
        {
            p_cb->int_mask = 0;
        }

        if (!(flags & NRFX_TWIM_FLAG_NO_SPURIOUS_STOP_CHECK))
        {
            p_cb->int_mask |= NRF_TWIM_INT_STOPPED_MASK;
        }

        // Interrupts for ERROR are implicitly enabled, regardless of driver configuration.
        p_cb->int_mask |= NRF_TWIM_INT_ERROR_MASK;
        nrf_twim_int_enable(p_twim, p_cb->int_mask);

#if NRFX_CHECK(NRFX_TWIM_NRF52_ANOMALY_109_WORKAROUND_ENABLED)
        if ((flags & NRFX_TWIM_FLAG_HOLD_XFER) && (p_xfer_desc->type != NRFX_TWIM_XFER_RX))
        {
            twim_list_enable_handle(p_twim, 0);
            p_twim->FREQUENCY = 0;
            nrf_twim_event_clear(p_twim, NRF_TWIM_EVENT_TXSTARTED);
            nrf_twim_int_enable(p_twim, NRF_TWIM_INT_TXSTARTED_MASK);
        }
        else
        {
            nrf_twim_frequency_set(p_twim, p_cb->bus_frequency);
        }
#endif
    }
    else
    {
        bool transmission_finished = false;
        do {
            if (nrf_twim_event_check(p_twim, NRF_TWIM_EVENT_SUSPENDED))
            {
                NRFX_LOG_DEBUG("TWIM: Event: %s.", EVT_TO_STR_TWIM(NRF_TWIM_EVENT_SUSPENDED));
                transmission_finished = true;
            }

            if (nrf_twim_event_check(p_twim, NRF_TWIM_EVENT_STOPPED))
            {
                nrf_twim_event_clear(p_twim, NRF_TWIM_EVENT_STOPPED);
                NRFX_LOG_DEBUG("TWIM: Event: %s.", EVT_TO_STR_TWIM(NRF_TWIM_EVENT_STOPPED));
                transmission_finished = true;
            }

            if (nrf_twim_event_check(p_twim, NRF_TWIM_EVENT_ERROR))
            {
                nrf_twim_event_clear(p_twim, NRF_TWIM_EVENT_ERROR);
                NRFX_LOG_DEBUG("TWIM: Event: %s.", EVT_TO_STR_TWIM(NRF_TWIM_EVENT_ERROR));

                bool lasttx_triggered = nrf_twim_event_check(p_twim, NRF_TWIM_EVENT_LASTTX);
                uint32_t shorts_mask = nrf_twim_shorts_get(p_twim);

                if (!(lasttx_triggered && (shorts_mask & NRF_TWIM_SHORT_LASTTX_STOP_MASK)))
                {
                    // Unless LASTTX event arrived and LASTTX_STOP shortcut is active,
                    // triggering of STOP task in case of error has to be done manually.
                    nrf_twim_task_trigger(p_twim, NRF_TWIM_TASK_RESUME);
                    nrf_twim_task_trigger(p_twim, NRF_TWIM_TASK_STOP);

                    // Mark transmission as not finished yet,
                    // as STOPPED event is expected to arrive.
                    // If LASTTX_SUSPENDED shortcut is active,
                    // NACK has been received on last byte sent
                    // and SUSPENDED event happened to be checked before ERROR,
                    // transmission will be marked as finished.
                    // In such case this flag has to be overwritten.
                    transmission_finished = false;
                }

                if (lasttx_triggered && (shorts_mask & NRF_TWIM_SHORT_LASTTX_SUSPEND_MASK))
                {
                    // When STOP task was triggered just before SUSPEND task has taken effect,
                    // SUSPENDED event may not arrive.
                    // However if SUSPENDED arrives it always arrives after ERROR.
                    // Therefore SUSPENDED has to be cleared
                    // so it does not cause premature termination of busy loop
                    // waiting for STOPPED event to arrive.
                    nrf_twim_event_clear(p_twim, NRF_TWIM_EVENT_SUSPENDED);

                    // Mark transmission as not finished yet,
                    // for same reasons as above.
                    transmission_finished = false;
                }
            }
        } while (!transmission_finished);

        uint32_t errorsrc =  nrf_twim_errorsrc_get_and_clear(p_twim);

        p_cb->busy = false;

        if (errorsrc)
        {
            err_code = twi_process_error(errorsrc);
        }
        else
        {
            if (!(flags & NRFX_TWIM_FLAG_NO_SPURIOUS_STOP_CHECK) &&
                !xfer_completeness_check(p_twim, p_cb))
            {
                err_code = NRFX_ERROR_INTERNAL;
            }
        }
    }
    return err_code;
}


nrfx_err_t nrfx_twim_xfer(nrfx_twim_t           const * p_instance,
                          nrfx_twim_xfer_desc_t const * p_xfer_desc,
                          uint32_t                      flags)
{
    NRFX_ASSERT(TWIM_LENGTH_VALIDATE(p_instance->drv_inst_idx,
                                     p_xfer_desc->primary_length,
                                     p_xfer_desc->secondary_length));

    nrfx_err_t err_code = NRFX_SUCCESS;
    twim_control_block_t * p_cb = &m_cb[p_instance->drv_inst_idx];

    // TXRX and TXTX transfers are supported only in non-blocking mode.
    NRFX_ASSERT( !((p_cb->handler == NULL) && (p_xfer_desc->type == NRFX_TWIM_XFER_TXRX)));
    NRFX_ASSERT( !((p_cb->handler == NULL) && (p_xfer_desc->type == NRFX_TWIM_XFER_TXTX)));

    NRFX_LOG_INFO("Transfer type: %s.", TRANSFER_TO_STR(p_xfer_desc->type));
    NRFX_LOG_INFO("Transfer buffers length: primary: %d, secondary: %d.",
                  p_xfer_desc->primary_length,
                  p_xfer_desc->secondary_length);
    NRFX_LOG_DEBUG("Primary buffer data:");
    NRFX_LOG_HEXDUMP_DEBUG(p_xfer_desc->p_primary_buf,
                           p_xfer_desc->primary_length * sizeof(p_xfer_desc->p_primary_buf[0]));
    NRFX_LOG_DEBUG("Secondary buffer data:");
    NRFX_LOG_HEXDUMP_DEBUG(p_xfer_desc->p_secondary_buf,
                           p_xfer_desc->secondary_length * sizeof(p_xfer_desc->p_secondary_buf[0]));

    err_code = twim_xfer(p_cb, (NRF_TWIM_Type *)p_instance->p_twim, p_xfer_desc, flags);
    NRFX_LOG_WARNING("Function: %s, error code: %s.",
                     __func__,
                     NRFX_LOG_ERROR_STRING_GET(err_code));
    return err_code;
}
```
実際に呼び出している関数はnrfx_twim_xferという関数なのでおそらくこのあたりのどこかだろうとは思いつつ見ていくと、flagsの引数を何やら色々と見ています。あれ、flagsって転送をホールドするNRFX_TWIM_FLAG_HOLD_XFERのためだけにあるわけじゃないのか。
## 総当たりです、はい(笑)
このflagsがきっと何かしているんだろうな、というところまでは分かったのでここから先は総当たりです。適当にソースコード内で比較しているdefineを片っ端から設定してみたところ・・・見つけました！
NRFX_TWIM_FLAG_REPEATED_XFERです。これを設定することでストップコンディションが毎回送信されるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/9af41546-5490-73a7-0b5a-b22ff105548e.png)
## やっと動いた！！！
いや、でもさ・・・改めて引数の名前を見れば納得できますが、これを何のヒントもなしに引き当てるのってかなりの難易度じゃないでしょうか？引き当てたから逆引きでInfocenterから引っ張ることもできますが、存在を知らない状態で引き当てるのはほぼ不可能だと思いますよ。

https://infocenter.nordicsemi.com/index.jsp?topic=%2Fsdk_nrf5_v17.0.0%2Fgroup__nrfx__twim.html

:::note
もしかしたらデキる人は最初にInfocenterをくまなく探すという方向に閃くのでしょうか。ちなみに僕には無理でした(笑)
:::

# おまけ
さて、ようやくTWIMをPPIで動かすことができたし、あとは本チャンの実装に組み込むだけだと満足したので先ほど引っ張り出してきた評価ボードを片付けた上であらためてこの記事を書くためにと思って動かしてみたら動かない・・・あれ、さっきたしかに動いたのにな・・・なんでだ？
と半ば混乱しつつ、やっぱりちゃんとACKじゃないと動かないのとしぶしぶ再度I2Cデバイスを引っ張り出してきて動かしてみると・・・動くじゃないですか。
なんと、PPIでTWIMを動かす場合はNACK応答をしてはダメなのですね。そりゃあらかじめセットしたデータを延々と繰り返し送るだけだからNACKが返ってきた時点でお察しではあるわけですが、それだったら送信するの止めた方がいいんじゃ・・・w
なぜにストップコンディションの制御だけを止めるのかが全く理解できません。というか、さっきも書きましたがこれはバグじゃないかと思っております。

::: note
もしかして最初のアドレス送信でNACKが返ってきたからステートマシンがストップコンディションに飛ばないってこと・・・？そんなのあるか？
:::