目次

Flashing toy with RTOS

 知人から次のようなLEDを使った玩具を見せて貰いました。



 これを出番の減ったPIC16F84Aで実現できるのでは思い
 次の状態遷移図を描いて考えてみました。




 状態値で担当する処理が異なる場合、RTOS(Real Time Operating System)
 を利用すれば簡単になると思い、自作のRTOSであるUSOを適用することに。

 USOは、使えるSRAMの量が少ないワンチップマイコンのために
 開発したRTOSなので、各タスクの仕事を次のように割当て。

 TASK1からTASK3は、LEDの点滅なので、個々にカウンタを
 用意して、カウンタの値で論理値の1か0を出力します。

#define LLED0 PORTB.F0
#define LLED1 PORTB.F1
#define LLED2 PORTB.F2

volatile UBYTE xtim0 ;
volatile UBYTE xtim1 ;
volatile UBYTE xtim2 ;

#define OFF 0
#define ON  OFF+1

/* LED0 flashing */
void tsk1_proc(void)
{
  /* impress */
  LLED0 = xtim0 & ON ;
  /* increment */
  xtim0++ ;
  /* 250ms */
  wai_tsk( 25 ) ;
}

/* LED1 flashing */
void tsk2_proc(void)
{
  /* impress */
  LLED1 = xtim1 & ON ;
  /* increment */
  xtim1++ ;
  /* 250ms */
  wai_tsk( 25 ) ;
}

/* LED2 flashing */
void tsk3_proc(void)
{
  /* impress */
  LLED2 = xtim2 & ON ;
  /* increment */
  xtim2++ ;
  /* 250ms */
  wai_tsk( 25 ) ;
}

 USOでは、時間待ちにシステムコールwai_tskを利用。
 wai_tskのパラメータは、10msの整数倍の整数として
 PICの場合は、0から255とします。

 各タスクでは、カウンタのLSB(Least Significant Bit)を使い
 1と0で点滅を扱うため、250msx2ごとに点灯と消灯が変化。

 LEDには、トランジスタを接続して電流増幅します。



 トランジスタのインバータが接続されているので、PICの
 ピンから1を出力すると点灯になります。

 TASK4は全LEDを消灯するので、ポートBに0を出力して終了
 させればよいと判断。

void tsk4_proc(void)
{
  /* turn off all LED */
  PORTB = 0x00 ;
  /* exit */
  slp_tsk();
}

 USOでは、他のタスクに起動して貰い、必要な仕事を
 終えて、次に起動するまで休息に入る場合、システム
 コールslp_tskを使います。

 チャタリングは、スイッチを使っていると発生する
 ON、OFFが高速で切り替わる現象。30msくらいで収束
 するので、時間間隔をおいて何度かスイッチの状態
 を読み込んで、変化を捉えます。




 USOでは、周期的な処理で時間間隔を作りたいときに
 システムコールwai_tskを使えばよく、チャタリング
 除去は簡単。
 次のようにTASK5を定義します。

#define MASK0F 0x0F
#define MASK0E 0x0e

volatile UBYTE xsft ;
volatile UBYTE ysft ;
volatile UBYTE zsft ;

