オリジナルライドガジェットをつくる 〜ファイズアクセルX(テン)〜

前回作成・ご紹介したカイザフォンXですが、ありがたいことに好意的に受け止めてくれた方が多く、Twitterでいいねを3,500件ほど、YouTubeの動画再生回数で21万回ほどを記録しました。ありがとうございます。何より嬉しかったのは、カイザフォンXが呼び水になる形で、過去に作ったスマブラガシャットジーニアスフルボトルフルフルレジェンドライダーエボルボトルなどを再評価してくださる方々がたくさんいたことです。結果として、YouTubeのチャンネル登録者数が一気に2倍近くまで増えて、一万人近くまで来ました。「一応こいつの動向はチェックしておくか」と思ってくださる方が一万人近くいるというのは、一般人の自分からすれば中々に恐れ多いことです、ありがとうございます。

さて、そんな感じでチェックしてくださる方が増えたこともあり、次の作品はなるべく早めにお見せできるよう、カイザフォンXよりはシンプルなものを作ることにしました。それでも丸々一ヶ月かかってしまいましたが。。。

というわけで、今回ご紹介するのはカイザフォンX同様のオリジナルライドガジェット、『ファイズアクセルX(テン)』です。どんだけファイズ好きなんだ、て感じですね。

 

これを作ろうと思ったきっかけですが、ウチの長男が私のCSMファイズアクセルで遊んでいるのを見ていて、ふと気づいたのです。『ファイズアクセルもライドウォッチもどっちも時計じゃん!』と。気づいてしまえばどうってことない話ですが、自分的には結構目からウロコな気づきでした。これほどライドウォッチと親和性の高い歴代アイテムは他にないんじゃないかと思ったぐらいです。その気づきが嬉しくて、これは是非とも作らねばと思い、作ってみることにしました。

で、出来上がったものがこちらです。

ベースは食玩版のライドウォッチです。食玩版なら何でもよかったのですが、何となくファイズライドウォッチをベースにしています。

並べてみると結構違いはありますが、パッと見たときになるべくオリジナルのファイズアクセルを思い起こさせるような色で塗装したつもりです。ラベルもできるだけオリジナルに近づけてみました。リング上の赤い部分は、リングがシルバー一色だとちょっと寂しい感じがしたので、百均で買ってきたホログラムシールを切って貼り付けています。こんな細かいところを塗装できるような技術はないので。。。

認識ピンのプレートは、カイザフォンXを作った際にファイズフォンXの認識ピンのプレートが余ったので、それを削って使用しています。ピンをほぼ削ってしまっているので、当然ジクウドライバーでは認識されません。

この仕様にするか、ファイズライドウォッチの認識ピンプレートを使用するかは結構迷いました。素直に考えればファイズの認識プレートを使うべきかな、とも思ったのですが、この場合、ジクウドライバーに装填したときにベルトの変身待機が発生してしまい、必然的にベルトを一回転させて「アーマータイム!」を発動させる必要が出てきます。それはそれで良いのかもしれませんが、自分としてはこの『ファイズアクセルX』は、『ファイズアクセルフォームのアーマーを身につけるためのライドウォッチ』というよりは、『ファイズアクセルフォームと同様の超加速能力を得るためだけの強化アイテム』という、あくまでウォッチではなくガジェットであるという位置付けにしたかったので、あえてジクウドライバーには認識させない方向にしました。この仕様により、ファイズアクセルXをドライバーに装填してボタンを押すと超加速が始まることになり、オリジナルのファイズアクセルでミッションメモリーをファイズドライバーに挿してファイズアクセルを起動、そこからさらに必殺技を発動させるという劇中の遊び方に近いシーケンスを実現できるようになりました。

 

それから、オリジナルの機能として時計/カレンダー機能を実装してみました。中央ボタンの短押しで時刻表示&読み上げ、長押しで日付表示&読み上げができるようにしています。

 

 

時計機能は元々、『ジオウは時計がモチーフ』ということがわかってから、いつかは実装したいと思っていた機能でした。ただ、普通のライドウォッチは長針や短針、7セグなどの時刻を表現するための手段を持っていなかったので、無理やり入れ込んでも不自然になるかと思い、ずっと保留にしていました。今回、ファイズアクセルという、時計としてより不自然でない(?)ものを題材にすることができましたので、満を持して(?)の搭載になりました。

時計として使う以上は時刻を常時表示できるようにするのが一番ベストではあるのですが、それだとArduinoの省電力制御などをしっかり考える必要があり、ちょっと時間がかかりそうでしたので、今回はメインの電源スイッチをONにしているときのみ時刻を表示する仕様にしています。電源を切っていてもちゃんと時間情報は保持できるよう、RTC(Real Time Clock)を積んでいます。

カレンダー機能は当初は実装する予定はなかったのですが、ファイズアクセルXが「ソウゴたちが時間移動する先々で、常に正しい年月・日時を表示してくれるもの」という位置付けなら、ライドガジェットの存在理由として説得力が出るかなと思い、実装することにしました。我々が使う分には「2019年」としか表示されませんが、脳内では2000年とか2048年とかが表示されるようなイメージです。

