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

【1】はじめに

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

具体的には,次の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;
    }
}


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

タスクスイッチはCPUの仕組みに依存する部分があり,これをH8CPUに依存した形で作るものである。
なお,開発環境はインラインアセンブリ言語記述の使える日立評価版コンパイラVer.2を用いている。

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


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

ダウンロード

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

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

テストプログラム taskstartDemo1.c

/**********************************************************
タイマ割り込み処理に入った瞬間の出来事
タイマ割り込みだけれど1回だけ割り込みが起こる
**********************************************************/
#include <3048f.h>
#include "h8_3048.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を押すと,さらに進んで以下の
ようになって止まったように見える。

  *** 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」のプログラムの割り込みルーチンをアセンブリ言語で記述すると次のようになる。プログラムの動作は
変化せず,全く同じになる。
 

テストプログラム taskstartDemo2.c

/**********************************************************
タイマ割り込み処理に入った瞬間の出来事
タイマ割り込みだけれど1回だけ割り込みが起こる
**********************************************************/
#include <3048f.h>
#include "h8_3048.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)が用い
られる。

H8CPUの割り込みは,割り込み要因が生じ,CPUが割り込みを受け付ける状態であると,
(1)現在作業中の次の命令のアドレス3バイトとCCR(コンディションコードレジスタ)
   1バイトの4バイトをスタックにPUSHする
   これは,割り込み処理が終了し,割り込み前の作業に戻るためである

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

スタックの様子

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

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

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

テストプログラム taskstartDemo3.c

/**********************************************************
タイマ割り込み処理に入った瞬間の出来事
タイマ割り込みだけれど1回だけ割り込みが起こる
**********************************************************/
#include <3048f.h>
#include "h8_3048.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 00 00 00 00 00 00 00 00 00
fff00 : 00 00 00 00 00 00 0a 3c 00 00 0a 42 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 00 00 00 00 00 00 00 00 00 00 00 00 00
fff00 : 00 00 0a 94 00 00 0d 52 04 00 0a b0 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 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fff00 : 00 00 00 00 00 00 0b a2 04 00 0a b0 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'00000A16           DAT
      _printSituation                      H'00000B42           DAT

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

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

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

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のスタックの一番上にある。



(どちらのコンテキスト保存領域にしまうべきかを考えて)汎用レジスタER6〜ER0をpush命令でレジスタ退避領域へ保存する。



スタックポインタ(ER7)をタスク0のER7保存場所に保存

(ここまででスタック0のコンテキストの保存完了)
タスクスイッチャが自分の仕事を行うことができるところ
(ここからはタスク1起動準備)



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



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



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


ダウンロード

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

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

最初のタスクを起動するときに,タスク(関数)のアドレスをスタックポインタに載せて,割り込みルーチン
用のリターン命令を行っている。
タスク切り替えに動作によるタスク終了の時には,タイマ割り込みでその時実行中のプログラムカウンタが,
スタックに入り,スタックポインタがそのアドレスを指すようになっている状態で保存されるので,その逆の
ことをすることによって,タスクの最初の起動を行っていることになる。
(赤字のところに注意)

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

/**********************************************************
インターバルタイマ割り込みによって2つのタスクの切り替えを行う
**********************************************************/
#include <3048f.h>
#include "h8_3048.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 char Bccr;      /*CCRを取り込む*/
unsigned long int Ber0;  /*ER0の退避場所*/
unsigned long int returnAddress;  /*コンテキスト生成作業用バッファ*/
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割り込み許可*/

    /*割り込み許可した後のCCRの取り込み*/
#pragma asm
    STC.b  CCR, R0l
    mov.b  R0l, @_Bccr
#pragma endasm

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

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

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

    /*ここで2つのタスクの起動する。最初はtask0が起動*/
    tasksw=0;
#pragma asm
    mov.l  er7,  @_systemstackPtr  ;自分のスタックポインタを退避
    mov.l  @_stackPtr0Address, er0 ;タスク0のスタックポインタを復帰
    mov.l  @er0, er7               ;     :
    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;

    ; ** 旧コンテキスト保存 **
    mov.l  er0,  @_Ber0                   ;ER0を使いたいのでER0を退避★
    mov.w  @_tasksw, r0
    xor.w  #1, r0
    mov.w  r0,  @_tasksw
    beq    L01  ;現在がタスク1,次がタスク0ならジャンプ
    ;直前はタスク0だったのでコンテキスト0に保存
    mov.l  @_stackPtr0Address, er0
    bra L02