void tsk5_proc(void)
{
  UBYTE tmp;
  /* get switch state */
  tmp = PORTA ;
  tmp ^= MASK0F ;
  /* shift */
  xsft <<= 1;
  ysft <<= 1;
  zsft <<= 1;
  /* mask */
  xsft &= MASK0E ;
  ysft &= MASK0E ;
  zsft &= MASK0E ;
  /* update LSB */
  if ( tmp & 1 ) { xsft |= ON ; }
  if ( tmp & 2 ) { ysft |= ON ; }
  if ( tmp & 4 ) { zsft |= ON ; }
  /* generate event flag */
  if ( xsft == 1 ) { XFLG = ON ; }
  if ( ysft == 1 ) { YFLG = ON ; }
  if ( zsft == 1 ) { ZFLG = ON ; }
  /* delay 50ms */
  wai_tsk( 5 );
}

 シフトレジスタを使い、4回読み込んで0→1のエッジ
 変化でイベントフラグを設定しています。
 スイッチの状態読込み周期を50msとしました。

 スイッチは、次の回路のように負論理動作なので
 状態を反転して正論理で処理しています。





 TASK0は、各LEDの動作管理をしますが、スイッチの
 状態変化を捉えて、状態遷移図に合わせて状態値を
 遷移させるだけにとどめます。

 TASK1からTASK4は、電源を入れたときには休息状態で
 TASK0とTASK5は、常に与えられた仕事をしていれば
 よいと考え、システム起動時の状態を決めます。

 READYは、順番が回ってきたらCPUを使って仕事をし
 SUSPENDは、他のタスクがREADYにしてくれるまで
 休息状態としています。

 USOではWAITという状態も用意しています。
 WAITは、周期的に仕事をさせたいときに使います。

 タスクは、次のように状態を遷移していきます。



 TASK1からTASK4は、TASK0がSUSPENDからREADYに状態を
 変化させます。TASK0は、状態遷移をスイッチのエッジ
 変化をトリガーに使い、判断します。

 スイッチのエッジ変化は、イベント通知フラグで捉えて
 状態値を変化させればよいと考えます。

 TASK0の処理は、単純で以下。

void tsk0_proc(void)
{
  /* judge */
  if ( XFLG == ON ) {
    /* clear event flag */
    XFLG = OFF ;
    /* update state */
    if ( state == 1 ) { state = 4 ; }
    if ( state == 0 ) { state = 1 ; }
    /* change state */
    SFLAG = ON ;
  }
  if ( YFLG == ON ) {
    /* clear event flag */
    YFLG = OFF ;
    /* update state */
    if ( state == 2 ) { state = 4 ; }
    if ( state == 0 ) { state = 2 ; }
    /* change state */
    SFLAG = ON ;
  }
  if ( ZFLG == ON ) {
    /* clear event flag */
    ZFLG = OFF ;
    /* update state */
    if ( state == 3 ) { state = 4 ; }
    if ( state == 0 ) { state = 3 ; }
    /* change state */
    SFLAG = ON ;
  }
  /* update task state */
  if ( SFLAG == ON ) {
    /* clear flag */
    SFLAG = OFF ;
    /* stage 1 */
    if ( state == 1 ) { rsm_tsk( TSK_ID1 ); }
    /* stage 2 */
    if ( state == 2 ) { rsm_tsk( TSK_ID2 ); }
    /* stage 3 */
    if ( state == 3 ) { rsm_tsk( TSK_ID3 ); }
    /* return first state */
    if ( state == 4 ) {
      state = 0 ;
      sus_tsk( TSK_ID1 );
      sus_tsk( TSK_ID2 );
      sus_tsk( TSK_ID3 );
      rsm_tsk( TSK_ID4 );
    }
  }
}

 前半で状態値を遷移させ、後半でタスクの状態を変えています。

 TASK4は、TASK1からTASK3をSUSPENDにしたとき
 LEDを消灯するために用意。

 TASK0は、他のタスクの状態を変えるだけで、ハードウエアに
 関係する処理には関与していないことが重要です。

 PICのUSOでは、タスク数を最大8として、状態遷移は
 round robin方式を採用しました。状態遷移は、単純。

    if ( is_tsk_ready( run_tsk ) ) {
      switch ( run_tsk ) {
        case TSK_ID0 : tsk0_proc(); break ;
        case TSK_ID1 : tsk1_proc(); break ;
        case TSK_ID2 : tsk2_proc(); break ;
        case TSK_ID3 : tsk3_proc(); break ;
        case TSK_ID4 : tsk4_proc(); break ;
        case TSK_ID5 : tsk5_proc(); break ;
        case TSK_ID6 : tsk6_proc(); break ;
        case TSK_ID7 : tsk7_proc(); break ;
        default      : break ;
      }
    }
    run_tsk++;
    if ( run_tsk == TSK_ID_MAX ) { run_tsk = TSK_ID0 ; }

 10msごとにカウンタ値を減らすのは、タイマー割込みで
 イベント通知フラグをセットして知らせています。

    if ( EFLAG == ON ) {
      EFLAG = OFF ;
      timer_handler();
    }