あと、どちらも音声の読み上げ機能が付いていますが、基本的にライドウォッチが喋りまくる玩具なので、時間情報の表示のときだけ喋らないのも違和感あるかなと思い、実装してみることにしました。基本的に音声はCSMファイズアクセルから録音したものを使用していますが、当然時間情報の読み上げ音声なんてものはCSMには存在しないので、Googleの音声合成に頑張ってもらいました。

今回初めて利用しましたが、少なくとも英語はかなり使えるなあという感じです。

 

さて、メインの超加速、カウントダウンの機能についてです。これは

  • ドライバー装填後に中央スイッチ押下でカウントダウンスタート
  • ドライバー装填後にベルト一回転でカウントダウンスタート

のどちらで実装するかで結構迷ったのですが、別に相反するものでもないということに気づきましたので、結局どちらからでもカウントダウンをスタートできるようにしました。ファイズの本編っぽく使用したいなら前者で起動すれば良いし、ジオウの玩具っぽく遊びたいなら後者で起動すれば良い、という考え方です。

7セグLEDの発光パターンについては、CSM版のファイズアクセルの挙動を参考に、できるだけ近いものになるよう、見よう見まねでプログラムを書いています。CSM版はさすがCSMという感じで、7セグの各セグメントがアナログ制御されていて発光がとても綺麗なのですが、そこまで再現しようと思うと配線が大変なことになりそうでしたので、今回はシンプルなデジタル表現に落ち着けています。

 

以上がファイズアクセルXの機能紹介です。カイザフォンXとかに比べるとだいぶシンプルですね。続いて、中身の説明に入ります。

今回使用した具材は以下です。まずはおなじみの具材から。

DF Playerは今までリンクを貼っていたものと異なっていますが、おそらくほぼ同等品だと思います。

 

続いて、以下が今回の工作で特徴的な具材です。

時計/カレンダー機能の搭載に必要なRTCです。精度に拘らなければ、もう少し安価なモジュールでも良いと思います。動作に必要な電池(CR1220)は百均で調達しました。

それから、今回の工作の要の7セグLEDです。

秋月電子で取り扱いのモジュールです。素の7セグLEDはそのまま配線すると大変なことになってしまうのですが、これは2個並べて使ってもArduinoとの配線が5本だけで済んでしまうという優れものです。これを見つけたおかげで、今回のファイズアクセルXを実際に作る気になれました。

ちなみに、今回はスピーカーも秋月電子で売られているものを使っています。

食玩版に組み込みのスピーカーだと大きい上に、音質もあまり良くないので。

あと、今回食玩版がベースということで、本来機能しない中央スイッチを押せるように改造する必要があったのですが、手元にあった以下のプッシュスイッチで対応しています。

図体としてはちょっと大きいですが、おかげで良い感じのクリック感が得られました。

 

電子工作として必要な具材は以上です。あとはラベル作成用のプリンタ用紙(白/透明)とか、塗装用のスプレーとかが必要になります。

 

回路図としては以下のようになります。

これを実際に配線すると、こんな感じになります。

細かいところを見ていきましょう。

7セグはこんな感じで、Arduino本体との接続用には5本の線を引き出すだけで済んでいます。

ドライバー認識部分のスイッチの配置はこんな感じ。スイッチが1個でスペースに余裕があるので、万丈ライドウォッチのときに比べて調整がだいぶラクです。

中央スイッチはこんな感じです。グルーガン(ホットボンド)でガチガチに固定しています。本体を閉じたときにスイッチが押しにくくなってしまう場合は、スイッチの口の縁の部分をデザインナイフなどで少し削ってスペースを開けてあげると、押しやすくなります。

MP3プレイヤーとRTCの配置はこんな感じです。RTCはこの上に絶縁用のテープを貼って、その上にArduino Pro mini基板を重ねて収容するようにしています。

最後はこんな感じになりました。カイザフォンXに比べれば余裕がありますが、それでも思ったよりは大変でした。Arduinoのソースコードは最後に載せます。

 

ということで、オリジナルライドガジェット『ファイズフォンX』のご紹介でした。前回のカイザフォンXが結構大掛かりなものになってしまったので、今回はシンプルに、実際に玩具として出てきてもおかしくなさそうなモノを目指して作ってみました。時計/カレンダー機能を入れたせいで読み上げ音声の用意にすごく時間がかかってしまいましたが、この機能を省いてしまえば、部品点数的にもプログラム的にもだいぶ作りやすい部類に入ると思います。

せっかくなので何枚かパチリと。

ライドウォッチホルダーにセットすることで、オリジナルのファイズアクセルのように腕時計型にすることができます。DXゴーストライドウォッチ欲しさに購入しただけのライドウォッチホルダーですが、ここにきて有効活用することができました。

ゲイツライドウォッチと一緒に。ファイズの力はゲイツが使う、という形になっているようなので、しっくりくる感じがします。

カイザフォンXと一緒に。ファイズ本編では実現しなかったカイザ アクセルフォーム(?)です。

最後に、本来の持ち主(?)であるファイズと。

 

さて、次に作るものですが。。。とりあえず、ファイズ編(?)はこれにて完結です。次は労力の割には、見た目には地味なものになってしまいそうな気がします。

 

 

ソースコード

/*-------------------------- 基本定義 --------------------------*/
#define SW_CENTER_PIN 4
#define SW_DRIVER_PIN 5

#define LOOP_INTERVAL_MS 20

#define SW_ON  LOW
#define SW_OFF HIGH

