FreeRTOSのconfigTICK_RATE_HZに1001以上を設定した場合の、USB Host Mass Storage Class(r_usb_hmsc)の動作について

こんにちは、B.Ishiiと申します。

以下の事象について、質問があります。

■現象
FreeRTOSで、FITモジュールのUSB Host Mass Storage Class(r_usb_hmsc)を利用していますが、configTICK_RATE_HZに10000を設定したところ、USBメモリを操作できなくなりました。

■解析
以下のportTICK_PERIOD_MSマクロが0になってしまい、ゼロ除算など、意図しない計算結果を引き起こしているようです。

■対策
以下のソース変更をしたところ、私が利用しているUSB機能部分については、動作するようになりました。


※左は変更後、右は変更前

■質問
※ここまで長々とすみません。

上記は、制限事項か不具合かのどちらなんでしょう?
制限事項の場合、将来的に対策される(制限から外れる)予定などありますでしょうか?

※コミュニティではなく、ルネサスの中の人に聞いたほうが良いですかね。
※portTICK_PERIOD_MSは、他のFITモジュールでも使われています。
 configTICK_RATE_HZが1001以上の場合は、上記同様、意図しない動作が起きそうです。

■環境
・RX72N
・FreeRTOS(with IoT Libraries)
 Ver. afr-v202012.00-rx-1.0.1
・USB basic(low-level) driver(r_usb_basic)
 Ver. 1.40
・USB Host Mass Storage Class(r_usb_hmsc)
 Ver. 1.31