void  timer_handler(void)
{
  UBYTE loop;
  for ( loop = 0 ; loop < TSK_ID_MAX ; loop++ ) {
    if ( waitq & *(bpat+loop) ) {
      *(wcount+loop) = *(wcount+loop) - 1 ;
      if ( *(wcount+loop) == 0 ) { rsm_tsk(loop); }
    }
  }
}

 タスクの状態は、次の3変数で管理。

volatile UBYTE ready  ;
volatile UBYTE suspend;
volatile UBYTE waitq  ;

 タスクは3状態のどれかに属するので、タスクIDが3変数の
 該当するビットのセットで判断できるようになっています。



 電源を入れたときに、タスクの初期状態を決めておいた方が
 楽として、システムコールsta_tskで各タスクの状態を指定
 します。そのとき、変数ready、suspend、waitqのタスクID
 で示すビットをセット。

 システムコールは、次のように使います。

  sta_tsk(TSK_ID0,TTS_READY);
  sta_tsk(TSK_ID1,TTS_SUSPEND);
  sta_tsk(TSK_ID2,TTS_SUSPEND);
  sta_tsk(TSK_ID3,TTS_SUSPEND);
  sta_tsk(TSK_ID4,TTS_SUSPEND);
  sta_tsk(TSK_ID5,TTS_READY);
  sta_tsk(TSK_ID6,TTS_SUSPEND);
  sta_tsk(TSK_ID7,TTS_SUSPEND);

 USOを使い、スイッチでLEDを点滅させる玩具の
 ファームウエアは以下。

typedef unsigned char UBYTE ;
typedef unsigned int  UWORD ;

typedef union {
  struct {
    unsigned B7:1;
    unsigned B6:1;
    unsigned B5:1;
    unsigned B4:1;
    unsigned B3:1;
    unsigned B2:1;
    unsigned B1:1;
    unsigned B0:1;
  } BIT ;
  unsigned char DR ;
} FLAGSP ;

volatile FLAGSP xflags ;

#define SFLAG xflags.BIT.B0
#define EFLAG xflags.BIT.B1
#define XFLG  xflags.BIT.B2
#define YFLG  xflags.BIT.B3
#define ZFLG  xflags.BIT.B4

#define OFF 0
#define ON  OFF+1

#define MASKFF 0xff
#define MASK0F 0x0F
#define MASK0E 0x0e

#define LLED0 PORTB.F0
#define LLED1 PORTB.F1
#define LLED2 PORTB.F2

#define CNTBEGIN 6
#define CNTMAX   50

#define TSK_ID_MAX 8

#define TSK_ID0 0
#define TSK_ID1 1
#define TSK_ID2 2
#define TSK_ID3 3
#define TSK_ID4 4
#define TSK_ID5 5
#define TSK_ID6 6
#define TSK_ID7 7

#define TTS_SUSPEND 0
#define TTS_WAIT    TTS_SUSPEND+1
#define TTS_READY   TTS_SUSPEND+2

volatile UBYTE ready  ;
volatile UBYTE suspend;
volatile UBYTE waitq ;
volatile UBYTE run_tsk;
volatile UBYTE bpat[8] ;
volatile UBYTE wcount[8] ;

volatile UBYTE xtim0 ;
volatile UBYTE xtim1 ;
volatile UBYTE xtim2 ;

volatile UBYTE timcnt ;
volatile UBYTE state ;

volatile UBYTE xsft ;
volatile UBYTE ysft ;
volatile UBYTE zsft ;

