ラズピコ ゼロから作るOS 動作確認編 第4部2章

  • 2023.08.11
  • OS
ラズピコ ゼロから作るOS 動作確認編 第4部2章

皆さん こんにちは。

今回は7つ目のソースコードに入れ替えて動作確認してみます。

第4部2章のタイトルは「複数のタスク間で複雑な同期もできる…イベント・フラグ」です。

この章ではイベント・フラグが追加されました。
RTOSでは良く使われる機能ですから覚えておきましょう。

この記事は開発環境を構築することを前提にしています。
開発環境を構築したい方は ゼロから作るOS 環境構築編 をご覧になってください。

この記事はインターフェース誌2023年7月号の「ゼロから作るOS」を参考にしています。
まずは書籍の本章を読んで予習しておくことをお薦めします。

回路を追加する

スイッチと抵抗(10kΩ)を図のように配線します。(書籍の90ページ参照)
スイッチは押しボタン、タクトスイッチ等を使います。

()内の数字は端子の番号です。以下の図を参考になさってください。
私は赤枠で囲んだ4か所の端子を使いました。

ソースコードを入れ替える

これから前回製作したソースを削除するので、必要ならバックアップをとっておいてください。

Project Explorerでpico_tryknlのツリー下のフォルダを全て選択し、右クリックして Delete を選択して削除します。

次にソースファイルをIDEにコピーします。

C:\DevTools\src\IF2307TK\part_4\sect_2 下にある以下のフォルダを全て選択して、IDEの Project Explorer の pico_tryknl にドラッグ&ドロップします。

(書籍 第4部2章のソースファイルになります)

次のウィンドウが出るので、そのままOKを押してコピーします。

コピーしたフォルダを展開し、フォルダ下にもファイルがコピーされていれば成功です。

PC側でTera Termの設定を行う

この章のコードでは途中経過をUARTで送信するようになっているので、PC側で受信した文字列を表示する準備をしておきます。

PCアプリのTera Termを起動し新しい接続画面でシリアルを選択し、ポートに適切なCOMポートを選択します。

Tera Termをお持ちでない方はサイトからダウンロードしてインストールしてください。

設定 – シリアルポート でスピートを 115200 に設定します。(それ以外は以下の設定)

設定 – 端末 で改行コード 受信・送信を LF に設定します。(改行して表示を見やすくします)

プログラムを動かしてみる

Run – Debug History から pico_tryknl Debug を選択しResumeボタンが緑色になったらボタン(またはF8キー)を押してプログラムを実行します。

起動後、Tera Termには “Start Try Kernel” が表示されます。
その後、スイッチを交互に押すたびに “BTN-0 ON” , “BTN-1 ON”が交互に表示されれば成功です。

プログラム概要

この章のプログラムの概略説明です。

つくられるタスクは今回4つあり、まずmain()で最初のタスク(初期タスク)が生成されます。
その後、tk_sta_task()で初期タスクが実行されます。
このタスクの優先順位は 1 に設定されていて、つくられるタスクの中では一番高くなっています。

初期タスク initsk() では “Start Try Kernel”の文字列をUARTで送信します。
その後、usermain()関数を実行して tk_ext_tsk() で休止します。

usermain()内では、task_btn()とtask_led1()及びtask_led2()が生成されて実行されます。
実行されると書きましたが実際にはキューに入れるだけです。
task_btn()とtask_led1()、task_led2()の優先順位は 10 で 1 よりも低いため、初期タスクが休止するまでこれらのタスクは実行されません。
(数値が小さいほど優先順位は高い)

その後、tk_ext_tsk()により初期タスクが休止すると、task_btn()が動き出します。
tk_ext_tsk()で初期タスクを休止した後スケジューラーが動くためです。

task_btn()では tk_dly_tsk(100)により100msecの時間待ちがあり、実行権を他のタスクに譲ります。
そしてtask_led1()が動き出しますが、tk_wai_flg()により待ち状態に入ります。
そしてtask_led2()が動き出しますが、tk_wai_flg()により待ち状態に入ります。

スイッチ2が押されたことを検出すると “BTN-0 ON” の文字列を UART で送信し、0.5秒間隔でしばらくLチカして消灯します。
スイッチ1が押されたことを検出すると “BTN-1 ON” の文字列を UART で送信し、0.1秒間隔でしばらくLチカして消灯します。

それでは追加された機能のコードを見ていきます。

以下の変更は前回説明したものと同じです。
提供されているソースコード(systimer.c)と異なっていたら変更しておいてください。

ソースコードの変更

書籍の83ページ リスト5の変更内容です。

tk_slp_tsk()による時間待ちの場合、タイムアウト時にはwaierrに E_TMOUT を設定します。

