タスクスイッチの基本部分を制作

【1】はじめに

この文書の目的は2つのタスクをスイッチャが一定時間ごとに切り替える動作を行なうプログラムを制作することである。

2つのタスクとは2つの無限ループの関数である。無限ループなのでそれぞれのタスクはなにもしなければ終わることが無い。
そこを上位のスイッチャが無理やり切り替えることになる。

2つのタスク

タスク1

タスク2

void task0(void)
{
    int cnt=0;
    while (1) {
        D_INT();  /*このデモでは通信中は割り込み禁止
                  にしてリソースの競合を避けている*/
        SCI1_printf("task0 %4d\n",cnt);
        E_INT();
        msecwait(20);
        cnt++;
        if (cnt==100) cnt=0;
    }
}
void task1(void)
{
    int cnt=1000;
    while (1) {
        D_INT();  /*このデモでは通信中は割り込み禁止
                  にしてリソースの競合を避けている*/
        SCI1_printf("task1 %4d\n",cnt);
        E_INT();
        msecwait(20);
        cnt++;
        if (cnt==1100) cnt=1000;
    }
}


期待される動作は,タスク1が一定時間
task0    1
task0    2
  :
を表示したら,タスク2に切り替わって,
task1    1001
task1    1002
  :
を表示し,これが交互に永遠に続くといったものである。(2つのタスクのラウンドロビン方式タスク切り替え)

タスクスイッチはCPUの仕組みに依存する部分があり,これをH8CPUに依存した形で作るものである。
なお,開発環境はインラインアセンブリ言語記述の使える日立評価版コンパイラVer.2を用いている。
インラインアセンブリ言語記述とはCプログラム中に無理やりアセンブリプログラムを挿入する方法であるが,
一般的には機種依存性が強いため使われることはない。

タスクの切り替えは,CPUの割り込み処理機構(例外処理)を使って行われるため,最初に割り込み処理機構の仕組み
を確認してからタスク切り替えの説明を行う。
インターバルタイマを用いたタイマー割り込みは,割り込み処理の1例であった。


【2】H8CPUにおける割り込み処理(例外処理)のとスタック

ダウンロード

2.1 タイマ割り込み割り込みを1回だけ行うプログラム

次のプログラムはタイマ割り込み割り込みを1回だけ行うものである。起動後,LEDが消灯しているのを確認した後,
EnterKeyを押すと,プログラムは先に進み,タイマ割り込みを可能にして,割り込みルーチンが変数doneを1にして
くれるのをループで待っている。タイマ割り込みは0.5秒周期なので,0.5秒経過するとタイマ割り込み関数が動作し,
LEDを点灯して,変数doneを1にする。処理が割り込みからmainに戻ると,タイマー割り込みが動作しなくなるようにして,
無限ループに入り,プログラムは停止したように見える。
ここまでは以前行なったタイマー割り込みプログラムの書き方の確認である。ただい,この割り込みは1回だけ起こる。
1回だけ起こることを確認するためにLEDを点灯させる。

テストプログラム taskstartDemo1.c

/**********************************************************
タイマ割り込み処理に入った瞬間の出来事
タイマ割り込みだけれど1回だけ割り込みが起こる
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"

volatile int done=0; /*最初は0,割り込み処理が終わったら1になる*/

main()
{
    initLed();   /*LEDユニットの初期化 点滅させるが,動作確認用*/
    initSCI1();  /*シリアル通信の初期化*/
    SCI1_printf("\n\n\n  *** interrupt function starting demo ***\n\n");
    SCI1_printf("Check LEDs. If you see LEDs are off, push [ENTER] key.\n");
    getCharSCI1();

    initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用*/
    E_INT();        /*CPU割り込み許可*/
    startTimer01();  /*時間割り込みタイマスタートch0,ch1*/

    while(!done); /*割り込み処理が終わるまでループ*/

    stopTimer01();   /*時間割り込みタイマーストップch0,ch1*/
    D_INT();         /*CPU割り込み禁止*/

    SCI1_printf("Done!   Check LEDs. If LEDs are lightend, OK!!\n");
    while(1);     /*無限ループ*/
}

/*この作業によってLEDが点灯し割り込み処理に入ったことが確認される*/
void lightenLED()
{
    SCI1_printf("Turning on LEDs.\n");
    turnOnLed(0);
    turnOnLed(1);
}

/*ベクトルテーブルの設定*/
#pragma asm
    .SECTION    MYVEC, DATA, LOCATE=H'000070
    .ORG        H'000070  ;IMIA1
    .DATA.L     _TimerIntFunction
    .SECTION    P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm

#pragma interrupt (TimerIntFunction)
void TimerIntFunction()
{
    lightenLED();
    done=1;
    ITU1.TSR.BIT.IMFA=0; /*タイマユニットに割込を受付けたことを知らせる*/
}

実行結果
起動後LEDが消えているのを確認し,EnterKeyを押すと,さらに進んで以下の
ようになって止まったように見える(mainの無限ループを実行している)。

  *** interrupt function starting demo ***

Check LEDs. If you see LEDs are off, push [ENTER] key.

Turning on LEDs.
Done!   Check LEDs. If LEDs are lightend, OK!!


2.2 割り込みルーチンをアセンブリ言語で記述する
 
「2.1」のプログラムの割り込みルーチンをアセンブリ言語で記述すると次のようになる。プログラムの動作は
変化せず,全く同じになる。
割り込みルーチンでレジスタR0を用いる場合は,呼び出し側がR0を使用中かもしれないので,本来は退避させる必要があるが,
mainではレジスタを使うような変数を操作していないため,ここでは省略している。

 

テストプログラム taskstartDemo2.c

/**********************************************************
タイマ割り込み処理に入った瞬間の出来事
タイマ割り込みだけれど1回だけ割り込みが起こる
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"

volatile int done=0; /*最初は0,割り込み処理が終わったら1になる*/

main()
{
    initLed();   /*LEDユニットの初期化 点滅させるが,動作確認用*/
    initSCI1();  /*シリアル通信の初期化*/
    SCI1_printf("\n\n\n  *** interrupt function starting demo ***\n\n");
    SCI1_printf("Check LEDs. If you see LEDs are off, push [ENTER] key.\n");
    getCharSCI1();

    initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用*/
    E_INT();        /*CPU割り込み許可*/
    startTimer01();  /*時間割り込みタイマスタートch0,ch1*/

    while(!done); /*割り込み処理が終わるまでループ*/

    stopTimer01();   /*時間割り込みタイマーストップch0,ch1*/
    D_INT();         /*CPU割り込み禁止*/

    SCI1_printf("Done!   Check LEDs. If LEDs are lightend, OK!!\n");
    while(1);     /*無限ループ*/
}

/*この作業によってLEDが点灯し割り込み処理に入ったことが確認される*/
void lightenLED()
{
    SCI1_printf("Turning on LEDs.\n");
    turnOnLed(0);
    turnOnLed(1);
}

/*ベクトルテーブルの設定*/
#pragma asm
    .SECTION    MYVEC, DATA, LOCATE=H'000070
    .ORG        H'000070  ;IMIA1
    .DATA.L     _TimerIntFunction
    .SECTION    P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm

#pragma asm
_TimerIntFunction:                   ; function: _TimerIntFunction()
    ;スタックポインタの値とスタック領域ををCの変数に保存する
    ;本来なら非割り込みルーチンはある仕事をしているはずなので,er0などのレジスタは
    ;使用中であり,割り込みルーチン内でこれらのレジスタを利用する場合は,レジスタの
    ;内容をスタックに退避させ,割り込みルーチン終了直前に復帰させるのが普通の使い方である。
    ;本プログラムでは,main関数は何も作業していないので,レジスタ退避を行っていない。

    ;  ここに来た証拠にLEDを点灯する関数をcall 
    jsr @_lightenLED

    ;  割り込み処理があったことをmainに知らせる
    mov.w  #1, r0
    mov.w  r0, @_done             ;Cの変数doneに1を代入

    ;タイマユニットに割り込みを受け付けたことを知らせる
    BCLR.B #0,@H'FFFF71:8         ;ITU1.TSR.BIT.IMFA=0;

    rte                    ; ReTurn from Exception
#pragma endasm

実行結果
起動後LEDが消えているのを確認し,EnterKeyを押すと,さらに進んで以下の
ようになって止まったように見える。

  *** interrupt function starting demo ***

Check LEDs. If you see LEDs are off, push [ENTER] key.

Turning on LEDs.
Done!   Check LEDs. If LEDs are lightend, OK!!


2.3 割り込みの仕組みをスタックポインタとスタックの使われ方まで掘り下げてみる
 
割り込み関数は,通常の関数に似ており,割り込み関数起動時に,それまで作業していたプログラム
のプログラムカウンタをスタックに保存し,作業が終わったら,スタックに保存されているアドレス
をたよりに,元の作業に復帰する。細かなところで,コンディションコードレジスタ(8bit)も同時
にスタックに保存しているところが,通常にサブルーチンコールと異なる。
割り込み関数からの復帰には,通常のリターン命令ではなく「rte」(ReTurn from Exception)が用い
られる。