Parents
  • B.Ishiiさん、こんにちは。NoMaYと申します。

    コミュニティゆえ、ざっくばらんな会話も出来る、ということはあると思います。他方で、会社の看板を背負っていないので、リプライの正確さや適切さに欠ける場合もある、ということも。それでportmacro.hの以下の部分ですけれども、Amazon FreeRTOSが発表されてルネサスさんが FreeRTOSに深く関わるようになる以前からの記述ですね。

    #define portTICK_PERIOD_MS              ( ( TickType_t ) 1000 / configTICK_RATE_HZ )

     
    以前にFreeRTOSの本家のコミュニティで見掛けた話として、FreeRTOSでのTickRateは、そもそもそんなに速くするものでは無いですよ、というのがあります。TickRateは、10msとか、あるいは、もっと遅くても良い筈である、というものです。

    もともと、RTOSでは、割り込みでイベントを発生させれば関連するタスクがすぐに起動するようになっているものであり、FreeRTOSでのTickRateは、同一優先度で実行可能状態にある複数のタスクをタイムスライスで実行させていく場合の、タスクの切り替え時間、みたいな発想のようでした。そういった複数のタスクを超高速で切り替える必然性は殆ど無いよね、という話だったと記憶しています。

    その感覚は、カーネルソースのprojdef.hの以下のマクロ定義にも現れています。あくまで、ミリ秒を単位とする時間をTick数に変換するものであって、マイクロ秒をTick数に変換するものでは無い、という点です。(そして、そのような変換をする pdMicroSec_TO_TICKS() のようなものは無いです。)

    /* Converts a time in milliseconds to a time in ticks.  This macro can be
    overridden by a macro of the same name defined in FreeRTOSConfig.h in case the
    definition here is not suitable for your application. */
    #ifndef pdMS_TO_TICKS
        #define pdMS_TO_TICKS( xTimeInMs ) ( ( TickType_t ) ( ( ( TickType_t ) ( xTimeInMs ) * ( TickType_t ) configTICK_RATE_HZ ) / ( TickType_t ) 1000 ) )
    #endif

     
    なお、そこで見掛けた話として、ある人は以前に利用していたChibi OSというRTOSでは、ミリ秒以下のTickが使用されていて、それからFreeRTOSへ移行するのに、その点で困っているのだけれども、というのもあったと記憶しています。なので、そういう超高速なTickを使用するRTOSもあるようです。

    2~3年前の記憶ですので、今は本家の考えも変わっているかも知れませんが、その当時の感覚ですと、本家に相談したとしても(あるいは本家ゆえ)、FreeRTOSの設計思想ですので、といった感じで対応してくれないと思います。


    ただ、B.Ishiiさんがミリ秒以下のTickを使用したいと思われた理由もあると思いますので、そこを知りたいです。

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

    NoMayさん、回答ありがとうございます。
    設計思想ですか。
    私も他に、タスク切り替えオーバーヘッド、機能需要、設計複雑度などを天秤にかけて、最小1msに着地させたのかなと推測しました。

    > ただ、B.Ishiiさんがミリ秒以下のTickを使用したいと思われた理由もあると思いますので、そこを知りたいです。

    同じシステムには、UART機能があり、自前タスクは15個ほどあります。
    そして、UART送受信の時間仕様が、(受信タイムアウトが2 msなど、)結構シビアなのです。
    それをデフォルトのconfigTICK_RATE_HZ=1000(1 tick/ms)で動かすと、オシロではタイムアウトしていませんが、プログラムはちょくちょくタイムアウトを検出してしまいです。

    そもそも、タスク数多すぎとか、FreeRTOSでその時間仕様は止めておいたほうが良い、という突込みがありそうですが・・・。

    ちなみに、configTICK_RATE_HZ=10000にすると、当たり前かもしれませんが、タイムアウト検出は改善しました。
    CPU使用率もほぼ変わらずです。(高性能なRX72Nだからか?)


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

    設計思想という考え方には、適切にFreeRTOSを使っていることを前提とした上で、ということも含まれるだろうと私は考えていますが、頂いたリプライには、とっさに何か変な気がする、と感じました。

    幾つか確認したいのです。

    (1) 受信タイムアウトが2ms

    これは、連続して相手から送られてくる筈のデータが、何故か2ms以上途切れた異常事態、のことですよね?

    (2) オシロではタイムアウトしていませんが、プログラムはちょくちょくタイムアウトを検出してしまい

    これは、 オシロでは相手側から連続して(つまり2ms以上途切れることなどなく)送られて来ていた、ということですよね?
    でも、プログラムは2ms以上途切れた異常事態が発生した、と認識することがちょくちょくある、ということですよね?

    (3) そちらでのタイムアウトの検出手法はどういうものでしょうか?こんな感じでしょうか?

    (3-1) データを受信した時に、時刻(内蔵周辺機能のタイマのどれかで刻んでいるティック値など)を変数に格納する
    (3-2) 次のデータを受信した時に、上記時刻と現在時刻を比較して、2ms以上空いていたらタイムアウトとする
    (3-3) 次のデータが永遠に来ないケースもあるので、何らかの手法で、2ms後に何らかのタスク(それ専用のタスクで無くても良い)を起動して、そのタスクが起動した時、もしも次のデータが来ていなければタイムアウトとする

    FreeRTOSを適切に使っているなら、そして2msといったレベルで時間判定をするのであれば、以下のようなことが行われていると思うのです。

    (A-1) データを受信したら、しかるべく高い優先度を設定されたタスクが起動される、といったこと

    ですが、失礼かも知れませんが、以下のような使い方をされていないだろうかと気になりました。

    (A-2) ひょっとして、同一優先度の15個の自前タスクをラウンドロビンで回している、というだけの作りになっていないでしょうか?

    ちなみに、とっさに何か変な気がする、と感じたのは、以下の点です。

    (B-1) 上の(A-1)のような作りになっていて、240MHzのRX72Nで、2msといったレベルでタスクの起動が遅延する筈が無いと思うのだけれども、、、

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

    (1)~(3-2)は、その通りです。
    (3-3)は、UART送受信と同じタスクで、TaskDelay(1 /* tick */)しては、起きたらデータが届いてないか周期監視しています。
    そして、もう一つ、受信タイムアウトよりも長い別のタイムアウト値をもっています。

    受信タイムアウト2msが必要なUARTチャンネルが二つあり、現在はそれぞれのチャンネルにタスクを割り当てています。
    更にそれらのUARTチャンネルに送受信操作をしたい、別のタスクがもう一つあります。
    そのため合計3タスク優先度を上げる必要があるのですが、それに対して2msでは、自分の番が回ってこないことがあるのかなと考えています。

    タスク数を削減するなど、他にもやり方はあると思いますが、一番良い対策方法を模索しています。
    また、受信と送信の間を100us以上あけるという要求仕様もあり、configTICK_RATE_HZを変えるほうが最短で間をあけられる、より良い方法かと考えています。

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

    (1)~(3-2)は、その通りです。
    (3-3)は、UART送受信と同じタスクで、TaskDelay(1 /* tick */)しては、起きたらデータが届いてないか周期監視しています。
    そして、もう一つ、受信タイムアウトよりも長い別のタイムアウト値をもっています。

    受信タイムアウト2msが必要なUARTチャンネルが二つあり、現在はそれぞれのチャンネルにタスクを割り当てています。
    更にそれらのUARTチャンネルに送受信操作をしたい、別のタスクがもう一つあります。
    そのため合計3タスク優先度を上げる必要があるのですが、それに対して2msでは、自分の番が回ってこないことがあるのかなと考えています。

    タスク数を削減するなど、他にもやり方はあると思いますが、一番良い対策方法を模索しています。
    また、受信と送信の間を100us以上あけるという要求仕様もあり、configTICK_RATE_HZを変えるほうが最短で間をあけられる、より良い方法かと考えています。

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

    こんな感じなのでしょうか?(概念コードです。) 同一優先度の3個のタスクをvTaskDelay(1/* tick */)でラウンドロビンで回しているのでしょうか?すみません、正確にFreeRTOSの動作を覚えているのではないので、不確実ではありますけれども、これだと空いた時間はIdleタスクへ行ってしまうだけではないでしょうか?そして、現状ではvTaskDelay(1/* tick */)は1msのディレイのことですよね?それで、Idleタスクへ行くのではなく、同一優先度の次のタスクへ行く、というようなFreeRTOS APIとしてtaskYIELD()があるのですけれども、以下のような感じなのであれば、taskUART_chAとtaskUART_chBはtaskYIELD()を使う、taskUART_mainはvTaskDelay()とtaskYIELD()を両方使う、というのが良いのではないでしょうか?(すみません、自分のFreeRTOSの知識は大半が耳学問ですので、API動作を誤解しているかも知れませんけれども。) こんな感じなのであれば、ですけれども。

    taskYIELD
    www.freertos.org/a00020.html#taskYIELD

    taskYIELD() is used to request a context switch to another task. However, if there are no other tasks at a higher or equal priority to the task that calls taskYIELD() then the RTOS scheduler will simply select the task that called taskYIELD() to run again.

    If configUSE_PREEMPTION is set to 1 then the RTOS scheduler will always be running the highest priority task that is able to run, so calling taskYIELD() will never result in a switch to a higher priority task.


    taskUART_chA()
    {
        for(;;)
        {
            FITのSCIモジュールのAPIでchAの受信データがあるかチェック
            if(あった)
            {
                現在時刻を変数へ格納
                FITのSCIモジュールのAPIでchAの受信データを読み出し
                あれこれの処理
            }
            vTaskDelay(1/* tick */);
        }
    }

    taskUART_chB()
    {
        for(;;)
        {
            FITのSCIモジュールのAPIでchBの受信データがあるかチェック
            if(あった)
            {
                現在時刻を変数へ格納
                FITのSCIモジュールのAPIでchBの受信データを読み出し
                あれこれの処理
            }
            vTaskDelay(1/* tick */);
        }
    }

    taskUART_main()
    {
        for(;;)
        {
            あれこれの処理
            vTaskDelay(1/* tick */);
        }
    }

     

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

    下のコメントに書かれているpdMS_TO_TICKS()マクロは、vTaskDelay(pdMS_TO_TICKS(1))のように使っております。
    誤解を招く書き方で申し訳ございませんでした。

    > こんな感じなのでしょうか?(概念コードです。) 

    下記以外は、大まかに概念コードの通りです。
    ・pdMS_TO_TICKS()マクロを使っていること
    ・taskUART_main()は一律1ms待ちではないこと


    > taskYIELD

    こういうやり方があるのですね。
    別の場面でこの関数の存在は知っていたのですが、今回利用できるという発想がありませんでした。

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

    NoMaY said:
    > というか、昨今のFreeRTOSであれば、そもそもpdMS_TO_TICKS()マクロを使うのが作法であるような気がします。

    すみません、勘違いさせてしまったと思いますが、これはルネサスさんのソース記述に対してです。ここが作法の記述だったら、あとは、FreeRTOSカーネルのpdMS_TO_TICKS()マクロの上書きの小技で済むかな、ということです。