スパイス  組み込み制御装置の受注製作

Z80にマルチタスク機能を
平成26年 5月 2日

 タスクコンテキストの保存(続き)
 先の割り込み処理ルーチンの雛形からタスク切り替えの雛形は下記のようになります。主要なレジスタの保存復帰はアセンブラで記述しますが、sp値の保存と次のタスク用のsp値の準備はC言語で記述します。最初はC言語でのspの管理と次タスクの決定部分を示します。
 ここでGAIO CではC標準のstdbool.hヘッダーファイルが不足していますので、他のCコンパイラから持ってきます。念のため、stdbool.hのリストも示しておきます。これを標準ヘッダファイルのディレクトリに保管します。
stdbool.hのリスト
#ifndef __bool_true_and_false_are_defined
#define __bool_true_and_false_are_defined

typedef unsigned char bool;

#define true 1
#define false 0
#endif

spの管理と次タスクの決定部のリスト
#include <stdio.h>
#include <stdbool.h>
#include "myint.h"
#include "aki80.h"

#define MAX_TASK    2
typedef struct {
    uint8   now;            //現在のタスク番号
    uint    sp[MAX_TASK+1];
} TASK_DATA_t;

static TASK_DATA_t task_data;

#define STACK_SIZE  128
static char sp[MAX_TASK][STACK_SIZE];

typedef struct {
    uint af;
    uint bc;
    uint de;
    uint hl;
    uint ix;
    uint iy;
    uint pc;
} STACK_t;

#define INIT_STACK(stack)   (stack+STACK_SIZE-(sizeof(STACK_t)))

bool init_task(uchar no,void (*func)(void))
{
    uint *p;
    if(no==0)
        return false;
    p = (uint*)INIT_STACK(sp[no -1]);
    task_data.sp[no] = (uint)p;
    *p++ = 0;   //iy
    *p++ = 0;   //ix
    *p++ = 0;   //hl
    *p++ = 0;   //de
    *p++ = 0;   //bc
    *p++ = 0;   //af
    *p = (uint*)func;
    return true;
}

/* 次に実行するタスクのsp値を返す */
uint switch_sp(uint sp)
{
    uchar next = task_data.now + 1;
    if(next > MAX_TASK)
        next = 0;
    task_data.sp[task_data.now] = sp;
    task_data.now = next;
    return task_data.sp[next];
}
 一緒にタスクを開始するときに初期スタックを構成するinit_task()も示してあります。最初のタスク呼び出しの時に継続実行と同じIFになるようにスタックを構成します。
 タスクとスタックを管理するための変数がtask_dataで、現在実行中のタスク番号と個々のタスクのsp値を保管しています。タスクを切り替えるときには、現タスクのsp値を引数としてしてswitch_sp()が呼ばれます。関数はsp値を該当する配列に保管して、次のタスク番号を(順番に従って)決め更新します。また、次タスクのsp値を取り出して関数の戻り値として返します。
 この部分をC言語で書くことで、実はやっている事が非常に単純であることがよく分かります。アセンブラで書くことも出来ますが、移植性の問題が無い限りC言語で書く方が勝ります。

タスク切り替え部のリスト(抜粋)
    global _task_switch, _inp, _outp
    extnal _switch_sp

C_inp   SECT    CODE
_task_switch:
    di         ;割り込み禁止
    push AF
    push BC
    push DE
    push HL
    push IX
    push IY
    ld IX,0     ;SPの値を引数に(SPの値を直接IXにコピーする命令が無いので回りくどい書き方になっている)
    add IX,SP
    push IX
    call _switch_sp ;現在のsp値をsaveして次タスクのspを返す
    ld H,B          ;関数の返り値BCレジスタ(sp値)をspに設定する
    ld L,C
    ld SP,HL        ;ここで次タスク用のsp値に変更される
    pop IY          ;このpopは次タスクの値が戻される
    pop IX
    pop HL
    pop DE
    pop BC
    pop AF
    ret
(平成26年5月5日追記)
 先の割り込み処理ルーチンとの比較を書き忘れています。
  ・この処理を開始する段階で割り込みを禁止する
  ・レジスタの退避後、現在のsp値を引数にswitch_sp()を呼び出す
  ・関数の返り値をspに設定する
  ・レジスタの復帰後の割り込み許可を行わない
  ・ret命令の種類が異なる(CPU自体の動作は同一)
 違いはこれだけです。基本的にはタスク毎にsp値を交換することが大きな違いです。
(追記ここまで)
 次は、このタスク切り替えを使用して簡単なプログラムを動かしてみます。

目次 前へ 次へ