CPUが割り込みを受け付ける状態( E_INT(); が実行されている)のとき,割り込みを受け付けると
すぐに次の3つのことを行う。
(1)現在作業中の次の命令のアドレス3バイトとCCR(コンディションコードレジスタ)
   1バイトの4バイトをスタックにPUSHする
   これは,割り込み処理が終了し,割り込み前の作業に戻るためである

(2)割り込みベクトルテーブルに設定されたアドレスに配置されているルーチン(割り
   込み関数)にジャンプする
(3)CPUを割り込み禁止状態にする(CCRの割り込み禁止ビットを変更する)
この時,スタックポインタ(レジスタer7)とスタックの状態は次のようになっている。

スタックの様子

割り込み関数では,自分の仕事をした後に,
「rte」(ReTurn from Exception)
で,スタックから戻り番地を読みだして元の場所に戻り,CCRもスタックから読み戻す。
図のようにスタックポインタが割り込み前の位置を指すようになり,スタックは未使用領域に戻る。

ためしに,プログラムでこのことを確かめてみよう。
プログラムの流れは,先の2つのプログラム(taskstartDem1.c,taskstartDem2.c)と同じである。
 また,main起動直後のスタックポインタとスタック領域のメモリを表示し,割り込み処理中でもス
タックポインタとスタック領域のメモリを表示している。さらに割り込み処理から抜け出した直後の
スタックポインタとスタック領域のメモリを表示している。
 スタックポインタとスタック領域の表示作業では,この作業を直接行わずに,インラインアセンブ
リ言語記述でスタックポインタとスタック領域を保存し,後から保存した値を表示している。(表示
したいところで表示ルーチン呼び出してしまうと,スタックやスタックポインタが変化してしまう。
そのため,一度別の領域に保存してから表示ルーチンで保存内容を表示している。)

 インラインアセンブリ言語記述をC関数内で書く場合は,使用したいレジスタがCプログラムで変数と
して使用されている可能性がある場合は,インラインアッセンブリ言語記述の先頭と末尾で使用中の
レジスタをスタックに退避・復帰しなければならない場合がある。

C言語での変数名stackpointer,stackは,アセンブリ言語中では_stackpointer,_stackのように記述
されているが,同じ変数である。

テストプログラム taskstartDemo3.c

/**********************************************************
タイマ割り込み処理に入った瞬間の出来事
タイマ割り込みだけれど1回だけ割り込みが起こる
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"

unsigned long int stackpointer; /*スタックポインタの値を保存する場所*/
unsigned char stack[0x30];      /*スタック領域を保存する場所*/

volatile int done=0; /*最初は0,割り込み処理が終わったら1になる*/

void dumpStackBuffer(void)
{
    unsigned long int address=0xFFEF0;
    int i;
    SCI1_printf("address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F\n");
    for (i=0; i<0x30; i++,address++) {
        if (i%0x10==0) SCI1_printf ("%05lx : ",address);
        SCI1_printf("%02x ",stack[i]);
        if (i%0x10==0xf) SCI1_printf("\n");
    }
}


main()
{
    initLed();   /*LEDユニットの初期化 点滅させるが,動作確認用*/
    initSCI1();  /*シリアル通信の初期化*/
    SCI1_printf("\n\n\n  *** interrupt function starting demo ***\n\n");
    SCI1_printf("Check LEDs. If you see LEDs are off, push [ENTER] key.\n");
    getCharSCI1();

    /*スタックポインタの値とスタック領域をCの変数に保存する アセンブリ言語でないと書けない*/
#pragma asm
    mov.l er7, @_stackpointer       ;er7(SP)の値をCの変数stackpointerに格納
                                    ;stackpointerはCの変数stackpointerのアドレスを意味するので
                                    ;「@」をつけて,そのアドレスの指す場所の意味にしている
    mov.l #H'FFEF0, er0             ;er0に0xffef0を格納
    mov.l #_stack,  er1             ;er1にCの変数stackのアドレスを格納
                                    ;すなわちCの配列変数stackの先頭アドレス
    mov.b #H'30:8,  r2h             ;r2hに0x30(8bit値)を格納
    bra   la02
la01:
    mov.b @er0+,     r2l            ;er0の指しているバイト値をr2lに格納し,その後er0の値を1増やす
    mov.b r2l,      @er1            ;r2lの値を,er1の指しているアドレスに保存
    inc.l #1,       er1             ;er1の値を1増やす
    dec.b           r2h             ;r2hの値を1減らす
la02:
    bne   la01                      ;直前の演算結果が0でなかったらla01へジャンプ
#pragma endasm

    SCI1_printf("Stack Area (main satrting)\n");
    dumpStackBuffer();
    SCI1_printf("StackPointer= %05lx\n\n", stackpointer);

    initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用*/
    E_INT();        /*CPU割り込み許可*/
    startTimer01();  /*時間割り込みタイマスタートch0,ch1*/

    while(!done); /*割り込み処理が終わるまでループ*/

    /*スタックポインタの値とスタック領域をCの変数に保存する アセンブリ言語でないと書けない*/
    /*上にあったアセンブリプログラムの部分と同じ*/
#pragma asm
    mov.l er7, @_stackpointer
    mov.l #H'FFEF0, er0
    mov.l #_stack,  er1
    mov.b #H'30:8,  r2h
    bra   lb02
lb01:
    mov.b @er0+,     r2l
    mov.b r2l,      @er1
    inc.l #1,       er1
    dec.b           r2h
lb02:
    bne   lb01
#pragma endasm

    stopTimer01();   /*時間割り込みタイマーストップch0,ch1*/
    D_INT();         /*CPU割り込み禁止*/

    SCI1_printf("Stack Area (main terminating)\n");
    dumpStackBuffer();
    SCI1_printf("StackPointer= %05lx\n\n", stackpointer);

    SCI1_printf("Done!   Check LEDs. If LEDs are lightend, OK!!\n");
    while(1);     /*無限ループ*/
}  /*end of main*/


/*この作業によってLEDが点灯し割り込み処理に入ったことが確認される*/
void lightenLED()
{
    SCI1_printf("Turning on LEDs.\n");
    turnOnLed(0);
    turnOnLed(1);
}


void printSituation(void)
{
    SCI1_printf("Stack Area in interrupt routine\n");
    dumpStackBuffer();
    SCI1_printf("StackPointer= %05lx\n\n", stackpointer);
}


/*ベクトルテーブルの設定*/
#pragma asm
    .SECTION    MYVEC, DATA, LOCATE=H'000070
    .ORG        H'000070  ;IMIA1
    .DATA.L     _TimerIntFunction
    .SECTION    P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm

#pragma asm
_TimerIntFunction:                   ; function: _TimerIntFunction()
    ;スタックポインタの値とスタック領域ををCの変数に保存する
    ;本来なら非割り込みルーチンはある仕事をしているはずなので,er0などのレジスタは
    ;使用中であり,割り込みルーチン内でこれらのレジスタを利用する場合は,レジスタの
    ;内容をスタックに退避させ,割り込みルーチン終了直前に復帰させるのが普通の使い方である。
    ;本プログラムでは,main関数は何も作業していないので,レジスタ退避を行っていない。

    ;この部分は上にあったアセンブリプログラムの部分と同じ

    mov.l er7, @_stackpointer;
    mov.l #H'FFEF0, er0
    mov.l #_stack,  er1
    mov.b #H'30:8,  r2h
    bra   lx02
lx01:
    mov.b @er0+,     r2l
    mov.b r2l,      @er1
    inc.l #1,       er1
    dec.b           r2h
lx02:
    bne   lx01

    ;  保存したスタックポインタの値とスタック領域を表示する 
    jsr @_printSituation

    ;  ここに来た証拠にLEDを点灯 
    jsr @_lightenLED

    ;  割り込み処理があったことをmainに知らせる
    mov.w  #1, r0
    mov.w  r0, @_done             ;Cの変数doneに1を代入

    ;タイマユニットに割り込みを受け付けたことを知らせる
    BCLR.B #0,@H'FFFF71:8         ;ITU1.TSR.BIT.IMFA=0;

    rte                    ; ReTurn from Exception
#pragma endasm

実行結果
起動後LEDが消えているのを確認し,EnterKeyを押すと,さらに進んで以下の
ようになって止まったように見える。
main起動直後(main starting),割り込みルーチン内に入った時(in interrupt routin),
mainに戻ってきた時(main terminating)の3か所での出力が見える。
青字のところは,前の2つのプログラムと同じ内容である。

  *** interrupt function starting demo ***

Check LEDs. If you see LEDs are off, push [ENTER] key.

Stack Area (main satrting)
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ffef0 : 00 00 00 00 00 00 00 04 00 00 0c b4 00 0f ff 0c
fff00 : 00 00 00 00 00 00 0e ea 00 00 0e f0 00 00 01 56
fff10 : ff ff ff ff ff ff ff ff ff ff ff ff 7f 00 00 00
StackPointer= fff0c

Stack Area in interrupt routine
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ffef0 : 00 00 00 04 00 00 0c b4 00 0f ff 08 00 00 00 00
fff00 : 00 00 0f 42 00 00 11 f4 44 00 0f 5e 00 00 01 56
fff10 : ff ff ff ff ff ff ff ff ff ff ff ff 7f 00 00 00
StackPointer= fff08

