DMACAを利用したSCI 非同期通信で、2回目の受信ができません

お世話になっております。B.Ishiiと申します。

FITモジュールのDMACAを利用したSCI 非同期通信で、2回目の受信ができず、悩んでいます。
恐らく、私の使い方が間違っているのだと思うのですが、ご助言いただけるとありがたいです。

状況といたしまして、
・1回目の受信はできます
・送信側はDMACAを使ってうまく動作しています
・DMACAを利用しない設定では受信できます(下記のソースコードとは別のもので確認しました)

ソースコードは、以下のような感じです。
(とりあえず動作を見たいため、エラーハンドリングはしていません)

 char cpRecvData[sizeof("ABCDE")] = {0}; // "ABCDE"が送信されることは分かっている

while (1)
{
sci_err_t stSciErr = R_SCI_Receive(stSciHdl, (uint8_t *)cpRecvData, sizeof(cpRecvData));

if (stSciErr == SCI_SUCCESS)
{
break;
}

vTaskDelay(pdMS_TO_TICKS(1));
}

while (1)
{
dmaca_stat_t dmac_status;

dmaca_return_t ret =
R_DMACA_Control(DMACA_CH1, DMACA_CMD_STATUS_GET, &dmac_status);
if (ret == DMACA_SUCCESS && dmac_status.dtif_stat == true)
{
break;
}

vTaskDelay(pdMS_TO_TICKS(1));
}

デバッガで調べたところ、2回目の受信で、R_SCI_Receive()ループは抜けるのですが、R_DMACA_Control()ループは、dmac_status.dtif_stat == trueにならず、抜けられません。
cpRecvDataへの"ABCDE"格納もされていません。

■環境
・RX72N
・FreeRTOS(with IoT Libraries)
 Ver. afr-v202012.00-rx-1.0.1
・r_dmaca_rx
 Ver. 2.90
・r_sci_rx
 Ver. 4.40

