皆さん こんにちは。
今回は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制御をとりあうために起こるものです。
このようなリソースをうまく使うために排他制御があります。
次回はそのひとつであるセマフォを実装します。
いかがでしたか?
皆さんはイベントフラグを理解できましたか?
お疲れさまでした。
この記事の続きは こちら です。