Turning on LEDs.
Stack Area (main terminating)
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ffef0 : 00 00 0c b4 00 0f ff 04 00 00 00 00 00 00 00 00
fff00 : 00 00 00 00 00 00 10 50 44 00 0f 5e 00 00 01 56
fff10 : ff ff ff ff ff ff ff ff ff ff ff ff 7f 00 00 00
StackPointer= fff0c

Done!   Check LEDs. If LEDs are lightend, OK!!

mapファイルの抜粋
      _main                                H'00000EC4           DAT
      _printSituation                      H'00000FF0           DAT

スタートアップルーチンでは
        MOV.L   #H'FFF10,ER7 ;set stack pointer
を実行しているため,最初はstackpointer=fff10であったはずである。
スタートアップルーチンから,このプログラムのmainへサブルーチンコール(jsr命令)でmainに飛んできているので,
この時に,スタートアップルーチンの戻り番地がスタックの先頭(実際には最後尾)に積まれているはずである。

実行結果ではmain中でスタックとスタックポインタを保存した時は,stackpointer=fff0cであった。
スタックは4バイト消費され,00000156が保存されている。これがスタートアップルーチンへの戻り番地である。

割り込みルーチン起動直後ではstackpointer=fff08となり,さらにスタックは4バイト使用されている。
44000f5eのうち44はCCRの値であり,000f5eは,タイマ割り込みが生じた時の戻りアドレスである。
実際mapファイルで確認するとmainは000ec4から始まっており,mainの次の関数printSituationは000ff0から始まって
いるため,mainの無限ループを回っていた時に割り込みがかかり,戻りアドレスとして000f5eが格納されているのは
妥当である。

mainに戻ってきた時には,スタックポインタは割り込み処理以前の状態に戻っている。スタックには消されずに残骸
が残っている。

割り込みルーチン起動直後のスタックの様子は,マニュアルに書かれているとおりであることが検証された。

 

【3】割り込み時にタスクを切り替えるアイディア

2つのタスクを交替で実行するのは,それぞれのタスクを実行しているCPUの各レジスタの値やスタック領域をメモリ上に退避させたり復元させたりす ることで実現できる。あるタスクを実行中のCPUの各レジスタの値やスタック領域は,コンピュータ内部のタスク実行環境であり,コンテキストと呼ばれてい る。

タイマ割り込みを使って,2つのタスクを交替で実行するには,スタック領域,すべての汎用レジスタ(ER0..ER7,ER7はスタックポインタ),CCR(コンディションコードレジスタ),PC(プログラムカウンタ)を退避させたり復元させたりすればよい。

割り込みの仕組み上,PCおよびCCRはすでにスタックに載っているため,スタック領域と汎用レジスタのみ退避させたり復元させたりすればよい。すなわち,コンテキストはスタック領域と汎用レジスタ領域で構成すればよい。
またスタック領域は,あらかじめタスクごとに使用されるバイト数を予測して,割り当てておけばよい。
そうすると,スタック領域は他の場所に保存する必要がなくなり,スタックポインタでいつでも使えるようになっているので,コンテキストとして退避・復帰させるのは汎用レジスタのみでよいことになる。

タイマ割り込み時に,タスク0からタスク1に切り替えるには,(逆の切り替えも同様に考えればよい)

(1)スタックポインタはタスク0のスタック領域の次に行うべき命令のアドレスが保存されてい
   る場所を示した状態になっているので,これを保全した上で,全汎用レジスタをタスク0コ
   ンテキスト保存領域に保存。
(2)タスク1コンテキスト保存領域の内容を全汎用レジスタに復帰,この時スタックポインタは
   タスク1のスタック領域の次に行うべき命令のアドレス(戻り番地+CCR)が保存されている
   場所を示した状態になっている。
(3)割り込み処理から復帰(rte)命令でタスク1の続きの処理に入る

とすればよい。

タスクを最初に起動するときも,割り込みから復帰するふりをしなくてはならないため,各タスクのコンテキストは,割り込みルーチン入ってきた時の状 況を作っておかなければならない。すなわち,タスクの先頭番地を実行しようとした時に,「割り込みがかかってしまって先頭番地とCCRがスタックに積まれ てしまっている状態」を作っておかなければならない。汎用レジスタはどうでもよいが,スタックポインタは積まれている「戻り番地(タスク関数の先頭番地) とCCR」の4バイトの先頭(CCRのところ)を指すように作らなければならない。
スタックの状態を次に示す。


上が若いアドレス,スタックポインタが指しているところ以下が有効なデータ
ER0-ER7はそれぞれ4バイトで退避される

コンテキストの保存領域とスタックの状態

この考え方に従って作ったデモプログラムを次に示す。
割り込み処理は関数TaskSwitcher()(アセンブリ言語表現は_TaskSwitcher)である。(レジスタを壊さないように工夫されている)

TaskSwitcher()の作業でタスク0からタスク1に切り替わる時の手順
(逆方向の切り替えも同様になる)

タスク切り替えルーチン(割り込み処理ルーチン)に来た時
スタックの状態
「タスク0の戻り番地とCCR」がタスク0のスタックの一番上にある。



どちらのコンテキスト保存領域にしまうべきかを考えて,
スタックポインタ(ER7)をタスク0のER7保存場所に保存し,
汎用レジスタER6..ER0をpush命令でタスク0のレジスタ退避領域へ保存する。
(ここまででスタック0のコンテキストの保存完了)


タスクスイッチャが自分の仕事を行うことができるところ
(しかしここでは何もしない)



(ここからはタスク1起動準備)
スタックポインタを一時的に変更して
スタック1よりpop命令により汎用レジスタER0..ER7を復帰
スタックの性質により、popの順番が逆になることに注意



この段階で「タスク1の戻り番地とCCR」がタスク1のスタックの一番上にある。



「rte」命令でタスク1の実行に戻る


ダウンロード

ここで2つのプログラム例を提示する。
1つは,2つのタスクを切り替えながら作業を行うプログラムである。もう1つは,基本的に動作は同じだが,
コンテキスト領域やスタックを表示しながら,2つのタスクを行うプログラムである。

3.1 2つのタスク切り替えを行うプログラム

タイマ割り込みにより,周期的なタスク切り替えが行われる。タイマ割り込みによりタスクスイッチルーチン
TaskSwitcher)が起動し,タスク切り替えを行なっている。
タスク切り替え動作によるタスク終了時には,その時実行中のプログラムカウンタが,スタックに入り,スタ
ックポインタがそのアドレスを指すようになっている状態で保存される。

mainでは,2つのタスクのコンテキストの準備を行なった後に最初のタスクを起動する。
int tasksw=0; は現在起動中のタスク番号を保持している。
最初のタスクを起動するときには,タスク(関数)の先頭アドレスをスタックポインタに載せて,割り込み
ルーチン用のリターン命令を行なって,最初のタスクが起動されるが,直接タスク(関数)の先頭アドレス
をスタックポインタに載せるわけではなく,コンテキストのスタックポインタ領域に載せておく。
タスク切り替え動作によるタスク終了時には,その時実行中のプログラムカウンタが,スタックに入り,スタ
ックポインタがそのアドレスを指すようになっている状態で保存されるので,その逆のことをすることによっ
て,タスクの最初の起動を行っていることになる。
この作業は,mainの最後のところで行われ,mainには二度と戻っては来ない。

その後一定時間経つとタイマ割り込みが起こり,タスクスイッチルーチン(TaskSwitcher)が起動し,タスク
切り替えを行なっている。
taskswで,直前まで動作していたタスク番号を読み取り,その番号のコンテキスト
バッファにコンテキストを保存する。続いて次の起動すべきタスクのコンテキストをコンテキストバッファか
ら取り出して,SP領域に起動スべきアドレスをセットして,割り込み処理をrteで終了すると,次のタスクが
始まる。

タスク切り替えデモプログラム taskSwitcherDemo1.c

/**********************************************************
インターバルタイマ割り込みによって2つのタスクの切り替えを行う
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"

/*************** 2つのタスクの実体 ここから ********************/
void msecwait(int msec)
/*mesc間なにもしない時間稼ぎ関数*/
{
    int i,j;
    for (i=0;i<msec;i++) {
        for (j=0;j<1588;j++);    /*1588は実測によって求めた値*/
    }
}

void task0(void)
{
    int cnt=0;
    while (1) {
        D_INT();  /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
        SCI1_printf("task0 %4d\n",cnt);
        E_INT();
        msecwait(20);
        cnt++;
        if (cnt==100) cnt=0;
    }
}

void task1(void)
{
    int cnt=1000;
    while (1) {
        D_INT();  /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
        SCI1_printf("task1 %4d\n",cnt);
        E_INT();
        msecwait(20);
        cnt++;
        if (cnt==1100) cnt=1000;
    }
}

/*************** 2つのタスクの実体 ここまで ********************/

typedef union {
    unsigned char Bccr;
    unsigned long int returnAddress;
} stack_t;

typedef struct {
    unsigned char stack[128]; /*stackとstack0の合わさったものが実際のスタック*/
    stack_t stack0;           /*初期状態はここに戻りアドレスとCCRが入っている*/
    unsigned long int Ber0;   /*ここより下のBer0~Ber7は各レジスタの保存領域*/
    unsigned long int Ber1;
    unsigned long int Ber2;
    unsigned long int Ber3;
    unsigned long int Ber4;
    unsigned long int Ber5;
    unsigned long int Ber6;
    unsigned long int Ber7; /*初期状態ではstack0をさしている*/
} context_t;

