/*
   リアルタイムクロックデータ取得
   2019/05/25
   written by KK
*/

#include <Wire.h>
#include <MsTimer2.h>

/*===== 定義文 =====*/
// RTC設定用
#define RTC_ADDR  0x51  //RTC-8564NBスレーブアドレス(1010001x)
#define SEC  0x02
#define MIN  0x03
#define HOUR 0x04
#define DAY  0x05
#define WEEK  0x06
#define MONTH 0x07
#define YEAR  0x08

#define SW1 digitalRead(3) // Pin3 : SW1
#define SW2 digitalRead(4) // Pin4 : SW2
//#define RCK(int value) digitalWrite(9, value) // Pin9 : シフトレジスタRCK

enum {CLOCK, SETTING}; // モード切り替え用


/*===== グローバル変数宣言 =====*/
unsigned char now_time[8] = {0}; // 日付時刻情報保存
unsigned char time_mask[8] = {0x7F, 0x7F, 0x3F, 0x3F, 0x07, 0x1F, 0xFF}; // データマスク処理情報
unsigned int roop_end = 3;  // 取得データ個数設定
volatile unsigned int rtc_flag = 0; // RTCクロック取得フラグ

unsigned int mode = CLOCK;  // 動作モード

unsigned int sw1_push = 0;  // SW1押下数カウント
unsigned int sw2_push = 0;  // SW2押下数カウント
unsigned int sw1_long_flag = 0;   // SW1長押し検知
volatile unsigned int sw1_cnt = 0; // SW1用カウンタ
volatile unsigned int sw2_cnt = 0; // SW2用カウンタ


/*===== 初期設定 =====*/
void setup() {
  //PIN 入出力設定
  pinMode(3, INPUT);
  pinMode(4, INPUT);
//  pinMode(9, OUTPUT);
  digitalWrite(3, HIGH); // 3Pin プルアップ有効
  digitalWrite(4, HIGH); // 4Pin プルアップ有効

  //UART 初期設定
  Serial.begin(9600);//PC-Arduino 通信

  // i2c 初期設定
  Wire.begin();
  RTC_Write(0x00, 0x00);  // CONTROL1へ0x00書き込み
  RTC_Write(0x0D, 0x83);  // CLKOUT有効,周波数1Hz

  // Timer2 タイマ割り込み
  MsTimer2::set(1, tmr2_isr); // 1msに設定
  MsTimer2::start();  // 割り込み開始

  // 外部割り込み
  attachInterrupt(0, int_isr, RISING); // Pin2,割り込み時"int_isr"関数実行,立ち上がりエッジ
}

/*===== メイン関数 =====*/
void loop() {

  // モード切り替え
  switch (mode) {
    case CLOCK:
      clock_mode();
      break;

    case SETTING:
      setting_mode();
      break;

    default:
      break;
  }

  // スイッチ検出
  sw1_check();
  sw2_check();
}

/*===== 時計動作(通常モード) =====*/
void clock_mode() {
  static unsigned int init_flag = 0;
  char output_str[20] = {0};

  // モード移行時のみ実行
  if (init_flag == 0) {
    Serial.print("\r\n");
    Serial.println("CLOCK MODE");
    while (!SW1);
    init_flag = 1;
    rtc_flag = 0;
    sw1_long_flag = 0;
  }

  // 外部割り込み検知で実行
  if (rtc_flag == 1) {

    // 日付データ取得(秒,分,時のみ取得)
    RTC_time_reading(now_time);

    // UART出力
    sprintf(output_str, "\rTIME: %2d:%2d:%2d", now_time[2], now_time[1], now_time[0]);
    Serial.print(output_str);

    rtc_flag = 0; // フラグクリア
  }

  // SW1長押しで設定モードへ
  if (sw1_long_flag == 1) {
    mode = SETTING;
    init_flag = 0;
  }
}

/*===== 時刻設定モード =====*/
void setting_mode() {
  static unsigned int init_flag = 0;
  static unsigned int setting_sel = 0;
  char output_str[20] = {0};

  // モード移行時のみ実行
  if (init_flag == 0) {
    Serial.print("\r\n");
    Serial.println("SETTING MODE");
    while (!SW1);
    init_flag = 1;
    sw1_push = 0;
    sw2_push = 0;
    setting_sel = 0;
    sw1_long_flag = 0;
  }

  // 外部割り込み検知で実行
  if (rtc_flag == 1) {
    // 日付データ取得(秒,分,時のみ取得)
    RTC_time_reading(now_time);
  }

  switch (setting_sel) {
    case 0: // 時設定
      if (sw2_push != 0) {  // SW2押下で+1
        if ((++now_time[2]) > 23) {
          now_time[2] = 0;
        }
        RTC_Write(HOUR, (((now_time[2] / 10) << 4)) | (((now_time[2] % 10) & 0x0F)));
        
        sprintf(output_str, "\rHOUR? : %2d", now_time[2]);
        Serial.print(output_str);
        sw2_push = 0;
      }

      if (rtc_flag == 1) {  // 時間経過で表示更新
        sprintf(output_str, "\rHOUR? : %2d", now_time[2]);
        Serial.print(output_str);
        rtc_flag = 0;
      }

      if (sw1_push != 0) {
        setting_sel = 1;
        sw1_push = 0;
      }
      break;

    case 1: // 分設定
      if (sw2_push != 0) {  // SW2押下で+1
        if ((++now_time[1]) > 59) {
          now_time[1] = 0;
        }
        RTC_Write(MIN, (((now_time[1] / 10) << 4)) | (((now_time[1] % 10) & 0x0F)));
        
        sprintf(output_str, "\r MIN? : %2d", now_time[1]);
        Serial.print(output_str);
        sw2_push = 0;
      }

      if (rtc_flag == 1) {  // 時間経過で表示更新
        sprintf(output_str, "\r MIN? : %2d", now_time[1]);
        Serial.print(output_str);
        rtc_flag = 0;
      }

      if (sw1_push != 0) {
        setting_sel = 2;
        sw1_push = 0;
      }
      break;

    case 2: // 秒設定
      if (sw2_push != 0) {  // SW2押下で秒をゼロクリア
        //        if ((now_time[0]++) > 59) {
        //          now_time[0] = 0;
        //        }
        now_time[0] = 0;
        RTC_Write(SEC, (((now_time[0] / 10) << 4)) | (((now_time[0] % 10) & 0x0F)));
        
        sprintf(output_str, "\r SEC? : %2d", now_time[0]);
        Serial.print(output_str);
        sw2_push = 0;
      }

      if (rtc_flag == 1) {  // 時間経過で表示更新
        sprintf(output_str, "\r SEC? : %2d", now_time[0]);
        Serial.print(output_str);
        rtc_flag = 0;
      }

      if (sw1_push != 0) {
        setting_sel = 0;
        sw1_push = 0;
      }
      break;

    default:
      break;
  }

  // SW1長押しで時計モードへ
  if (sw1_long_flag == 1) {
    mode = CLOCK;
    init_flag = 0;
  }
}