/* システムタイマ割込みハンドラ */
void systimer_handler(void)
{
  TCB     *tcb;

  for( tcb = wait_queue; tcb != NULL; tcb = tcb->next) {
    if(tcb->waitim == TMO_FEVR) {
      continue;
    } else if(tcb->waitim > TIMER_PERIOD) {
      tcb->waitim -= TIMER_PERIOD;  // 待ち時間から経過時間を減じる。
    } else {                          // 待ち時間が経過したタスクを実行できる状態に戻す
      tqueue_remove_entry( &wait_queue, tcb);             // タスクをウェイトキューから外す
      if (tcb->waifct == TWFCT_DLY) {
        *tcb->waierr = E_OK;
      } else {
        *tcb->waierr = E_TMOUT;
      }
      tcb->state      = TS_READY;
      tcb->waifct     = TWFCT_NON;
      tqueue_add_entry( &ready_queue[tcb->itskpri], tcb); // タスクをレディキューにつなぐ
    }
  }
  scheduler();        // スケジューラを実行する
}

tk_slp_tsk()による待ちでタイムアウトした場合には waierr に E_TMOUT を設定する処理が追加になっています。

イベント・フラグの生成API

tk_cre_flg()

/* イベントフラグの生成API */
ID tk_cre_flg( const T_CFLG *pk_cflg )
{
  ID      flgid;
  UINT    intsts;

  DI(intsts);     // 割込み禁止
  for(flgid = 0; flgcb_tbl[flgid].state != KS_NONEXIST; flgid++);

  if(flgid < CNF_MAX_FLGID) {
    flgcb_tbl[flgid].state = KS_EXIST;
    flgcb_tbl[flgid].flgptn = pk_cflg->iflgptn;
    flgid++;
  } else {
    flgid = E_LIMIT;
  }
  EI(intsts);      // 割込み許可
  return flgid;
}

処理概要:

割り込みを禁止し、イベントフラグ管理ブロックの flgcb_tbl の空きを探して、フラグパターンを設定します。
その後割り込みを許可して、ID を返します。

イベント・フラグのセットAPI

tk_set_flg()

/* イベントフラグのセットAPI */
ER tk_set_flg( ID flgid, UINT setptn )
{
  FLGCB   *flgcb;
  TCB     *tcb;
  ER      err = E_OK;
  UINT    intsts;

  if(flgid <= 0 || flgid > CNF_MAX_FLGID) return E_ID;
  DI(intsts);     // 割込み禁止
  flgcb = &flgcb_tbl[--flgid];
  if(flgcb->state == KS_EXIST) {
    flgcb->flgptn |= setptn;            // フラグのセット

    for( tcb = wait_queue; tcb != NULL; tcb = tcb->next) {
      if((tcb->waifct == TWFCT_FLG) && (tcb->waiobj == flgid)) { // *** 条件を追加 ***
        if(check_flag(flgcb->flgptn, tcb->waiptn, tcb->wfmode)) {   // 条件成立の確認
          tqueue_remove_entry( &wait_queue, tcb);                 // タスクをウェイトキューから外す
          tcb->state	= TS_READY;
          tcb->waifct	= TWFCT_NON;
          *tcb->p_flgptn = flgcb->flgptn;
          tqueue_add_entry( &ready_queue[tcb->itskpri], tcb);     // タスクをレディキューへつなぐ
          scheduler();                                            // スケジューラを実行

          if ((tcb->wfmode & TWF_BITCLR) != 0 ) {
            if ( (flgcb->flgptn &= ~(tcb->waiptn)) == 0 ) {     // 対象フラグのクリア
              break;
            }
          }
          if ((tcb->wfmode & TWF_CLR) != 0 ) {
            flgcb->flgptn = 0;                                  // 全フラグのクリア
            break;
          }
        }
      }
    }
  } else {
    err = E_NOEXS;
  }
  EI(intsts);     // 割込み許可
  return err;
}

処理概要:

割り込みを禁止しイベントフラグ管理ブロック flgcb_tbl の指定 ID の state が登録されている場合、forループでウェイト・キューを走査します。
waifctメンバがTWFCT_FLGでかつwaiobjメンバがflgidの場合、check_flag()でフラグパターンの条件が成立しているか確認します。

条件が成立していたらタスクをウィエイト・キューから削除し、レディ・キューに登録後スケジューラーを実行します。
これによりフラグパターンを待っていたタスクが動き始めます。
その後にフラグパターンをクリアします。

flgcb_tbl の指定 ID の state が登録されていない場合、割り込みを許可し err を返します。

イベント・フラグのクリアAPI