#define STATE_INIT           0
#define STATE_INIT_TO_READY  1
#define STATE_READY          2
#define STATE_READY_TO_INIT  3
#define STATE_READY_TO_ACCEL 4
#define STATE_ACCEL          5

#define WAIT_MS_CALENDAR 1000
const uint16_t WAIT_COUNT_CALENDAR = WAIT_MS_CALENDAR / LOOP_INTERVAL_MS;

uint8_t state = STATE_INIT;
uint8_t prev_state = STATE_INIT;
uint8_t driver_counter = 0;

unsigned long state_change_start_time = 0;

uint8_t sw_center = SW_OFF;
uint8_t sw_driver = SW_OFF;
uint8_t prev_sw_center = SW_OFF;
uint8_t prev_sw_driver = SW_OFF;

uint16_t sw_center_press_counter = 0;
boolean is_valid_long_press = false;

/*-------------------------- 7セグメントLED関係 --------------------------*/

/* 7セグの位置とデータビットの関係
  A(0)
 -----=           例)LSBFIRSTの場合
|F(5)  |B(1)         0 = 0b11111100
| G(6) |             1 = 0b01100000
 ------              2 = 0b11011010
|E(4)  |C(2)         3 = 0b11110010
| D(3) |             4 = 0b01100110
 ------  .DP(7)      5 = 0b10110110
*/

#include<SPI.h>
#define SPI_SCK_PIN  13
#define SPI_SDI_PIN  11
#define SPI_LATCH_PIN 10

#define LED_ALL_ON  0b11111110
#define LED_ALL_OFF 0b00000000
#define LED_FIG_0   0b11111100
#define LED_FIG_1   0b01100000
#define LED_FIG_2   0b11011010
#define LED_FIG_3   0b11110010
#define LED_FIG_4   0b01100110
#define LED_FIG_5   0b10110110
#define LED_FIG_6   0b10111110
#define LED_FIG_7   0b11100000
#define LED_FIG_8   0b11111110
#define LED_FIG_9   0b11110110

const byte LED_FIG[] PROGMEM = {
  LED_FIG_0, LED_FIG_1, LED_FIG_2, LED_FIG_3, LED_FIG_4,
  LED_FIG_5, LED_FIG_6, LED_FIG_7, LED_FIG_8, LED_FIG_9
};

uint16_t led_pattern_index = 0;
unsigned long prev_flash_time = 0;
boolean is_lighting = false;

