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});

プログラムの解説を行う前に,まずは実際に実行してみましょう

<コンパイルから実行までの作業>
(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
 hello
 (何度もhelloと出力するので途中省略)
 SWタスク 00
 hello
 SWタスク 60
 hello
 SWタスク 5c
 hello

helloとSWタスクの間にLed点滅も行います.

◇プログラムの流れ

まず初期化関数が3つのタスクを実行可能状態へ状態を遷移させます。3つのタスクは初期化関数が全て終わってから実行を開始し,すぐにslp_tskで実行を停止し,待ち状態へ状態を遷移します。
次に周期ハンドラがそれぞれの時間になると実行を開始し,3つのタスクの待ち状態を解除し, タスクを実行可能状態へ状態を遷移させます。その後,タスクはslp_tskの次の行から実行を開始し, 1ループ実行すると またslp_tskによって実行を停止し,実行可能状態へ状態を遷移します。

3つの周期ハンドラが3つのタスクの待ち状態をそれぞれ決められた時間間隔で解除し, それぞれのタスクが1ループすると実行を停止することで, 決められた周期に1度だけタスクの内容を実行するようにしています。

◇周期ハンドラ

CRE_CYC(ID, {属性, 拡張情報, 起動番地, 起動周期, 起動位相})
という静的APIを用いる事により,周期ハンドラを生成することができます。
周期ハンドラを用いる事で,周期ハンドラの内容を,決められた時間間隔で実行することができます。

□引数の説明
IDは周期ハンドラの識別子です。
属性は,高級言語用のインタフェース(TA_HLNG)かアセンブリ言語用のインタフェース(TA_ASM)を選択することができ, さらに,周期ハンドラを生成したときに動作している状態とするTA_STA属性を選択することができ, 周期ハンドラの動作を開始する時に,周期ハンドラの起動位相を保存してをして,次に実行すべき時刻を決定するTA_PHS属性を選択することができる。
拡張情報は,周期ハンドラを起動するときに渡すパラメータです。
起動番地はプログラム中のハンドラ名と一致します。
起動周期は,周期ハンドラが動作を開始してから次に実行するまでの時間間隔です。周期ハンドラを停止しない限り,次の実行をすると,さらに次の実行をするまでの時間間隔がリセットされ続けます。
起動位相は,最初に周期ハンドラが動作を開始するまでの位相を設定できます。

◇セマフォ

CRE_SEM(ID, {属性, セマフォ資源数の初期値, セマフォ資源数の最大値})
を用いる という静的APIを用いる事により,セマフォを生成することができます。

□引数の説明
IDはセマフォの識別子です。
属性は,セマフォの待ち行列をFIFOとするTA_TFIFOか,タスクの優先度順にするTA_TPRIを選択することができます。
セマフォ資源数の初期値は,セマフォを生成したときにセマフォが持っている資源数のことです。
セマフォ資源数の最大値は,プログラムが動作する上でセマフォ資源数が増えた場合の最大値のことです。

3つのタスクのうち2つのタスクがSCIを利用するので, 片方のタスクがSCIを利用中にもう片方のタスクへ実行権が移ってしまうと, 競合が起こってしまいます。
そのため,セマフォによってSCIを管理することで,競合を防いでいます。
セマフォは生成時の初期値を設定しておき, タスクはその数値を獲得することでセマフォの許可を得ることが出来ます。今回はその許可が必要なものにSCIが当てはまり, 2つのタスクはそれぞれセマフォを獲得しなければSCIを利用することが出来ない仕組みになっています。
今回のセマフォの初期値は1となっており, どちらかのタスクがそれを獲得した状態で,もう片方のタスクへ実行権が移った場合,もう片方のタスクはセマフォを獲得しようと した時点で待ち状態へと状態が遷移され,セマフォ資源を獲得しているタスクへと実行権が移ることになります。

<注意>
周期ハンドラはタイマキューに繋がれたイベントとして処理され, 時間が来ると割り込みをかけて周期ハンドラの内容を実行させます。
今回のプログラムでは周期ハンドラが起動するときアイドルタスクが処理されているので問題はありませんが, 何らかのタスクを実行させている時に周期ハンドラが起動するのならば, 割り込み対策をきちんと行っておく必要があります。



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});