context_t context0,context1; /*コンテキストバッファの実体*/

int tasksw=0; /* 0 または 1 :どちらのタスクが実行されているかを示している*/
unsigned long int Ber0;  /*ER0の退避場所 タスクスイッチャ起動直後にちょっと使う*/
const unsigned long int buff0Address=(unsigned long int)&context0.Ber0;
const unsigned long int buff1Address=(unsigned long int)&context1.Ber0;
const unsigned long int stackPtr0Address=(unsigned long int)&context0.Ber7;
const unsigned long int stackPtr1Address=(unsigned long int)&context1.Ber7;
unsigned long int systemstackPtr;  /*スイッチャ用スタックポインタ退避場所*/


main()
{
    initLed();   /*LEDユニットの初期化 点滅させるが,動作確認用*/
    initSCI1();  /*シリアル通信の初期化*/
    SCI1_printf("\n\n\n  *** task switcher demo ***\n\n");
    initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用 このタイミングでタスク切り替えが起こる*/
    E_INT();        /*CPU割り込み許可*/

    /*コンテキストバッファにコンテキストを2つの作成*/
    context0.stack0.returnAddress=(unsigned long int)task0;
    context0.Ber7=(unsigned long int)&(context0.stack0);

    context1.stack0.returnAddress=(unsigned long int)task1;
    context1.Ber7=(unsigned long int)&(context1.stack0);

    startTimer01();  /*時間割り込みタイマスタートch0,ch1*/

    /*ここまでで2つのタスクの起動準備完了。最初はtask0を起動する*/
    tasksw=0;
#pragma asm
    mov.l  er7,  @_systemstackPtr  ;タスクスイッチャのスタックポインタを退避
    mov.l  @_stackPtr0Address, er0 ;タスク0のスタックポインタを設定(復帰と同じ手順)
    mov.l  @er0, er7               ;     (er0経由でer7(=SP)に設定)
    rte                            ;ReTurn from Exception によりタスク0起動
#pragma endasm

} /*end of main*/


/*ベクトルテーブルの設定*/
#pragma asm
    .SECTION    MYVEC, DATA, LOCATE=H'000070
    .ORG        H'000070  ;IMIA1
    .DATA.L     _TaskSwitcher
    .SECTION    P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm

/*
インターバルタイマ割り込みで起動するルーチン/タスクスイッチャ本体
タスク切り替えの作業を行う
TaskSwitcherでは次のことを行う
(1)現在のタスクがtask0,task1のどちらであったか検出
(2)現在の全レジスタをcontext0,またはcontext1として保存
(3)次のタスクにむけてcontext0,またはcontext1を全レジスタに載せる
(4)rteで新タスクを起動(割り込みから戻るふりをする)

ただし,タスクスイッチャが起動した時点では,すべてのレジスタは,
スイッチャ起動前に動作していたタスクが使用中である。
そのため,スイッチャが起動直後において,全レジスタを保存する前にいくつかの
レジスタを使用する場合は,そのレジスタを退避し,作業が終了した時点で復帰させ
全レジスタをコンテキスト保存場所に保存する手順となる。以下の★参照
*/
#pragma asm
_TaskSwitcher:                   ; function: TaskSwitcher
    ;タイマユニットに割り込みを受け付けたことを知らせる
    BCLR.B #0,@H'FFFF71:8         ;ITU1.TSR.BIT.IMFA=0;

    ; ** 旧コンテキスト保存 **
    mov.l  er0,  @_Ber0  ;直前タスク番号確認のためER0を使いたいのでER0を退避★↓
    mov.w  @_tasksw, r0
    cmp.w  #1, r0         ;r0が1であるかどうか調べる
    beq    RETUNEDFROMTASK1  ;直前がタスク1,次がタスク0ならジャンプ
RETUNEDFROMTASK0:
    mov.w #1, r0          ;次のタスクはタスク1
    mov.w  r0,  @_tasksw  ;この段階で変数taskswの中身は次のコンテキスト番号
    ;直前はタスク0だったのでコンテキスト0に保存する準備
    mov.l  @_stackPtr0Address, er0
    bra SAVECONTEXT
RETUNEDFROMTASK1:
    mov.w #0, r0          ;次のタスクはタスク0
    mov.w  r0,  @_tasksw  ;この段階で変数taskswの中身は次のコンテキスト番号
    ;直前はタスク1だったのでコンテキスト1に保存する準備
    mov.l  @_stackPtr1Address, er0
SAVECONTEXT:
    ;コンテキストの保存
    mov.l  er7, @er0  ;er0の指しているアドレスにer7(=SP)を保存する
    mov.l  er0, er7   ;er0の値をer7(=SP)に設定する
    mov.l  @_Ber0,  er0                            ;ER0を使い終えたので復帰★↑
    push.l er6
    push.l er5
    push.l er4
    push.l er3
    push.l er2
    push.l er1
    push.l er0

    ;この段階で,すべてのレジスタをスイッチャが自由に使えるようになった

    ; ** 次に実行すべき新コンテキスト設定 **
    mov.w  @_tasksw, r0  ;変数taskswの中身は次のタスク番号である
    cmp.w  #1, r0
    beq    SETREADYTASK1  ;新タスクが1ならジャンプ
SETREADYTASK0:
    ;コンテキスト0用のスタックを指すようにスタックポインタを設定
    mov.l  @_buff0Address, er7
    bra SETCONTEXTANDGO
SETREADYTASK1:
    ;コンテキスト1用のスタックを指すようにスタックポインタを設定
    mov.l  @_buff1Address, er7
SETCONTEXTANDGO:
    ;コンテキストを復旧させて,次のタスク起動
    pop.l  er0
    pop.l  er1
    pop.l  er2
    pop.l  er3
    pop.l  er4
    pop.l  er5
    pop.l  er6
    pop.l  er7

    rte                            ;ReTurn from Exception により新タスク起動

#pragma endasm
実行結果

  *** task switcher demo ***

task0    0
task0    1
task0    2
task0    3
task0    4
task0    5
task0    6
task0    7
task0    8
task0    9
task0   10
task0   11
task0   12
task0   13
task0   14
task0   15
task0   16
task0   17
task0   18
task0   19
task0   20
task0   21
task0   22
task0   23
task0   24
task0   25
task0   26
task0   27
task0   28
task0   29
task0   30
task0   31
task0   32
task0   33
task0   34
task0   35
task0   36
task0   37
task0   38
task0   39
task0   40
task0   41
task0   42
task0   43
task0   44
task0   45
task0   46
task0   47
task0   48
task1 1000
task1 1001
task1 1002
task1 1003
task1 1004
task1 1005
task1 1006
task1 1007
task1 1008
task1 1009
task1 1010
task1 1011
task1 1012
task1 1013
task1 1014
task1 1015
task1 1016
task1 1017
task1 1018
task1 1019
task1 1020
task1 1021
task1 1022
task1 1023
task1 1024
task1 1025
task1 1026
task1 1027
task1 1028
task1 1029
task1 1030
task1 1031
task1 1032
task1 1033
task1 1034
task1 1035
task1 1036
task1 1037
task1 1038
task1 1039
task1 1040
task1 1041
task1 1042
task1 1043
task1 1044
task1 1045
task1 1046
task1 1047
task1 1048
task0   49
task0   50
task0   51
task0   52
task0   53
task0   54
task0   55
task0   56
task0   57
task0   58
task0   59
task0   60
task0   61
task0   62
task0   63
task0   64
task0   65
task0   66
task0   67
task0   68
task0   69
task0   70
task0   71
task0   72
task0   73
task0   74
task0   75
task0   76
task0   77
task0   78
task0   79
task0   80
task0   81
task0   82
task0   83
task0   84
task0   85
task0   86
task0   87
task0   88
task0   89
task0   90
task0   91
task0   92
task0   93
task0   94
task0   95
task0   96
task0   97
task1 1049
task1 1050
task1 1051
task1 1052
task1 1053
task1 1054
task1 1055
task1 1056
task1 1057
task1 1058
task1 1059
task1 1060
task1 1061
task1 1062
task1 1063
task1 1064
task1 1065
task1 1066
task1 1067
task1 1068
task1 1069
task1 1070
task1 1071
task1 1072
task1 1073
task1 1074
task1 1075
task1 1076
task1 1077
task1 1078
task1 1079
task1 1080
task1 1081
task1 1082
task1 1083
task1 1084
task1 1085
task1 1086
task1 1087
task1 1088
task1 1089
task1 1090
task1 1091
task1 1092
task1 1093
task1 1094
task1 1095
task1 1096
task1 1097
task0   98
task0   99
task0    0
task0    1
task0    2
task0    3
task0    4
task0    5
task0    6
task0    7
task0    8
task0    9

 

3.2 状況記述を加えたタスク切り替えプログラム

次のプログラムは,「3.1」のプログラムにおいて,動作を観察するためにタスク切り替え時の,スタックやスタックポ
インタなどを表示しながら作業するものである。
実行結果ではタスク切り替えが成功している様子がわかる。切り替え時に2つのコンテキスト保存状態の表示を行なっている。
また,タスク切り替え処理に入ったことを確認するために,LEDを点滅させている。