/*------------------------*/
/* task function protoype */
/*------------------------*/
void tsk0_proc(void);
void tsk1_proc(void);
void tsk2_proc(void);
void tsk3_proc(void);
void tsk4_proc(void);
void tsk5_proc(void);
void tsk6_proc(void);
void tsk7_proc(void);

/*-----------------------*/
/* system call prototype */
/*-----------------------*/
void  init_os(void);
void  cre_tsk(UBYTE tid);
void  sta_tsk(UBYTE tid,UBYTE sta);
void  rsm_tsk(UBYTE tid);
void  sus_tsk(UBYTE tid);
void  slp_tsk(void);
void  wai_tsk(UBYTE x);
UBYTE is_tsk_ready(UBYTE tid);
void  timer_handler(void);

/* function prototype */
void  init_usr(void);

/* interrupt handler */
void interrupt(void)
{
  /* generate 100Hz */
  if ( INTCON.T0IF == ON ) {
    /* clear flag */
    INTCON.T0IF = OFF ;
    /* initialize */
    TMR0 = CNTBEGIN ;
    /* increment */
    timcnt++ ;
    /* judge */
    if ( timcnt == CNTMAX ) {
      /* clear */
      timcnt = 0 ;
      /* set flag */
      EFLAG = ON ;
    }
  }
}

void main(void)
{
  /* initialize */
  init_usr() ;
  /* initialize task */
  init_os();
  /* endless loop */
  run_tsk = TSK_ID0 ;
  while ( ON ) {
    /* round robbin */
    if ( is_tsk_ready( run_tsk ) ) {
      switch ( run_tsk ) {
        case TSK_ID0 : tsk0_proc(); break ;
        case TSK_ID1 : tsk1_proc(); break ;
        case TSK_ID2 : tsk2_proc(); break ;
        case TSK_ID3 : tsk3_proc(); break ;
        case TSK_ID4 : tsk4_proc(); break ;
        case TSK_ID5 : tsk5_proc(); break ;
        case TSK_ID6 : tsk6_proc(); break ;
        case TSK_ID7 : tsk7_proc(); break ;
        default      : break ;
      }
    }
    run_tsk++;
    if ( run_tsk == TSK_ID_MAX ) { run_tsk = TSK_ID0 ; }
    /* 10ms counter */
    if ( EFLAG == ON ) {
      EFLAG = OFF ;
      timer_handler();
    }
  }
}

/* define function body */
void init_usr(void)
{
  /* I/O values */
  PORTB = 0x00 ;
  /* I/O directions */
  TRISA = 0xff ;
  TRISB = 0x00 ;
  /* initialize Timer 0 */
  {
    /*
       10MHz/4 = 2.5MHz -> 2.5MHz/2 = 1,250kHz prescaler = 1:2
       1,250kHz / 250 = 5kHz
    */
    OPTION_REG = 0x80 ;
    /* 256 - 6 = 250 */
    TMR0 = CNTBEGIN ;
    /* enable timer0 overflow interrupt */
    INTCON.TMR0IE = ON ;
  }
  /* enable general interrupt */
  INTCON.GIE = ON ;
  /* clear flag */
  xflags.DR = 0 ;
  /* initialize variables */
  timcnt = 0 ;
  xtim0 = 0 ;
  xtim1 = 0 ;
  xtim2 = 0 ;
  state = 0 ;
  xsft = 0 ;
  ysft = 0 ;
  zsft = 0 ;
}

/*----------------*/
/* task functions */
/*----------------*/

