マルチタスク3

TimerTaskクラス TimerTask.h

TimerTaskInterruptTaskの拡張です。ここではその構成をみるだけで、TimerTaskを用いているTaskToneGeneratorについては次の項目で例題を紹介します。次のリストはTimerTaskです。


TimerTask.h
#ifndef TIMERTASK
#define TIMERTASK
#include "InterruptTask.h"

/**
 * 単純なタイマーを基本として運用するタスク
 * タイマーが開始されたときにタスクが開始するように設計されている。
 * handleTimeoutメソッドがタイムアウトが起こった
 * 時によばれる。
 * タイマーは再開(restart)されることができ、あるいは
 * タスクは終了することができる。
 *
 * 典型的な使用法はTaskManager用いた音発生である。
 * これはタイマーが走っている間に他のタスクが実行できる。
 * handleTimeoutメソッドは同様に定義される。
 * デフォールトでinterruptメソッドを呼ぶことは
 * タイマーを止めます。
 */

class TimerTask : public InterruptTask
{
public:
  const static int timeout = 1;

  TimerTask(){};
  bool handleInterrupt ( int interruptValue );
  virtual bool handleTimeout () ;
  bool timerRunning ();
  void execute ();
  void sleep ( int hi, int lo );
  void sleep ( int timeMS );
  void sleepSec ( int timeS );
};

#endif


TimerTaskクラス TimerTask.cpp



TimerTask.cpp
#include "TimerTask.h"
/**
 * 単純なタイマーを基本として運用するタスク
 * タイマーが開始されたときにタスクが開始するように設計されている。
 * handleTimeoutメソッドがタイムアウトが起こった
 * 時によばれる。
 * タイマーは再開(restart)されることができ、あるいは
 * タスクは終了することができる。
 *
 * 典型的な使用法はTaskManager用いた音発生である。
 * これはタイマーが走っている間に他のタスクが実行できる。
 * handleTimeoutメソッドは同様に定義される。
 * デフォールトでinterruptメソッドを呼ぶことは
 * タイマーを止めます。
 */
  bool TimerTask::handleTimeout ()
  {
  };

  /**
   * 割り込みが起動されたときに呼ばれる
   * もしサブクラスがこの状態を処理するならば、再定義される。
   *
   * 戻り:boolean false
   */
bool TimerTask::handleInterrupt ( int interruptValue ) {
    return false ;
}

  /**
   * タイマーが走っているかどうかをチェックする。
   *
   * 戻り:boolean true: タイマーが入っていれば
   */
  bool TimerTask::timerRunning () {
    return state == checkTimer ;
  }

  /**
   * TaskManagerに呼ばれるタスク実行ルーチン
   */
  void TimerTask::execute () {
    switch ( state ) {
      case interrupt:
        if ( handleInterrupt ( interruptValue ))
          resumeInterrupt () ;
        else
          nextState ( terminate ) ;
        break ;

      case timeout:
        if ( handleTimeout ())
          break ;     // タイムアウトが処理されたら続ける
                      // でなければ、タスクをstop()するように下に落ちる
      default:
        stop () ;
        break ;
    }
  }

  /**
   * Start timer and call  on timeout.
   * タイムアウト時にタイマーを開始し、handleTimeout()を呼ぶ。
   * 入力:int hi タイムアウト値、詳しくはTimer.timeout(hi,lo)を見よ
   * 入力:int lo タイムアウト値
   */
  void TimerTask::sleep ( int hi, int lo ) {
    Task::sleep ( timeout, hi, lo ) ;
    resume () ;
  }

  /**
   * タイムアウト時にタイマーを開始し、handleTimeout()を呼ぶ。
   *
   * 入力:int timeMS: ミリ秒単位のタイムアウト値
   */
  void TimerTask::sleep ( int timeMS ) {
    // スーパークラスを用いる
    Task::sleep ( timeout, timeMS ) ;
    resume () ;
  }

  /**
   * タイムアウト時にタイマーを開始し、handleTimeout()を呼ぶ。
   *
   * 入力:int timeS: 秒単位のタイムアウト値
   */
  void TimerTask::sleepSec ( int timeS ) {
    Task::sleepSec ( timeout, timeS );
    resume () ;
  }