Cのプログラム中で 関数printContext(); を呼び出すのは差し支えないが,アセンブリプログラム中から jsr @_printContext
で 関数printContext(); を呼び出す場合は,ER0などのレジスタの内容が変更されても差し支えない状態のときに呼び出す必
要がある。あるいは必要なレジスタは退避させて呼び出す必要がある。本プログラムではレジスタの内容が変更されても差し
支えない状態のときに呼び出している。


タスク切り替えデモプログラム taskSwitcherDemo2.c

/**********************************************************
インターバルタイマ割り込みによって2つのタスクの切り替えを行う
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"

/*************** 2つのタスクの実体 ここから ********************/
void msecwait(int msec)
/*mesc間なにもしない時間稼ぎ関数*/
{
    int i,j;
    for (i=0;i<msec;i++) {
        for (j=0;j<1588;j++);    /*1588は実測によって求めた値*/
    }
}

void task0(void)
{
    int cnt=0;
    while (1) {
        D_INT();  /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
        SCI1_printf("task0 %4d\n",cnt);
        E_INT();
        msecwait(20);
        cnt++;
        if (cnt==100) cnt=0;
    }
}

void task1(void)
{
    int cnt=1000;
    while (1) {
        D_INT();  /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
        SCI1_printf("task1 %4d\n",cnt);
        E_INT();
        msecwait(20);
        cnt++;
        if (cnt==1100) cnt=1000;
    }
}

/*************** 2つのタスクの実体 ここまで ********************/

typedef union {
    unsigned char Bccr;
    unsigned long int returnAddress;
} stack_t;

typedef struct {
    unsigned char stack[128]; /*stackとstack0の合わさったものが実際のスタック*/
    stack_t stack0;           /*初期状態はここに戻りアドレスとCCRが入っている*/
    unsigned long int Ber0;   /*ここより下のBer0~Ber7は各レジスタの保存領域*/
    unsigned long int Ber1;
    unsigned long int Ber2;
    unsigned long int Ber3;
    unsigned long int Ber4;
    unsigned long int Ber5;
    unsigned long int Ber6;
    unsigned long int Ber7; /*初期状態ではstack0をさしている*/
} context_t;

context_t context0,context1; /*コンテキストバッファの実体*/

int tasksw=0; /* 0 または 1 :どちらのタスクが実行されているかを示している*/
unsigned long int Ber0;  /*ER0の退避場所 タスクスイッチャ起動直後にちょっと使う*/
const unsigned long int buff0Address=(unsigned long int)&context0.Ber0;
const unsigned long int buff1Address=(unsigned long int)&context1.Ber0;
const unsigned long int stackPtr0Address=(unsigned long int)&context0.Ber7;
const unsigned long int stackPtr1Address=(unsigned long int)&context1.Ber7;
unsigned long int systemstackPtr;  /*スイッチャ用スタックポインタ退避場所*/

/*コンテキストバッファの中のレジスタの表示 確認用*/
void printContext1(context_t *con)
{
    SCI1_printf("ER0 %08lx    ",con->Ber0);
    SCI1_printf("ER4 %08lx\n",con->Ber4);
    SCI1_printf("ER1 %08lx    ",con->Ber1);
    SCI1_printf("ER5 %08lx\n",con->Ber5);
    SCI1_printf("ER2 %08lx    ",con->Ber2);
    SCI1_printf("ER6 %08lx\n",con->Ber6);
    SCI1_printf("ER3 %08lx    ",con->Ber3);
    SCI1_printf("ER7 %08lx\n",con->Ber7);
}

/*スタックのおわり周辺の表示*/
void printStackArea(context_t *con)
{
    unsigned long int address=(unsigned long int)(&(con->Ber0));
    int i;
    address-=0x20;
    address&=0xfffffff0;
    SCI1_printf("tail of stack + 1 =%05lx\n",&(con->Ber0));
    SCI1_printf("address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F\n");
    for (i=0; i<0x30; i++,address++) {
        if (i%0x10==0) SCI1_printf ("%05lx : ",address);
        SCI1_printf("%02x ",*(unsigned char*)address);
        if (i%0x10==0xf) SCI1_printf("\n");
    }
}

/*コンテキストバッファのレジスタ表示関数 確認用*/
void printContext(void)
{
    SCI1_printf("  context for task0\n");
    printContext1(&context0);
    printStackArea(&context0);
    SCI1_printf("  context for task1\n");
    printContext1(&context1);
    printStackArea(&context1);
}

main()
{
    initLed();   /*LEDユニットの初期化 点滅させるが,動作確認用*/
    initSCI1();  /*シリアル通信の初期化*/
    SCI1_printf("\n\n\n  *** task switcher demo ***\n\n");
    initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用 このタイミングでタスク切り替えが起こる*/
    E_INT();        /*CPU割り込み許可*/

    /*コンテキストバッファにコンテキストを2つの作成*/
    context0.stack0.returnAddress=(unsigned long int)task0;
    context0.Ber7=(unsigned long int)&(context0.stack0);
    SCI1_printf("task0,stack,er7=%p %08lx %08lx\n", task0, context0.stack0.returnAddress, context0.Ber7);

    context1.stack0.returnAddress=(unsigned long int)task1;
    context1.Ber7=(unsigned long int)&(context1.stack0);
    SCI1_printf("task1,stack,er7=%p %08lx %08lx\n", task1, context1.stack0.returnAddress, context1.Ber7);
   
    printContext();

    startTimer01();  /*時間割り込みタイマスタートch0,ch1*/

    /*ここまでで2つのタスクの起動準備完了。最初はtask0を起動する*/
    tasksw=0;
#pragma asm
    mov.l  er7,  @_systemstackPtr  ;自分のスタックポインタを退避
    mov.l  @_stackPtr0Address, er0 ;タスク0のスタックポインタを設定(復帰と同じ手順)
    mov.l  @er0, er7               ;     (er0経由でer7(=SP)に設定)
    rte                            ;ReTurn from Exception によりタスク0起動
#pragma endasm

} /*end of main*/


/*この作業はおまけ,インターバルタイマの動作が監視できる*/
void blinkLED()
{
    static int tick=0;
    if (tick==1) {
        turnOnLed(0);
        turnOffLed(1);
    } else {
        turnOffLed(0);
        turnOnLed(1);
    }
    tick=1-tick;
}


/*ベクトルテーブルの設定*/
#pragma asm
    .SECTION    MYVEC, DATA, LOCATE=H'000070
    .ORG        H'000070  ;IMIA1
    .DATA.L     _TaskSwitcher
    .SECTION    P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm


/*
インターバルタイマ割り込みで起動するルーチン/タスクスイッチャ本体
タスク切り替えの作業を行う
TaskSwitcherでは次のことを行う
(1)現在のタスクがtask0,task1のどちらであったか検出
(2)現在の全レジスタをcontext0,またはcontext1として保存
(3)次のタスクにむけてcontext0,またはcontext1を全レジスタに載せる
(4)rteで新タスクを起動(割り込みから戻るふりをする)

ただし,タスクスイッチャが起動した時点では,すべてのレジスタは,
スイッチャ起動前に動作していたタスクが使用中である。
そのため,スイッチャが起動直後において,全レジスタを保存する前にいくつかの
レジスタを使用する場合は,そのレジスタを退避し,作業が終了した時点で復帰させ
全レジスタをコンテキスト保存場所に保存する手順となる。以下の★参照
*/
#pragma asm
_TaskSwitcher:                   ; function: TaskSwitcher
    ;タイマユニットに割り込みを受け付けたことを知らせる
    BCLR.B #0,@H'FFFF71:8         ;ITU1.TSR.BIT.IMFA=0;

    ; ** 旧コンテキスト保存 **
    mov.l  er0,  @_Ber0  ;直前タスク番号確認のためER0を使いたいのでER0を退避★↓
    mov.w  @_tasksw, r0
    cmp.w  #1, r0         ;r0が1であるかどうか調べる
    beq    RETUNEDFROMTASK1  ;直前がタスク1,次がタスク0ならジャンプ
RETUNEDFROMTASK0:
    mov.w #1, r0          ;次のタスクはタスク1
    mov.w  r0,  @_tasksw  ;この段階で変数taskswの中身は次のコンテキスト番号
    ;直前はタスク0だったのでコンテキスト0に保存する準備
    mov.l  @_stackPtr0Address, er0
    bra SAVECONTEXT
RETUNEDFROMTASK1:
    mov.w #0, r0          ;次のタスクはタスク0
    mov.w  r0,  @_tasksw  ;この段階で変数taskswの中身は次のコンテキスト番号
    ;直前はタスク1だったのでコンテキスト1に保存する準備
    mov.l  @_stackPtr1Address, er0