L01:
    ;直前はタスク1だったのでコンテキスト1に保存
    mov.l  @_stackPtr1Address, er0
L02:
    mov.l  er7, @er0
    mov.l  er0, er7
    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
    xor.w  #1, r0
    beq    L03  ;新タスクが1ならジャンプ
    ;コンテキスト0用のスタックを指すようにスタックポインタを設定
    mov.l  @_buff0Address, er7
    bra L04
L03:
    ;コンテキスト1用のスタックを指すようにスタックポインタを設定
    mov.l  @_buff1Address, er7
L04:
    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
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
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
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
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
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
task0   69
task0   70
task0   71
task0   72
task0   73
task0   74
task0   75
task0   76

 

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

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

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

/**********************************************************
インターバルタイマ割り込みによって2つのタスクの切り替えを行う
**********************************************************/
#include <3048f.h>
#include "h8_3048.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 char Bccr;      /*CCRを取り込む*/
unsigned long int Ber0;  /*ER0の退避場所*/
unsigned long int returnAddress;  /*コンテキスト生成作業用バッファ*/
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割り込み許可*/

    /*割り込み許可した後のCCRの取り込み*/
#pragma asm
    STC.b  CCR, R0l
    mov.b  R0l, @_Bccr
#pragma endasm

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

    context1.stack0.returnAddress=(unsigned long int)task1;
    context1.stack0.Bccr=Bccr;
    context1.Ber7=(unsigned long int)&context1.stack0.returnAddress;
    SCI1_printf("task1,Bcc,stack,er7=%p %02x %08lx %08lx\n", task1, Bccr, 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               ;     :
    rte                            ;ReTurn from Exception によりタスク起動
#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
    xor.w  #1, r0
    mov.w  r0,  @_tasksw
    beq    L01  ;現在がタスク1,次がタスク0ならジャンプ
    ;直前はタスク0だったのでコンテキスト0に保存
    mov.l  @_stackPtr0Address, er0
    bra L02
L01:
    ;直前はタスク1だったのでコンテキスト1に保存
    mov.l  @_stackPtr1Address, er0
L02:
    mov.l  er7, @er0
    mov.l  er0, er7
    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
    xor.w  #1, r0
    beq    L03  ;新タスクが1ならジャンプ
    ;コンテキスト0用のスタックを指すようにスタックポインタを設定
    mov.l  @_buff0Address, er7
    bra L04
L03:
    ;コンテキスト1用のスタックを指すようにスタックポインタを設定
    mov.l  @_buff1Address, er7
L04:
    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,Bcc,stack,er7=000009A0 08 080009a0 000fefa2
task1,Bcc,stack,er7=000009D0 08 080009d0 000ff046
  context for task0
ER0 00000000    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 00000000
ER3 00000000    ER7 000fefa2
tail of stack + 1 =fefa6
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 08 00 09 a0 00 00 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 000ff046
tail of stack + 1 =ff04a
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 08 00 09 d0 00 00 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
  context for task0
ER0 05a50014    ER4 00000000
ER1 00000003    ER5 000f0008
ER2 00000000    ER6 000f0022
ER3 00000000    ER7 000fef9e
tail of stack + 1 =fefa6
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef80 : 00 2b 00 0a 00 00 03 7c 00 00 00 00 00 00 00 00
fef90 : 00 00 00 00 00 0f 00 08 00 0f 00 22 00 00 21 00
fefa0 : 09 94 00 00 09 c4 05 a5 00 14 00 00 00 03 00 00
  context for task1
ER0 00000000    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 00000000
ER3 00000000    ER7 000ff046
tail of stack + 1 =ff04a
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 08 00 09 d0 00 00 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
  context for task0
ER0 05a50014    ER4 00000000
ER1 00000003    ER5 000f0008
ER2 00000000    ER6 000f0022
ER3 00000000    ER7 000fef9e
tail of stack + 1 =fefa6
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef80 : 00 2b 00 0a 00 00 03 7c 00 00 00 00 00 00 00 00
fef90 : 00 00 00 00 00 0f 00 08 00 0f 00 22 00 00 21 00
fefa0 : 09 94 00 00 09 c4 05 a5 00 14 00 00 00 03 00 00
  context for task1
ER0 00000ed9    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 000003fc
ER3 00000000    ER7 000ff046
tail of stack + 1 =ff04a
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff020 : f0 19 00 20 00 2b 00 0a 00 00 03 7c 00 00 00 00
ff030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 fc
ff040 : 00 00 09 e8 00 00 00 00 09 f0 00 00 0e d9 00 00
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
task0   49
task0   50
task0   51
task0   52
task0   53
task0   54
  context for task0
ER0 016a0014    ER4 00000000
ER1 00000004    ER5 000f0008
ER2 00000000    ER6 000f0036
ER3 00000000    ER7 000fef9e
tail of stack + 1 =fefa6
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef80 : 00 2b 00 0a 00 00 03 7c 00 00 00 00 00 00 00 00
fef90 : 00 00 00 00 00 0f 00 08 00 0f 00 36 00 00 21 00
fefa0 : 09 94 00 00 09 c4 01 6a 00 14 00 00 00 04 00 00
  context for task1
ER0 00000ed9    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 000003fc
ER3 00000000    ER7 000ff046
tail of stack + 1 =ff04a
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff020 : f0 19 00 20 00 2b 00 0a 00 00 03 7c 00 00 00 00
ff030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 fc
ff040 : 00 00 09 e8 00 00 00 00 09 f0 00 00 0e d9 00 00
task1 1021
task1 1022
task1 1023
task1 1024
task1 1025
task1 1026
task1 1027
task1 1028
task1 1029
task1 1030



stackにはccrの08とtask0の先頭アドレス9a0が見える
stackにはccrの08とtask1の先頭アドレス9e0が見える
ccrの値には意味はない




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


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





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


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

































タスク0が戻ってきた

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



スタックはfef9eからfefa5まで
使われている


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






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


タスク1が起動



















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











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


スタックはff046からff049まで
使われている


00がCCR,0009f0が戻り番地
タスク0が起動



















タスク0が戻ってきた

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



スタックはfef9eからfefa5まで
使われている
















 

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

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

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


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

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

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

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

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



汎用レジスタ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 <3048f.h>
#include "h8_3048.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);
}