tk_clr_flg()

/* イベントフラグのクリアAPI */
ER tk_clr_flg( ID flgid, UINT clrptn )
{
  FLGCB   *flgcb;
  ER      err = E_OK;
  UINT    intsts;
  if(flgid <= 0 || flgid > CNF_MAX_FLGID) return E_ID;

  DI(intsts);     // 割込み禁止
  flgcb = &flgcb_tbl[--flgid];
  if(flgcb->state == KS_EXIST) {
    flgcb->flgptn &= clrptn;        // フラグのクリア
  } else {
    err = E_NOEXS;
  }
  EI(intsts);     // 割込み許可
  return err;
}

処理概要:

イベントフラグ管理ブロック flgcb_tbl の指定 ID の state が登録されている場合、フラグパターンをクリアします。
このAPIはタスクのstateに影響を与えません。

イベント・フラグ待ちAPI

tk_wai_flg()

/* イベントフラグ待ちAPI */
ER tk_wai_flg( ID flgid, UINT waiptn, UINT wfmode, UINT *p_flgptn, TMO tmout )
{
  FLGCB   *flgcb;
  ER      err = E_OK;
  UINT    intsts;

  if(flgid <= 0 || flgid > CNF_MAX_FLGID) return E_ID;

  DI(intsts);     // 割込み禁止
  flgcb = &flgcb_tbl[--flgid];
  if(flgcb->state == KS_EXIST) {
    if(check_flag(flgcb->flgptn, waiptn, wfmode)) {     // 待ち条件が成立している場合
      *p_flgptn = flgcb->flgptn;                      // 条件成立時のフラグ値を返す
      if ( (wfmode & TWF_BITCLR) != 0 ) {
        flgcb->flgptn &= ~waiptn;                   // 該当フラグのクリア
      }
      if ( (wfmode & TWF_CLR) != 0 ) {
        flgcb->flgptn = 0;                          // 全フラグのクリア
      }
    } else if(tmout == TMO_POL) {                       // 待ち条件不成立、かつ、待ち時間0の場合
      err = E_TMOUT;
    } else {                                            // 待ち条件不成立、待ち状態に移行
      tqueue_remove_top(&ready_queue[cur_task->itskpri]);     // タスクをレディキューから外す

      /* TCBの各種情報を変更する */
      cur_task->state     = TS_WAIT;      // タスクの状態を待ち状態に変更
      cur_task->waifct    = TWFCT_FLG;    // 待ち要因を設定
      cur_task->waiobj    = flgid;        // *** TCBにwaiobjを追加 ***
      cur_task->waitim    = ((tmout == TMO_FEVR)? tmout: tmout + TIMER_PERIOD);    // 待ち時間を設定
      cur_task->waiptn    = waiptn;
      cur_task->wfmode    = wfmode;
      cur_task->p_flgptn  = p_flgptn;
      cur_task->waierr    = &err;

      tqueue_add_entry(&wait_queue, cur_task);                // タスクをウェイトキューに繋ぐ
      scheduler();                                            // スケジューラを実行
    }
  } else {
    err = E_NOEXS;      // 未登録のイベントフラグ
  }
  EI(intsts);     // 割込み許可
  return err;
}

処理概要:

割り込みを禁止しイベントフラグ管理ブロック flgcb_tbl の指定 ID の state が登録されている場合、
待ち条件が成立している場合にはフラグをクリアします。
待ち条件が不成立でかつ、タイムアウト時間が0の場合は err に E_TMOUT をセットします。

それ以外の(待ち条件が不成立の)場合
まずタスクをレディ・キューから削除し以下の設定を行います。
 メンバの state を TS_WAIT (待ち)
 メンバの waifct を TWFCT_FLG (待ち要因をイベントフラグ)
 メンバの waiobj にフラグID *** 追加 ***
 メンバの waitim に待ち時間
 メンバの waiptn にフラグパターン
 メンバの wfmode に wfmode (モード: AND/OR , 全ビット/指定ビット)
 メンバの p_flgptn に 条件成立時のフラグパターン(へのポインタ)
その後タスクをウェイト・キューに登録しスケジューラーを実行します。

時々イベントフラグを使ってきましたが、そのしくみについて学ぶことができました。

書籍に書いてありますが、ボタンを押した直後にもうひとつのボタンを押すとLEDの点滅パターンがかわります。
これは2つのタスクがLED制御をとりあうために起こるものです。

このようなリソースをうまく使うために排他制御があります。
次回はそのひとつであるセマフォを実装します。

いかがでしたか?
皆さんはイベントフラグを理解できましたか?

お疲れさまでした。

この記事の続きは こちら です。

OSカテゴリの最新記事