H8/3052でHOSを用いたRTOSの基礎を修得
Copyright(C)31Dec2005
R Iguchi & T Kosaka CS TNCT
1.はじめに
この文書は,前章でRTOSの基礎を修得した方が, より現実的な使い方の学習をするためのページです。
サンプルプログラムのダウンロード
DownLoad |
本文書で記述してある作業の特徴
説明を読みながらプログラム例を理解し, 実際に動かしてみる事でその確認をします。2.プログラム例1:周期ハンドラとセマフォを利用
HOSのカーネル機能として,周期ハンドラ・セマフォというものがある.この二つの機能はHOSのカーネル機能の中でも特に利用価値の高いものと思われる.
そこで,この二つの機能を使ったシステムを実際に開発しておくことで,その理解を深めることを目的とする.
具体的に作成するプログラムの内容を以下に示す.
0.1secごとにhelloと画面に出力し,0.5secごとにledを点滅させ,
1secごとに8bitSWからの情報を画面に出力するプログラムを作成します。
上でダウンロードしたサンプルプログラムフォルダにある, CycSemフォルダにプログラム例が入っています。
ダウンロードしたフォルダは以下のような構成になっています。
井口のテンプレートフォルダ
ファイル | 内容 |
3052F.h | レジスタのアドレス配置ヘッダファイル |
crt0.s | スタートアップルーチン |
h8-3052.h | 小坂の標準ヘッダ |
h8_sci1.c | SCI1に関するファイル |
h8_sci1.h | SCI1に関するヘッダファイル |
h83052.x | リンカスクリプト |
mak_receiver.cmd | gcc.makの開始と書き込みプログラムを行うコマンドファイル |
ostimer0.c.c | ITU0を使ったOSタイマーに関するファイル |
ostimer0.h | ITU0を使ったOSタイマーに関するヘッダファイル |
gcc.mak | makeコマンドファイル環境によって多少変更してください. |
sample.c | タスク等のプログラムファイル |
sample.h | タスク等のヘッダファイル |
system.cfg | コンフィギュレータに指示を出すファイル |
vector.s | 割込みベクタファイル |
to_H8.ht | 設定済みターミナルソフト |
プログラムの解説を行う前に,まずは実際に実行してみましょう
<作成したプログラム> #include "kernel.h" #include "kernel_id.h" #include "h8_sci1.h" #include "h8-3052.h" #define putCharSCI1(x) Sci_PutChar(x) #define getCharSCI1() Sci_GetChar(x) /* メイン関数 */ int main() { Sci_Initialize(SCI_38400); SCI1_printf("HOS is starting\n"); sta_hos(); return 0; } /* Helloの初期化ハンドラ */ void HelloInitialize(VP_INT exinf) { act_tsk(TSKID_HELLO); } /*Ledの初期化ハンドラ*/ void LedInitialize(VP_INT exinf) { act_tsk(TSKID_LED); } /*SWの初期化ハンドラ*/ void SWInitialize(VP_INT exinf) { act_tsk(TSKID_SW); } /*Helloの周期ハンドラ*/ void HelloCYC(VP_INT exinf) { wup_tsk(TSKID_HELLO); } /*Ledの周期ハンドラ*/ void LedCYC(VP_INT exinf) { wup_tsk(TSKID_LED); } /*SWの周期ハンドラ*/ void SWCYC(VP_INT exinf) { wup_tsk(TSKID_SW); } /* Helloタスク */ void Hello(VP_INT exinf) { sta_cyc(CYCID_HELLO); //周期ハンドラのタイマカウントを開始 while(1) { /*周期ハンドラに呼び起こしてもらうまで寝ている*/ slp_tsk(); wai_sem(SEMID_SCI); SCI1_printf("hello\n"); sig_sem(SEMID_SCI); } } /*Ledタスク*/ void Led(VP_INT exinf) { int i=1; initLed(); sta_cyc(CYCID_LED); while(1) { slp_tsk(); if(i == 1) turnOnLed(1); else turnOffLed(1); i = 1 - i; } } /*SWタスク*/ void SW(VP_INT exinf) { init8BitSW(); sta_cyc(CYCID_SW); while(1) { slp_tsk(); wai_sem(SEMID_SCI); SCI1_printf("SWタスク %02x\n", get8BitSW()); sig_sem(SEMID_SCI); } }<system.cfg> INCLUDE("\"sample.h\""); INCLUDE("\"ostimer0.h\""); INCLUDE("\"h8_sci1.h\""); /* HOS 独自の設定 */ HOS_KERNEL_HEAP(0); /* カーネルヒープの設定(省略時 0) */ HOS_TIM_TIC(1, 1); /* タイムティックの設定(省略時 1/1 ) */ HOS_MAX_TPRI(8); /* 最大優先度(省略時 16) */ HOS_MIN_INTNO(0); /* 割り込み番号の最小値(省略時 0) */ HOS_MAX_INTNO(256); /* 割り込み番号の最大値(省略時 0) */ HOS_MAX_TSKID(8); /* 最大タスクID番号(省略時静的生成に必要なだけ) */ /*ATT_INIは初期化関数の追加*/ /*ATT_ISRは割り込みハンドラの追加*/ /*CRE_TSKはタスクの静的生成*/ /*CRE_CYCは周期ハンドラの静的生成*/ /*CRE_SEMはセマフォの静的生成*/ /*これらの機能はμITRON仕様書を参照のこと*/ /* OS タイマ */ ATT_INI({TA_HLNG, 0, OsTimer_Initialize}); ATT_ISR({TA_HLNG, 0, 24, OsTimer_TimerHandler}); /* SCI */ ATT_ISR({TA_HLNG, 0, 57, Sci_RxiHandler}); /* helloタスク */ ATT_INI({TA_HLNG, 0, HelloInitialize}); CRE_TSK(TSKID_HELLO, {TA_HLNG, 1, Hello, 1, 256, NULL}); CRE_CYC(CYCID_HELLO, {TA_HLNG, 1, HelloCYC, 100, 0}); /* ledタスク */ ATT_INI({TA_HLNG, 0, LedInitialize}); CRE_TSK(TSKID_LED, {TA_HLNG, 1, Led, 1, 256, NULL}); CRE_CYC(CYCID_LED, {TA_HLNG, 1, LedCYC, 500, 0}); /* swタスク */ ATT_INI({TA_HLNG, 0, SWInitialize}); CRE_TSK(TSKID_SW, {TA_HLNG, 1, SW, 1, 256, NULL}); CRE_CYC(CYCID_SW, {TA_HLNG, 1, SWCYC, 1000, 0}); /*セマフォ*/ CRE_SEM(SEMID_SCI, {TA_TPRI, 1, 1});
◇実行結果
実行結果は次のようになります。
実行結果 |
HOS is starting |
3.プログラム例2:ラウンドロビンとスタック領域の計算
周期ハンドラとrot_rdq関数により,複数のタスクのラウンドロビン管理システムを
作成することができる.ラウンドロビンでタスクを管理すると,
タスク優先度の同じものを平等に動作することができる.そこで,
実際にラウンドロビン管理システムを作成し,この理解を深める事を目標とする.
具体的に作成するプログラムの内容を以下に示す。
A B Cタスクが処理を繰り返し,
rot_rdq()システムコールを利用し, ラウンドロビン管理システムを行うプログラムを
作成します.その際割込みがかかった時のA B
Cにそれぞれ必要なスタック領域を計算します.
ラウンドロビン管理システムでタスクを管理すると, タスクの実行順序はA→B→C→の繰り返しとなります。
しかし今回のシステムでは,タスクA,B,Cにはそれぞれ意味的な重要性があると想定し,
最も重要性の高いタスクをタスクA. Aよりは重要性が低いタスクをタスクB,Cとしている.
なので,単純なラウンドロビン管理システムではタスクの重要性を考慮しているとは言えない.
そこで,タスクA,B,Cがそれぞれ何回実行したかを観察し,AをB,Cよりも多く実行するよう管理する.
タスクが何回実行したかを管理するためにグローバル変数(今回のプログラム例ではmanageA,B,C)を用意し,これをプログラム例の
TaskManage関数が観察することで,実行回数を観察している.
そして,実行回数によってタスクの優先度を上げ下げし,AをB,Cよりも多く実行するようにしている.
具体的には,まずA→B→C→の繰り返しを二回行ったら,Cの優先度を下げ,A→B→の繰り返しを二回行い,その後,Cの優先度を上げ,Bの優先度を下げる事で,
A→C→の繰り返しを二回行う.そして,Bの優先度を上げ,元のA→B→C→の繰り返しを行う.
この管理方法を実現するために,TaskManage関数とラウンドロビン管理システムを併用する.
上でダウンロードしたサンプルプログラムフォルダにある,
Interruptフォルダにプログラム例が入っています。
ダウンロードしたフォルダは以下のような構成となっています。
井口のテンプレートフォルダ
ファイル | 内容 |
3052F.h | レジスタのアドレス配置ヘッダファイル |
crt0.s | スタートアップルーチン |
h8-3052.h | 小坂の標準ヘッダ |
h8_sci1.c | SCI1に関するファイル |
h8_sci1.h | SCI1に関するヘッダファイル |
h83052.x | リンカスクリプト |
mak_receiver.cmd | gcc.makの開始と書き込みプログラムを行うコマンドファイル |
ostimer0.c.c | ITU0を使ったOSタイマーに関するファイル |
ostimer0.h | ITU0を使ったOSタイマーに関するヘッダファイル |
gcc.mak | makeコマンドファイル環境によって多少変更してください. |
sample.c | タスク等のプログラムファイル |
sample.h | タスク等のヘッダファイル |
system.cfg | コンフィギュレータに指示を出すファイル |
vector.s | 割込みベクタファイル |
to_H8.ht | 設定済みターミナルソフト |
<作成したプログラム> #include "kernel.h" #include "kernel_id.h" #include "h8_sci1.h" #include "h8-3052.h" volatile int manageA = 0, manageB = 0, manageC = 0; volatile int a = 0, b = 0, c = 0; volatile int OverFlagA = 0, OverFlagB = 0, OverFlagC = 0; #define putCharSCI1(x) Sci_PutChar(x) #define getCharSCI1() Sci_GetChar(x) /* メイン関数 */ int main() { Sci_Initialize(SCI_38400); SCI1_printf("HOS is starting\n"); sta_hos(); return 0; } /* 全体的な初期化ハンドラ */ void Initialize(VP_INT exinf) { act_tsk(TSKID_A); act_tsk(TSKID_B); act_tsk(TSKID_C); sta_cyc(CYCID_INT); } /* 割り込み周期ハンドラ */ void IntCYC(VP_INT exinf) { rot_rdq(1); TaskManage(); } /* 管理関数 */ void TaskManage(void) { a = manageA + a; b = manageB + b; c = manageC + c; if(manageA == 1) SCI1_printf("TASK--A\n"); if(manageB == 1) SCI1_printf("TASK--B\n"); if(manageC == 1) SCI1_printf("TASK--C\n"); if(a >= 2 && b >= 2 && c >= 2) { chg_pri(TSKID_C, 2); a = 0; b = 0; c = 0; } if(OverFlagA == 1) { chg_pri(TSKID_B, 1); chg_pri(TSKID_C, 1); b = 0; c = 0; a = 0; OverFlagA = 0; } if(a >= 2 && b >= 2 && c != 1) { chg_pri(TSKID_B, 2); chg_pri(TSKID_C, 1); a = 0; b = 0; c = 0; } if(OverFlagB == 1) { chg_pri(TSKID_A, 1); chg_pri(TSKID_C, 1); a = 0; b = 0; c = 0; OverFlagB = 0; } if(a >= 2 && c >= 2) { chg_pri(TSKID_C, 2); chg_pri(TSKID_B, 1); chg_pri(TSKID_C, 1); a = 0; b = 0; c = 0; } if(OverFlagC == 1) { chg_pri(TSKID_A, 1); chg_pri(TSKID_B, 1); a = 0; b = 0; c = 0; OverFlagC = 0; } manageA = manageB = manageC = 0; } /* Aタスク */ void TaskA(VP_INT exinf) { unsigned int roopA=0, roopendA=0; while(roopendA != 200) { roopA++; if(roopA == 65000) { roopendA++; roopA = 0; } manageA = 1; } OverFlagA = 1; } /* Bタスク */ void TaskB(VP_INT exinf) { unsigned int roopB=0; char roopendB=0; while(roopendB != 120) { roopB++; if(roopB == 65000) { roopendB++; roopB = 0; } manageB = 1; } OverFlagB = 1; } /* Cタスク */ void TaskC(VP_INT exinf) { int roopC=0; char roopendC=0; while(roopend != 100) { roopC++; if(roopC == 20000) { roopendC++; roopC == 0; } manageC = 1; } OverFlagC = 1; }<system.cfg> INCLUDE("\"sample.h\""); INCLUDE("\"ostimer0.h\""); INCLUDE("\"h8_sci1.h\""); /* HOS 独自の設定 */ HOS_KERNEL_HEAP(0); /* カーネルヒープの設定(省略時 0) */ HOS_TIM_TIC(1, 1); /* タイムティックの設定(省略時 1/1 ) */ HOS_MAX_TPRI(8); /* 最大優先度(省略時 16) */ HOS_MIN_INTNO(0); /* 割り込み番号の最小値(省略時 0) */ HOS_MAX_INTNO(256); /* 割り込み番号の最大値(省略時 0) */ HOS_MAX_TSKID(8); /* 最大タスクID番号(省略時静的生成に必要なだけ) */ /* OS タイマ */ ATT_INI({TA_HLNG, 0, OsTimer_Initialize}); ATT_ISR({TA_HLNG, 0, 24, OsTimer_TimerHandler}); /* SCI */ ATT_ISR({TA_HLNG, 0, 57, Sci_RxiHandler}); /* 全体的なイニシャライザ */ ATT_INI({TA_HLNG, 0, Initialize}); /* Aタスク */ CRE_TSK(TSKID_A, {TA_HLNG, 1, TaskA, 1, 64, NULL}); /* Bタスク */ CRE_TSK(TSKID_B, {TA_HLNG, 1, TaskB, 1, 64, NULL}); /* Cタスク */ CRE_TSK(TSKID_C, {TA_HLNG, 1, TaskC, 1, 64, NULL}); /* Interrupt周期ハンドラ */ CRE_CYC(CYCID_INT, {TA_HLNG, 1, IntCYC, 1000, 0});
◇実行結果
実行結果は次のようになります。
実行結果 |
HOS is starting |
4.プログラム例3:ラウンドロビンの現実的な利用方法
Aをセンサタスク,Bをセンサからの値を処理するタスク,Cをアクチュエータの制御タスクとした場合,次のようなシステムを考えることが出来る.
Aは常に周囲を見張り,目的の値を得るとタスクBを呼び出し処理をさせる.
Bは処理を行い,その結果をCに知らせると同時にCを呼び出す.
CはBの結果に従ってアクチュエータを駆動させる.
この場合,B
Cが動いているときもAは常に動いていなければならず,Aの処理が早くCの処理が遅いとA B
Cを同時に動かさなければならない.
これは,一つ上のプログラム例の具体的な話である.
ただし,今回のシステムでは全てのタスクが初めから動作を行っていません.
最初に動作を行っているタスクはAタスクのみで,B,Cタスクは条件を満たさなければ動作を開始しません.
具体的なタスク管理方法は上のプログラム例と同じですが,動作を行っているタスクの数によりタスク管理方法が変わってきます.
全てのタスクが動作を行っている場合は上のプログラム例と同じ管理方法で良いのですが,動作を行っているタスクが1つ,もしくは2つの場合は,ラウンドロビン管理システムのみで管理します.
そこで,タスクの状態を検査し,全てのタスクが実行状態・実行可能状態である場合のみTaskManage関数が機能するよう変更します.
そこで,以下のプログラムを作成した.
上でダウンロードしたサンプルプログラムフォルダにある, practiceフォルダにプログラム例が入っています。
ダウンロードしたフォルダは以下のような構成となっています。
井口のテンプレートフォルダ
ファイル | 内容 |
3052F.h | レジスタのアドレス配置ヘッダファイル |
crt0.s | スタートアップルーチン |
h8-3052.h | 小坂の標準ヘッダ |
h8_sci1.c | SCI1に関するファイル |
h8_sci1.h | SCI1に関するヘッダファイル |
h83052.x | リンカスクリプト |
mak_receiver.cmd | gcc.makの開始と書き込みプログラムを行うコマンドファイル |
ostimer0.c.c | ITU0を使ったOSタイマーに関するファイル |
ostimer0.h | ITU0を使ったOSタイマーに関するヘッダファイル |
gcc.mak | makeコマンドファイル環境によって多少変更してください. |
sample.c | タスク等のプログラムファイル |
sample.h | タスク等のヘッダファイル |
system.cfg | コンフィギュレータに指示を出すファイル |
vector.s | 割込みベクタファイル |
to_H8.ht | 設定済みターミナルソフト |
<作成したプログラム> #include "kernel.h" #include "kernel_id.h" #include "h8_sci1.h" #include "h8-3052.h" volatile int manageA = 0, manageB = 0, manageC = 0; volatile int a = 0, b = 0, c = 0; volatile int OverFlagB = 0, OverFlagC = 0; volatile int TaskAresult, TaskBresult, TaskCresult; #define putCharSCI1(x) Sci_PutChar(x) #define getCharSCI1() Sci_GetChar(x) /* メイン関数 */ int main() { Sci_Initialize(SCI_38400); SCI1_printf("HOS is starting\n"); sta_hos(); return 0; } /* 全体的な初期化ハンドラ */ void Initialize(VP_INT exinf) { act_tsk(TSKID_A); sta_cyc(CYCID_INT); } /* 割り込み周期ハンドラ */ void IntCYC(VP_INT exinf) { rot_rdq(1); TaskManage(); } /* 管理関数 */ void TaskManage(void) { T_RTST tsk_a, tsk_b, tsk_c; ref_tst(TSKID_A, &tsk_a); ref_tst(TSKID_B, &tsk_b); ref_tst(TSKID_C, &tsk_c); if(tsk_a.tskstat == TTS_RDY && tsk_b.tskstat == TTS_RDY && tsk_c.tskstat == TTS_RDY) { a = manageA + a; b = manageB + b; c = manageC + c; if(a >= 2 && b >= 2 && c >= 2) { chg_pri(TSKID_C, 2); a = 0; b = 0; c = 0; } if(a >= 2 && b >= 2 && c != 1) { chg_pri(TSKID_B, 2); chg_pri(TSKID_C, 1); a = 0; b = 0; c = 0; } if(OverFlagB == 1) { chg_pri(TSKID_A, 1); chg_pri(TSKID_C, 1); a = 0; b = 0; c = 0; OverFlagB = 0; } if(a >= 2 && c >= 2) { chg_pri(TSKID_C, 2); chg_pri(TSKID_B, 1); chg_pri(TSKID_C, 1); a = 0; b = 0; c = 0; } if(OverFlagC == 1) { chg_pri(TSKID_A, 1); chg_pri(TSKID_B, 1); a = 0; b = 0; c = 0; OverFlagC = 0; } manageA = manageB = manageC = 0; } } /* Aタスク */ void TaskA(VP_INT exinf) { unsigned int roopA=0, roopend=0; T_RTST tsk_b; while(1) { roopA++; if(roopA == 65000) { roopendA++; SCI1_printf("roopendA = %d\n", roopendA); roopA = 0; if(roopendA == 100) { SCI1_printf("!!!TaskA is over!!!\n"); TaskAresult = roopendA; ref_tst(TSKID_B, &tsk_b); if(tsk_b.tskstat != TTS_RDY) { act_tsk(TSKID_B); } roopA = 0; roopendA = 0; } } manageA = 1; } } /* Bタスク */ void TaskB(VP_INT exinf) { unsigned int roopB=0; char roopendB=0; T_RTST tsk_c; while(roopendB != 80) { roopB++; if(roopB == 65000) { roopendB++; SCI1_printf("roopendB = %d\n", roopendB); roopB = 0; } if(roopendB == 80) { SCI1_printf("!!!TaskB is over!!!\n"); TaskBresult = TaskAresult + roopendB; ref_tst(TSKID_C, &tsk_c); if(tsk_c.tskstat != TTS_RDY) { act_tsk(TSKID_C); } roopB = 0; roopendB = 0; } manageB = 1; } OverFlagB = 1; } /* Cタスク */ void TaskC(VP_INT exinf) { int roopC=0; char roopendC=0; while(roopendC != 20) { roopC++; if(roopC == 20000) { roopendC++; SCI1_printf("roopendC = %d\n", roopendC); roopC == 0; } if(roopendC == 20) { TaskCresult = roopendC + TaskBresult; if(TaskCresult == 200) SCI1_printf("All tasks were finished normally!!\n"); else SCI1_printf("Some tasks were not finished normally\n"); } roopC = 0; roopendC = 0; } manageC = 1; } OverFlagC = 1; }
プログラムの解説を行う前に,まずは実際に実行してみましょう
<コンパイルから実行までの作業>
(1)AKI-H8/3052マイコンの電源をOFFにしてください。
(2)AKI-H8/3052マイコンの動作モードをbootあるいはwriteモードにしてください。
(3)AKI-H8/3052マイコンの電源をONにしてください。
(4)gcc.makをmak_receiver.cmdにドラッグ&ドロップしてみてください。自動的に 書き込みまで行ってくれます。
(5)AKI-H8/3052マイコンの電源をOFFにしてください。
(6)AKI-H8/3052マイコンの動作モードをrunモードにしてください。
(7)ハイパーターミナルを立ち上げてください。
(8)AKI-H8/3052マイコンの電源をONにして,実行します。
◇実行結果
実行結果は次のようになります。
実行結果 |
HOS is starting |