/* system control */
void tsk0_proc(void)
{
  /* judge */
  if ( XFLG == ON ) {
    /* clear event flag */
    XFLG = OFF ;
    /* update state */
    if ( state == 1 ) { state = 4 ; }
    if ( state == 0 ) { state = 1 ; }
    /* change state */
    SFLAG = ON ;
  }
  if ( YFLG == ON ) {
    /* clear event flag */
    YFLG = OFF ;
    /* update state */
    if ( state == 2 ) { state = 4 ; }
    if ( state == 0 ) { state = 2 ; }
    /* change state */
    SFLAG = ON ;
  }
  if ( ZFLG == ON ) {
    /* clear event flag */
    ZFLG = OFF ;
    /* update state */
    if ( state == 3 ) { state = 4 ; }
    if ( state == 0 ) { state = 3 ; }
    /* change state */
    SFLAG = ON ;
  }
  /* update task state */
  if ( SFLAG == ON ) {
    /* clear flag */
    SFLAG = OFF ;
    /* stage 1 */
    if ( state == 1 ) { rsm_tsk( TSK_ID1 ); }
    /* stage 2 */
    if ( state == 2 ) { rsm_tsk( TSK_ID2 ); }
    /* stage 3 */
    if ( state == 3 ) { rsm_tsk( TSK_ID3 ); }
    /* return first state */
    if ( state == 4 ) {
      state = 0 ;
      sus_tsk( TSK_ID1 );
      sus_tsk( TSK_ID2 );
      sus_tsk( TSK_ID3 );
      rsm_tsk( TSK_ID4 );
    }
  }
}

/* LED0 flashing */
void tsk1_proc(void)
{
  /* impress */
  LLED0 = xtim0 & ON ;
  /* increment */
  xtim0++ ;
  /* 250ms */
  wai_tsk( 25 ) ;
}

/* LED1 flashing */
void tsk2_proc(void)
{
  /* impress */
  LLED1 = xtim1 & ON ;
  /* increment */
  xtim1++ ;
  /* 250ms */
  wai_tsk( 25 ) ;
}

/* LED2 flashing */
void tsk3_proc(void)
{
  /* impress */
  LLED2 = xtim2 & ON ;
  /* increment */
  xtim2++ ;
  /* 250ms */
  wai_tsk( 25 ) ;
}

void tsk4_proc(void)
{
  /* turn off all LED */
  PORTB = 0x00 ;
  /* exit */
  slp_tsk();
}

/* debouncing */
void tsk5_proc(void)
{
  UBYTE tmp;
  /* get switch state */
  tmp = PORTA ;
  tmp ^= MASK0F ;
  /* shift */
  xsft <<= 1;
  ysft <<= 1;
  zsft <<= 1;
  /* mask */
  xsft &= MASK0E ;
  ysft &= MASK0E ;
  zsft &= MASK0E ;
  /* update LSB */
  if ( tmp & 1 ) { xsft |= ON ; }
  if ( tmp & 2 ) { ysft |= ON ; }
  if ( tmp & 4 ) { zsft |= ON ; }
  /* generate event flag */
  if ( xsft == 1 ) { XFLG = ON ; }
  if ( ysft == 1 ) { YFLG = ON ; }
  if ( zsft == 1 ) { ZFLG = ON ; }
  /* delay 50ms */
  wai_tsk( 5 );
}

void tsk6_proc(void)
{
}

void tsk7_proc(void)
{
}

/*------------------*/
/* system call body */
/*------------------*/
void init_os(void)
{
  /* clear RTOS values */
  ready   = 0 ;
  suspend = 0 ;
  waitq   = 0 ;
  /* bit pattern */
  *(bpat+0) = 0x01 ; *(bpat+1) = 0x02 ;
  *(bpat+2) = 0x04 ; *(bpat+3) = 0x08 ;
  *(bpat+4) = 0x10 ; *(bpat+5) = 0x20 ;
  *(bpat+6) = 0x40 ; *(bpat+7) = 0x80 ;
  /* TASK create */
  cre_tsk(TSK_ID0); cre_tsk(TSK_ID1);
  cre_tsk(TSK_ID2); cre_tsk(TSK_ID3);
  cre_tsk(TSK_ID4); cre_tsk(TSK_ID5);
  cre_tsk(TSK_ID6); cre_tsk(TSK_ID7);
  /* TASK state */
  sta_tsk(TSK_ID0,TTS_READY);
  sta_tsk(TSK_ID1,TTS_SUSPEND);
  sta_tsk(TSK_ID2,TTS_SUSPEND);
  sta_tsk(TSK_ID3,TTS_SUSPEND);
  sta_tsk(TSK_ID4,TTS_SUSPEND);
  sta_tsk(TSK_ID5,TTS_READY);
  sta_tsk(TSK_ID6,TTS_SUSPEND);
  sta_tsk(TSK_ID7,TTS_SUSPEND);
}