// -----------------------------------------------------------------------------------------------------------------------------------
#define LEN_LED_PATTERN_ON 12
#define INTERVAL_MS_LED_PATTERN_ON 100
const byte LED_PATTERN_ON[][2] PROGMEM = {
  {0b00000100, 0b00000000},{0b00001100, 0b00000000},{0b10001100, 0b00000000},{0b10001110, 0b00000000},{0b10001110, 0b00000100}, // 0.5s
  {0b10001110, 0b00001100},{0b10001110, 0b10001100},{0b10001110, 0b11001100},{0b10001110, 0b11101100},{0b10001110, 0b11101110}, // 1.0s
  {0b00000000, 0b00000000},{0b10001110, 0b11101110}
};
// -----------------------------------------------------------------------------------------------------------------------------------
#define INTERVAL_MS_LED_PATTERN_INIT 1000
// -----------------------------------------------------------------------------------------------------------------------------------
#define LEN_LED_PATTERN_TIME 32
#define INTERVAL_MS_LED_PATTERN_TIME 100
const byte LED_PATTERN_TIME[][2] PROGMEM = {
  {LED_ALL_ON, LED_ALL_ON},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_ON, LED_ALL_ON},{LED_ALL_ON, LED_ALL_ON},{LED_ALL_ON, LED_ALL_ON},
  {LED_ALL_ON, LED_ALL_ON},{LED_ALL_ON, LED_ALL_ON},  {LED_ALL_ON, LED_ALL_ON},{LED_ALL_ON, LED_ALL_ON},{LED_ALL_ON, LED_ALL_ON},
  {LED_ALL_ON, LED_ALL_ON},{LED_ALL_ON, LED_ALL_ON}, // 12

  {LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF}, // 5

  {0b00010100, 0b10100000},{0b10001000, 0b01010000},{0b00010100, 0b10100000},{0b10001000, 0b01010000},{0b00010100, 0b10100000},
  {0b10001000, 0b01010000},{0b00010100, 0b10100000},{0b10001000, 0b01010000},{0b00010100, 0b10100000},{0b10001000, 0b01010000},
  {0b00010100, 0b10100000},{0b10001000, 0b01010000},{0b00010100, 0b10100000},{0b10001000, 0b01010000},{0b00010100, 0b10100000} // 15
};
// -----------------------------------------------------------------------------------------------------------------------------------
#define LEN_LED_PATTERN_INIT_TO_READY 16
#define INTERVAL_MS_LED_PATTERN_INIT_TO_READY 80
const byte LED_PATTERN_INIT_TO_READY[][2] PROGMEM = {
  {0b00100000, 0b00000000},{0b00110000, 0b00000000},{0b00111000, 0b00000000},{0b00111100, 0b00000000},{0b10111100, 0b00000000},
  {0b11111100, 0b00000000},{0b11111110, 0b00000000},{0b11111110, 0b00100000},{0b11111110, 0b00110000},{0b11111110, 0b00111000},
  {0b11111110, 0b00111100},{0b11111110, 0b10111100},{0b11111110, 0b11111100},{0b11111110, 0b11111110},{0b00000000, 0b00000000},
  {0b11111110, 0b11111110}
};
const unsigned long WAIT_MS_COMPLETE = INTERVAL_MS_LED_PATTERN_INIT_TO_READY * LEN_LED_PATTERN_INIT_TO_READY;
// -----------------------------------------------------------------------------------------------------------------------------------
#define DURATION_MS_COMPLETE 5000
// -----------------------------------------------------------------------------------------------------------------------------------
#define INTERVAL_MS_LED_PATTERN_READY 800
// -----------------------------------------------------------------------------------------------------------------------------------
#define LEN_LED_PATTERN_READY_TO_INIT 15
#define INTERVAL_MS_LED_PATTERN_READY_TO_INIT 80
const byte LED_PATTERN_READY_TO_INIT[][2] PROGMEM = {
  {0b11111110, 0b11111110},{0b11111110, 0b11111100},{0b11111110, 0b10111100},{0b11111110, 0b00111100},{0b11111110, 0b00111000},
  {0b11111110, 0b00110000},{0b11111110, 0b00100000},{0b11111110, 0b00000000},{0b11111100, 0b00000000},{0b10111100, 0b00000000},
  {0b00111100, 0b00000000},{0b00111000, 0b00000000},{0b00110000, 0b00000000},{0b00100000, 0b00000000},{0b00000000, 0b00000000}
};
const unsigned long WAIT_MS_REFORMATION = INTERVAL_MS_LED_PATTERN_READY_TO_INIT * LEN_LED_PATTERN_READY_TO_INIT;
// -----------------------------------------------------------------------------------------------------------------------------------
#define LEN_LED_PATTERN_READY_TO_ACCEL 10
#define INTERVAL_MS_LED_PATTERN_READY_TO_ACCEL 50
const byte LED_PATTERN_READY_TO_ACCEL[][2] PROGMEM = {
  {0b00110000, 0b00110000},{0b00001010, 0b00001010},{0b01000100, 0b01000100},{0b10000000, 0b10000000}, {0b00000000,0b00000000},
  {0b10000000, 0b10000000},{0b01000100, 0b01000100},{0b00001010, 0b00001010},{0b00110000, 0b00110000}, {0b00000000,0b00000000}
};
// -----------------------------------------------------------------------------------------------------------------------------------
#define LEN_LED_PATTERN_ACCEL_A 24
#define INTERVAL_MS_LED_PATTERN_ACCEL_A 120
const byte LED_PATTERN_ACCEL_A[][2] PROGMEM = {
  {LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},
  {LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF}, // 19

  {0b01100010, 0b00001110},{0b10010000, 0b10010000},{0b00001100, 0b01100000},
  {0b01100010, 0b00001110},{0b10010000, 0b10010000},{0b00001100, 0b01100000},
  {0b01100010, 0b00001110},{0b10010000, 0b10010000},{0b00001100, 0b01100000},
  {0b01100010, 0b00001110},{0b10010000, 0b10010000},{0b00001100, 0b01100000},
  {0b01100010, 0b00001110},{0b10010000, 0b10010000},{0b00001100, 0b01100000}  // 15
};