TimerTaskは音を発生するような単純な時間を基本としたタスクをサポートするように作られています。これはwaitの代わりにタスクのtimerオブジェクトを用いる一つの方法です。waitを用いることはマルチタスク環境では、バックグラウンドの仮想周辺装置を除いて全ては完全な停止になるため、良い方法ではありません。

TimerTaskはPWMを用いて音を発生を開始するような操作ができ、PWMを停止する時刻を決定することができます。これはここで説明するソースコードになります。

最初にexecuteメソッドを見てみましょう。スイッチ文のdefaultの部分にあり、(i>initialStateには無いことに注意しましょう(コメントに注意が書いてある)。この場合、defaultの部分はタスクを実行する最初の状態になり、その後タスクが停止します。つまり、TimerTaskに対する通常の状態が停止するからです。行う仕事があるときに開始されます。そのタスクはいくつかの設定を行い、ある時間経過するまで待機します。このタイプの仕事はTimerTaskクラスに基づいたTimerToneGeneratorクラスの中で実行されます。TimerTaskクラスは仮想クラスなので、TaskToneGeneratorクラスのようなサブクラスが必要になります。

executeのスイッチ文は二つの状態を扱います。:timeoutinterruptです。これは通常のタスクとは違ってTimerTaskサブクラスにあります。TimerTaskの場合ではサブクラスはexecuteメソッドではなくhandleTimeoutメソッドを定義しなければいけません。

TimerTaskの典型的な機能は開始したタスクを停止したり待機させたりさせることです。通常、メソッドはサブクラスの中で定義され、そのタスクオブジェクトはTaskToneGeneratorクラスの中のplayToneのように開始させることができます。このメソッドは設定を行い、TimerTasksleepメソッドの一つを呼びます。これらのメソッドはタイムアウト値を設定しタスクを開始します。handleTimeoutメソッドはタイムアウトが発生したときに呼ばれます。もし、handleTimeoutメソッドがtrueを返した場合、タスクは実行を続けることを仮定します。これは通常他のアクションが設定された場合、そのタスクが待機期間に入ることを意味します。もしそうでなかったら、タスクは停止します。

使用できるsleepメソッドは3個あり、Timerクラスで使えるメソッドの型と一致します。これらはtick、ミリ秒、秒を基にしたタイムアウトを持っています。

TimerTaskは割り込み処理も設定します。handleInterruptメソッドはそのタスクが割り込まれたときに呼ばれます。このメソッドの結果は、もしそのタスクがタイムアウトが起こるまで走り続けるならばtrueにします。そのタスクが終了すべき場合はfalseの値を返すべきです。最後の場合、そのメソッドは必要な整理を実行します。TaskToneGeneratorクラスでは、この整理プロセスは音の出力を停止します。

TimerTaskオブジェクトは様々な目的で用いることができます。例えば、watchdog timerタスクは他のタスクから周期的な割り込みが必要です。もしその割り込み発生に失敗すると、watchdog timerタスクは何かがおかしいと仮定し、それに応じた行動をします。watchdog timerタスクは組み込みシステムでは常識です。

TimerTaskの実世界の例としてTaskToneGeneratorクラスに行きましょう。



TaskToneGeneratorクラス TaskToneGenerator.h



TaskToneGeneratorオブジェクトはマルチタスク環境では以前で用いたFreqoutクラスの代わりに使うべきでしょう。FreqoutオブジェクトはCPU.delayメソッドを用いて固定長の音を発生します。マルチタスク環境ではFreqoutオブジェクトを用いているタスクは音が止まるまでJavelinは独占されます。 TaskToneGeneratorTimerTaskのサブクラスです。単音あるいは音のリストで指定された音の列を発生することが出来るためTimerTasksより多少複雑です。次のリストはTaskToneGeneratorクラスです。
TaskToneGenerator.h
#ifndef TASKGENERATOR
#define TASKGENERATOR

#include "TimerTask.h"
#include "mbed.h"

/**
 * TaskManagerを用いた音発生器
 *
 * TaskTimerサポートを用いて指定された期間音を発生させる。
 * Soundがしていたようにwaitは用いていない。
 */
class TaskToneGenerator : public TimerTask {
  protected:
    PwmOut* pwm ;
    PinName pin ;
    int* tones ;
    int  tonesLength;
    int tonesIndex ;
    bool pwmRunning;
    int halfCycleTime;

  /**
   * 出力周波数を設定する
   *
   * 入力:int frequency 1ヘルツ単位の周波数
   */
  void setFrequency ( unsigned int frequency );
  void update(int high, int low);
  void playToneNow ( int frequency, int time );
  void pwmstart();
  void pwmstop();

  /* 内部ルーチン  */
  void startNextTone ();
public:
  static int endTone;  // 終了時の状態
  static int noMoreTones;

  /**
   * 音発生出力を設定する
   *
   * 入力: int pin: p21のように出力ピンを設定する
   */
  TaskToneGenerator ( PinName pin );
  /**
   * タスク名を得る
   *
   * 戻り:String タスク名
   */
  char* name ();

  /**
   * 固定時間だけ音を発生する
   *
   * 入力:int frequency  1ヘルツ単位の周波数
   * 入力:int time      ミリ秒単位の持続時間
   */
  void playTone (int frequency, int time);
  /**
   * 音の組を発生する。配列は偶数個の要素が含まれる。
   * 要素の始めの値は周波数
   * 二番目は持続時間
   * 配列に少なくとも二つないと何もしない。
   *
   * 入力:int[] tones: 音の配列(frequency, duration)の対
   */
  void playTones ( int tones[], int n);
  /**
   * 音が出ていれば音の発生を切る
   */
  void stopTone ();
  /**
   * 音が出ていれば音の発生を切る。TimerTaskからの override
   *
   * 入力:int interruptValue: 無視される
   *
   * 戻り:タスクが停止したときfalse
   */
  bool handleInterrupt ( int interruptValue );
  /**
   * TaskManagerで呼ばれるタスク実行ルーチン
   *
   * 戻り:boolean: 終了したときfalse、残りがあるときtrue
   */
  bool handleTimeout ();
  bool running();
};

#endif


TaskToneGeneratorクラス TaskToneGenerator.cpp



TaskToneGenerator.cpp
#include "TaskToneGenerator.h"
#include "mbed.h"

/**
 * TaskManagerを用いた音発生器
 *
 * TaskTimerサポートを用いて指定された期間音を発生させる。
 */
/**
 * 出力周波数を設定する
 *
 * 入力:int frequency 1ヘルツ単位の周波数
 */
void TaskToneGenerator::setFrequency ( unsigned int frequency ) {
  if ( frequency < 1 )
    frequency = 1 ;
  halfCycleTime = (unsigned int)50000 / frequency ;        // 要修正
  update ( halfCycleTime, halfCycleTime ) ;
}

void TaskToneGenerator::update(int high, int low)
{
  pwm->period_us( high+low) ;
  pwm->pulsewidth_us(high);
}
/* Internal routine */
void TaskToneGenerator::playToneNow ( int frequency, int time ) {
    // PWMを設定する
  setFrequency ( frequency ) ;
    // PWMとタスクがすでに入っているかどうかチェックする
  if ( ! pwmRunning ) {
     pwmRunning = true ;
      // mbedではできない
     // pwm->start () ;    // PWMを開始する
      pwmstart();
  }

  /* 指定した時間Sleepする
   * 後でtimerRunningのstatusを設定するからtimerRunning
   * のチェックをした後に実行しなければならない
   */
  TimerTask::sleep(time) ;   //
}

void TaskToneGenerator::pwmstart()
{
   update(halfCycleTime,halfCycleTime);
}
void TaskToneGenerator::pwmstop()
{
  update(0,halfCycleTime);
}


  /* 内部ルーチン  */
void TaskToneGenerator::startNextTone () {
    // tonesIndexはいつも正しい対を参照する

  playToneNow ( tones[tonesIndex], tones[tonesIndex+1] ) ;
  tonesIndex += 2 ;

    // リストの中の最後の音かどうかtonesIndexを調整する
  if (( tonesIndex + 2 ) > tonesLength ) {
    tonesIndex = noMoreTones ;
  }

}


TaskToneGenerator::TaskToneGenerator ( PinName pin ) {
  pwmRunning = false ;
  this->pin = pin ;
  pwm = new PwmOut ( pin ) ;
}

/**
 * タスク名を得る
 *
 * 戻り:String タスク名
 */
char* name () {
  return "TaskToneGenerator" ;
}


/**
 * 固定時間だけ音を発生する
 *
 * 入力:int frequency  1ヘルツ単位の周波数
 * 入力:int time      ミリ秒単位の持続時間
 */
void TaskToneGenerator::playTone (int frequency, int time) {
  tonesIndex = noMoreTones ;
  playToneNow ( frequency, time ) ;
}

/**
 * 音の組を発生する。配列は偶数個の要素が含まれる。
 * 要素の始めの値は周波数
 * 二番目は持続時間
 * 配列に少なくとも二つないと何もしない。
 *
 * 入力:int[] tones: 音の配列(frequency, duration)の対
 */
void TaskToneGenerator::playTones ( int tones[], int n) {
  tonesLength = n;
  if ( tonesLength >= 2 )
  {
    this->tones = tones ;
    tonesIndex = 0 ;
    startNextTone () ;
  }
}


/**
 * 音が出ていれば音の発生を切る
 */
void TaskToneGenerator::stopTone () {
  Interrupt ( 0 ) ;
}

/**
 * 音が出ていれば音の発生を切る。TimerTaskからの override
 *
 * 入力:int interruptValue: 無視される
 *
 * 戻り:タスクが停止したときfalse
 */
bool TaskToneGenerator::handleInterrupt ( int interruptValue ) {
  if ( pwmRunning ) {
    tonesIndex = noMoreTones ;    //次のタイムアウトでdisableにする
    handleTimeout () ;            //タイムアウトさせる
  }
  return false ;
}

/**
 * TaskManagerで呼ばれるタスク実行ルーチン
 *
 * 戻り:boolean: 終了したときfalse、残りがあるときtrue
 */
bool TaskToneGenerator::handleTimeout () {
  if ( tonesIndex == noMoreTones ) {
    // 残りの音がないときPWMを切る
    pwmRunning = false ;
    pwmstop();          // 音を切る
    return false ;     // タスクを停止
  } else {
    // Get next tone
    startNextTone () ; // タイマーと次の音を開始
    return true ;      // タスクを続ける
  }
}

/**
 *
 */
bool TaskToneGenerator::running() {
    if( pwmRunning ) return true;
    return false;
}

int TaskToneGenerator::endTone=1;  // 終了時の状態
int TaskToneGenerator::noMoreTones=-1;


TaskToneGeneratorクラスはPWMオブジェクトと出力ピン番号を含んで多くのオブジェクト変数を持っています。他の変数は音リストで指定された音を演奏する際の音を発生させるために用います。

TaskToneGeneratorのコンストラクタはp21のようにピン番号を引数として持ちます。これはPWMオブジェクトを設定するのに用いられます。システムはスピーカーを一つだけ持っているので通常一つのTaskToneGeneratorオブジェクトが作られます。

setFrequencyメソッドはPWMの設定に用いられますが、これはprotectedメソッドです。これはそのオブジェクトによってではなく、他のメソッドからのみ呼ばれることを意味します。音の発生を扱うオブジェクトメソッドによって用いられます。

playToneplayTonesはタスクを開始するメソッドです。playToneメソッドは定数のnoMoreTonestonesIndexオブジェクト変数を設定します。この音はplayToneNowを用いて設定されます。

playTonesメソッドはバラメータとして整数型の配列を用います。その配列の要素数は整数でなければいけません。2つの要素の最初は周波数で二番目は持続時間です。playTonesメソッドに渡す同様のタイプの引数があります。リストの中の全ての音が演奏されたときにそのタスクは停止します。playTonesメソッドは始めに配列の中に少なくとも一対あることを確かめます。配列の参照をオブジェクトのインスタンス変数に保存し、配列の最初の要素を参照するtonesIndexを設定します。startNextToneメソッドは新しい音を出すための参照された要素を用います。

残りのメソッドはprotectedであり、クラスの中のみで用いられます。playToneNowメソッドは音の周波数を設定し、PWMを開始します。そのタスクは音の持続時間sleepします。

リストの最後にあるhandleTimeoutメソッドはタスクがsleepからwake upしたときに呼ばれます。もしPWMを行う音がない場合には停止し、スピーカに用いられているピンは入力に設定され確実に次の音は開始されません。もし、多くの音があったならば、startNextToneメソッドが呼ばれます。handleTimeoutメソッドはもし音がもうない場合にfalseを戻します。これはタスクが停止するようにします。次のplayToneあるいはplayTones呼び出しで再開します。もし戻り値がtrueの場合にはタスクは実行し続けます。startNextToneメソッドは次の音を設定し、sleepメソッドを呼びます。

stopToneはアクティブなplayToneを停止するために呼ばれるか、あるいはplayTonesは出力を初期化します。stopToneメソッドはこれを行うためにinterruptを用いています。タスクはhandleInterruptメソッドを呼ぶことによって割り込みを通知されます。このメソッドはPWMがまだ実行しているかどうかをチェックし、もしそうだったらそれを止めます。

次のプログラムは音がどのように発生されるかを示します。

MultitaskingTest3クラス MultitaskingTest3.h



MultitaskingTest3.h
#include "Task.h"
#include "TaskToneGenerator.h"
#include "common.h"

class MultitaskingTest3 : public InterruptTask
{
  static const int waitForToneDone = 1 ;
  static const int waitForTonesDone = 2 ;
 public:
  // 音リストは周波数と持続時間(msec)の対の組である
    static int toneList [];// = { 120, 50, 240, 50, 360, 50 } ;

    TaskToneGenerator* toneGenerator;

    MultitaskingTest3();

protected:
  void execute ();
};


MultitaskingTest3クラス MultitaskingTest3.cpp



MultitaskingTest3.cpp
#include "MultitaskingTest3.h"

MultitaskingTest3::MultitaskingTest3()
{
  toneGenerator = new TaskToneGenerator ( p21 ) ;   //
  state = 0;
}

void MultitaskingTest3::execute () {
  switch ( state ) {
    case initialState:
      pc.printf ( "Initial state\n" ) ;
      toneGenerator->playTone ( 200, 1000 ) ;
      nextState ( waitForToneDone ) ;
      break ;

    case waitForToneDone:
      if ( toneGenerator->running ()) {
        pc.printf ( "Waiting for tone to end\n" ) ;
      } else {
        toneGenerator->playTones ( toneList, 16 ) ;
        nextState ( waitForTonesDone ) ;
      }
      break ;

    case waitForTonesDone:
      if ( toneGenerator->running ()) {
        pc.printf ( "Waiting for tone list to end\n" ) ;
      } else {
        stop () ;
      }
      break ;

    default:    // 異常な状態を捕獲し停止するためにdefaultにする
      stop () ;
      break;
  }
}
//int MultitaskingTest3::toneList [] = { 120, 3000, 240, 3000, 360, 5000 } ;
//                                     1200Hz,3秒,2400Hz,3秒,3.6kHz,5秒
int MultitaskingTest3::toneList [] =  { 26, 500, 29, 500, 33, 500,35,500,39,500,44,500,49,500,52,500 } ;
// 音階


メインプログラム



main.cpp
#include "mbed.h"
#include "MultitaskingTest3.h"

Serial pc(USBTX, USBRX); // tx, rx

int main()
{
  pc.baud(115200);
  MultitaskingTest3 Mt3;
  Task::TaskManager () ;
  pc.printf( "All done\n" ) ;
}


サンプルプログラムは二つのタスクを実際に作ります。一つはMultiaskingTest3でもう一つはTaskToneGeneratorです。MultitaskingTest3タスクはinitialState中で一つのtoneGeneratorを開始します。playTonesメソッド呼び出しで再び開始される前にtoneGeneratorが停止するのを待機します。

通常、TaskToneGeneratorは他のタスクが仕事をしている間に、音の発生を扱います。MultitaskingTest3タスクは多くの状態メッセージを単純にプリントします。もしSoundオブジェクトが用いられた場合はこのようなことはできません。

テキストの塊をプリントする能力は重要でないと思われますが、音が発生されている間にいかに他のアクションができるかを示しています。