皆さん こんにちは。
今回は6つ目のソースコードに入れ替えて動作確認してみます。
第4部1章のタイトルは「主に1対1のタスク同期に使われる…起床待ちと起床」です。
この章ではタスク起床API、タスク起床待ちAPIなどの機能が追加されました。
この記事は開発環境を構築することを前提にしています。
開発環境を構築したい方は ゼロから作るOS 環境構築編 をご覧になってください。
この記事はインターフェース誌2023年7月号の「ゼロから作るOS」を参考にしています。
まずは書籍の本章を読んで予習しておくことをお薦めします。
回路を追加する
スイッチと抵抗(10kΩ)を図のように配線します。(書籍の84ページ参照)
スイッチは押しボタン、タクトスイッチ等を使います。

()内の数字は端子の番号です。以下の図を参考になさってください。
私は赤枠で囲んだ3か所の端子を使いました。
(注)Picoprobeではなくターゲットボードに配線してください。

次の章ではスイッチと抵抗をそれぞれ2つ使った回路が必要になります。(書籍の90ページ参照)
あらかじめこちらの回路で製作しておけば手間が省けそうです。
(本章ではGPIO14につないだスイッチ2の方を使えば良い)

ソースコードを入れ替える
これから前回製作したソースを削除するので、必要ならバックアップをとっておいてください。
Project Explorerでpico_tryknlのツリー下のフォルダを全て選択し、右クリックして Delete を選択して削除します。

次にソースファイルをIDEにコピーします。
C:\DevTools\src\IF2307TK\part_4\sect_1 下にある以下のフォルダを全て選択して、IDEの Project Explorer の pico_tryknl にドラッグ&ドロップします。
(書籍 第4部1章のソースファイルになります)
次のウィンドウが出るので、そのまま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 ON” が表示されれば成功です。

プログラム概要
この章のプログラムの概略説明です。
つくられるタスクは3つあり、main()で最初のタスク(初期タスク)が生成されます。
その後、tk_sta_task()で初期タスクが実行されます。
このタスクの優先順位は 1 に設定されていて、つくられるタスクの中では一番高くなっています。
初期タスク initsk() では “Start Try Kernel”の文字列をUARTで送信します。
その後、usermain()関数を実行して tk_ext_tsk() で休止します。
usermain()内では、task_btn()とtask_led()が生成されて実行されます。
実行されると書きましたが実際にはキューに入れるだけです。
task_btn()とtask_led()の優先順位は 10 で 1 よりも低いため、初期タスクが休止するまでこれらのタスクは実行されません。
(数値が小さいほど優先順位は高い)
その後、tk_ext_tsk()により初期タスクが休止すると、task_btn()が動き出します。
tk_ext_tsk()で初期タスクを休止した後スケジューラーが動くためです。
task_btn()では tk_dly_tsk(100)により100msecの時間待ちがあり、実行権を他のタスクに譲ります。
そしてtask_led()が動き出しますが、tk_slp_tsk()によりスリープします。
この状態では task_led()はスリープし続け、task_btn()は100msec周期で動きスイッチ(2)の状態を読み込みます。
スイッチ(2)が押されたことを検出すると “BTN ON” の文字列を UART で送信し、tk_wup_tsk()によりtask_led()を起こします。
起こされたtask_led()は1秒間LEDを点灯させた後、消灯してスリープします。
それでは追加された機能のコードを見ていきます。
タスク起床待ちAPI
tk_slp_tsk()
tk_dly_tsk()に良く似ていますが、tk_dly_tak()は指定した時間待ちになるのに対してtk_slp_tsk()は他のタスクからwk_wup_tsk()で起床されるまで待ちになります。
(もしくはタイムアウト)
/* タスク起床待ちAPI */
ER tk_slp_tsk( TMO tmout )
{
UINT intsts;
ER err = E_OK;
DI(intsts); // 割込みの禁止
if ( cur_task->wupcnt > 0 ) { // 起床要求有り
cur_task->wupcnt--;
} else { // 起床要求無し
tqueue_remove_top(&ready_queue[cur_task->itskpri]); // タスクをレディキューから外す
/* TCBの各種情報を変更する */
cur_task->state = TS_WAIT; // タスクの状態を待ち状態に変更
cur_task->waifct = TWFCT_SLP; // 待ち要因を設定
cur_task->waitim = (tmout==TMO_FEVR)?tmout:(tmout+TIMER_PERIOD); // 待ち時間を設定
cur_task->waierr = &err;
tqueue_add_entry(&wait_queue, cur_task); // タスクをウェイトキューに繋ぐ
scheduler(); // スケジューラの実行
}
EI(intsts); // 割込みの許可
return err;
}
処理概要:
割り込みを禁止し、カレントタスクの wupcnt (ウェイクアップカウンタ)がゼロより大きければカウンタをデクリメントします。
そうでなければタスクをレディ・キューからはずし、stateをTS_WAITにし、waifct(待ち要因)をTWFCT_SLPにして待ち時間を設定します。
そして実行中のタスクをウェイト・キューに登録し、スケジューラーを実行し割り込みを許可します。
wupcntは起床要求と起床待ちの帳尻合わせに使います。
帳尻合わせとは「指定するタスクが寝てなかったら起こさない」ようにすることです。
カウンタで実現しているのはTry Kernelの仕様です。
起こしたいタスクが寝ていたらウェイト・キューに そのTCB が入っている。だから取り出すなどの処理を実行することができます。
でも寝ていなかったらウェイト・キューに 起こしたい TCB がないのでウェイクアップカウンタをカウントダウンするのみに留めます。
タスク起床API
tk_wup_tsk(ID)
IDで指定したタスクを起こします。
/* タスクの起床 API */
ER tk_wup_tsk( ID tskid )
{
TCB *tcb;
UINT intsts;
ER err = E_OK;
if(tskid <= 0 || tskid > CNF_MAX_TSKID) return E_ID; /* ID番号チェック */
DI(intsts); // 割込みの禁止
tcb = &tcb_tbl[tskid-1];
if((tcb->state == TS_WAIT) && (tcb->waifct == TWFCT_SLP)) { // tk_slp_tskで待ち状態か?
tqueue_remove_entry(&wait_queue, tcb); // タスクをウェイトキューから外す
/* TCBの各種情報を変更する */
tcb->state = TS_READY;
tcb->waifct = TWFCT_NON;
tqueue_add_entry(&ready_queue[tcb->itskpri], tcb); // タスクをレディキューに繋ぐ
scheduler(); // スケジューラの実行
} else if(tcb->state == TS_READY || tcb->state == TS_WAIT) { // 実行できる状態の場合
tcb->wupcnt++; // 起床要求数を増やす
} else {
err = E_OBJ; // 起床できるタスクの状態ではない
}
EI(intsts); // 割込みの許可
return err;
}
処理概要:
割り込みを禁止し、起こすべきタスクのstateがTS_WAITかつ待ち要因がTWFCT_SLPなら(tk_slp_tskによる待ちなら)
そのタスクをウェイト・キューから削除し、stateをTS_READYにします。
そしてそのタスクをレディ・キューに登録します。
そうでなく(tk_slp_tskによる待ちでなく)、stateがTS_READY か TS_WAIT なら
wupcnt(ウェイクアップカウンタ)をインクリメントします。
更にそれ以外なら err に E_OBJ を設定します。
最後に割り込みを許可し、errを返します。
ソースコードの変更
書籍の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 を設定する処理が追加になっています。
皆さんの環境ではうまくタスクを起こすことができましたか?
お疲れさまでした。
この記事の続きは こちら です。