SAVECONTEXT:
    ;コンテキストの保存
    mov.l  er7, @er0  ;er0の指しているアドレスにer7(=SP)を保存する
    mov.l  er0, er7   ;er0の値をer7(=SP)に設定する
    mov.l  @_Ber0,  er0                            ;ER0を使い終えたので復帰★↑
    push.l er6
    push.l er5
    push.l er4
    push.l er3
    push.l er2
    push.l er1
    push.l er0

    ;この段階で,すべてのレジスタをスイッチャが自由に使えるようになった
    mov.l  @_systemstackPtr,  er7 ;自分(スイッチャ)用のスタックの復帰

    ;この段階で,スイッチャ用のスタックが使えるようになった
    jsr @_printContext            ;contextのスナップショット(確認用)
    jsr @_blinkLED                ;LED点滅 (動作点検用)
    mov.l  er7,  @_systemstackPtr ;自分(スイッチャ)用のスタックの保存

    ; ** 次に実行すべき新コンテキスト設定 **
    mov.w  @_tasksw, r0  ;変数taskswの中身は次のタスク番号である
    cmp.w  #1, r0
    beq    SETREADYTASK1  ;新タスクが1ならジャンプ
SETREADYTASK0:
    ;コンテキスト0用のスタックを指すようにスタックポインタを設定
    mov.l  @_buff0Address, er7
    bra SETCONTEXTANDGO
SETREADYTASK1:
    ;コンテキスト1用のスタックを指すようにスタックポインタを設定
    mov.l  @_buff1Address, er7
SETCONTEXTANDGO:
    ;コンテキストを復旧させて,次のタスク起動
    pop.l  er0
    pop.l  er1
    pop.l  er2
    pop.l  er3
    pop.l  er4
    pop.l  er5
    pop.l  er6
    pop.l  er7

    rte                            ;ReTurn from Exception により新タスク起動

#pragma endasm

実行結果
(ER7がスタックポインタ)

  *** task switcher demo ***

task0,stack,er7=00000E4E 00000e4e 000fefa4
task1,stack,er7=00000E7E 00000e7e 000ff048
  context for task0
ER0 00000000    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 00000000
ER3 00000000    ER7 000fefa4
tail of stack + 1 =fefa8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fef90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fefa0 : 00 00 00 00 00 00 0e 4e 00 00 00 00 00 00 00 00
  context for task1
ER0 00000000    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 00000000
ER3 00000000    ER7 000ff048
tail of stack + 1 =ff04c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff040 : 00 00 00 00 00 00 00 00 00 00 0e 7e 00 00 00 00
task0    0
task0    1
task0    2
task0    3
task0    4
task0    5
task0    6
task0    7
task0    8
task0    9
task0   10
task0   11
task0   12
task0   13
task0   14
task0   15
task0   16
task0   17
task0   18
task0   19
task0   20
task0   21
task0   22
task0   23
task0   24
task0   25
task0   26
task0   27
task0   28
task0   29
task0   30
task0   31
task0   32
task0   33
task0   34
task0   35
task0   36
task0   37
task0   38
task0   39
task0   40
task0   41
task0   42
task0   43
task0   44
task0   45
task0   46
task0   47
task0   48
  context for task0
ER0 03be0014    ER4 00000000
ER1 00000011    ER5 00000000
ER2 00000000    ER6 000f0030
ER3 00000000    ER7 000fefa0
tail of stack + 1 =fefa8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef80 : 00 00 00 00 00 00 00 00 00 00 00 0f 00 48 00 00
fef90 : 00 04 00 00 0c b4 00 0f ef a6 00 0f 00 30 00 00
fefa0 : 21 00 0e 42 00 00 0e 72 03 be 00 14 00 00 00 11
  context for task1
ER0 00000000    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 00000000
ER3 00000000    ER7 000ff048
tail of stack + 1 =ff04c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff040 : 00 00 00 00 00 00 00 00 48 00 0e 7e 00 00 00 00
task1 1000
task1 1001
task1 1002
task1 1003
task1 1004
task1 1005
task1 1006
task1 1007
task1 1008
task1 1009
task1 1010
task1 1011
task1 1012
task1 1013
task1 1014
task1 1015
task1 1016
task1 1017
task1 1018
task1 1019
task1 1020
task1 1021
task1 1022
task1 1023
task1 1024
task1 1025
task1 1026
task1 1027
task1 1028
  context for task0
ER0 03be0014    ER4 00000000
ER1 00000011    ER5 00000000
ER2 00000000    ER6 000f0030
ER3 00000000    ER7 000fefa0
tail of stack + 1 =fefa8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef80 : 00 00 00 00 00 00 00 00 00 00 00 0f 00 48 00 00
fef90 : 00 04 00 00 0c b4 00 0f ef a6 00 0f 00 30 00 00
fefa0 : 21 00 0e 42 00 00 0e 72 03 be 00 14 00 00 00 11
  context for task1
ER0 00390014    ER4 00000000
ER1 00000013    ER5 00000000
ER2 00000000    ER6 00000404
ER3 00000000    ER7 000ff044
tail of stack + 1 =ff04c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff020 : 06 be 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff030 : 00 00 00 00 00 04 00 00 0c b4 00 0f f0 4a 00 00
ff040 : 04 04 00 00 21 00 0e 44 00 00 0e a4 00 39 00 14
task0   49
task0   50
task0   51
task0   52
task0   53
task0   54
task0   55
task0   56
task0   57
task0   58
task0   59
task0   60
task0   61
task0   62
task0   63
task0   64
task0   65
task0   66
task0   67
task0   68
task0   69
task0   70
task0   71
task0   72
task0   73
task0   74
task0   75
task0   76
task0   77
  context for task0
ER0 02a80014    ER4 00000000
ER1 00000012    ER5 00000000
ER2 00000000    ER6 000f004d
ER3 00000000    ER7 000fefa0
tail of stack + 1 =fefa8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef80 : 00 00 00 00 00 00 00 00 00 00 00 0f 00 48 00 00
fef90 : 00 04 00 00 0c b4 00 0f ef a6 00 0f 00 4d 00 00
fefa0 : 21 00 0e 42 00 00 0e 72 02 a9 00 14 00 00 00 12
  context for task1
ER0 00390014    ER4 00000000
ER1 00000013    ER5 00000000
ER2 00000000    ER6 00000404
ER3 00000000    ER7 000ff044
tail of stack + 1 =ff04c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff020 : 06 be 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff030 : 00 00 00 00 00 04 00 00 0c b4 00 0f f0 4a 00 00
ff040 : 04 04 00 00 21 00 0e 42 00 00 0e a4 00 39 00 14
task1 1029
task1 1030
task1 1031
task1 1032
task1 1033
task1 1034
task1 1035
task1 1036
task1 1037
task1 1038
task1 1039
task1 1040
task1 1041



stackにはtask0の先頭アドレスe4eが見える
stackにはtask1の先頭アドレスe7eが見える





スタックの末尾アドレスはfefa7
タスク起動前のスタックの様子


戻り番地として開始アドレス000e4e(task0)が設定





スタックの末尾アドレスはff04b
タスク起動前のスタックの様子


戻り番地として開始アドレス000e7e(task1)が設定
タスク0が起動















































タスク0が戻ってきた

ER0,ER1,ER5,ER6が使われている



スタックはfefa0からfefa7まで
使われている


21がCCR,000e42が戻り番地
スタックは後ろから前に向けて使用される






こちらはまだ使われていない


タスク1が起動



























タスク1が戻ってきた
タスク0のコンテキストは変化していない











ER0,ER1,ER6が使われている


スタックはff044からff04bまで
使われている


21がCCR,000e44が戻り番地
タスク0が起動



























タスク0が戻ってきた

ER0,ER1,ER6が使われている



スタックはfefa0からfefa7まで
使われている
















 

【4】割り込み時にタスクを切り替えるアイディア その2

【3】のプログラムは,コンテキストの存在そのものに焦点を当てているため,レジスタ保存領域が固定されており,タスク切り替えをイメージするにはよいが,切り替え操作がスマートではない。
そこで,コンテキスト保存領域を次のように使うと切り替え操作が楽になる。(プログラム記述が楽になる)
レジスタ退避領域を別に設けるのではなく,タスクが使用中のスタック上に退避させ,スタックポインタのみ特別に退避させる。
この方法は,レジスタ保存領域が固定されていないため,とらえにくいが,コンテキスト切り替えはスマートになる。

タスク切り替え処理に来た時とタスク復帰時のスタックの状態は先の例と同じだが,コンテキストが保存されている段階では,レジスタER0..ER6もスタック上に載せるため,スタックポインタはさらに先を(若いアドレス側を)指すようになっている。


上が若いアドレス,スタックポインタが指しているところ以下が有効なデータ
ER0..ER7はそれぞれ4バイトで退避される

コンテキストの保存領域とスタックの状態

この考え方に従って作ったデモプログラムを次に示す。
割り込み処理は関数TaskSwitcher()(アセンブリ言語表現は_TaskSwitcher)である。

TaskSwitcher()の作業でタスク0からタスク1に切り替わる時の手順
(逆方向の切り替えも同様になる)

タスク切り替えルーチン(割り込み処理ルーチン)に来た時

汎用レジスタER0..ER6をpush命令でタスク0領域のスタックの一番上にのせる。


直前のタスク番号を考慮して
スタックポインタ(ER7)をコンテキスト0のER7保存場所に保存
(ここまででスタック0のコンテキストの保存完了)



タスクスイッチャが自分の仕事を行うことができるところなので
スイッチャの状況を表示することができる。