Parents
  • B.Ishiiさん、こんにちは。NoMaYです。

    どうしてDMA制御関数を呼び出しているのだろうかと咄嗟に思ったのですが、もしかして、DMA/DTCを使った場合にR_SCI_Control()では送信完了や受信完了を知る方法が無い、ということでの苦肉の策なのでしょうか?

  • こんにちは。NoMaYさん

    > R_SCI_Control()では送信完了や受信完了を知る方法が無い、ということでの苦肉の策なのでしょうか?

    はい、R_SCI_Control()によるDMA転送完了を知る方法を見つけられないでいます。

    • R_SCI_Control(SCI_CMD_RX_Q_BYTES_AVAIL_TO_READ) → ×
       DMA設定では、適切な結果を得られませんでした。
       (r_sci_rxの制限事項に書かれているように、受信時、中間バッファにBYTEQを使わないので、そのBYTEQの読み込み可能なバイト数を取得できないようです。
    • R_SCI_Control(SCI_CMD_CHECK_XFER_DONE) → ×
       調歩同期式モードでは、このパラメータは使用可能となっておらず、ダメもとでやってみましたが、やはり引数不正(SCI_ERR_INVALID_ARG)で返ってきました。

    他に、R_SCI_Open()で設定されるコールバック関数で、SCI_EVT_RX_DONEイベントを監視してみました。
    1回目は、期待通り(1回)受信できていました。
    2回目は、何故か複数回受信していました。
     (しかし、dmac_status.dtif_statはtrueにならない)
     (「複数回受信」は、SCI_SUCCESSが返ってくるまで、受信前から複数回R_SCI_Receive()を呼び出しているのですが、その影響ですかね)

  • B.Ishiiさん、こんにちは。NoMaYです。

    やはりそういうことでしたか。ところで以下の使い方では駄目な気がするのです。受信未完了状態では呼び出しをスキップする、という処理が必要だと思うのです。いや、それより前に、R_SCI_Receive()でSCI_SUCCESSが返って来ない、という状況の詳細を伺った方がいいのかな、とも思いました。

    > 2回目は、何故か複数回受信していました。

    > (「複数回受信」は、SCI_SUCCESSが返ってくるまで、受信前から複数回R_SCI_Receive()を呼び出しているのですが、その影響ですかね)

    [追記]

    後で調べてみようと思ったことですけれども、、、

    ・ DMA/DTCを使うモードで受信タイムアウトが発生したら、どうやって受信動作を解除するのか?R_SCI_Close()を呼び出せば、自動的に全ての後片付けをしてくれるのだろうか?

  • こんにちは。NoMaYさん

    > R_SCI_Receive()でSCI_SUCCESSが返って来ない、という状況の詳細を伺った方がいいのかな、

    2回目の受信時は、しばらくR_SCI_Receive() = 7(SCI_ERR_XCVR_BUSY)が返ってきて、その後SCI_SUCCESSが返ってきます。
    おっしゃる通り、(1回目の受信で、dmac_status.dtif_stat == trueを検出していますが、)受信未完了状態と読み取れる戻り値です。
    なお、1回目の受信時は、ループ初回のR_SCI_Receive()呼び出しでSCI_SUCCESSが返ってきます。

Reply
  • こんにちは。NoMaYさん

    > R_SCI_Receive()でSCI_SUCCESSが返って来ない、という状況の詳細を伺った方がいいのかな、

    2回目の受信時は、しばらくR_SCI_Receive() = 7(SCI_ERR_XCVR_BUSY)が返ってきて、その後SCI_SUCCESSが返ってきます。
    おっしゃる通り、(1回目の受信で、dmac_status.dtif_stat == trueを検出していますが、)受信未完了状態と読み取れる戻り値です。
    なお、1回目の受信時は、ループ初回のR_SCI_Receive()呼び出しでSCI_SUCCESSが返ってきます。

Children
  • B.Ishiiさん、こんにちは。NoMaYです。

    そういうことですか。2回目の受信の1回目のR_SCI_Receive()でSCI_ERR_XCVR_BUSYというエラーが返るのですね。そこでドキュメントを確認してみると、そもそも調歩同期式モードの場合には返らない筈のエラーのようですね。(予感的には、これ自体はドキュメントの誤記かな、という気がしますけれども。)

    そして、何度かリトライを続けていると、いずれR_SCI_Receive()でSCI_SUCCESSが返る、ということなのですね。

    r_sci_rx Ver. 4.40 ですよね。少しこちらでソースを追ってみようかと思います。

    その前に、ひとつ確認したいのですけれども、現象が出る今回は、送受信ともDMAですか?(現状のr_sci_rxでは送信と受信でやり方を同じにしておかないといけない、ということになっていましたので。)


    R_SCI_Receive()

    調歩同期式モードで、RXI 割り込みによってセットされたデータをキューから取得します。その他のモードでは、送信、または受信中でなければ、受信処理を行います。

    。。。途中省略。。。

    Return Values
    [SCI_SUCCESS] /* 要求バイト数のデータがp_dst に配置されました(調歩同期式)。受信初期化処理が完了しました(SSPI/クロック同期式)。
    [SCI_ERR_NULL_PTR] /* “hdl”がNULL です。
    [SCI_ERR_BAD_MODE] /* 指定されたモードはサポートされていません。
    [SCI_ERR_INSUFFICIENT_DATA] /* 受信キューに十分なデータがありません(調歩同期式)。
    [SCI_ERR_XCVR_BUSY] /* チャネルは現在使用中です(SSPI/クロック同期式)。

    。。。以後省略。。。


    [追記]

    すみません、私のメモです。

    ・ 送信であれば、DMA/DTC転送自体が終わっても、送信動作自体は、DMA/DTC転送が終わった直後から、あるいはその時点で送信中の送信動作が終わった後で、さらにそれ以上のFIFOがあればもっと後で、行われることもある。

    ・他方で、受信であれば、DMA/DTC転送自体が終わった=受信動作自体も終わっている、ということの筈である。

    ・ 受信オーバーランが変な風に認識されている可能性は???

    ・ DMA/DTCを使う機能は、割と最近追加された機能だと記憶しているのだが、ドキュメントが追い付いていない?

  • NoMaYさん、こんにちは。

    その前に、ひとつ確認したいのですけれども、現象が出る今回は、送受信ともDMAですか?(現状のr_sci_rxでは送信と受信でやり方を同じにしておかないといけない、ということになっていましたので。)

    はい、送信側もセットでDMA設定にしています。
    NoMaYさんが別スレッドで回答されてましたが、r_sci_rxの制限事項ですよね。

  • B.Ishiiさん、こんにちは。NoMaYです。

    r_sci_rx Ver. 4.40のソースを追ってみると、DMA転送終了割り込みでDMA転送チャンネルをクローズしています。ですので、B.Ishiiさんのやり方の、御自身がR_DMACA_Control()を呼び出すのは、そもそも危険かなと思います。なぜなら、DMA転送終了割り込みでDMA転送チャンネルがクローズされた後、クローズされたDMA転送チャンネルに対してR_DMACA_Control()を呼び出してしまう、そういう可能性があると思ったからです。

    static void sci_dmac_rx_handler(sci_hdl_t const hdl)
    {
        volatile sci_fifo_ctrl_t  *p_ctrl;

        sci_cb_args_t              args;


    #if (RX_DTC_DMACA_ENABLE & 0x02)
        if (SCI_DMACA_ENABLE == hdl->rom->dtc_dmaca_rx_enable)
        {
            if(4 == hdl->rom->dmaca_rx_channel || 5 == hdl->rom->dmaca_rx_channel || 6 == hdl->rom->dmaca_rx_channel || 7 == hdl->rom->dmaca_rx_channel)
            {
                dmaca_stat_t   stat_dmaca;
                R_DMACA_Control(hdl->rom->dmaca_rx_channel, DMACA_CMD_DTIF_STATUS_CLR, &stat_dmaca);
            }

            R_DMACA_Int_Disable(hdl->rom->dmaca_rx_channel);
            R_DMACA_Close(hdl->rom->dmaca_rx_channel);
        }
    #endif
        p_ctrl = &hdl->queue[hdl->qindex_int_rx];

     
    ですので、まず処理を以下のように変更して動作するかどうか見て頂けませんか?

    (1) コード生成機能でSCI受信をする場合の定型手段の通り(以下の手順も定型手段ですが)、受信終了フラグを自前でひとつ定義する
    (2) R_SCI_Receive()の呼び出し前に(1)のフラグをクリアする
    (3) R_SCI_Receive()のコールバック関数内で(1)のフラグをセットする
    (4) R_SCI_Receive()の呼び出し後は(1)のフラグをチェックして、受信終了となったかどうかを調べる、という判定をする

    ただ、既に以下の情報を頂いていますので、まだ何かありそうな予感はしていますけれども、ひとまず上のようにしてみて頂けませんか?

    > 他に、R_SCI_Open()で設定されるコールバック関数で、SCI_EVT_RX_DONEイベントを監視してみました。
    > 1回目は、期待通り(1回)受信できていました。
    > 2回目は、何故か複数回受信していました。

  • NoMaYさん、こんにちは。

    > ですので、まず処理を以下のように変更して動作するかどうか見て頂けませんか?

    変更して動作を見ました。
    2回目の受信で、転送完了確認ループは抜けるようになりましたが、受信バッファcpRecvDataは""(初期値のまま空)でした。
    そして、3回目の受信で、R_SCI_Receive()ループを抜けなくなりました。

    変更後のソースコードは、以下のような感じです。

    static volatile bool m_bRxDone;

    // R_SCI_Open()で設定した割り込み関数
    static void SciCallback(void *vpArgs)
    {
    sci_cb_args_t *stpArgs = (sci_cb_args_t *)vpArgs;

    switch (stpArgs->event)
    {
    case SCI_EVT_RX_DONE:
    m_bRxDone = true;
    break;
    default:
    break;
    }
    }

    // タスクから定期的に呼ばれる関数
    {
    char cpRecvData[sizeof("ABCDE")] = {0}; // "ABCDE"が来ることは分かっている

    m_bRxDone = false;

    // R_SCI_Receive()ループ
    while (1)
    {
    sci_err_t stSciErr = R_SCI_Receive(
    stSciHdl, (uint8_t *)cpRecvData, sizeof(cpRecvData));

    if (stSciErr == SCI_SUCCESS)
    {
    break;
    }

    printf("R_SCI_Receive() = %d\n", stSciErr);
    vTaskDelay(pdMS_TO_TICKS(100));
    }

    // 転送完了確認ループ
    while (m_bRxDone == false)
    {
    vTaskDelay(pdMS_TO_TICKS(100));
    }

    printf("Rx: \"%s\"\n", cpRecvData);
    }

    ログは、以下のような感じです。

    Rx: "ABCDE"
    R_SCI_Receive() = 7
    R_SCI_Receive() = 7
    ...(上のログを繰り返し)
    Rx: ""
    R_SCI_Receive() = 7
    R_SCI_Receive() = 7
    ...(上のログを繰り返し)
  • B.Ishiiさん、こんにちは。NoMaYです。

    まず、sizeof("ABCDE") の値として 5 を期待されていると思いますが 6 です。そのせいで、期待した動作になっていなかった、ということはないでしょか?

  • B.Ishiiさん、こんにちは。NoMaYです。

    送信側も文字列の最後に '\0' を送ってきているのですかね?

  • B.Ishiiさん、こんにちは。NoMaYです。

    再度ソースを追ってみました。DMA転送による受信が一旦終わった後 ~ 次のDMA転送による受信を起動する前、の期間に相手側からデータが来てしまった時、というのが気になってきました。本来は、内蔵周辺機能の該当SCIの受信動作を止めていないといけないような気がするのですけれども、動きっぱなしのままになっていて、想定外の受信割り込みが発生しそうな(発生してしまっていそうな)気がしてきたのです。

    それで、お聞きしたいのですが、送信側はどのようなタイミングでデータを送ってきているのでしょうか?例えば、

    A B C D E \0

    200msインターバル

    A B C D E \0

    200msインターバル

    A B C D E \0

     
    というようなタイミングでしょうか?それとも、

    A B C D E \0 A B C D E \0 A B C D E \0

     
    という連続送信のようなタイミングでしょうか?

  • NoMaYさん、こんにちは。

    送信側のタイミングについて回答します。
    前述のソースコードのR_SCI_Receive()ループ前に、実は送信処理がありまして、それを受け取った側は同じ内容をエコーバックしています。
    それが送信側のタイミングとなります。
    全体として、5秒周期で実行されます。

    sizeof("ABCDE") は、終端'\0'を含めて6であることを、エコーバック側でも確認しました。

    DMA有効時は、R_SCI_Receive()は送信処理よりも前に呼び出して、待ち受け状態にしておくべきことに気づきました・・・。
    そこで、ソースを以下のように変更しました。

    ※ソースコードのインデントを保って貼り付ける方法ないんでしょうか・・・?

    #include <stdint.h>
    #include <stdio.h>

    #include "Pin.h"
    #include "r_pinset.h"
    #include "r_dmaca_rx_if.h"
    #include "r_sci_rx_if.h"

    static volatile bool m_bRxDone;

    static void SciCallback(void *vpArgs)
    {
    sci_cb_args_t *stpArgs = (sci_cb_args_t *)vpArgs;

    switch (stpArgs->event)
    {
    case SCI_EVT_RX_CHAR:
    case SCI_EVT_RX_DONE:
    m_bRxDone = true;
    break;
    default:
    break;
    }
    }

    void main(void)
    {
    sci_cfg_t stSciCfg = {
    .async.baud_rate = 115200,
    .async.clk_src = SCI_CLK_INT,
    .async.data_size = SCI_DATA_8BIT,
    .async.parity_en = SCI_PARITY_OFF,
    .async.parity_type = SCI_EVEN_PARITY,
    .async.stop_bits = SCI_STOPBITS_1,
    .async.int_priority = SCI_CFG_ERI_TEI_PRIORITY,
    };
    sci_hdl_t stSciHdl;
    uint8_t u8pTxData[] = "ABCDE";
    uint8_t u8pRxData[sizeof(u8pTxData)] = {0};

    R_Pins_Create();
    R_SCI_PinSet_SCI10();

    #if SCI_CFG_CH10_RX_DTC_DMACA_ENABLE == 2 // DMA有効時
    R_DMACA_Init();
    #endif

    sci_err_t stSciErr =
    R_SCI_Open(SCI_CH10, SCI_MODE_ASYNC, &stSciCfg, SciCallback, &stSciHdl);
    printf("R_SCI_Open() = %d\n", stSciErr);

    while (1)
    {
    m_bRxDone = false;

    #if SCI_CFG_CH10_RX_DTC_DMACA_ENABLE == 2 // DMA有効時
    while (1)
    {
    stSciErr = R_SCI_Receive(stSciHdl, u8pRxData, sizeof(u8pRxData));

    if (stSciErr == SCI_SUCCESS)
    {
    break;
    }

    printf("R_SCI_Receive() = %d\n", stSciErr);
    vTaskDelay(pdMS_TO_TICKS(100));
    }

    stSciErr = R_SCI_Send(stSciHdl, u8pTxData, sizeof(u8pRxData));
    printf("R_SCI_Send() = %d\n", stSciErr);
    #else // DMA無効時
    stSciErr = R_SCI_Send(stSciHdl, u8pTxData, sizeof(u8pRxData));
    printf("R_SCI_Send() = %d\n", stSciErr);

    while (1)
    {
    stSciErr = R_SCI_Receive(stSciHdl, u8pRxData, sizeof(u8pRxData));

    if (stSciErr == SCI_SUCCESS)
    {
    break;
    }

    printf("R_SCI_Receive() = %d\n", stSciErr);
    vTaskDelay(pdMS_TO_TICKS(100));
    }
    #endif

    while (m_bRxDone == false)
    {
    vTaskDelay(pdMS_TO_TICKS(100));
    }

    printf("u8pRxData = \"%s\"\n", u8pRxData);
    vTaskDelay(pdMS_TO_TICKS(5000));
    }
    }

    エコーバック側は、以下のTeraTermマクロです。

    while 1
    waitn 5
    send inputstr
    send $00
    endwhile

    結果、まだうまくいきません。以下は実行ログです。

    R_SCI_Open() = 0
    R_SCI_Send() = 0
    u8pRxData = "ABCDE"
    R_SCI_Receive() = 7
    R_SCI_Receive() = 7
    R_SCI_Receive() = 7
    ...


    解析するため、デバッガで以下を見ました。
    ・次の処理ABCの順番
     A. R_SCI_Receive()から呼ばれるsci_receive_async_data()内のhdl->rx_idle = false;
     B. rxi_handler()内のhdl->rx_idle = false;
     C. sci_dmac_rx_handler()内のコールバック呼び出し行
      ※B, Cは、コールバック関数を呼び出し、m_i8RxDone = trueが実行される
    ・sci_hdl_t hdl->rx_idle変数の変化(R_SCI_Receive() = 7を返される原因)
    ・m_i8RxDone変数の変化

    結果は以下でした。カッコ内は、(hdl->rx_idle, m_i8RxDone)値の変化です。

    ~R_SCI_Open()直後~
    (true, false)

    ~1回目の受信~
    A (false, false)
    C (false, true)

    ~2回目の受信~
    どの処理も呼ばれない

    これは、ログと辻褄が合います。

    なお、試しにsci_dmac_rx_handler()内のR_DMACA_Close(hdl->rom->dmaca_rx_channel)呼び出しの次の行でtrueに設定したところ、外見上、正しく動作するようになりました。

    sci_dmac_rx_handler()で、sci_hdl_t hdl->rx_idle = trueにする処理が漏れているのではないでしょうか。
    むしろ、rxi_handler()内のDMA関連処理でhdl->rx_idle = trueにしていますが、これらDMA関連処理をsci_dmac_rx_handler()に引っ越すべきではないか。
    またNoMaYさんがおっしゃるように、内蔵周辺機能の該当SCIの受信動作を止めていないのであれば、そちらも処置が必要ではないか、と推測します。

  • B.Ishiiさん、こんにちは。NoMaYです。

    情報ありがとうございました。まだ振る舞いがおかしいですね。それで、この時点で同意しますのは、使い方の問題では無いですね、ということです。(ルネサスさんに、ちゃんと評価したのか、とクレームを入れたくなりますね。(私はルネサスさんの中の人では無いです。)) 現象再現プログラムをルネサスさんの会社の問い合わせ窓口に送って相談した方が良いのかも、とも思いますが、咄嗟には(直感的には)、確かに以下の点に同意です。

    > sci_dmac_rx_handler()で、sci_hdl_t hdl->rx_idle = trueにする処理が漏れているのではないでしょうか。

    FITのR_SCI_RXの中で以下のようなことをしているのですから、それをしていないのはおかしいですよね。

    src/smc_gen/r_sci_rx/src/r_sci_rx.c (r_sci_rx Ver. 4.40)

    static sci_err_t sci_receive_async_data(sci_hdl_t const hdl,
                                            uint8_t         *p_dst,
                                            uint16_t const  length)
    {
    。。。途中省略。。。

    #if (RX_DTC_DMACA_ENABLE & 0x02)
            if (SCI_DMACA_ENABLE == hdl->rom->dtc_dmaca_rx_enable)
            {
                if(true == hdl->rx_idle)
                {
                    hdl->rx_idle = false;
                    sci_fifo_ctrl_t *p_ctrl;
                    p_ctrl = &hdl->queue[hdl->qindex_app_rx];
                    p_ctrl->p_rx_buf = p_dst;
                    p_ctrl->rx_cnt = length;      /* length must be set
    after buf ptr */
                    p_ctrl->p_rx_fraction_buf = p_dst;
                    p_ctrl->rx_fraction = length;
    #if (SCI_CFG_FIFO_INCLUDED)
                    if (true == hdl->fifo_ctrl)
                    {
                        err = sci_rxfifo_dmaca_create(hdl, p_dst, length);
                    }
                    else
    #endif
                    {
                        err = sci_rx_dmaca_create(hdl, p_dst, length);
                    }
                }
                else
                {
                    return SCI_ERR_XCVR_BUSY;
                }
            }
            else
    #endif
            {
    。。。途中省略。。。
            }
        }
        return err;
    } /* End of function sci_receive_async_data() */

     
    [メモ]

    すみません、私のメモです。置かせて下さい。

    ・DTC転送には、転送終了後に、起動要因となった割り込みを起動する機能があるが、DMA転送にはその機能は無い
    ・だから、もしかすると、DTC転送では、その機能が使われていて、問題無く動作している、のかも知れない(ただ、そのことと上のコードは関係無い、けれども)

    [追記]

    > むしろ、rxi_handler()内のDMA関連処理でhdl->rx_idle = trueにしていますが、これらDMA関連処理をsci_dmac_rx_handler()に引っ越すべきではないか。

    ここのことですね。

    src/smc_gen/r_sci_rx/src/r_sci_rx.c (r_sci_rx Ver. 4.40)

    void rxi_handler(sci_hdl_t const hdl)
    {
    。。。途中省略。。。

    #if (RX_DTC_DMACA_ENABLE)
                if((SCI_DTC_ENABLE == hdl->rom->dtc_dmaca_rx_enable)
    || (SCI_DMACA_ENABLE == hdl->rom->dtc_dmaca_rx_enable))
                {
                    sci_fifo_ctrl_t        *p_rctrl;
                    p_rctrl = &hdl->queue[hdl->qindex_app_rx];
                    sci_cb_args_t   args;
    #if (SCI_CFG_SSPI_INCLUDED || SCI_CFG_SYNC_INCLUDED)
                    if ((SCI_MODE_SYNC == hdl->mode)
    || (SCI_MODE_SSPI == hdl->mode))
                    {
                        hdl->tx_idle = true;
                    }
    #endif
    #if (SCI_CFG_ASYNC_INCLUDED)
                    if (SCI_MODE_ASYNC == hdl->mode)
                    {
                        hdl->rx_idle = true;
                    }
    #endif

    。。。以後省略。。。
    }