void cre_tsk(UBYTE tid)
{
  wcount[tid] = 0;
}

void sta_tsk(UBYTE tid,UBYTE sta)
{
  UBYTE tmp ;
  /* get bit pattern */
  tmp = *(bpat+tid);
  /* set bit */
  if ( sta == TTS_READY   ) { ready   |= tmp; }
  if ( sta == TTS_SUSPEND ) { suspend |= tmp; }
  if ( sta == TTS_WAIT    ) { waitq   |= tmp; }
}

void rsm_tsk(UBYTE tid)
{
  UBYTE tmp ;
  UBYTE tmpx ;
  /* get bit pattern */
  tmp  = *(bpat+tid);
  tmpx = tmp ^ MASKFF ;
  /* bit handling */
  ready   |= tmp ;
  suspend &= tmpx;
  waitq   &= tmpx;
}

void sus_tsk(UBYTE tid)
{
  UBYTE tmp ;
  UBYTE tmpx ;
  /* get bit pattern */
  tmp  = *(bpat+tid);
  tmpx = tmp ^ MASKFF ;
  /* bit handling */
  ready   &= tmpx;
  suspend |= tmp ;
  waitq   &= tmpx;
}

void slp_tsk(void)
{
  sus_tsk(run_tsk);
}

void wai_tsk(UBYTE x)
{
  UBYTE tmp ;
  UBYTE tmpx ;
  /* get bit pattern */
  tmp  = *(bpat+run_tsk);
  tmpx = tmp ^ MASKFF ;
  /* bit handling */
  ready   &= tmpx;
  suspend &= tmpx;
  waitq   |= tmp ;
  /* update counter */
  *(wcount+run_tsk) = x ;
}

UBYTE is_tsk_ready(UBYTE tid)
{
  return( ready & *(bpat+tid) ) ;
}

void  timer_handler(void)
{
  UBYTE loop;
  for ( loop = 0 ; loop < TSK_ID_MAX ; loop++ ) {
    if ( waitq & *(bpat+loop) ) {
      *(wcount+loop) = *(wcount+loop) - 1 ;
      if ( *(wcount+loop) == 0 ) { rsm_tsk(loop); }
    }
  }
}

 このファームエアは、次のハードウエアでテストしました。



 スイッチとLEDは、別の基板上に半田付けしてあります。

 PIC16F84Aのテスト基板は、クロックを水晶、セラミック
 振動子と交換できるようになっています。

 PIC16F84Aで動かすと、ファームウエアのROMとRAMの利用率は以下。

 ROM 583 bytes / 1024 bytes (57%)
 RAM 38 bytes / 64 bytes (73%)

 PIC12F1501のような8ピンのPICでも、充分対応できるサイズ。

 PIC12F1501では、3ピンの入力が面倒なので、74LS148のような
 プライオリティエンコーダを使う等の工夫が必要。

 ICの数を増やすくらいなら、PIC16F1823のような14ピンの安価な
 (秋月電子で¥100)で実現した方が、費用面でお得。

 3個のLEDを同一周期で点滅させるだけであれば、RTOSを利用
 することもないです。点滅周期が、個々のLEDで異なるように
 という拡張仕様がきたとき、RTOSであれば点滅を担当している
 タスクの中にあるパラメータを変えるだけで済みます。


目次

inserted by FC2 system