コンテキスト1のER7保存場所からスタックポインタ(ER7)に復帰



スタック1よりpop命令により汎用レジスタER6..ER0を復帰
スタックの性質により、popの順番が逆になることに注意



この段階で「タスク1の戻り番地とCCR」がタスク1のスタックの一番上にある。



「rte」命令でタスク1の実行に戻る

 

ダウンロード

ここで1つのプログラム例だけを提示するが,ダウンロードファイルには2つプログラムが入っている。
1つは,2つのタスクを切り替えながら作業を行うプログラムである。もう1つは,基本的に動作は同じだが,
コンテキスト領域やスタックを表示しながら,2つのタスクを行うプログラムである。

次のプログラムは,コンテキスト領域やスタックを表示しながら,2つのタスクを行うプログラムである。
実行結果ではタスク切り替えが成功している様子がわかる。切り替え時に2つのコンテキスト保存状態の表示を行なっている。
また,タスク切り替え処理に入ったことを確認するために,LEDを点滅させている。

タスク切り替えデモプログラム taskSwitcherDemoB2.c

/**********************************************************
インターバルタイマ割り込みによって2つのタスクの切り替えを行う
**********************************************************/
#include <3048fone.h>
#include "h8_3048fone.h"

/*************** 2つのタスクの実体 ここから ********************/
void msecwait(int msec)
/*mesc間なにもしない時間稼ぎ関数*/
{
    int i,j;
    for (i=0;i<msec;i++) {
        for (j=0;j<1588;j++);    /*1588は実測によって求めた値*/
    }
}

void task0(void)
{
    int cnt=0;
    while (1) {
        D_INT();  /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
        SCI1_printf("task0 %4d\n",cnt);
        E_INT();
        msecwait(20);
        cnt++;
        if (cnt==100) cnt=0;
    }
}

void task1(void)
{
    int cnt=1000;
    while (1) {
        D_INT();  /*このデモでは通信中は割り込み禁止にしてリソースの競合を避けている*/
        SCI1_printf("task1 %4d\n",cnt);
        E_INT();
        msecwait(20);
        cnt++;
        if (cnt==1100) cnt=1000;
    }
}

/*************** 2つのタスクの実体 ここまで ********************/

typedef union {
    unsigned char Bccr;
    unsigned long int returnAddress;
} stack_t;

typedef struct {
    unsigned long int Ber7;   /*初期状態ではBer0をさしている*/
    unsigned char stack[128]; /*stack~stack0の合わさったものが実際のスタック*/
    unsigned long int Ber0;   /*初期状態だけER0があると考える,後はスタックの一部*/
    unsigned long int Ber1;   /*初期状態だけER1があると考える,後はスタックの一部*/
    unsigned long int Ber2;   /*初期状態だけER2があると考える,後はスタックの一部*/
    unsigned long int Ber3;   /*初期状態だけER3があると考える,後はスタックの一部*/
    unsigned long int Ber4;   /*初期状態だけER4があると考える,後はスタックの一部*/
    unsigned long int Ber5;   /*初期状態だけER5があると考える,後はスタックの一部*/
    unsigned long int Ber6;   /*初期状態だけER6があると考える,後はスタックの一部*/
    stack_t stack0;           /*初期状態はここに戻りアドレスとCCRが入っている*/
} context_t;

context_t context0,context1; /*コンテキストバッファの実体*/
int tasksw=0; /* 0 または 1 稼働中のタスク番号*/
unsigned long int systemstackPtr;  /*スイッチャ用スタックポインタ退避場所*/

/*コンテキストバッファの中のレジスタの表示 確認用*/
void printContext1(context_t *con)
{
    unsigned long int *currentPtr=(unsigned long int *)(con->Ber7);
    SCI1_printf("ER0 %08lx    ",currentPtr[0]);
    SCI1_printf("ER4 %08lx\n",  currentPtr[4]);
    SCI1_printf("ER1 %08lx    ",currentPtr[1]);
    SCI1_printf("ER5 %08lx\n",  currentPtr[5]);
    SCI1_printf("ER2 %08lx    ",currentPtr[2]);
    SCI1_printf("ER6 %08lx\n",  currentPtr[6]);
    SCI1_printf("ER3 %08lx    ",currentPtr[3]);
    SCI1_printf("ER7 %08lx\n",con->Ber7);
}

/*スタックのおわり周辺の表示*/
void printStackArea(context_t *con)
{
    unsigned long int address=(unsigned long int)(&(con->stack0))+4;
    int i;
    SCI1_printf("tail of stack + 1 =%05lx\n",address);
    address-=0x30;
    address&=0xfffffff0;
    SCI1_printf("address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F\n");
    for (i=0; i<0x40; i++,address++) {
        if (i%0x10==0) SCI1_printf ("%05lx : ",address);
        SCI1_printf("%02x ",*(unsigned char*)address);
        if (i%0x10==0xf) SCI1_printf("\n");
    }
}

/*コンテキストバッファのレジスタ表示関数 確認用*/
void printContext(void)
{
    SCI1_printf("  context for task0\n");
    printContext1(&context0);
    printStackArea(&context0);
    SCI1_printf("  context for task1\n");
    printContext1(&context1);
    printStackArea(&context1);
}

main()
{
    initLed();   /*LEDユニットの初期化 点滅させるが,動作確認用*/
    initSCI1();  /*シリアル通信の初期化*/
    SCI1_printf("\n\n\n  *** task switcher demo ***\n\n");
    initTimer01Int(500); /*時間割り込み500msec ITUch0,1使用 このタイミングでタスク切り替えが起こる*/
    E_INT();        /*CPU割り込み許可*/

    /*コンテキストバッファにコンテキストを2つの作成*/
    context0.stack0.returnAddress=(unsigned long int)task0;
    context0.Ber7=(unsigned long int)&context0.Ber0;
    SCI1_printf("task0,stack,er7=%p %08lx %08lx\n", task0, context0.stack0.returnAddress, context0.Ber7);

    context1.stack0.returnAddress=(unsigned long int)task1;
    context1.Ber7=(unsigned long int)&context1.Ber0;
    SCI1_printf("task1,stack,er7=%p %08lx %08lx\n", task1, context1.stack0.returnAddress, context1.Ber7);
    
    printContext();

    startTimer01();  /*時間割り込みタイマスタートch0,ch1*/

    /*ここまでで2つのタスクの起動準備完了。最初はtask0を起動する*/
    tasksw=0;
#pragma asm
    mov.l  er7,  @_systemstackPtr  ;スイッチャのスタックポインタを退避
    mov.l  @_context0, er7 ;タスク0のスタックポインタを復帰
                           ; context0とcontext0.Ber7は同じアドレス
    pop.l  er0
    pop.l  er1
    pop.l  er2
    pop.l  er3
    pop.l  er4
    pop.l  er5
    pop.l  er6
    rte                            ;ReTurn from Exception によりタスク起動
#pragma endasm

} /*end of main*/

/*ベクトルテーブルの設定*/
#pragma asm
    .SECTION    MYVEC, DATA, LOCATE=H'000070
    .ORG        H'000070  ;IMIA1
    .DATA.L     _TaskSwitcher
    .SECTION    P,CODE,ALIGN=2 ;これを忘れてはいけない
#pragma endasm


/*
インターバルタイマ割り込みで起動するルーチン,タスク切り替えの作業を行う
TaskSwitcherでは次のことを行う
(1)現在のタスクがtask0,task1のどちらであったか検出
(2)現在の全レジスタをcontext0,またはcontext1として保存
(3)次のタスクにむけてcontext0,またはcontext1を全レジスタに載せる
(4)rteで新タスクを起動(割り込みから戻るふりをする)
*/
#pragma asm
_TaskSwitcher:                   ; function: TaskSwitcher
    ;タイマユニットに割り込みを受け付けたことを知らせる
    BCLR.B #0,@H'FFFF71:8         ;ITU1.TSR.BIT.IMFA=0;

    ; ** 旧コンテキスト保存 **
    push.l er6
    push.l er5
    push.l er4
    push.l er3
    push.l er2
    push.l er1
    push.l er0
    mov.w  @_tasksw, r0  ;直前のタスク番号の取得
    cmp.w  #1, r0
    beq RETURNEDFROMTASK1

RETURNEDFROMTASK0:
    ;直前がタスク0のときのタスクスイッチ作業
    mov.w #1, r0         ;次のタスク番号は1
    mov.w r0, @_tasksw   ;次のタスク番号を変数taskswに保存
    ;スタックポインタをコンテキスト0のBer7に保存
    mov.l  er7,  @_context0; (context0とcontext0.Ber7は同じアドレス)

    ;動作状態の表示
    mov.l  @_systemstackPtr,  er7 ;スイッチャのスタックポインタの復帰
    jsr @_printContext            ;contextのスナップショット(確認用)
    jsr @_blinkLED                ;LED点滅 (動作点検用)ここでは何をしてもよいことの検証
    mov.l  er7,  @_systemstackPtr ;自分用のスタックの保存

    ;コンテキスト1のBer7をスタックポインタにセット
    mov.l  @_context1, er7 ; (context1とcontext1.Ber7は同じアドレス)
    bra GONEXTTASK

