Arduinoでカウントダウンタイマー

aitendoで買い物する際に49円で特売していた16セグモジュール(2.3"/1桁/red)KA2311-42B-UR91で、カウントダウンタイマーを作った。 同じ16セグLEDは共立エレショップでも在庫限り50円である。

使用部品は以下のとおり。ネジやらスペーサやら入れて1500円くらいで収まった。



arduinoと16セグLEDは、「16 segment LEDをCharlieplexing続編 - ikkei blog」を参考に、Charlieplexingで接続。Charlieplexingについては、こちらが大変わかり易い。


CPUの入出力ピンの使用状況。

  • LED用 D1-D12,A0-A5の18ピン
  • ブザー用 D0
  • スイッチ用 XT1,XT2。RCduionoならば本来水晶発振器をつなぐピンも入出力で使える
  • 未使用はD13のみ

とりあえず動いたスケッチは次の通り。内蔵RC発信8MHzでは3分のタイマーでも数秒ずれるので、使用には耐えないかも。

#include <MsTimer2.h>

/*
  カウントダウンタイマー
  ohguma 2011-10-02

  Charlieplexing
  16segment LED display test
  by ikkei  2011.01.13

  http://web.mac.com/kxm_ikkei/Site/16segLED.html
  http://blog.goo.ne.jp/jh3kxm/e/f309b4d7fce979808e75db0e4bc7f7a3

  Arduinoより2ピン多く使えるRCduino
  http://web.me.com/kuwatay/morecat_lab./Blog/%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%BC/2011/7/2_RCduino.html

  arduinoのデジタル入出力について
  http://nekosan0.bake-neko.net/structure_digital_port.html

  MsTimer2(ミリ秒単位で指定するタイマ)
  http://www.musashinodenpa.com/arduino/ref/index.php?f=1&pos=1996

*/

                // 1st digit pin assign
#define L01 9   // Anode ・・・・Common
#define L02 2   // G2
#define L03 3   // B
#define L04 4   // A2
#define L05 5   // J
#define L06 6   // I
#define L07 7   // A1
#define L08 8   // H
#define L09 1   // F
#define L10 12  // G1
#define L11 11  // E
#define L12 10  // D2
#define L13 A0  // M
#define L14 A1  // L
#define L15 A2  // D1
#define L16 A3  // K
#define L17 A4  // C
#define L18 A5  // DP


const byte LINE[ 6 ][ 18 ] = {
{ L01, L02, L03, L04, L05, L06, L07, L08, L09, L10, L11, L12, L13, L14, L15, L16, L17, L18 },
{ L03, L02, L01, L04, L05, L06, L07, L08, L09, L10, L11, L12, L13, L14, L15, L16, L17, L18 },
{ L05, L02, L03, L04, L01, L06, L07, L08, L09, L10, L11, L12, L13, L14, L15, L16, L17, L18 },
{ L06, L02, L03, L04, L05, L01, L07, L08, L09, L10, L11, L12, L13, L14, L15, L16, L17, L18 },
{ L08, L02, L03, L04, L05, L06, L07, L01, L09, L10, L11, L12, L13, L14, L15, L16, L17, L18 },
{ L09, L02, L03, L04, L05, L06, L07, L08, L01, L10, L11, L12, L13, L14, L15, L16, L17, L18 }
};

const unsigned long FONT[]={
0x00000, 0x01010, 0x09000, 0x1915A, 0x05C5C, 0x03030, 0x03C1C, 0x06000,
0x02004, 0x00420, 0x03434, 0x11110, 0x0000A, 0x10100, 0x10000, 0x02020,
0x0EAEA, 0x01010, 0x1C9C8, 0x1C94A, 0x11310, 0x04B4C, 0x14BCA, 0x06820,
0x1CBCA, 0x1CB4A, 0x01010, 0x01020, 0x02004, 0x10148, 0x00420, 0x1CA10,
0x0C9DA, 0x1A022, 0x1D85A, 0x04AC8, 0x0D85A, 0x14BC8, 0x14B80, 0x14ACA,
0x18382, 0x05858, 0x080CA, 0x02384, 0x002C8, 0x0A682, 0x08686, 0x0CACA,
0x1CB80, 0x0CACE, 0x1CB84, 0x14B4A, 0x05810, 0x082CA, 0x022A0, 0x082A6,
0x02424, 0x02410, 0x06868, 0x00AC0, 0x12510, 0x0C00A, 0x00600, 0x00048,
0x00400, 0x019D8, 0x003D0, 0x001C0, 0x011D8, 0x001E0, 0x15110, 0x01B50,
0x00390, 0x000C0, 0x01050, 0x03014, 0x01018, 0x10192, 0x00190, 0x001D0,
0x01B80, 0x01B10, 0x00180, 0x1000C, 0x11118, 0x000D8, 0x000A0, 0x000DA,
0x02424, 0x1900A, 0x00160, 0x05118, 0x01010, 0x11850, 0x02600, 0x1FFFF,
};

unsigned long segment[6] = { 0, 0, 0, 0, 0, 0 };

void alloff(void){
  for ( byte i=0; i < 18; i++){
    digitalWrite( LINE[ 0 ][ i ], LOW );
    pinMode( LINE[ 0 ][ i ], INPUT );
  }
}

void LED_scan( void ){
  static byte digit = 0;
  // make dead time all output Off (Input)
  alloff();

  // anode common output High
  pinMode( LINE[ digit ][ 0 ], OUTPUT);
  digitalWrite( LINE[ digit ][ 0 ], HIGH);

  // segment data output Low
  for ( byte i=0; i < 17; i++){
    if ( bitRead( segment[ digit ], i)){
      pinMode( LINE[ digit ][ 17-i ], OUTPUT);
    }
  }
  // next digit
  digit++;
  if ( digit >= 6 ){
    digit = 0;
  }
}

//タイマー表示値
byte ms010 = 0;
byte ms100 = 0;
byte s01 = 0;
byte s10 = 0;
byte m01 = 0;
byte m10 = 0;
#define MINUTE      3
#define MINUTE_MIN  1
#define MINUTE_MAX  15
byte default_minute = MINUTE;

#define ST_STOP     0  //停止中
#define ST_RUN      1  //カウントダウン中
#define ST_COMPLETE 2  //カウントダウン終了
#define ST_CONFIG   3  //設定中
#define ST_RESET    4  //停止中でリセット後
byte status = ST_RESET;

#define BLINK_COMPLETE 100 //1=10ms,100=1sec
#define BLINK_CONFIG   50  //1=10ms, 50=0.5sec
byte blink = 0;

#define SW_START 20
#define SW_RESET 21
#define SW_ON    0          //pull up
#define SW_OFF   1          //pull up
#define SW_DELAY 100        //チャタリング防止期間[ms]
int sw_reset_val = SW_OFF;  //現在のリセットスイッチ値
int sw_start_val = SW_OFF;  //現在のスタートスイッチ値
int sw_reset_last = SW_OFF; //前回のリセットスイッチ値
int sw_start_last = SW_OFF; //前回のスタートスイッチ値
unsigned long sw_reset_delay = 0;     //リセットスイッチのチャタリング防止遅延時間
unsigned long sw_start_delay = 0;     //スタートスイッチのチャタリング防止遅延時間

#define BUZZER    0


void setup(){
  pinMode(BUZZER, OUTPUT);
  digitalWrite(BUZZER, LOW);
  pinMode(SW_START, INPUT);
  pinMode(SW_RESET, INPUT);
  digitalWrite(SW_START, HIGH);
  digitalWrite(SW_RESET, HIGH);

  alloff();
  MsTimer2::set( 2, LED_scan); // 2ms/digit
  MsTimer2::start();
  time_reset();
}


void loop(){
  if (status == ST_COMPLETE && blink < BLINK_COMPLETE / 2) {
      segment[0] = FONT[0];
      segment[1] = FONT[0];
      segment[2] = FONT[0];
      segment[3] = FONT[0];
      segment[4] = FONT[0];
      segment[5] = FONT[0];
  } else if (status == ST_CONFIG && blink < BLINK_CONFIG / 2) {
      segment[0] = FONT[0];
      segment[1] = FONT[0];
      segment[2] = FONT[0];
      segment[3] = FONT[0];
      segment[4] = FONT[0];
      segment[5] = FONT[0];
  } else {
    if (m10 == 0) {
      //10分の桁
      segment[0] = FONT[0];
    } else {
      segment[0] = FONT['0' - ' ' + m10 ];
    }
    segment[1] = FONT['0' - ' ' + m01 ];
    segment[2] = FONT['0' - ' ' + s10 ];
    segment[3] = FONT['0' - ' ' + s01 ];
    segment[4] = FONT['0' - ' ' + ms100 ];
    segment[5] = FONT['0' - ' ' + ms010 ];
  }

  if (status == ST_COMPLETE) {
    blink++;
    if (blink >= BLINK_COMPLETE) {
      blink = 0;
    }
  } else if (status == ST_CONFIG) {
    blink++;
    if (blink >= BLINK_CONFIG) {
      blink = 0;
    }
  }
  
  sw_common();

  //スタートスイッチ処理
  sw_start();

  //リセットスイッチ処理
  sw_reset();

  if (status == ST_RUN) {
    time_countdown();
  }

  delay(10);
}

#define SW_PERIOD_CANCEL 1000 //これ以上押してもキャンセル[ms]
#define SW_PERIOD_LONG   500  //長押ししきい値[ms]
int sw_val = SW_OFF;          //現在のリセットスイッチ値
int sw_last = SW_OFF;         //前回のリセットスイッチ値
unsigned long sw_delay = 0;   //リセットスイッチのチャタリング防止遅延時間ms
unsigned long sw_long = 0;    //長押しとなる時間ms
unsigned long sw_cancel = 0;  //キャンセルとなる時間ms
/**
 * 共通スイッチ処理
 *
 * @return byte 0:押されていない, 1:短押し, 2:長押し
 */
byte sw_common()
{
  unsigned long now;
  sw_val = digitalRead(SW_START);
  if ( sw_val == sw_last ) {
    //スイッチ値変化なし
    if (sw_val == SW_ON && sw_cancel > 0) {
      if (millis() > sw_cancel) {
        //計測開始していて、しきい値を超えたらキャンセル
        sw_long   = 0;
        sw_cancel = 0;
      }
    }
    return 0;
  }
  //以下はスイッチ値変化ありの際の処理
  if ( 0 == sw_delay ) {
    //チャタリング防止遅延時間の設定
    sw_delay =  millis();
    return 0;
  }
  now = millis();
  if ( sw_delay < now ) {
    //チャタリング防止遅延時間経過中
    return 0;
  }
  //チャタリング防止遅延時間経過済
  //前回値として現在の値を保存
  sw_last = sw_val;
  //チャタリング防止時間をクリア
  sw_delay = 0;  
  if ( SW_ON == sw_val ) {
    //SWがONの時、ボタン状態決定時間設定
    sw_long   = now + SW_PERIOD_LONG ;
    sw_cancel = now + SW_PERIOD_CANCEL;
    return 0;
  }
  //以下SWはOFF
  if (sw_cancel == 0) {
    //キャンセル処理
    return 0;
  }
  //ボタン離し処理
  byte ret = 1;
  if (now > sw_long) {
    ret = 2;
  }
  sw_long =  0;
  sw_cancel =  0;
  return ret;  
}

/**
 * スタートスイッチ処理
 */
void sw_start()
{
  digitalWrite(SW_START, HIGH);
  sw_start_val = digitalRead(SW_START);
  if ( sw_start_val == sw_start_last ) {
    //スイッチ値変化なし
    return;
  }
  if ( 0 == sw_start_delay ) {
    //チャタリング防止遅延時間の設定
    sw_start_delay =  millis() + SW_DELAY;
    return;
  }
  if ( sw_start_delay < millis() ) {
    //チャタリング防止遅延時間経過中
    return;
  }
  //チャタリング防止遅延時間経過済
  if ( SW_ON == sw_start_val ) {
    if ( ST_RESET == status ) {
      time_start();
      status = ST_RUN;
    } else if ( ST_STOP == status ) {
      status = ST_RUN;
    } else if ( ST_RUN == status ){
      status = ST_STOP;
    } else if ( ST_CONFIG == status ) {
      default_minute++;
      if (default_minute > MINUTE_MAX) {
        default_minute = MINUTE_MIN;
      }
      time_reset();
    }
  }
  //前回値として現在の値を保存
  sw_start_last = sw_start_val;
  //チャタリング防止時間をクリア
  sw_start_delay = 0;
}


/**
 * リセットスイッチ処理
 */
void sw_reset()
{
  digitalWrite(SW_RESET, HIGH);
  sw_reset_val = digitalRead(SW_RESET);
  if (sw_reset_val == sw_reset_last) {
    //スイッチ値変化なし
    return;
  }
  if (0 == sw_reset_delay) {
    //チャタリング防止遅延時間の設定
    sw_reset_delay =  millis() + SW_DELAY;
    return;
  }
  if ( sw_reset_delay <  millis() ) {
    //チャタリング防止遅延時間経過中
    return;
  }
  //チャタリング防止遅延時間経過済
  if ( SW_ON == sw_reset_val) {
    //SWがONになった時の処理
    if (( ST_STOP == status ) || ( ST_COMPLETE == status )) {
      time_reset();
      status = ST_RESET;
    } else if (ST_RESET == status) {
      status = ST_CONFIG;
    } else if (ST_CONFIG == status) {
      time_reset();
      status = ST_RESET;
    }
  }
  //前回値として現在の値を保存
  sw_reset_last = sw_reset_val;
  //チャタリング防止時間をクリア
  sw_reset_delay = 0;
}

/**
 * 開始アニメ
 */
void time_start()
{
    int i;
    int j;
    for (i = 3; i > 0; i--) {
      for (j = 7; j >= 0; j--) {
        segment[0] = FONT[0];
        segment[1] = FONT[0];
        segment[2] = FONT[0];
        segment[3] = FONT[0];
        segment[4] = FONT[0];
        segment[5] = FONT[0];
        if ( 1 <= j && 6 >= j) {
          segment[j - 1] = FONT[ '0' - ' ' + i];
          delay(100);       
        } else  if (7 == j) {
          digitalWrite(BUZZER, HIGH);
          delay(100);
          digitalWrite(BUZZER, LOW);
          delay(100);
        } else {
          delay(200);
        }
      }
    }

}

/**
 * カウントダウン処理
 */
void time_countdown()
{
  //カウントダウン終了
  if ( 0 == m10 && 0 == m01 && 0 == s10 && 0 == s01 && 0 == ms100 && 0 == ms010 ) {
    int i;
    for(i = 0; i < 4; i++) {
      digitalWrite(BUZZER, HIGH);
      delay(50);
      digitalWrite(BUZZER, LOW);
      delay(50);
    }
    status = ST_COMPLETE;
    return;
  }

  //ビープ音
  if ( 5 == s10 && 9 == s01 && 9 == ms100 ) {
    //XX分 59秒 9X : 1分毎にピピっとならす。
      digitalWrite(BUZZER, HIGH);
  } else if ( 0 == m10 && 0 == m01 && 0 == s10 && 9 == ms100 ) {
    //00分 0X秒 9X : 10秒切ったら1秒毎にピピっとならす。
    digitalWrite(BUZZER, HIGH);
  } else {
    digitalWrite(BUZZER, LOW);
  }

  ms010--;
  if ( ms010 > 9 ){
    ms010 = 9;
    ms100--;
  }
  if ( ms100 > 9 ){
    ms100 = 9;
    s01--;
  }
  if ( s01 > 9 ){
    s01 = 9;
    s10--;
  }
  if ( s10 > 9 ){
    s10 = 5;
    m01--;
  }
  if ( m01 > 9 ){
    m01 = 9;
    m10--;
  }
  if ( m10 > 9 ){
    m10 = 9;
  }
}


/**
 * リセット処理
 */
void time_reset()
{
   ms010 = 0;
   ms100 = 0;
   s01 = 0;
   s10 = 0;
   m01 = default_minute % 10;
   m10 = (default_minute - default_minute % 10) / 10;
}
<<