プログラムの解説を行う前に,まずは実際に実行してみましょう

<コンパイルから実行までの作業>
(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
 Task--A
 Task--B
 Task--C
 Task--A
 Task--B
 Task--C
 Task--A
 Task--B
 Task--A
 Task--B
 Task--A
 Task--C
 Task--A
 Task--C
 Task--A
 Task--B
 Task--C
 Task--A
 Task--B
 Task--C
 Task--A
 Task--B
 Task--A
 Task--B
 Task--A
 Task--C
 Task--A
 Task--A
 Task--C
 Task--A

□プログラムの流れ

全体的な初期化ハンドラですべてのタスクを実行可能状態へ状態を遷移させ,更に周期ハンドラの動作を開始させる。
タスクは実行可能にした順番でキューに繋がれます。今回はA→B→Cの順に繋がれます。
初期化ハンドラが終了すると,キューに繋がれた順にタスクが実行されるので,まずタスクAが実行を開始します。
Aを実行していると,周期ハンドラによりrot_rdqシステムコールが呼ばれ, タスク優先度が1の全てのタスクの中から,キューの先頭に繋がっているタスクををキューの最後に繋ぎ,次に繋がれていたタスクを実行状態にします。
rot_rdqシステムコールによりキューを回転させた後,TaskManage関数を呼び出すことで,タスクA B Cが何回実行したかを調べ, 実行した回数によりタスクの優先度を上げ下げし,rot_rdqシステムコールで順番が回ってくるか回ってこないかを 決定させます.
具体的には,rot_rdqシステムコールは引数で指定された優先度のタスクを対象とするので, chg_priシステムコールでタスクの優先度を変更すると,rot_rdqシステムコールの対象からはずすことができます.
優先度を下げられたタスクは,基本として優先度の高いタスクが全ての実行を停止するまで実行権を与えられることはありません。
また,rot_rdqシステムコールの対象となることもないので,またchg_priシステムコールにより優先度を上げられるまで, 実行することがなくなります。

□割り込みによるスタック回避

上で述べた方法が,この課題の一つであるラウンドロビン方式のやり方なのですが,この場合周期ハンドラによる 割込みはタスクの実行中に起こるので,スタックへの回避が必要になります.

タスクスタックの計算を行う前に, まずスタックの種類について説明しなければなりません。
スタックにはいくつかの種類があり,リセット直後のスタック・IDLEスタック・割込みスタック・タスクスタックが挙げられます.
このうちリセット直後のスタックはスタートアップルーチンで利用され, スタートアップルーチンを出るときに割込みスタック領域として引き継がれます.
IDLEスタックはアイドルループを実行している時に使用するスタック領域です.
割込みスタックは割込み処理を行う時に利用するスタック領域です.
タスクスタックはタスクが利用するスタック領域です.

□タスクスタックサイズの計算

タスクスタックを計算するには,タスクの内部で使用するスタックサイズ・ 割込み初期のスタックサイズ・関数のネスト(タスクから呼び出している関数のこと) に要するスタックサイズの計算が必要となります.
なぜ割込みスタックがあるのに割込み初期にタスクスタックを利用するか というと,割込みはまずタスクスタックに一時的に退避し,その後割込みスタックを利用し割込み処理を行い, タスクスタックの復帰を行うからです.

これで実際に手計算をしようとなると,SCI1_printfで必要なスタック量・ ネストを考えないで必要なタスクのスタック量・割込み初期のスタック量を計算せねばならず,いちいちタスクを作るたびに 計算するのは非常に面倒です.
ルネサス製のコンパイラツールを無償でなく購入された方は,callwalkerというツールで簡単 にタスクスタックサイズを調べることができるのですが,持っていない方は手計算で完璧に調べるか,適当にタスクスタックサイズ を設定するしかありません.
今回の256という数字は適当に設定しました.ただし,適当ではありますがなるべく最小にはとどめました.

サイズが分からないのに何故最小にとどめたと言えるかというと,今回system.cfgを見ると,タスクのスタックポインタ はNULLになっています.そのため,コンフィギュレータが自動でスタックの開始地点を設定するのですが,連続し てスタック領域は配置されます.
ですので,スタックをオーバーすると他のタスクに影響が出てくるのです.
スタッ クを破壊されたタスクはたいてい暴走します.そこでまず,大量のスタックをそれぞれのタスクに与え,徐々に減らしていき, 実際に実行してみて動作に影響が出ないギリギリのスタックサイズを割り当てました。
これだと,RAMがギリギリの状態でなければ動かないシステムでは使えないのですが,今回はRAMに余裕があったため適当 に設定しました.

ちなみにSCI1_printfを使わなければ,スタックサイズは64でも動きます.

<前回の補足説明>
今回4つのスタックを紹介しましたが,そのなかで割込みスタックとIDLEスタックの話は前回やりませんでした.
前回はIDLEループを使用し,割込みも行っていました.このとき何故IDLEスタックと割込みスタックの指定を行っていないのに動作したのでしょうか.
これは,IDLEスタックはsystem.cfgにHOS_IDL_STKという静的APIで指定が行われていないと,コンフィギュレータが自動的に64のスタックサイズをIDLEループにあたえるからです.
このため,アイドルループから割込みがかかったときに他のタスクのスタックを壊すことなく動作できていたのです.
割込みスタックは上でも説明しましたが,リセット直後のスタック領域が引き継がれ,それで十分であったため動作できていました.
IDLEループはプロセッサや処理系によって必要なスタック量が変わってくるので,一応設定できるようになっています.
こちらもRAMがギリギリの方は正確に調べておいたほうがいいと思います.

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
 roopendA = 1
 …
 roopendA = 100
 !!! TaskA is over!!!
 roopendA = 1
 roopendB = 1
 …
 roopendB = 80
 !!! TaskB is over !!!
 roopendA = 100
 !!! TaskA is over !!!
 roopendB = 1
 roopendA = 1
 roopendC = 1
 …
 roopendC = 20
 !!! TaskC is over !!!
 All tasks were finished normally!!

□プログラムの流れ

まず初期化ハンドラにより,タスクAが実行可能状態へ状態を遷移され,周期ハンドラが動作を開始します。
その後,タスクAが最後まで実行を行うとタスクBを実行可能状態へと状態を遷移させ,タスクA, Bが実行可能状態となります。
その後,タスクBが最後まで実行を行い,タスクCを実行可能状態へと状態を遷移させ,タスクAが最後まで実行を行い,タスクA, B, Cが実行可能状態となります。
そして,タスクCが最後まで実行を行い,タスクA, B, Cの計算結果に誤りがあったかどうかを確認し,実行を終了します。

□タスク管理方法

今回のプログラム例では, タスク管理でref_tst関数を使っています。 この関数を使うことで, タスクのステータスを参照しています。
タスクステータスにはタスクの状態に関するステータスがあり, 実行状態なのか実行可能状態なのか等を知ることが出来ます。
今回のプログラム例では, 実行可能状態にあるタスクが, Aのみの場合, A・Bのみの場合, A・Cのみの場合, A・B・Cの場合である ことが考えられます。
このうち, A・B・C全てが実行可能状態である場合以外はタスク管理処理を行わないようにプログラムされています。

また, タスクA, Bでもref_tstを使っています.これはタスクが実行中に実行要求を与えられると, 不具合が起こってしまうため, 実行要求を行う前にタスクの状態を調べておく事で, この問題を解決しています.

□まとめ

これで,フリーのRTOSで基礎の修得,を終わりとしたいと思いますが,ここで紹介していない機能もまだまだhosには存在します.
自分が作りたいシステムに特化したOSをコンフィギュレーションするためにも,一度μITRONの仕様書に目を通しておくことをお勧 めします.