H8/3052でHOSを用いたRTOSの基礎を修得

Copyright(C)31Dec2005
R Iguchi & T Kosaka CS TNCT


1.はじめに
この文書は,HOS(FreeのRTOS)を実際に利用することで, RTOSの基礎を修得します。 AKI-H8/3052の基本的なプログラミング技術があり, AKI-H8/3052でのRTOSを利用した プログラミングを学習したい方がプログラミングを行い, 動作するまでを解説しま す。なお,環境はWindowsXP,WindowsVista,Windows7を想定しています。

サンプルプログラムのダウンロード

DownLoad

本文書で記述してある作業の特徴

説明を読みながらサンプルプログラムを理解し, 実際に動かしてみる事でその確認をします。

前提

(1)Cygwinのシステムがc:\cygwinに導入されているものとします。
(2)H8/3052対応のクロスコンパイル環境が構築されているものとします。
(3)HOSの利用環境が構築されているものとします.
(4)転送ソフト「h8write.exe」を利用します。
(5)μITRON ver4.0の仕様書がすぐに参照できる環境にあるとします。
(6)ハイパーターミナルが使えること。

HOSは次のURLから入手できます.
https://sourceforge.jp/projects/hos

転送ソフト[h8write.exe]は次のURLから入手できます.
http://mes.sourceforge.jp/h8/writer-j.html
[h8write.exe]はgccのコンパイラと同じディレクトリに置くか, windowsでpathの通っているフォルダに入れておいてください。

μITRON ver4の仕様書は次のURLから入手できます.
http://www.assoc.tron.org/jpn/document.html#uITRON4.02

HOSをダウンロードし解凍するとフォルダhos-v4が得られます。これをC:\に置くことにします.
C:\hos-v4\documentのhos4cfg.txtを参考にし,コンフィギュレータのmakeを行ってください.そして,h83.txtを参考にし,ライブラリのmakeを行ってください.

2.hos-v4のサンプルプログラムの解説
hos-v4をダウンロードすると, はじめからh83のサンプルプログラムが入っています。そこで, まずはこのプログラムとsystem.cfgを理解し, RTOSにおけるタスク生成の仕組み等を理解します。
ただし, このプログラムではprintfが使えない等不便なところがあるため, 少し手を加えています。

上でダウンロードしたサンプルプログラムが,手を加えたプログラムです。

ダウンロードしたフォルダは以下のような構成となっています。


井口のテンプレートフォルダ

ファイル 内容
3052F.h レジスタのアドレス配置ヘッダファイル
crt0.s スタートアップルーチン
h8-3052.h 小坂の標準ヘッダ
h8_sci1.c SCI1に関するファイル
h8_sci1.h SCI1に関するヘッダファイル
h83052.x リンカスクリプト
mak_receiver.cmd gcc.makの開始と書き込みプログラムを行うコマンドファイル
ostimer0.c ITU0を使ったOSタイマーに関するファイル
ostimer0.h ITU0を使ったOSタイマーに関するヘッダファイル
gcc.mak makeコマンドファイル環境によって多少変更してください.
sample.c タスク等のプログラムファイル
sample.h タスク等のヘッダファイル
system.cfg コンフィギュレータに指示を出すファイル
vector.s 割込みベクタファイル
to_H8.ht 設定済みターミナルソフト

<サンプルプログラム sample.c>
#include "kernel.h"
#include "kernel_id.h"
#include "h8_sci1.h"
#include "h8-3052.h"

#define putCharSCI1(x)  Sci_PutChar(x)    //printfを使うためのマクロ
#define getCharSCI1()   Sci_GetChar(x)    //printfを使うためのマクロ

/* メイン関数 */
int main()
{
    /* SCIの初期化 */
    Sci_Initialize(SCI_38400);

    /* 開始メッセージ */
    SCI1_printf("HOS is starting\n");
    
    sta_hos();
    
    return 0;
}


/* 初期化ハンドラ */
void Initialize(VP_INT exinf)
{
    act_tsk(TSKID_SAMPLE1);
}