#define LEN_LED_PATTERN_ACCEL_B 155
#define INTERVAL_MS_LED_PATTERN_ACCEL_B 100
const byte LED_PATTERN_ACCEL_B[][2] PROGMEM = {
  // {LED_FIG_9, LED_FIG_9},{LED_FIG_9, LED_FIG_8},{LED_FIG_9, LED_FIG_7},{LED_FIG_9, LED_FIG_6},{LED_FIG_9, LED_FIG_5},{LED_FIG_9, LED_FIG_4},
  {LED_FIG_9, LED_FIG_3},{LED_FIG_9, LED_FIG_2},{LED_FIG_9, LED_FIG_1},{LED_FIG_9, LED_FIG_0},
  {LED_FIG_8, LED_FIG_9},{LED_FIG_8, LED_FIG_8},{LED_FIG_8, LED_FIG_7},{LED_FIG_8, LED_FIG_6},{LED_FIG_8, LED_FIG_5},
  {LED_FIG_8, LED_FIG_4},{LED_FIG_8, LED_FIG_3},{LED_FIG_8, LED_FIG_2},{LED_FIG_8, LED_FIG_1},{LED_FIG_8, LED_FIG_0},
  {LED_FIG_7, LED_FIG_9},{LED_FIG_7, LED_FIG_8},{LED_FIG_7, LED_FIG_7},{LED_FIG_7, LED_FIG_6},{LED_FIG_7, LED_FIG_5},
  {LED_FIG_7, LED_FIG_4},{LED_FIG_7, LED_FIG_3},{LED_FIG_7, LED_FIG_2},{LED_FIG_7, LED_FIG_1},{LED_FIG_7, LED_FIG_0},
  {LED_FIG_6, LED_FIG_9},{LED_FIG_6, LED_FIG_8},{LED_FIG_6, LED_FIG_7},{LED_FIG_6, LED_FIG_6},{LED_FIG_6, LED_FIG_5},
  {LED_FIG_6, LED_FIG_4},{LED_FIG_6, LED_FIG_3},{LED_FIG_6, LED_FIG_2},{LED_FIG_6, LED_FIG_1},{LED_FIG_6, LED_FIG_0},
  {LED_FIG_5, LED_FIG_9},{LED_FIG_5, LED_FIG_8},{LED_FIG_5, LED_FIG_7},{LED_FIG_5, LED_FIG_6},{LED_FIG_5, LED_FIG_5},
  {LED_FIG_5, LED_FIG_4},{LED_FIG_5, LED_FIG_3},{LED_FIG_5, LED_FIG_2},{LED_FIG_5, LED_FIG_1},{LED_FIG_5, LED_FIG_0},
  {LED_FIG_4, LED_FIG_9},{LED_FIG_4, LED_FIG_8},{LED_FIG_4, LED_FIG_7},{LED_FIG_4, LED_FIG_6},{LED_FIG_4, LED_FIG_5},
  {LED_FIG_4, LED_FIG_4},{LED_FIG_4, LED_FIG_3},{LED_FIG_4, LED_FIG_2},{LED_FIG_4, LED_FIG_1},{LED_FIG_4, LED_FIG_0},
  {LED_FIG_3, LED_FIG_9},{LED_FIG_3, LED_FIG_8},{LED_FIG_3, LED_FIG_7},{LED_FIG_3, LED_FIG_6},{LED_FIG_3, LED_FIG_5},
  {LED_FIG_3, LED_FIG_4},{LED_FIG_3, LED_FIG_3},{LED_FIG_3, LED_FIG_2},{LED_FIG_3, LED_FIG_1},{LED_FIG_3, LED_FIG_0},
  {LED_FIG_2, LED_FIG_9},{LED_FIG_2, LED_FIG_8},{LED_FIG_2, LED_FIG_7},{LED_FIG_2, LED_FIG_6},{LED_FIG_2, LED_FIG_5},
  {LED_FIG_2, LED_FIG_4},{LED_FIG_2, LED_FIG_3},{LED_FIG_2, LED_FIG_2},{LED_FIG_2, LED_FIG_1},{LED_FIG_2, LED_FIG_0},
  {LED_FIG_1, LED_FIG_9},{LED_FIG_1, LED_FIG_8},{LED_FIG_1, LED_FIG_7},{LED_FIG_1, LED_FIG_6},{LED_FIG_1, LED_FIG_5},
  {LED_FIG_1, LED_FIG_4},{LED_FIG_1, LED_FIG_3},{LED_FIG_1, LED_FIG_2},{LED_FIG_1, LED_FIG_1},{LED_FIG_1, LED_FIG_0},
  {LED_FIG_0, LED_FIG_9},{LED_FIG_0, LED_FIG_8},{LED_FIG_0, LED_FIG_7},{LED_FIG_0, LED_FIG_6},{LED_FIG_0, LED_FIG_5},
  {LED_FIG_0, LED_FIG_4},{LED_FIG_0, LED_FIG_3},{LED_FIG_0, LED_FIG_2},{LED_FIG_0, LED_FIG_1},{LED_FIG_0, LED_FIG_0}, // 94

  {LED_FIG_0,   LED_FIG_0},  {LED_FIG_0,   LED_FIG_0},  {LED_FIG_0,   LED_FIG_0},
  {LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},
  {LED_FIG_0,   LED_FIG_0},  {LED_FIG_0,   LED_FIG_0},  {LED_FIG_0,   LED_FIG_0},
  {LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},
  {LED_FIG_0,   LED_FIG_0},  {LED_FIG_0,   LED_FIG_0},  {LED_FIG_0,   LED_FIG_0},   // 15

  {LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},
  {LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},
  {LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},
  {LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0},
  {LED_FIG_0, LED_FIG_0},{LED_FIG_0, LED_FIG_0}, // 22

  {0b01111100, 0b01111100},{0b01111100, 0b01111100},{0b01111100, 0b01111100},{0b01111100, 0b01111100},
  {0b00111000, 0b00111000},{0b00111000, 0b00111000},{0b00111000, 0b00111000},{0b00111000, 0b00111000},
  {0b00010000, 0b00010000},{0b00010000, 0b00010000},{0b00010000, 0b00010000},{0b00010000, 0b00010000}, //12

  {LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF}, {LED_ALL_OFF, LED_ALL_OFF}, {LED_ALL_OFF, LED_ALL_OFF},
  {LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF}, {LED_ALL_OFF, LED_ALL_OFF}, {LED_ALL_OFF, LED_ALL_OFF},
  {LED_ALL_OFF, LED_ALL_OFF},{LED_ALL_OFF, LED_ALL_OFF} // 12
};

const unsigned long DURATION_MS_ACCEL = INTERVAL_MS_LED_PATTERN_ACCEL_A * LEN_LED_PATTERN_ACCEL_A + INTERVAL_MS_LED_PATTERN_ACCEL_B * LEN_LED_PATTERN_ACCEL_B;
const unsigned long WAIT_MS_STARTUP = 600;
// -----------------------------------------------------------------------------------------------------------------------------------

void flash_7seg_led_pattern(byte (*led_patttern)[2], uint16_t index){
  digitalWrite(SPI_LATCH_PIN, LOW);
  SPI.transfer(pgm_read_byte(&led_patttern[index][0]));//10の桁
  SPI.transfer(pgm_read_byte(&led_patttern[index][1]));//1の桁
  digitalWrite(SPI_LATCH_PIN, HIGH);
}

