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

  • 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

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

  • 他の割り込みが多くなるとタイミング違反のためにエラーが発生するのでアイデアを実現しました。RXI駆動でDTCをチェイン動作させて1つ目の転送でPODRを操作してDEを途中でネゲート、2つ目の転送でRDRを受信バッファにコピーし、指定の回数実行で割り込みハンドラに飛んで受信後の処理をすることで規定されたタイミングに収まるようになりました、。

  • Yamamotoさん、こんにちは。NoMaYです。

    DTC便利ですよね。こういう機能はSTM32/LPC/Kinetis/SAM/MSP432等のCortex-Mマイコンにもあるのでしょうか?(それとも、多重割り込みとDMAがあれば、こんな機能はそもそも要らない?)

  • 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

    。。。以後省略。。。
    }

     

  • 今回の要件では送信完了後、DEを下げるタイミングの都合DTCが必須だと思って取り組みました(データシートには0.5usと、割り込み駆動ではわずかに遅れる時が忙しさによって発生します)。ただ、一緒に動作するステッピングドライバ IC向けのパルス出力がなぜかDTCを使っている場合、パルス出力に使用するGTPW0タイマ(GTPW0のコンペアマッチ割り込みにて1msタスクでパルス列バッファを作成して、割り込みのたびに新しいパルス周波数をセットする)が2パルス目を以降パルスを出さなくなるという症状で半日格闘した末、DTC使用を諦めるという結果に・・・内部で1msタスク(EtherCATの処理)と0.1msタスク(BLDC用の速度サーボ処理)が走っているがこちらは影響がないみたい。もう、納品まで時間がないので1ms周期からSCIを使う通信を割り込み駆動でキックして、0.1msタスクで成功するまでリトライをすることで次の1msタスクで新しいデータを取得するという実装としました。DTCを使った場合、RS485+BLDCは大丈夫でしたが、RS485+BLDC+STEPPERやRS485+STEPPERだとSTEPPERがちゃんと動かない。なおBLDCは120度通電でホール ICの信号でICUの両エッジ割り込みが発生して三相の出力変更、ホール ICのパターンをベースにした速度計算、Duty変更をしています。

    おそらくDTCの使い方、間違いがあったんだろうと思いますが、いかんせん時間がない。あとDTC動作に0x800という結構馬鹿にならないヒープ領域を使う必要がるし・・・もう3日欲しい。

    他のマイコンでDTCみたいなものはわかりません。DMACみたいな1つの転送後、割り込みみたいなものが殆どだと思います。チェイン動作ってのは特殊だなと思います。私の今での用途だとタッチパネルの画像データを一括で描画RAMから送り出すのにDMAしたくらいで今回みたいにGPIOの出力変更+受信データの引き抜きみたいなことは初めてでした。