/* サンプルタスク */
void Task1(VP_INT exinf)
{
    SYSTIM st;
    while(1)
    {
        /* タイマ値取得 */
        get_tim(&st);
        
        /* タイマ値出力 */
        SCI1_printf("%5lu : Task1\n",st.ltime);
        
        /* 1秒待つ */
        dly_tsk(1000);
    }
}


<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});          /*「OsTimer_Initialize」は「ostimer0.c」にあるタイマ初期化ハンドラの名前*/
ATT_ISR({TA_HLNG, 0, 24, OsTimer_TimerHandler});    /*「OsTimer_TimerHandler」は「ostimer0.c」にあるタイマ割り込み関数の名前*/

/* SCI */
ATT_ISR({TA_HLNG, 0, 57, Sci_RxiHandler});          /*「Sci_RxiHandler」は「h8_sci1.c」にあるシリアル通信受信割り込み関数の名前*/

/* サンプルプログラム */
ATT_INI({TA_HLNG, 0, Initialize});                          /*「Initialize」は「sample.c」にある初期化ハンドラの名前*/
CRE_TSK(TSKID_SAMPLE1, {TA_HLNG, 1, Task1, 1, 256, NULL});  /*「Task1」は「sample.c」にあるtask1の関数名*/


サンプルプログラムの解説は後にして,まずは実際にh8/3052で動かしてみましょう。

<コンパイル作業前の注意>
(1)gcc.makの中身を見てみてください。
(1.1)HOSのルートディレクトリの名前はhos-v4ですが,もしhos-v4フォルダの置 いてある場所が次の設定と違っていたらhos-v4の置いてある場所を指してください。 (cygwinを利用しているので日本語は不可です。)
HOSROOT = c:/hos-v4
(1.2)コンパイラツールの名前が次のように設定されていますが,コンパイラ ツールの名前は,コンパイラツールをコンパイルする時に決まりますので,別の名前 になっていたらすべて変更してください。
CC = h8300-hms-gcc
CPP = h8300-hms-gcc
ASM = h8300-hms-gcc
LINK = h8300-hms-gcc
OBJCNV = h8300-hms-objcopy -O srec
コンパイラツールの名前としてメジャーなものにはh8300-hms-gcc : h8300-coff-gcc : h8300-coff-hms-gccがあります。
例えば,次のように変更します。
CC = h8300-coff-gcc
CPP = h8300-coff-gcc
ASM = h8300-coff-gcc
LINK = h8300-coff-gcc
OBJCNV = h8300-coff-objcopy -O srec
(2)mak_receiver.cmdの中身を見てみてください。
「rem コンパイル作業に必要なパスの追加を行います。」のところには
set mypath=C:\cygwin\usr\local\H8300\bin
と書いてありますが,自分のPCの設定に合わせてください。