void seg_led_on_all(){
  digitalWrite(SPI_LATCH_PIN, LOW);
  SPI.transfer(LED_ALL_ON);//10の桁
  SPI.transfer(LED_ALL_ON);//1の桁
  digitalWrite(SPI_LATCH_PIN, HIGH);
}

void seg_led_off_all(){
  digitalWrite(SPI_LATCH_PIN, LOW);
  SPI.transfer(LED_ALL_OFF);//10の桁
  SPI.transfer(LED_ALL_OFF);//1の桁
  digitalWrite(SPI_LATCH_PIN, HIGH);
}

void seg_led_on_figure(uint8_t num){
  digitalWrite(SPI_LATCH_PIN, LOW);
  SPI.transfer(pgm_read_byte(&LED_FIG[num/10])); //10の桁
  SPI.transfer(pgm_read_byte(&LED_FIG[num%10])); //1の桁
  digitalWrite(SPI_LATCH_PIN, HIGH);
}

void seg_led_on_dot_right(){
  digitalWrite(SPI_LATCH_PIN, LOW);
  SPI.transfer(0b00000000);//10の桁
  SPI.transfer(0b00000001);//1の桁
  digitalWrite(SPI_LATCH_PIN, HIGH);
}

void seg_led_on_a(){
  digitalWrite(SPI_LATCH_PIN, LOW);
  SPI.transfer(LED_ALL_OFF); //10の桁
  SPI.transfer(0b11101110);  //1の桁
  digitalWrite(SPI_LATCH_PIN, HIGH);
}

void seg_led_on_p(){
  digitalWrite(SPI_LATCH_PIN, LOW);
  SPI.transfer(LED_ALL_OFF); //10の桁
  SPI.transfer(0b11001110);  //1の桁
  digitalWrite(SPI_LATCH_PIN, HIGH);
}

/*-------------------------- 音声関係 --------------------------*/
#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>

#define SOUND_VOLUME_DEFAULT 24
#define SOUND_VOLUME_TIME    29

#define SOUND_ON          1
#define SOUND_COMPLETE    2
#define SOUND_READY       3
#define SOUND_STARTUP     4
#define SOUND_REFORMATION 5
#define SOUND_TIME        6

// 「分」は0分〜59分を7~66に割り当て
#define SOUND_OFFSET_MINUTE 7
// 「時」は1時〜12時を67~78に割り当て
#define SOUND_OFFSET_HOUR 66
// 「午前/午後」は79,80に割り当て
#define SOUND_AM 79
#define SOUND_PM 80
// 「日」は81〜111に割り当て
#define SOUND_OFFSET_DAY 80
// 「月」は1月〜12月を112~123に割り当て
#define SOUND_OFFSET_MONTH 111
// 「年」はとりあえず2018~2020を124~126に割り当て
#define SOUND_OFFSET_YEAR 124

SoftwareSerial ss_mp3_player(2, 3); // RX, TX
DFRobotDFPlayerMini mp3_player;

/*-------------------------- 時計関係 --------------------------*/
#include <Wire.h>
#include "RTClib.h"

RTC_DS3231 rtc;
#define START_YEAR 2018

void notify_time(){
  mp3_player.playMp3Folder(SOUND_TIME);
  DateTime t_now    = rtc.now();
  uint8_t  t_hour   = t_now.hour();
  uint8_t  t_minute = t_now.minute();
  boolean  is_am    = true;
  if(t_hour == 0){ // 0時は12として音声再生
    t_hour = 12;
  }else if(t_hour > 12){ // 12時間表示に対応させる
    t_hour -= 12;
    is_am = false;
  }

  // ローディング演出発光処理
  for(uint8_t i=0;i<LEN_LED_PATTERN_TIME;i++){
    flash_7seg_led_pattern(LED_PATTERN_TIME, i);
    delay(INTERVAL_MS_LED_PATTERN_TIME);
  }
  mp3_player.volume(SOUND_VOLUME_TIME);
  delay(100);

  mp3_player.playMp3Folder(SOUND_OFFSET_HOUR + t_hour);
  seg_led_on_figure(t_hour);
  delay(1200);
  mp3_player.playMp3Folder(SOUND_OFFSET_MINUTE + t_minute);
  seg_led_on_figure(t_minute);
  delay(1200);
  if(is_am){
    mp3_player.playMp3Folder(SOUND_AM);
    seg_led_on_a();
  }else{
    mp3_player.playMp3Folder(SOUND_PM);
    seg_led_on_p();
  }
  delay(1200);

  mp3_player.volume(SOUND_VOLUME_DEFAULT);
  delay(100);

  if(t_hour == 5 && t_minute == 55){ // 隠しコマンド。5時55分(or 17時55分)に時刻通知をすると、変身音発動
    seg_led_on_figure(55);
    mp3_player.playMp3Folder(SOUND_COMPLETE);
    delay(5500);
  }
}