RETURNEDFROMTASK1:
    ;直前がタスク1のときのタスクスイッチ作業
    mov.w #0, r0         ;次のタスク番号は0
    mov.w r0, @_tasksw   ;次のタスク番号を変数taskswに保存
    ;スタックポインタをコンテキスト1のBer7に保存
    mov.l  er7,  @_context1; (context1とcontext1.Ber7は同じアドレス)

    ;動作状態の表示
    mov.l  @_systemstackPtr,  er7 ;スイッチャのスタックポインタの復帰
    jsr @_printContext            ;contextのスナップショット(確認用)
    jsr @_blinkLED                ;LED点滅 (動作点検用)ここでは何をしてもよいことの検証
    mov.l  er7,  @_systemstackPtr ;自分用のスタックの保存

    ;コンテキスト0のBer7をスタックポインタにセット
    mov.l  @_context0, er7 ; (context0とcontext0.Ber7は同じアドレス)

GONEXTTASK:
    pop.l  er0
    pop.l  er1
    pop.l  er2
    pop.l  er3
    pop.l  er4
    pop.l  er5
    pop.l  er6

    rte

#pragma endasm

/*この作業はおまけ,インターバルタイマの動作が監視できる*/
void blinkLED()
{
    static int tick=0;
    if (tick==1) {
        turnOnLed(0);
        turnOffLed(1);
    } else {
        turnOffLed(0);
        turnOnLed(1);
    }
    tick=1-tick;
}

実行結果
(ER7がスタックポインタ)

  *** task switcher demo ***

task0,stack,er7=00000E4E 00000e4e 000fefa8
task1,stack,er7=00000E7E 00000e7e 000ff04c
  context for task0
ER0 00000000    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 00000000
ER3 00000000    ER7 000fefa8
tail of stack + 1 =fefc8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fefa0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fefb0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fefc0 : 00 00 00 00 00 00 0e 4e 00 0f f0 4c 00 00 00 00
  context for task1
ER0 00000000    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 00000000
ER3 00000000    ER7 000ff04c
tail of stack + 1 =ff06c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff060 : 00 00 00 00 00 00 00 00 00 00 0e 7e 00 00 00 00
task0    0
task0    1
task0    2
task0    3
task0    4
task0    5
task0    6
task0    7
task0    8
task0    9
task0   10
task0   11
task0   12
task0   13
task0   14
task0   15
task0   16
task0   17
task0   18
task0   19
task0   20
task0   21
task0   22
task0   23
task0   24
task0   25
task0   26
task0   27
task0   28
task0   29
task0   30
task0   31
task0   32
task0   33
task0   34
task0   35
task0   36
task0   37
task0   38
task0   39
task0   40
task0   41
task0   42
task0   43
task0   44
task0   45
task0   46
task0   47
task0   48
  context for task0
ER0 03c00014    ER4 00000000
ER1 00000011    ER5 00000000
ER2 00000000    ER6 00000030
ER3 00000000    ER7 000fefa4
tail of stack + 1 =fefc8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef90 : 03 5a 00 2b 00 00 03 3c 00 0a 00 00 06 be 00 00
fefa0 : 00 00 00 00 03 be 00 14 00 00 00 11 00 00 00 00
fefb0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30
fefc0 : 21 00 0e 42 00 00 0e 72 00 0f f0 4c 00 00 00 00
  context for task1
ER0 00000000    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 00000000
ER3 00000000    ER7 000ff04c
tail of stack + 1 =ff06c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff060 : 00 00 00 00 00 00 00 00 48 00 0e 7e 00 0f fe f8
task1 1000
task1 1001
task1 1002
task1 1003
task1 1004
task1 1005
task1 1006
task1 1007
task1 1008
task1 1009
task1 1010
task1 1011
task1 1012
task1 1013
task1 1014
task1 1015
task1 1016
task1 1017
task1 1018
task1 1019
task1 1020
task1 1021
task1 1022
task1 1023
task1 1024
task1 1025
task1 1026
  context for task0
ER0 03be0014    ER4 00000000
ER1 00000011    ER5 00000000
ER2 00000000    ER6 00000030
ER3 00000000    ER7 000fefa4
tail of stack + 1 =fefc8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef90 : 03 5a 00 2b 00 00 03 3c 00 0a 00 00 06 be 00 00
fefa0 : 00 00 00 00 03 c0 00 14 00 00 00 11 00 00 00 00
fefb0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30
fefc0 : 21 00 0e 42 00 00 0e 72 00 0f f0 4c 00 00 00 00
  context for task1
ER0 0000135f    ER4 00000000
ER1 00000300    ER5 00000000
ER2 00000000    ER6 00000402
ER3 00000000    ER7 000ff04c
tail of stack + 1 =ff06c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff030 : 20 00 00 00 03 5a 00 2b 00 00 03 3c 00 0a 00 00
ff040 : 06 be 00 00 00 00 00 00 00 00 00 00 00 00 13 5f
ff050 : 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00
ff060 : 00 00 00 00 00 00 04 02 00 00 0e 9e 00 0f ff 08
task0   49
task0   50
task0   51
task0   52
task0   53
task0   54
task0   55
task0   56
task0   57
task0   58
task0   59
task0   60
task0   61
task0   62
task0   63
task0   64
task0   65
task0   66
task0   67
task0   68
task0   69
task0   70
task0   71
task0   72
task0   73
task0   74
  context for task0
ER0 03310014    ER4 00000000
ER1 0000000f    ER5 00000000
ER2 00000000    ER6 0000004a
ER3 00000000    ER7 000fefa4
tail of stack + 1 =fefc8
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef90 : 03 5a 00 2b 00 00 03 3c 00 0a 00 00 06 be 00 00
fefa0 : 00 00 00 00 03 31 00 14 00 00 00 0f 00 00 00 00
fefb0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4a
fefc0 : 21 00 0e 42 00 00 0e 72 00 0f f0 4c 00 00 00 00
  context for task1
ER0 0000135f    ER4 00000000
ER1 00000300    ER5 00000000
ER2 00000000    ER6 00000402
ER3 00000000    ER7 000ff04c
tail of stack + 1 =ff06c
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff030 : 20 00 00 00 03 5a 00 2b 00 00 03 3c 00 0a 00 00
ff040 : 06 be 00 00 00 00 00 00 00 00 00 00 00 00 13 5f
ff050 : 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00
ff060 : 00 00 00 00 00 00 04 02 40 00 0e 9e 00 0f ff 08
task1 1027
task1 1028
task1 1029
task1 1030
task1 1031
task1 1032
task1 1033
task1 1034
task1 1035
task1 1036
task1 1037
task1 1038
task1 1039
task1 1040
task1 1041
task1 1042
task1 1043
task1 1044
task1 1045
task1 1046
task1 1047
task1 1048
task1 1049



stackにはtask0の先頭アドレスe4eが見える
stackにはtask1の先頭アドレスe7eが見える






スタックの末尾アドレスはfefc7
タスク起動前のスタックの様子
fefa8から未使用のER0..ER6が保存

戻り番地として開始アドレスが設定






スタックの末尾アドレスはff06b
タスク起動前のスタックの様子
ff04cから未使用のER0..ER6が保存

戻り番地として開始アドレスが設定
タスク0が起動
















































タスク0が戻ってきた

ER0,ER1,ER5,ER6が使われている



スタックはfefa4からfefc7まで
使われている
fefa4からER0..ER6が順に保存されているのがわかる
61がCCR,000e42が戻り番地
スタックは後ろから前に向けて使用される






こちらはまだ使われていない




タスク1が起動

























タスク1が戻ってきた


タスク0のコンテキストは変化していない











ER0,ER1,ER5,ER6が使われている


スタックはff04cからff06bまで
使われている
ff04cからER0..ER6が順に保存されているのがわかる

00がCCR,0009f0が戻り番地
タスク0が再開した












課題1 「2つのタスク切り替えのプログラム」を2つ紹介してあるが,
読み取って,自分の言葉でタスク切り替えの要点を説明しなさい。
(mtsk01.txt) 

課題2 「2つのタスク切り替えのプログラム」を2つ紹介してあるが,どちらかを改造して3つのタスクが順番に
動作するプログラムを作成し,報告しなさい。
プログラミングは「4」のtaskSwitcherDemoB1.c,taskSwitcherDemoB2.cを使った方が容易かもしれない。

ただし3つ目のタスクは

task2 10001
task2 10002
task2 10003
  :

のように先の2つの例(taskSwitcherDemo1.c,taskSwitcherDemo2.c)と同様に表示するものとする。
提出課題には2周分(task0→task1→task2→task0→task1→task2)の実行結果を貼付すること。

ヒント 現行の2つのタスクに加え,3つ目のタスクを用意する。コンテキストバッファも3つ必要となる。
現在どのタスクを実行しているかを示す変数taskswは0,1,2を繰り返すようにする。
タスクスイッチャは起動時にtaskswを見て,コンテキストを対応するコンテキストバッファに退避させる。
(taskswが0なら,コンテキストバッファ0に,taskswが1なら,コンテキストバッファ1に,..)
taskswの値を変化させる。(0→1,1→2,2→0)
変化したtaskswを見ながら対応するコンテキストバッファの内容をコンテキストに戻す。
rteでタスクスイッチャ終了。

(mtsk02.txt)