6
5

More than 3 years have passed since last update.

RTOSにおけるタスク設計(もちろんオレ流)

Last updated at Posted at 2020-02-14

タスク設計の基準は二つある!

タスク設計の基準には実物の物に対してタスクを構成する場合と機能に対してタスクを構成する場合です。

  • モーター
  • アクチュエーター
  • キーボード基板
  • 不揮発メモリ 等

機能

  • 媒体の搬送機能
  • 媒体の識別機能
  • 印字機能
  • 表示機能
  • 入力装置 等

機構制御には機能を基準としたタスク設計をしよう

機構制御には基本的に機能を主としたタスクを設計しましょう。
例えば、媒体の搬送機能をタスクとした場合、制御するアクチュエータはモーター2個、ソレノイド2個
と複数の物を制御します。 制御すると言っても実際の制御は 行いません
前々回のエントリーに書いた、シーケンスの最初の部分はコーディングしたりしますが、それもせずに割り込みに対するシーケンスだけをセットする場合もあります。

タスク構成図

例えば、ドキュメントスキャナーのようなユニットだったりすると、私だと下図のようなタスク構成にします。
このドキュメントスキャナーはRS-422のようなレガシーな通信

RTOS設計におけるメモ.jpg

上記の様に割と簡単なタスク構成にします。

  • MAINタスク

    • 全体制御を行います。具体的には各タスクからの通知をキーにして関連するタスクにメッセージを送信します。
  • FEEDタスク

    • 機能・紙をフィード(搬送する)してスキャンするタスクです。機構の制御、イメージセンサーの処理は更に下層の割り込みで行う事とします。
  • LINEタスク

    • 機能・外部通信タスクです。通信プロトコルはこのタスクの更に下層の割り込み内で行います。
  • KEYタスク

    • スキャンキー入力監視とLED表示を行うタスクです。 キー入力の監視とLED表示直接行うルーチンは割り込み処理内で行います。
  • SENSタスク

    • 各ポジションセンサの状態を監視します。ただ、このセンサの状態はMAINタスクやLINEタスクでの状態を参照する為にのみ使用します。
  • FLASHタスク

    • フラッシュメモリの読み書き、消去などを行う為のタスクです。

以上が私が作成したならばというタスク構成です。