<コンパイルから実行までの作業>
(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にして,実行します。

3.実行結果

実行結果は次のようになります。

実行結果

HOS is starting
    0 : Task1
 1003 : Task1
 2006 : Task1
 3009 : Task1
 4012 : Task1
 5015 : Task1
 6018 : Task1
 7021 : Task1

実行結果を見ると,main()で,開始のメッセージを表示し,その後,関数Task1()が呼び出されたように思えます。
プログラム本体(sample.c)を見るといくつか疑問がわきます。
(1)main()は,シリアル通信のための初期化(SCI1の初期化)をして,メッセージをシリアル通信で送信して,sta_hos()関数を呼び出して,終了してしまう。
(2)関数Initialize()はどこからも呼び出されていない。
(3)関数Task1()が起動しているようだが,起動する仕組みが不明。

では, sta_hos()では何をしているのか見てみましょう.hos-v4/src/kernel/hosにソースファイルがあります.

<sta_hos>
ER sta_hos(void)
{
	/* コンフィギュレーター変数の初期化 */
	kernel_cfg_init();

	/* カーネルの初期化 */
	mknl_ini_sys();		/* μカーネルシステムの初期化 */
	
	/* 擬似的に割り込みハンドラと見なす */
	kernel_int_cnt = 1;

	/* スタートアップ用コンテキストに移行 */
	mknl_sta_startup();
	
	/* 初期化 */
	kernel_ini_tim();	/* 時間管理機能の初期化 */
	kernel_cfg_start();	/* コンフィギュレーションの初期化 */
	
	/* 割り込みネスト回数クリア */
	kernel_int_cnt = 0;

	/* カーネル動作状態へ移行 */
	mknl_ext_startup();

	return E_OK;
}


◇sta_hos関数の説明

kernel_cfg_init();
・タスクを管理するためのタスクコントロールブロック(以後TCBと呼びます) の初期化や割り込みテーブルの追加が行われます。
mknl_ini_sys();
・アイドルタスクを生成します。
mknl_sta_strtup();
・スタートアップ用のスタック領域等のコンテキストを使用している。
kernel_ini_tim();
・コンフィギュレートすることで設定された時刻で初期化します。
kernel_cfg_start();
・タスクを初期化し, system.cfgにかかれた初期化ルーチンを実行します。 ここからsample.cのInitialize関数が呼び出されます.
mknl_ext_startup();
・最高優先度の実行可能タスクを調べ, ヒットすればコンテキストを切り替えて動作させます。 ここからTask1が呼び出されます.

先ほど不明だった部分は,sta_hos関数によって解決されていることが分かりました。

◇システム依存部の解決

sta_hos関数では,まずkernel_cfg_init()で初期化等を行うのですが,これらの作業はこれから実行するタスクの数や割り込みの種類によって変わってきます。
サンプルプログラムでは一つのタスクと時間管理とSCIが使われています。なので一つのTCBと時間割り込み・Sci割り込みが必要になります。ではその情報をどうやって知らせているのでしょうか?

こういったこれから作るシステムに依存する部分の設定を行うファイルがsystem.cfgなのです。 system.cfgにはこれらのタスクや割り込みのことが書かれています。

今回のサンプルプログラムのsystem.cfgを見てみると, OSタイマに関わる依存部,SCIに関わる依存部,サンプルプログラムに関わる依存部のことが書かれています。
依存部の解決には静的APIを用いるのですが,静的APIは複数用意されており, サンプルプログラムに関わる部分では,ATT_INIとCRE_TSKの二つの静的APIが使われています。
この二つの静的APIと,SCIに関わる部分で用いられているATT_ISRについて仕様書を見てみると,以下のことが分かります。

□ATT_INI({初期化ルーチンの属性, 初期化ルーチンの拡張情報, 初期化ルーチンの起動番地})

・引数の説明

初期化ルーチンの属性には,高級言語用のインタフェース(TA_HLNG)か アセンブリ言語用のインタフェース(TA_ASM)を選択することができ,それぞれ選択されたインタフェースで 初期化ルーチンを実行します。
拡張情報は,HOSがタスクや初期化ハンドラ等を呼び出すときに与えるパラメータです。実際に初期化ハンドラのVP exinfの値を調べてみると,system.cfgの拡張情報と一致します。
初期化ルーチンの起動番地は,初期化ハンドラ名と一致します。これは、関数名が起動番地と解釈されるためです。

・ATT_INIの効果の説明

この静的APIを用いることで,タスク等が動き出す前に初期化が必要な処理を行う関数を追加することができます。
ATT_INIはAttach_Initializationの略

□CRE_TSK(タスクID, {タスクの属性, タスクの拡張情報, タスクの起動番地, タスクの起動時の優先度, タスクスタック領域のサイズ, タスクスタック領域の先頭番地})

・引数の説明

タスクIDは,生成するタスクに付加するタスク識別番号のことである。
タスク属性には,ATT_INIと同じインタフェースを選択することができ,さらに,タスクを生成したときに実行可能状態として生成するTA_ACT属性も指定することができる。
タスクの拡張情報/タスクの起動番地はATT_INIの拡張情報と同じ役割である。
タスクの起動時の優先度は,その名の通り生成した直後のタスクに付加される優先度である。
system.cfgのHOS_MAX_TPRI(8)で優先度が設定されているので,優先度は1〜8の間で設定でき,1が最高優先度である。 タスクスタック領域のサイズは,生成するタスクが利用することのできるスタックのサイズである。タスクスタックサイズの計算方法はRTOS2で説明します。
タスクスタック領域の先頭番地は,H8のRAMのどの番地からタスクのスタック領域とするかの指定をするものである。NULLにするとコンフィギュレータが自動で設定してくれます。

・CRE_TSKの効果の説明

この静的APIを用いることで,タスクを生成することができます。
ちなみに,コンフィギュレータを通すときにタスクを生成する方法を静的生成と呼び, H8にプログラムを載せて,動作している最中にタスクを生成することを動的生成と呼びます。
CRE_TSKはCreate_Taskの略

□ATT_ISR({割り込みサービスルーチン属性, 割り込みサービスルーチンの拡張情報, 割り込みサービスルーチンを付加する割り込み番号, 割り込みサービスルーチンの起動番地})

・引数の説明

割り込みサービスルーチン属性/割り込みサービスルーチンの拡張情報は,ATT_INIと同じ役割である。
割り込みサービスルーチンを付加する割り込み番号とは,割り込みベクタのことである。
割り込みサービスルーチンの起動番地は,ATT_INIと同じ役割である。

・ATT_ISRの効果の説明

この静的APIを用いることで,割り込みサービスルーチンを追加することができる。
今回はSCI1とITU0を使っているので, この二つの割り込みサービスルーチンを追加しています。
ATT_ISRはAttach_Interrupt Service Routineの略

このような静的APIを用いることで,システムに依存する部分をコンフィギュレータによって解決しているのです..
また,サンプルプログラムで使われるタスクや初期化ハンドラを,OSをコンフィギュレートする時に知らせておくことで, プログラマーは関数宣言等を意識することなく,サンプルプログラムの作成が行えます。


4.コンフィギュレータとは?
前回の章でsystem.cfgを hosコンフィギュレータに通すことで依存部の解決を行っていると説明しましたが, これは今まで普通のCプログラムしか作ったことのない人にとっては新しい概念です。
何故コンフィギュレータが存在するのかについて, 説明をしていきたいと思います。


◇何故コンフィギュレータが存在するのか?


HOSは組み込みRTOSであるため, 汎用PCのように豊富なROMやRAMがあるわけではない。
そこで, これから作るシステムに必要なものを事前に教えてやり, 必要のないものはHOSの機能から捨ててしまおう。を実現するための仕組みがコンフィギュレーションシステムであり、 それを行うのがコンフィギュレータなのです。
こうすることで,少ないROM/RAMでも活躍できるOSとして存在することができるのです。
また,少しでもプログラマーの労力を少なくしようと色々な仕組みが施されており,プログラマーは命令を行うだけで,内部の詳しい情報を知らなくとも HOSの機能を利用することができるようになっています。


5.タスクとは?
組み込みRTOSでは関数とは別の概念としてタスクというものが存在します。このタスクとは何なのか?
そもそも何故今までのようにvoid Task1(VP_INT exinf)をmain関数から普通に動かしていけないのかについて,説明していきたいと思います。


◇何故故main関数からvoid Task1(VP_INT exinf)をただの関数として処理できないのか?


上のサンプルプログラムを見てみると, main関数はsta_hosを呼び出し, その後はOSの機能によってタスクを起動させ, 処理を行っています。
これをmain関数から,関数としてTask1を呼び出した場合どうなるのだろうか。
そもそも関数とタスクの違いとは何かというと, タスクとはある機能を実現するための関数・変数群であり, 関数とはタスクの一部分となりえる処理の集まりです。

例えば,Task1から何らかの関数呼び出したすると, その関数はTask1として扱われます。もしmain関数からTask1を呼び出した場合,Task1はタスクとしてではなく,main関数を構成する一つの関数として扱われます.

もしこれが,Task2, Task3と複数のタスクをmain関数から関数として呼び出した場合はどうなるのだろうか。

全てのタスクはmain関数に属する単なる関数と扱われるため, Task2の処理と中断させてTask3の処理を優先したいと思い ,Task2の処理を中断させたときに,mainタスクを中断させることになってしまうので,Task3の処理までも中断してしまうのです。
つまり, RTOSを使っている意味が全く無くなってしまうのです。

タスクとは現実世界でハードウェア等が行う機能を実現するための単位であり, RTOSはこの現実的な単位を管理するOSです。
よって, main関数からタスクを関数のように呼び出してしまうと意味がなくなってしまうのです。