void notify_calendar(){
  mp3_player.playMp3Folder(SOUND_TIME);
  DateTime t_now    = rtc.now();
  uint16_t t_year   = t_now.year();
  uint8_t  t_month  = t_now.month();
  uint8_t  t_day    = t_now.day();

  // ローディング演出発光処理
  for(uint8_t i=0;i<LEN_LED_PATTERN_TIME;i++){
    flash_7seg_led_pattern(LED_PATTERN_TIME, i);
    delay(INTERVAL_MS_LED_PATTERN_TIME);
  }
  mp3_player.volume(SOUND_VOLUME_TIME);
  delay(100);

  mp3_player.playMp3Folder(SOUND_OFFSET_YEAR + t_year - START_YEAR);
  seg_led_on_figure(t_year/100);
  delay(500);
  seg_led_on_figure(t_year%100);
  delay(1200);
  mp3_player.playMp3Folder(SOUND_OFFSET_MONTH + t_month);
  seg_led_on_figure(t_month);
  delay(1200);
  mp3_player.playMp3Folder(SOUND_OFFSET_DAY + t_day);
  seg_led_on_figure(t_day);
  delay(2400);

  mp3_player.volume(SOUND_VOLUME_DEFAULT);
  delay(100);
}

/*-------------------------- ユーティリティ関数 --------------------------*/
void state_change(uint8_t new_state){
  state = new_state;
  led_pattern_index = 0;
  prev_flash_time = 0;
  driver_counter = 0;
}

/*-------------------------- セットアップ処理 --------------------------*/

void setup() {
  Serial.begin(115200);
  pinMode(SW_CENTER_PIN, INPUT_PULLUP);
  pinMode(SW_DRIVER_PIN, INPUT_PULLUP);

  //---------- 7セグメントLED ----------
  pinMode(SPI_LATCH_PIN, OUTPUT);
  pinMode(SPI_SCK_PIN,   OUTPUT);
  pinMode(SPI_SDI_PIN,   OUTPUT);
  SPI.begin();
  SPI.setBitOrder(LSBFIRST);
  SPI.setDataMode(0);

  //Serial.println(F("LED setup finished."));

  //---------- MP3プレイヤー ----------
  ss_mp3_player.begin(9600);
  if(!mp3_player.begin(ss_mp3_player)) {  //Use softwareSerial to communicate with mp3.
    //Serial.println(F("Unable to begin music_player:"));
    //Serial.println(F("1.Please recheck the connection!"));
    //Serial.println(F("2.Please insert the SD card!"));
    while(true);
  }
  //Serial.println(F("mp3_player online."));
  mp3_player.setTimeOut(500); //Set serial communictaion time out 500ms
  mp3_player.volume(SOUND_VOLUME_DEFAULT);  //Set volume value (0~30).

  //---------- 起動処理 ----------
  for(int i;i<LEN_LED_PATTERN_ON;i++){ // 「FA」
    digitalWrite(SPI_LATCH_PIN, LOW);
    SPI.transfer(pgm_read_byte(&LED_PATTERN_ON[i][0]));//10の桁
    SPI.transfer(pgm_read_byte(&LED_PATTERN_ON[i][1]));//1の桁
    digitalWrite(SPI_LATCH_PIN, HIGH);
    delay(INTERVAL_MS_LED_PATTERN_ON);
  }
  mp3_player.playMp3Folder(SOUND_ON);
  delay(2000);
  digitalWrite(SPI_LATCH_PIN, LOW);   // 「10」
  SPI.transfer(LED_FIG_1);//10の桁
  SPI.transfer(LED_FIG_0);//1の桁
  digitalWrite(SPI_LATCH_PIN, HIGH);
  delay(2000);
  seg_led_off_all();
}

/*-------------------------- メイン処理 --------------------------*/