/*===== INT0 外部割り込み =====*/
void int_isr() {
  rtc_flag = 1; //フラグON
}

/*===== MsTimer2 割り込み =====*/
void tmr2_isr() {
  // 1msカウンタ
  sw1_cnt++;
  sw2_cnt++;
}

/*===== SW1検出 =====*/
void sw1_check() {
  static unsigned int sw_mode = 0;

  switch (sw_mode) {
    case 0: // 定常時
      if (SW1 == 0) {
        sw_mode = 1;
        sw1_cnt = 0;
      }
      break;

    case 1: // 押下時チャタリング対策
      if ((sw1_cnt >= 10) && (SW1 == 0)) {
        sw_mode = 2;
        sw1_cnt = 0;
      } else if ((sw1_cnt < 10) && (SW1 == 1)) {
        sw_mode = 0;
      }
      break;

    case 2: // 押下時実行する処理
      sw1_push++; // 押下数カウント
      sw_mode = 3;
      break;

    case 3: // 押下時
      if (SW1 == 1) {
        sw_mode = 4;
        sw1_cnt = 0;
      }

      // 長押し検出 2s以上
      if (sw1_cnt >= 2000) {
        sw1_long_flag = 1;
      }
      break;

    case 4: // 離した時のチャタリング対策
      if ((sw1_cnt >= 10) && (SW1 == 1)) {
        sw_mode = 0;
      } else if ((sw1_cnt < 10) && (SW1 == 0)) {
        sw_mode = 3;
      }
      break;

    default:
      break;
  }
}

/*===== SW2検出 =====*/
void sw2_check() {
  static unsigned int sw_mode = 0;

  switch (sw_mode) {
    case 0: // 定常時
      if (SW2 == 0) {
        sw_mode = 1;
        sw2_cnt = 0;
      }
      break;

    case 1: // 押下時チャタリング対策
      if ((sw2_cnt >= 10) && (SW2 == 0)) {
        sw_mode = 2;
        sw2_cnt = 0;
      } else if ((sw2_cnt < 10) && (SW2 == 1)) {
        sw_mode = 0;
      }
      break;

    case 2: // 押下時実行する処理
      sw2_push++; // 押下数カウント
      sw_mode = 3;
      break;

    case 3: // 押下時
      if (SW2 == 1) {
        sw_mode = 4;
        sw2_cnt = 0;
      }
      break;

    case 4: // 離した時のチャタリング対策
      if ((sw2_cnt >= 10) && (SW2 == 1)) {
        sw_mode = 0;
      } else if ((sw2_cnt < 10) && (SW2 == 0)) {
        sw_mode = 3;
      }
      break;

    default:
      break;
  }
}

/*===== RTC情報取得 =====*/
void RTC_time_reading(unsigned char* t) {
  unsigned char r_data = 0; // 受信データ一時格納
  unsigned int i;

  // 日付データ取得(秒,分,時のみ取得)
  for (i = 0; i < roop_end; i++) {
    r_data = RTC_Read(0x02 + i);  // データ取得
    r_data &= time_mask[i];   // マスク処理
    *t = ((r_data >> 4) * 10) + (r_data & 0x0F);  // データ変換(BCDデータ)
    t++;
  }
}

/*===== RTCへデータを書き込む =====*/
void RTC_Write(unsigned char addr, unsigned char data) {
  Wire.beginTransmission(RTC_ADDR);   // アドレス指定
  Wire.write(addr); // アドレス設定
  Wire.write(data); // 書き込みデータ設定
  Wire.endTransmission(true);  //データ送信,i2cバス開放
}

/*===== RTCからデータを取得 =====*/
unsigned char RTC_Read(unsigned char addr) {
  Wire.beginTransmission(RTC_ADDR);   // アドレス指定
  Wire.write(addr); // アドレス設定
  Wire.endTransmission(false);  //データ送信,コネクション維持
  Wire.requestFrom(RTC_ADDR, 1);  //1byteデータ受信

  //1byte以上データ受信有りの場合データ受信
  if (Wire.available() > 0) {
    return (Wire.read());
  }
}
