連載
» 2006年08月08日 00時00分 公開

T-Engineプログラミング入門(3):必修技術:タスク制御とチャタリング対策 (3/5)

[中村 大真 パーソナルメディア株式会社,@IT MONOist]

スロットマシンプログラムの詳細

 ではリスト1を詳しく見ていきましょう。

タスクの生成/起動、スリープ/起床

 main関数では、前回と同じようにtk_def_intで割り込みハンドラを定義しているほか、「左ドラムの処理タスク」と「右ドラムの処理タスク」を別々に生成、起動しています。タスクの生成がtk_cre_tsk(意味:T-Kernel Create Task)、起動がtk_sta_tsk(意味:Start Task)です。この例では、タスクの優先度を130、スタックサイズを4096bytesにしています。


  T_CTSK ct_left = { NULL, TA_HLNG | TA_RNG0, task_left, 130, 4096 };
(中略)
    /* 左ドラムの処理タスクを生成, 起動 */
    tid_left = tk_cre_tsk( &ct_left );
    tk_sta_tsk( tid_left, 0 ); 

 こうして起動されたタスクは「実行可能状態」になり、優先度の高いものから実行されていきます。

 タスクは自分でtk_slp_tsk(意味:Sleep Task)を発行することで、引数で指定した時間だけ待ち状態に入ることができます。例えば、tk_slp_tsk( 30 )で30ミリ秒だけ待ち状態に入ります。タスクが待ち状態に入ると、次に優先度の高い別の実行可能なタスクが実行されます。

 スリープによる待ち状態は、次の場合に解除されます。

引数で指定された時間が経過する前に、ほかのハンドラやタスクからtk_wup_tsk(意味:Wake-up Task)で起床させた場合
この場合、tk_slp_tskはE_OK(正常)という値を返します。

引数で指定された時間が経過しても何も起きなかった場合(タイムアウト)
この場合、tk_slp_tskはE_TMOUT(タイムアウト・エラー)という値を返します。

 いずれにせよ、タスクの待ちが解除されると実行可能状態に戻ります。

 なお、tk_slp_tskの引数として「無限大の時間(TMO_FEVR)」を指定することもできます。この場合は、ほかのハンドラやタスクから起床されるまで永久に待ち続けます(図5)。

タスクのスリープと起床 図5 タスクのスリープと起床

ドラムの回転と停止の処理

 リスト1のドラムの処理タスクは、tk_slp_tskで通常は30を指定して、30ミリ秒だけ待ち状態に入るようにしています。30ミリ秒経過してタイムアウトしたら、ドラムのパターンを1つ進めて、次のパターンを7セグメントLEDに表示します。これを繰り返すことで、ドラムの回転を表します。

/* 左ドラムの処理タスク */
void task_left( INT stacd, VP exinf )
{
  ER er; int count_left;
  time_left = 30; count_left = 0; 
  for (;;) {
    /* スリープ */
    er = tk_slp_tsk( time_left );
    /* タイムアウトの場合 */
    if (er == E_TMOUT) {
      /* 左ドラムを次へ進める */
      count_left++; if (count_left >= 6) count_left = 0;
      /* 左7セグメントLEDに表示する */
      out_h( 0x16100002, p[count_left] );
    /* 割り込みハンドラから起床された場合 */
    } else if (er == E_OK) {
      /* 左ドラムを止める */
      if (time_left == 30) time_left = TMO_FEVR;
      /* 左ドラムの回転を再スタートさせる */
      else if (time_left == TMO_FEVR) time_left = 30;
    }
  }
} 

 ここでストップボタンを押して割り込みを発生させると、割り込みハンドラの中で、対応するドラムの処理タスクをtk_wup_tskで起床させます。起こされたタスクは、待ち時間を30ミリ秒から無限大の時間(TMO_FEVR)に変えて、永久にスリープするようにします。これで、ドラムの回転が完全に停止するわけです。

/* 左ボタンに対する割り込みハンドラ */
void int_left( UINT dintno )
{
  /* 割り込み要求クリア */
  ClearInt( dintno );
  /* 左ドラムの処理タスクを起床 */
  if (time_left == 30) tk_wup_tsk( tid_left );
} 

 トグルスイッチによって割り込みが発生すると、割り込みハンドラの中で、左右両方のドラムの処理タスクをtk_wup_tskで起床させます。起こされたタスクは、待ち時間をTMO_FEVRから30ミリ秒に戻して、ドラムの回転を再スタートさせます。

/* トグルスイッチ(SW7)に対する割り込みハンドラ */
void int_toggle( UINT dintno )
{
  /* 割り込み要求クリア */
  ClearInt( dintno );
  /* 左ドラムの処理タスクを起床 */
  if (time_left == TMO_FEVR) tk_wup_tsk( tid_left );
  /* 右ドラムの処理タスクを起床 */
  if (time_right == TMO_FEVR) tk_wup_tsk( tid_right );
} 

※コラム:変数のvolatile宣言
リスト1では、変数time_leftは「左ドラムの処理タスク」と「左ボタンに対する割り込みハンドラ」の2カ所からアクセスされます。このように、複数のタスクやハンドラからアクセスされる可能性のある変数は必ず、
volatile int time_left;

のようにvolatile宣言しましょう。これをうっかり忘れて、
int time_left; 

のようにvolatileなしで宣言してしまうと、コンパイラが行う最適化のために、タスク/ハンドラで行った変数への代入が、別のタスク/ハンドラから見た場合の変数の値に反映されない可能性があり、バグの温床になります。
これは普通のC言語の入門書などにはあまり書かれていないかもしれませんが、特に組み込みの世界では必須です。しっかり覚えておきましょう。


Copyright © ITmedia, Inc. All Rights Reserved.