void loop() {
  // ---------- 共通処理 ----------
  unsigned long now = millis();

  // ---------- 7セグLED発光処理 ----------
  switch(state){
  case STATE_INIT:
    if(now - prev_flash_time >= INTERVAL_MS_LED_PATTERN_INIT){
      if(is_lighting){
        seg_led_on_dot_right();
      }else{
        seg_led_off_all();
      }
      is_lighting = !is_lighting;
      prev_flash_time = now;
    }
    break;
  case STATE_INIT_TO_READY:
    if(now - prev_flash_time >= INTERVAL_MS_LED_PATTERN_INIT_TO_READY){
      if(led_pattern_index < LEN_LED_PATTERN_INIT_TO_READY){
        flash_7seg_led_pattern(LED_PATTERN_INIT_TO_READY, led_pattern_index);
        led_pattern_index++;
        prev_flash_time = now;
      }
    }
    break;
  case STATE_READY:
    if(now - state_change_start_time > DURATION_MS_COMPLETE){ // はじめの「COMPLETE」中は発光を維持し、以後点滅
      if(now - prev_flash_time >= INTERVAL_MS_LED_PATTERN_READY){
        if(is_lighting){
          seg_led_off_all();
        }else{
          seg_led_on_all();
        }
        is_lighting = !is_lighting;
        prev_flash_time = now;
      }
    }
    break;
  case STATE_READY_TO_INIT:
    if(now - prev_flash_time >= INTERVAL_MS_LED_PATTERN_READY_TO_INIT){
      if(led_pattern_index < LEN_LED_PATTERN_READY_TO_INIT){
        flash_7seg_led_pattern(LED_PATTERN_READY_TO_INIT, led_pattern_index);
        led_pattern_index++;
        prev_flash_time = now;
      }
    }
    break;
  case STATE_READY_TO_ACCEL:
    if(now - prev_flash_time >= INTERVAL_MS_LED_PATTERN_READY_TO_ACCEL){
      if(led_pattern_index < LEN_LED_PATTERN_READY_TO_ACCEL){
        flash_7seg_led_pattern(LED_PATTERN_READY_TO_ACCEL, led_pattern_index);
        led_pattern_index++;
        prev_flash_time = now;
      }
    }
    break;
  case STATE_ACCEL:
    if(led_pattern_index < LEN_LED_PATTERN_ACCEL_A){
      if(now - prev_flash_time >= INTERVAL_MS_LED_PATTERN_ACCEL_A){
        flash_7seg_led_pattern(LED_PATTERN_ACCEL_A, led_pattern_index);
        led_pattern_index++;
        prev_flash_time = now;
      }
    }else{
      if(now - prev_flash_time >= INTERVAL_MS_LED_PATTERN_ACCEL_B){
        uint8_t shift_led_pattern_index = led_pattern_index - LEN_LED_PATTERN_ACCEL_A;
        if(shift_led_pattern_index < LEN_LED_PATTERN_ACCEL_B)
          flash_7seg_led_pattern(LED_PATTERN_ACCEL_B, shift_led_pattern_index);
          led_pattern_index++;
          prev_flash_time = now;
      }
    }
    break;
  default:
    ;
  }

  // ---------- 龍頭スイッチ処理 ----------
  sw_center = digitalRead(SW_CENTER_PIN);

  if(prev_sw_center == SW_ON){
    if(sw_center == SW_ON){ // スイッチが継続して押されているとき
      if(state == STATE_INIT){
        if(!is_valid_long_press){
          sw_center_press_counter++;
        }
        if(!is_valid_long_press && sw_center_press_counter > WAIT_COUNT_CALENDAR){
          is_valid_long_press = true;
          notify_calendar();// カレンダー処理
        }
      }
    }else{ // スイッチが離されたとき
      if(state == STATE_INIT){
        if(!is_valid_long_press){
          notify_time(); // 時計処理
        }
        sw_center_press_counter = 0;
        is_valid_long_press = false;
      }
    }
  }else{
    if(sw_center == SW_ON){ // スイッチが押されたとき
      if(state == STATE_READY){
        mp3_player.playMp3Folder(SOUND_READY);
        state_change(STATE_READY_TO_ACCEL);
        state_change_start_time = now;
      }
    }
  }

  prev_sw_center = sw_center;

  // ---------- ドライバー連動スイッチ処理 ----------
  sw_driver = digitalRead(SW_DRIVER_PIN);

  // スイッチが押された時の処理
  if(prev_sw_driver == SW_OFF && sw_driver == SW_ON){
    switch(state){
    case STATE_INIT:
      state_change(STATE_INIT_TO_READY);
      state_change_start_time = now;
      break;
    case STATE_READY_TO_INIT:
      driver_counter++;
      if(driver_counter > 1){
        state_change(STATE_READY_TO_ACCEL);
        state_change_start_time = now;
      }
      break;
    default:
      ;
    }
  }

  // スイッチが離された時の処理
  if(prev_sw_driver == SW_ON && sw_driver == SW_OFF){
    switch(state){
    case STATE_INIT_TO_READY:
      state_change(STATE_INIT);
      seg_led_off_all();
      break;
    case STATE_READY:
      state_change(STATE_READY_TO_INIT);
      state_change_start_time = now;
      break;
    case STATE_READY_TO_ACCEL:
      state_change(STATE_INIT);
      seg_led_off_all();
      mp3_player.playMp3Folder(SOUND_REFORMATION);
      break;
    case STATE_ACCEL:
      //state_change(STATE_INIT);
      //seg_led_off_all();
      //mp3_player.playMp3Folder(SOUND_REFORMATION);
      break;
    default:
      ;
    }
  }

  // 加速中にドライバーから外された場合は、カウントダウン終了後にREFORMATIONを発生させる
  if(state == STATE_READY && sw_driver == SW_OFF){
    state_change(STATE_INIT);
    seg_led_off_all();
    mp3_player.playMp3Folder(SOUND_REFORMATION);
  }

  // スイッチON/OFFの時間経過に対する処理
  if(state == STATE_INIT_TO_READY && now - state_change_start_time > WAIT_MS_COMPLETE){
    state_change(STATE_READY);
    mp3_player.playMp3Folder(SOUND_COMPLETE);
    state_change_start_time = now;
  }

  if(state == STATE_READY_TO_INIT){
    if(now - state_change_start_time > WAIT_MS_REFORMATION){
      state_change(STATE_INIT);
      mp3_player.playMp3Folder(SOUND_REFORMATION);
      delay(3000);
    }
  }

  if(state == STATE_READY_TO_ACCEL){
    if(now - state_change_start_time > WAIT_MS_STARTUP){
      state_change(STATE_ACCEL);
      mp3_player.playMp3Folder(SOUND_STARTUP);
      state_change_start_time = now;
    }
  }

  if(state == STATE_ACCEL && now - state_change_start_time > DURATION_MS_ACCEL){
    state_change(STATE_READY);
  }

  prev_sw_driver = sw_driver;

  delay(LOOP_INTERVAL_MS);
}