3-1. タスクの生成・実行のプログラム
μT-Kernel上で動作するプログラムの基本的な実行単位はタスクです。ユーザプログラムは一つまたは複数のタスクから構成されます。タスクを生成して実行するプログラムを作ってみましょう。
プログラム3-1:タスクの生成と実行
一つのタスクを生成し実行します。タスクはデバッグ用のシリアル通信出力にメッセージ「Start Task-A」を出力して終了します。
以下のプログラムを作成します。
本プログラムで使用するAPI
本プログラムで使用するタスク制御APIを以下に説明します。
タスクの生成
タスクは、タスク生成API tk_cre_tskにより生成します。
tk_cre_tskのAPIの仕様を以下に説明します。
関数定義 | ID tk_cre_tsk( CONST T_CTSK *pk_ctsk ); |
引数 | T_CTSK *pk_ctsk タスク生成情報 |
戻り値 | タスクID番号、またはエラーコード |
機能 | * pk_ctskで指定したタスク生成情報に基づき、タスクを生成します。 生成したタスクのID番号を戻り値として返します。 |
タスク生成情報T_CTSKは、タスクを生成するための情報を格納した構造体です。tk_cre_tskの引数にこの構造体を指すポインタを渡します。
T_CTSK構造体のメンバーは必要に応じて設定しますが、以下のメンバーは必ず設定する必要があります。
タスク属性 tskatr
生成するタスクがどのようなタスクであるか、タスクの属性を指定します(「2-2 タスクの基本」を参照)。
一般的なアプリケーションのタスクの属性は、TA_HLNGおよびTA_RNG3です。TA_HLNG属性はタスクがC言語で記述されていることを示します。TA_RNG3はタスクが一般的なユーザプログラムであることを示します。
なお、MMUによるメモリ保護を使用していないμT-Kernel 3.0では、保護レベルの指定があっても、実際の動作には影響しません。ただし、メモリ保護のあるシステムとの互換性を考慮し、タスクの用途に応じた保護レベルの設定を推奨します。
タスク起動アドレス task
タスクのプログラムの実行開始アドレスを示します。タスク属性がTA_HLNG(C言語で記述)の場合は、タスクの実行関数へのポインタを指定します。
タスクの実行関数は、以下の形式の関数です。
void task( INT stacd, void *exinf);
第一引数のstacdは、後述のタスク起動API tk_sta_tskで指定します。第二引数のexinfはタスク生成時に指定可能です(本記事では使用しません)。
タスク起動時優先度 itskpri
タスクの優先度の初期値です。タスクが実行を開始する際には、ここで指定された優先度で動作します。
タスクの優先度は、一般にはユーザプログラム全体を通じたシステム設計によって決める必要があります。本記事に記載のプログラムでは優先度10とします。
スタックサイズ stksz
タスクが使用するスタックのサイズです。タスクの生成時に指定したサイズのメモリがスタックとして確保されます。もし、タスクの実行中に指定を超えるサイズのスタックを使用した場合は、重大なエラーとなる危険があります。
スタックサイズは、そのタスクのプログラムのスタック必要量から計算し、それ以上のサイズを設定する必要があります。本記事に記載のプログラムでは、アプリケーションのタスクのスタックサイズを、余裕をみた値として1024バイトにしています。
tk_cre_tskは戻り値として新しく生成されたタスクのID番号を返します。以降は、このID番号を使って生成したタスクへの操作を行います。
タスクの実行
生成したタスクは休止状態であり、まだ実行されていません。タスク起動API tk_sta_tskを呼び出し、タスクを実行できる状態とします。
tk_sta_tskのAPIの仕様を以下に説明します。
関数定義 | ER tk_sta_tsk( ID tskid, INT stacd ); |
引数 | ID tskid タスクID番号 |
INT | stacd タスク起動コード |
戻り値 | エラーコード |
機能 | tskidで指定したタスクを休止状態から動作できる状態に移します。 stacdに設定した値は、タスクの実行関数の引数に渡されます。 |
タスクの起床待ち(タスクの一時停止)
タスクの動作を一時的に停止するには、タスク起床待ちAPI tk_slp_tskを呼び出して、タスクを起床待ち状態にします。起床待ち状態のタスクは他のタスクから起床されるまで動作を停止します。
tk_slp_tskのAPIの仕様を以下に説明します。
関数定義 | ER tk_slp_tsk( TMO tmout ); |
引数 | TMO tmout タイムアウト時間(単位ミリ秒) |
戻り値 | エラーコード |
機能 | APIを呼び出したタスクを起床待ち状態とします。 他のタスクから起床されると待ち状態は解除されます。またはtmoutで指定した時間内に、他のタスクから起床されなかった場合はタイムアウトのエラーで待ち状態は解除されます。 |
タスクの終了
タスクを終了する際には、自タスク終了API tk_ext_tskを呼び出して、タスクを終了させます。もしタスクを終了させることなく、タスクの実行関数からreturnが実行されてしまうと、そのタスクは暴走してしまいますので注意が必要です。
tk_ext_tskのAPIの仕様を以下に説明します。
関数定義 | void tk_ext_tsk( void ); |
引数 | なし |
戻り値 | なし |
機能 | APIを呼び出したタスクを終了します。 |
プログラム例
「プログラム3-1:タスクの生成・実行」のプログラムのリストを以下に示します。
リスト3-1 タスクの生成と実行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <tk/tkernel.h> #include <tm/tmonitor.h> /* ① タスクAの生成情報と関連データ */ LOCAL void task_a(INT stacd, void *exinf); // 実行関数 LOCAL ID tskid_a; // ID番号 LOCAL T_CTSK ctsk_a = { .itskpri = 10, // 初期優先度 .stksz = 1024, // スタックサイズ .task = task_a, // 実行関数のポインタ .tskatr = TA_HLNG | TA_RNG3, // タスク属性 }; /* ② タスクAの実行関数 */ LOCAL void task_a(INT stacd, void *exinf) { tm_printf((UB*)"Start Task-A\n"); // デバッグ出力 tk_ext_tsk(); // 自タスクの終了 } /* ③ usermain関数 */ EXPORT INT usermain(void) { tskid_a = tk_cre_tsk(&ctsk_a); // タスクの生成 tk_sta_tsk(tskid_a, 0); // タスクの実行 tk_slp_tsk(TMO_FEVR); // 起床待ち return 0; // ここは実行されない } |
リスト3-1のプログラムの内容を順番に説明していきます。
- タスクAの生成情報と関連データ
タスクAの実行関数task_aのプロトタイプ宣言、タスクAのID番号を格納するための変数tskid_a、タスクAの生成情報の変数ctsk_aを記述しています。 - タスクAの実行関数
関数task_aは、タスクAの実行関数です。本プログラムでは、デバッグ用出力関数tm_printfでメッセージ「Start Task-A」を出力したのち、自タスク終了API tk_ext_tskを呼び出してタスクAを終了します。 - usermain関数
usermain関数は、タスク生成API tk_cre_tskを呼び出してタスクAを生成します。続いてタスク起動API tk_sta_tskを呼び出して生成したタスクAの実行を開始します。
最後にusermain関数は、タスク起床待ちAPI tk_slp_tskを実行し、起床待ち状態となります。これは、usermain関数の終了によりμT-Kernelのシステム全体が終了することを防ぐためです。
なお、usermain関数を実行している初期タスクの優先度は最高優先度(優先度1)ですので、usermain関数の実行中は優先度10のタスクAは実行されません。
tk_slp_tskの呼び出しにより初期タスクが待ち状態になると、タスクAが実行されます。
3-2. マルチタスクのプログラム
複数のタスクを生成して同時に実行するマルチタスクのプログラムを作ってみましょう。
ここでは、LEDとスイッチの制御を、それぞれ別のタスクとして同時に実行させることにします。別々のプログラムに含まれていたタスクが、一つのプログラムの中で同時に動作することが確認できます。
なお、特定のハードウェアは想定せず、LEDとスイッチの制御関数があるものとしてプログラムを作成します。
以下のプログラムを作成します。
プログラム3-2:マルチタスクの実行
二つのタスクを生成、実行し、二つのタスクの機能が同時に動作することを確認します。二つのタスクは以下の動作をします。
- スイッチ制御タスク
ボタン・スイッチを監視し、ボタン・スイッチが押されると、デバッグ出力に「SW-ON」と表示します。 - LED制御タスク
LEDを0.5秒ごとに点滅させます。
本プログラムで使用するAPI
使用するμT-KernelのAPIは「3-1 タスクの生成と実行」と同じです。
以下のLEDとスイッチの制御関数があるものとします。実際のプログラムを動作させるには、実行するハードウェアに応じてこの関数を作成する必要があります。
LED制御関数
関数定義 | void led_ctl( UINT on_off ); |
引数 | UINT on_off LEDの状態(0で消灯、それ以外は点灯) |
戻り値 | なし |
機能 | 引数on_offの指定に応じてLEDを制御します。 |
スイッチ制御関数
関数定義 | UW get_sw( void ); |
引数 | なし |
戻り値 | スイッチの状態(0でオフ、それ以外はオン) |
機能 | スイッチの状態を戻り値に返します。 |
プログラム例
「プログラム3-2:マルチタスクの実行」のプログラムのリストを以下に示します。
リスト3-2 マルチタスクの実行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
#include <tk/tkernel.h> #include <tm/tmonitor.h> /* ① スイッチ制御タスクの生成情報と関連データ */ LOCAL void task_sw(INT stacd, void *exinf); // 実行関数 LOCAL ID tskid_sw; // ID番号 LOCAL T_CTSK ctsk_sw = { .itskpri = 10, // 初期優先度 .stksz = 1024, // スタックサイズ .task = task_sw, // 実行関数のポインタ .tskatr = TA_HLNG | TA_RNG3, // タスク属性 }; /* ② LED制御タスクの生成情報と関連データ */ LOCAL void task_led(INT stacd, void *exinf); // 実行関数 LOCAL ID tskid_led; // ID番号 LOCAL T_CTSK ctsk_led = { .itskpri = 10, // 初期優先度 .stksz = 1024, // スタックサイズ .task = task_led, // 実行関数のポインタ .tskatr = TA_HLNG | TA_RNG3, // タスク属性 }; /* ③ スイッチ制御タスクの実行関数 */ LOCAL void task_sw(INT stacd, void *exinf) { UW sw_data, pre_sw_data; pre_sw_data = get_sw(); // 一つ前のスイッチの状態を読み取る while(1) { sw_data = get_sw; // スイッチの状態を読み取る if( pre_sw_data != sw_data) { // スイッチに変化があったか? if(sw_data == 0) { // スイッチはONか? tm_printf((UB*)"SW-ON\n"); // デバッグ出力 } pre_sw_data = sw_data; // 変数の更新 } tk_dly_tsk(100); // 0.1秒間、一時停止 } tk_ext_tsk(); // ここは実行されない } /* ④ LED制御タスクの実行関数 */ LOCAL void task_led(INT stacd, void *exinf) { UW data_reg; while(1) { led_ctl(1); // LED点灯 tk_dly_tsk(500); // 0.5秒間、一時停止 led_ctl(0); // LED消灯 tk_dly_tsk(500); // 0.5秒間、一時停止 } tk_ext_tsk(); // ここは実行されない } /* ⑤ usermain関数 */ EXPORT INT usermain(void) { tskid_sw = tk_cre_tsk(&ctsk_sw); // スイッチ制御タスクの生成 tk_sta_tsk(tskid_sw, 0); // スイッチ制御タスクの実行 tskid_led = tk_cre_tsk(&ctsk_led); // LED制御タスクの生成 tk_sta_tsk(tskid_led, 0); // LED制御タスクの実行 tk_slp_tsk(TMO_FEVR); // 起床待ち return 0; // ここは実行されない } |
リスト3-2のプログラムの内容を順番に説明していきます。
- スイッチ制御タスクの生成情報と関連データ
スイッチ制御タスクの実行関数task_swのプロトタイプ宣言、スイッチ制御タスクのID番号を格納するための変数tskid_sw、スイッチ制御タスクの生成情報の変数ctsk_swを記述しています。 - LED制御タスクの生成情報と関連データ
LED制御タスクの実行関数task_ledのプロトタイプ宣言、LED制御タスクのID番号を格納するための変数tskid_led 、LED制御タスクの生成情報の変数ctsk_ledを記述しています。 - スイッチ制御タスクの実行関数
関数task_swは、スイッチ制御タスクの実行関数です。
関数の最後に自タスク終了API tk_ext_tskを記述していますが、本関数はwhile文による無限ループから抜けませんので、このAPIが実行されることはありません。 - LED制御タスクの実行関数
関数task_ledは、LED制御タスクの実行関数です。
関数の最後に自タスク終了API tk_ext_tskを記述していますが、本関数はwhile文による無限ループから抜けませんので、このAPIが実行されることはありません。 - usermain関数
usermain関数では、スイッチ制御タスクとLED制御タスクの生成および実行を行います。
最後にusermain関数は、タスクの起床待ちAPI tk_slp_tskを実行し、起床待ち状態となります。これはusermain関数の終了によりμT-Kernelのシステム全体が終了することを防ぐためです。tk_slp_tskの呼び出しによって初期タスクが待ち状態になると、LED制御タスクとスイッチ制御タスクが実行されます。
3-3. イベントフラグのプログラム
マルチタスクで実行するタスクの間では、イベントフラグを用いて、イベントの発生を通知し、動作の同期をとることができます(「2-4. イベントフラグ」を参照)。
「3-2. マルチタスクのプログラム」では、それぞれのタスクでLEDとスイッチの制御を行いました。今回はイベントフラグを用いて、ボタン・スイッチが押されたというイベントをタスク間で通知し、タスクの動作を同期してみましょう(LEDとスイッチの制御は「3-2. マルチタスクのプログラム」と同様にLEDとスイッチの制御関数があるものとします)。
以下のプログラムを作成します。
プログラム3-3:イベントフラグによる同期
ボタン・スイッチを押すとLEDが3秒間点灯するプログラムを、二つのタスクをイベントフラグで同期させて実現します。
二つのタスクは以下の動作をします。
- スイッチ制御タスク
ボタン・スイッチを監視し、ボタン・スイッチが押されると、イベントフラグをセットします。 - LED制御タスク
イベントフラグがセットされるのを待ち、セットされると、LEDを3秒間点灯します。
本プログラムで使用するAPI
本プログラムで使用するイベントフラグのAPIを以下に説明します。タスク制御APIに関しては「3-1タスクの生成と実行」で説明しています。
イベントフラグの生成
イベントフラグはイベントフラグの生成API tk_cre_flgにより生成します。
tk_cre_flgのAPIの仕様を以下に説明します。
関数定義 | ID tk_cre_flg( CONST T_CFLG *pk_cflg ); |
引数 | T_CFLG *pk_cflg イベントフラグ生成情報 |
戻り値 | イベントフラグID番号、またはエラーコード |
機能 | *pk_cflgで指定したイベントフラグ生成情報に基づき、イベントフラグを生成します。 生成したイベントフラグのID番号を戻り値として返します。 |
イベントフラグ生成情報T_CFLGは、イベントフラグを生成するための情報を格納した構造体です。tk_cre_flgの引数にこの構造体へのポインタを渡します。
T_CFLG構造体のメンバーは必要に応じて設定しますが、以下のメンバーは必ず設定する必要があります。
イベントフラグ属性 flgatr
イベントフラグの性質を表わす属性です。主な属性を表3-3-1に示します。
属性名 | 意味 |
---|---|
TA_TFIFO | 待ちタスクの並びは先着順(FIFO順)(※1) |
TA_TPRI | 待ちタスクの並びは優先度順(※1) |
TA_WSGL | 複数のタスクの待ちを許さない(※2) |
TA_WMUL | 複数のタスクの待ちを許す(※2) |
(※1)TA_TFIFOとTA_TPRIのいずれか一方を必ず指定する必要があります
(※2)TA_WSGLとTA_WMULのいずれか一方を必ず指定する必要があります
イベントフラグの初期値 iflgptn
イベントフラグの生成時の初期値です。イベントが何も発生してなければ、初期値は0とします。
イベントフラグのセット
イベントフラグのセットは、イベントフラグのセットAPI tk_set_flgを使用します。
tk_set_flgのAPIの仕様を以下に説明します。
関数定義 | ER tk_set_flg( ID flgid, UINT flgptn ); |
引数 | ID flgid イベントフラグのID番号 |
UINT | flgptn セットするビットパターン |
戻り値 | エラーコード |
機能 | flgidで指定したイベントフラグにflgptnで示されているビットパターンをセットします。具体的にはその時点のイベントフラグの値に、flgptnの値の論理和がとられます。 イベントフラグの待ち状態のタスクの中から、条件が成立したタスクの待ちを解除します。 |
イベントフラグ待ち
イベントフラグのセットを待つには、イベントフラグ待ちAPI tk_wai_flgを使用します。
tk_wai_flgのAPIの仕様を以下に説明します。
関数定義 | ER tk_wai_flg( ID flgid, UINT waiptn, UINT wfmode, UINT *p_flgptn, TMO tmout ); |
引数 | ID flgid イベントフラグのID番号 UINT waiptn 待ちビットパターン UINT wfmode 待ちモード UINT *p_flgptn 解除時のビットパターン TMO tmout タイムアウト時間(単位ミリ秒) |
戻り値 | エラーコード |
機能 | flgidで指定したイベントフラグに対して、waiptnで指定したビットパターンと、wfmodeで指定したモードで、イベントフラグがセットされるのを待ちます。 条件が成立し待ちが解除されると、*p_flgptnに解除時のビットパターンが返されます。またはtmoutで指定した時間内に、条件が成立しなかった場合はタイムアウトのエラーで待ち状態は解除されます。 |
イベントフラグの待ちモードには表3-3-2に示すものがあります。
属性名 | 意味 |
---|---|
TWF_ANDW | 指定した全てのビットがセットされるまで待つ(※) |
TWF_ORW | 指定したビットのいずれかがセットされるまで待つ(※) |
TWF_CLR | 条件が成立したら全ビットをクリアする |
TWF_BITCLR | 条件が成立したビットをクリアする |
(※)TWF_ANDWとTWF_ORWのいずれか一方を必ず指定する必要があります
プログラム例
「プログラム3-3:イベントフラグによる同期」のプログラムのリストを以下に示します。
リスト3-3 イベントフラグによる同期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
#include <tk/tkernel.h> #include <tm/tmonitor.h> /* ① イベントフラグの生成情報と関連データ */ LOCAL ID flgid_a; // イベントフラグのID番号 LOCAL T_CFLG cflg_a = { .flgatr = TA_TFIFO | TA_WMUL, // イベントフラグの属性 .iflgptn = 0, // イベントフラグの初期値 }; /* イベントを定義 */ #define FLG_SW_ON (1<<0) // スイッチオンイベント(ビット0) /* ② スイッチ制御タスクの生成情報と関連データ */ LOCAL void task_sw(INT stacd, void *exinf); LOCAL ID tskid_sw; // スイッチ制御タスクのID番号 LOCAL T_CTSK ctsk_sw = { .itskpri = 10, // 初期優先度 .stksz = 1024, // スタックサイズ .task = task_sw, // 実行関数のポインタ .tskatr = TA_HLNG | TA_RNG3, // タスク属性 }; /* ③ LED制御タスクの生成情報と関連データ */ LOCAL void task_led(INT stacd, void *exinf); LOCAL ID tskid_led; // LED制御タスクのID番号 LOCAL T_CTSK ctsk_led = { .itskpri = 10, // 初期優先度 .stksz = 1024, // スタックサイズ .task = task_led, // 実行関数のポインタ .tskatr = TA_HLNG | TA_RNG3, // タスク属性 }; /* ④ スイッチ制御タスクの実行関数 */ LOCAL void task_sw(INT stacd, void *exinf) { UW sw_data, pre_sw_data; pre_sw_data = get_sw() & (1<<13); // 一つ前のスイッチの状態を読み取る while(1) { sw_data = get_sw()&(1<<13); // スイッチの状態を読み取る if( pre_sw_data != sw_data) { // スイッチに変化があったか? if(sw_data == 0) { // スイッチはONか? tk_set_flg(flgid_a, FLG_SW_ON); // イベントフラグをセット } pre_sw_data = sw_data; // 変数の更新 } tk_dly_tsk(100); // 0.1秒間、一時停止 } tk_ext_tsk(); // ここは実行されない } /* ⑤ LED制御タスクの実行関数 */ LOCAL void task_led(INT stacd, void *exinf) { UW data_reg; UINT flgptn; while(1) { tk_wai_flg(flgid_a, FLG_SW_ON, (TWF_ANDW | TWF_BITCLR), &flgptn, TMO_FEVR); // イベント待ち led_ctl(1); // LED点灯 tk_dly_tsk(3000); // 3秒間、一時停止 led_ctl(0); // LED消灯 } tk_ext_tsk(); // ここは実行されない } /* ⑥ usermain関数 */ EXPORT INT usermain(void) { flgid_a = tk_cre_flg(&cflg_a); // イベントフラグの生成 tskid_sw = tk_cre_tsk(&ctsk_sw); // スイッチ制御タスクの生成 tk_sta_tsk(tskid_sw, 0); // スイッチ制御タスクの実行 tskid_led = tk_cre_tsk(&ctsk_led); // LED制御タスクの生成 tk_sta_tsk(tskid_led, 0); // LED制御タスクの実行 tk_slp_tsk(TMO_FEVR); // 起床待ち return 0; // ここは実行されない } |
リスト3-3のプログラムの内容を順番に説明していきます。
- イベントフラグの生成情報と関連データ
flgid_aはイベントフラグのID番号を格納するための変数です。
cflg_aはイベントフラグの生成情報の変数です。イベントフラグ属性はTA_TFIFO(待ちタスクは先着順)とTA_WMUL(複数のタスクの待ちを許す)としましたが、今回はイベントを待つタスクが一つだけですので、実際の動作には影響しません。
イベントフラグの初期値は、まだイベントが発生していないという意味で0とします。
イベントフラグの内容は、スイッチを押したことを通知するイベントのみですので、イベントフラグの第0ビット目をスイッチオンイベントとして、以下のように定義します。
#define FLG_SW_ON (1<<0) - スイッチ制御タスクの生成情報と関連データ
「プログラム3-2:マルチタスク」と同じです。 - LED制御タスクの生成情報と関連データ
「プログラム3-2:マルチタスク」と同じです。 - スイッチ制御タスクの実行関数
関数task_swは、0.1秒の間隔でスイッチの状態をポーリングし、スイッチが押されたことを検出した場合には、イベントフラグのセットAPI tk_set_flgを呼び出し、スイッチオンイベントをセットします。 - LED制御タスクの実行関数
関数task_ledは、最初にイベントフラグ待ちAPI tk_wai_flgを呼び出し、イベント待ち状態となって実行を一時停止します。その際、TWF_BITCLRモードを指定し、待ちが解除された際に条件ビットがクリアされるように指定します。
イベントフラグがセットされると、LEDを点灯してから、タスクの遅延API tk_dly_tskを呼び出してタスクの動作を3秒間停止します。3秒が経過し再び実行されるとLEDを消灯します。
以降、この動作を繰り返します。 - usermain関数
usermain関数は、最初にイベントフラグ生成API tk_cre_flgを呼び出してイベントフラグを生成します。以降は「プログラム3-2:マルチタスク」と同じです。