unsigned char Bccr;      /*CCRを取り込む*/

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

    /*割り込み許可した後のCCRの取り込み*/
#pragma asm
    STC.b  CCR, R0l
    mov.b  R0l, @_Bccr
#pragma endasm

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

    context1.stack0.returnAddress=(unsigned long int)task1;
    context1.stack0.Bccr=Bccr;
    context1.Ber7=(unsigned long int)&context1.Ber0;
    SCI1_printf("task1,Bcc,stack,er7=%p %02x %08lx %08lx\n", task1, Bccr, 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
    xor.w  #1, r0
    mov.w  r0,  @_tasksw
    beq    L01  ;現在がタスク1,次がタスク0ならジャンプ
    ;直前はタスク0だったのでスタックポインタをコンテキスト0のBer7に保存
    mov.l  er7,  @_context0; context0とcontext0.Ber7は同じアドレス
    bra L02
L01:
    ;直前はタスク1だったのでスタックポインタをコンテキスト1のBer7に保存
    mov.l  er7,  @_context1; context1とcontext1.Ber7は同じアドレス
L02:

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

    ; ** 新コンテキスト設定 **
    mov.w  @_tasksw, r0
    xor.w  #1, r0
    beq    L03  ;新タスクが1ならジャンプ
    ;コンテキスト0のBer7からスタックポインタを復帰
        mov.l  @_context0, er7 ; context0とcontext0.Ber7は同じアドレス
    bra L04
L03:
    ;コンテキスト1のBer7からスタックポインタを復帰
        mov.l  @_context1, er7; context1とcontext1.Ber7は同じアドレス
L04:
    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,Bcc,stack,er7=000009A0 08 080009a0 000fefa6
task1,Bcc,stack,er7=000009D0 08 080009d0 000ff04a
  context for task0
ER0 00000000    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 00000000
ER3 00000000    ER7 000fefa6
tail of stack + 1 =fefc6
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 08 00 09 a0 00 0f f0 4a 00 00 00 00 00 00
  context for task1
ER0 00000000    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 00000000
ER3 00000000    ER7 000ff04a
tail of stack + 1 =ff06a
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 08 00 09 d0 00 00 00 00 08 ff
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
  context for task0
ER0 05a40014    ER4 00000000
ER1 00000003    ER5 00000000
ER2 00000000    ER6 00000022
ER3 00000000    ER7 000fefa2
tail of stack + 1 =fefc6
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef90 : 00 00 00 33 34 00 00 00 00 00 00 0f ef 95 00 20
fefa0 :
00 2b 05 a4 00 14 00 00 00 03 00 00 00 00 00 00
fefb0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 22
21 00

fefc0 : 09 96 00 00 09 c4 00 0f f0 4a 00 00 00 00 00 00
  context for task1
ER0 00000000    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 00000000
ER3 00000000    ER7 000ff04a
tail of stack + 1 =ff06a
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 08 00 09 d0 00 0f fe f8 08 ff
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
  context for task0
ER0 05a40014    ER4 00000000
ER1 00000003    ER5 00000000
ER2 00000000    ER6 00000022
ER3 00000000    ER7 000fefa2
tail of stack + 1 =fefc6
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef90 : 00 00 00 33 34 00 00 00 00 00 00 0f ef 95 00 20
fefa0 : 00 2b 05 a4 00 14 00 00 00 03 00 00 00 00 00 00
fefb0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 22 21 00
fefc0 : 09 96 00 00 09 c4 00 0f f0 4a 00 00 00 00 00 00
  context for task1
ER0 00000ebe    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 000003fa
ER3 00000000    ER7 000ff04a
tail of stack + 1 =ff06a
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff030 : 00 00 00 00 00 31 30 31 38 00 00 00 00 00 00 0f
ff040 : f0 39 00 20 00 2b 00 0a 00 00 00 00 0e be 00 00
ff050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff060 : 00 00 00 00 03 fa 00 00 09 f0 00 0f fe f8 08 ff
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
task0   49
task0   50
task0   51
task0   52
  context for task0
ER0 04730014    ER4 00000000
ER1 00000001    ER5 00000000
ER2 00000000    ER6 00000034
ER3 00000000    ER7 000fefa2
tail of stack + 1 =fefc6
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
fef90 : 00 00 00 35 32 00 00 00 00 00 00 0f ef 95 00 20
fefa0 : 00 2b 04 73 00 14 00 00 00 01 00 00 00 00 00 00
fefb0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 34 21 00
fefc0 : 09 94 00 00 09 c4 00 0f f0 4a 00 00 00 00 00 00
  context for task1
ER0 00000ebe    ER4 00000000
ER1 00000000    ER5 00000000
ER2 00000000    ER6 000003fa
ER3 00000000    ER7 000ff04a
tail of stack + 1 =ff06a
address +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F
ff030 : 00 00 00 00 00 31 30 31 38 00 00 00 00 00 00 0f
ff040 : f0 39 00 20 00 2b 00 0a 00 00 00 00 0e be 00 00
ff050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ff060 : 00 00 00 00 03 fa 00 00 09 f0 00 0f fe f8 08 ff
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



stackにはccrの08とtask0の先頭アドレス9a0が見える
stackにはccrの08とtask1の先頭アドレス9d0が見える
ccrの値には意味はない





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

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






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

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


































タスク0が戻ってきた

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



スタックはfefa2からfefc5まで
使われている
fefa2からER0〜ER6が順に保存されているのがわかる
21がCCR,000996が戻り番地
スタックは後ろから前に向けて使用される






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




タスク1が起動

















タスク1が戻ってきた


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











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


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

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












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

ただし3つ目のタスクは

task2 10001
task2 10002
task2 10003
  :

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

(mtsk01.txt)