タスク間のやり取りにはメッセージバッファ、もしくはメールボックスを使用します。
私は基本的に固定のメッセージ領域に情報をセットするので、メッセージバッファを使用します。メールボックスはメモリプールでメールの領域を確保する必要があるのと、当然ながらメモリプールの解放が必要な為、メモリーリークする怒れがあるのと、どうせ固定長メモリを確保するなら確保するコードを書くのが手間だからです(^^;

  • FEEDタスク ここでFEEDタスクの簡単なサンプルコードを書いておきます。わざとitronのapiを直で呼び出していますが、普段は関数化しています。
void feed_task()
{
    unsigned long msg[8];     /* メッセージ */
    ERR err;                  /* itron APIのエラー */
    int result;               /* 処理の結果 */

    while(1)
    {
         err = rcv_mbf(MBFID_FEED, &msg[0]);
         if(err == E_OK)
         {
              if(msg[1] == MAFE_INIT)
              {
                    result = feed_init_proc();     /* 機構部初期化処理 */
           if (result == FR_SUCCESS)      /* 初期化処理が成功であればループから抜ける */
                    {
                         break;
           }
              }
         }
    }

     while(1)
     {
          err = rcv_mbf(MBFID_FEED, &msg[0]);
          if (err == E_OK)
          {
               switch(msg[1])
               {
               case MAFE_INIT:    /* 機構の初期化処理 */
                    result = feed_init_proc();
                    break;
               case MAFE_SCAN:    /* 紙送りしつつスキャンする処理 */
                    result = feed_scan_proc();
                    break;
               case MAFE_FEED:    /* 単純な紙送り */
                    result = feed_paper_feed_proc();
                    break;
               default:
                    break;
               }

               if (result != FR_SUCCESS)
               {
                   send_feed_err_msg(result);   /* エラー応答 */
               }
          }
     }
}

まぁ、ここまでは大抵は同じになるかと思います。
では。その処理の中の関数を書いてみましょう。


/**************************************************************************/
/**
 * @breaf 機構初期化処理 */
 */
/**************************************************************************/
int feed_init_proc()
{
     ERR err;
     UINT psw;
     FLGPTN ptn;

     psw = vdis_psw();    /* 割り込み禁止 */
     PORTA.BIT.B0 = 1;    /* モーター1を正転 */
     PORTA.BIT.B1 = 1;    /* モーター2を逆転 */
     PORTC.BIT.B0 = 0;    /* モーターへの電源イネーブル */
     feed_seq = 10;       /* 機構シーケンスを10へ */
     vset_psw(psw);       /* 割り込み許可 */

     err = wai_flg(FLGID_FEED, (INT_FEED_SUCCESS | INT_FEED_ERR), TWF_ORW, &ptn);
     if (err == E_OK)
    {
         if (ptn ==  INT_FEED_SUCCESS)
         {
             feed_init_msg(FR_SUCCESS);
         }
         else
         {
              feed_init_msg(feed_err_code);      /* 割り込みでセットされるエラーコードでMAINタスクに応答する */
         }
    }

     retuen result:
}

初期化処理では割り込みを禁止してモータ1、モータ2を駆動させて、シーケンスを10にセットしています。
初期化動作自体は周期的な割り込みで制御を任せてタスクでは成功、またはエラーのフラグがセットされるのを待ちます。

それでは悪い例を挙げてみましょう。

/**************************************************************************/
/**
 * @breaf 機構初期化処理 */
 */
/**************************************************************************/
int feed_init_proc()
{
     ERR err;
     UINT psw;
     FLGPTN ptn;

     PORTC.BIT.B0 = 0;    /* モーターへの電源イネーブル */
     PORTA.BIT.B0 = 1;    /* モーター1を正転 */
     PORTA.BIT.B1 = 0;    /* モーター1を正転 */
     PORTA.BIT.B2 = 0;    /* モーター2を逆転 */
     PORTA.BIT.B3 = 1;    /* モーター2を逆転 */
     feed_seq = 10;       /* 機構シーケンスを10へ */

     do
    {
        dly_tsk(100):     /* 100msディレイ */
    } (sens0_state == 0)  /* センサ0が紙ありになるまで待つ */

    PORTA.BIt.B2 = 0;    /* モーター2ブレーキ */
    PORTA.BIt.B3 = 0;
    dly_tsk(100);        /* 100ms ディレイ */.
    PORTA.BIt.B2 = 1;    /* モーター2正転 */
    PORTA.BIt.B3 = 0;

  ・
  ・
  ・
  ・
    return result:
  }

こういう風にタスク内で機構制御する人を多く見かけます。
これの何処がダメなのか上げていきます。

  1. 電源イネーブルを最初にしてるのでモーターのポートの状況によってはモーターがどちらに動作するか分からない。
  2. ポートの操作をビット構造体でセットしているが割り込みの禁止をしていないので、もし割り込み内でPORTAの操作をしているとタイミングによっては割り込み内で書き換えられてしまう場合があります。これはアセンブラレベルで考えないというけないのですが、ビット構造体の操作の場合、一度PORTAの内容(0x0000)をレジスタロードしてビットデータを書き換えてからPORTAにストアするように展開される場合があるので。もしもタスクが実行中のレジスタにロードした状態で割り込みが入ると割り込みでも同じ様にPORTAの内容(0x0000)をロードしてビット16を1にする処理をして割り込みが終了し(POARTの内容は0x8000)、再びタスクが実行するとレジスタの内容(0x0000)のビット0を1にセットしてからPORTAにストアするのでPORTAの内容が0x00001となります。本来はPORTAの内容は0x80001になって欲しいのにタイミングによってはこの様な状況になり不具合となります。
  3. センサ0の状態を100ms内で監視していますが、センサ0は10ms単位のサンプリングで3回ON状態でON(1)となるとしても上記のコードでは100ms単位のズレが生じます。 また、タスク内のAPIでの時間は相当なバラつきがあるのでもし、時間的に厳しい処理だとバラつきにより不具合を発生させます。

文字で書くと分かりにくいですね・・・・・。
これは次回以降のエントリーの題材としたいと思います。

とりあえず、オレ流の機構制御のタスク設計については以上とします。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5