目次

スイッチの状態取得(シフトレジスタ利用)

 スイッチには、押したり、離したりするときにチャタリングと
 呼ばれる論理値が確定しない状態があります。



 チャタリングは、10msから50ms程度でおさまるので
 この時間分だけ、間をおいてスイッチ状態を複数回
 リードして、判定するという定石があります。

 時間差を利用する場合、タイマー割込みを使うのが楽です。

 スイッチの状態を記憶するには、1バイトの変数を
 利用して、過去から現在までを1ビットの論理値で
 記録します。



 変数を1ビットの論理値を時系列で記録しているとき
 この変数をシフトレジスタと呼びます。

 タイマー割込みで、1ビット論理値の記憶タイミング
 を作っていると考えます。



 変数をシフトレジスタとするには、次のコードを使います。

  /* timer interrupt */
  if ( TFLAG == ON ) {
    /* clear flag */
    TFLAG = OFF ;
    /* get current switch state */
    tmp = PORTB ;
    /* shift */
    sregx <<= 1 ;
    sregy <<= 1 ;
    /* mask */
    sregx &= 0x03 ;
    sregy &= 0x03 ;
    /* update LSB */
    if ( tmp & 1 ) { sregx |= 1 ; }
    if ( tmp & 2 ) { sregy |= 1 ; }
    /* judge */
  }

 タイマー割込みで、希望の周期をつくり、スイッチ状態を
 シフトレジスタに入れるので、論理レベルだけに注目する
 場合、0→1か1→0の変化に注目する場合のどちらにも
 対応できます。

 タイマー割込み利用で、時間経過を常に監視する必要が
 なくなり、空いた時間を他の処理に振向けることが可能
 となります。

 2つのプッシュボタンを利用したLEDの点灯、消灯制御の
 処理をタイマー割込みを使い、書き換えてみます。

#define OFF 0
#define ON  OFF+1

typedef unsigned char UBYTE ;

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 TFLAG xflags.BIT.B0

volatile UBYTE state ;
volatile UBYTE btn1 ;
volatile UBYTE btn2 ;

/* interruption */
void interrupt(void)
{
  /* timer0 overflow interrupt 250Hz = 4ms */
  if ( INTCON.T0IF == ON ) {
    /* clear flag */
    INTCON.T0IF = OFF ;
    /* initialize */
    TMR0 = 6 ;
    /* set flag */
    TFLAG = ON ;
  }
}

/* prototype */
void usr_init(void);

void main(void)
{
  /* initialize PORT */
  usr_init();
  /* endless loop */
  while (ON) {
    /* timer interrupt */
    if ( TFLAG == ON ) {
      /* clear flag */
      TFLAG = OFF ;
      /* shift */
      btn1 <<= 1 ;
      btn2 <<= 1 ;
      /* mask */
      btn1 &= 0x03 ;
      btn2 &= 0x03 ;
      /* get current switch state */
      btn1 |= PORTB.F1 ;
      btn2 |= PORTB.F2 ;
      /* judge */
      if ( btn1 == 0x03 ) { state = ON ; }
      if ( btn2 == 0x03 ) { state = OFF; }
    }
    /* impress */
   PORTB.F0 = state & ON ;
  }
}

void usr_init(void)
{
  /* turn off LED */
  PORTB.F0 = OFF ;
  /* direction */
  TRISB = 0xfe ;
  /* clear flags */
  xflags.DR = 0 ;
  /* set first state */
  state = OFF ;
  btn1 = 0 ;
  btn2 = 0 ;
  /* initialize Timer 0 */
  {
    /*
       4MHz/4 = 1MHz -> 1MHz/16 = 62.5kHz prescaler = 1:16
    */
    OPTION_REG = 0x03 ;
    /* 256 - 250 = 6 */
    TMR0 = 6 ;
    /* enable timer0 overflow interrupt */
    INTCON.T0IE = ON ;
  }
  /* enable general interrupt */
  INTCON.GIE = ON ;
}

 利用する回路は、以下。



 この例では、4msごとにプッシュスイッチの状態を
 取得し8msごとに、スタートとストップの動作判定
 をします。

 判定周期を長くしたい場合、シフトレジスタのLSBに
 スイッチの状態を3から5程度入れておき、判定値
 を変更します。

 言葉の説明では、面倒に思えますが、コードは単純。

      /* mask */
      btn1 &= 0x0f ;
      btn2 &= 0x0f ;
      /* get current switch state */
      btn1 |= PORTB.F1 ;
      btn2 |= PORTB.F2 ;
      /* judge */
      if ( btn1 == 0x0f ) { state = ON ; }
      if ( btn2 == 0x0f ) { state = OFF; }

 このコードでは、2変数にそれぞれ過去3回分の
 スイッチ状態を記憶したことになります。



 過去3回の現在1回の情報となると、4msx8=32msだけ
 論理値の1が続いていることになります。マスク値と
 判定値を変えるだけで、より長い周期のサンプリング
 が可能です。

 仕様がかわり、0→1を捉えて状態を変更したいと
 なれば、判定値を変えて対応します。

 変化を捉えたいので、2回のサンプリングの結果で
 判定するようにコードを修正します。

      /* mask */
      btn1 &= 0x03 ;
      btn2 &= 0x03 ;
      /* get current switch state */
      btn1 |= PORTB.F1 ;
      btn2 |= PORTB.F2 ;
      /* judge */
      if ( btn1 == 0x01 ) { state = ON ; }
      if ( btn2 == 0x01 ) { state = OFF; }

 この場合、現在とひとつ前のスイッチの状態
 だけで考えればよくなります。



 仕様変更になった場合でも、修正箇所を極力少なくし
 やることを減らすと、バグが入りにくくなります。

 仕様変更があっても、使いまわせるコードを日頃から
 考えておくと、守備範囲が広がります。


目次

inserted by FC2 system