MENU

オリジナルライドウォッチをつくる 〜ソフト(プログラム)編〜

お待たせ致しました、ここからオリジナルライドウォッチ改造の詳細に入ります。今回がソフト(プログラム)編、次回がハード編になります。必要な具材につきましては、前回の記事をご参照ください。

プログラムの説明ですが、私の万丈ライドウォッチのプログラムをいきなり題材にしてしまうと、フォームチェンジのためのリードスイッチの処理やらフルカラーLEDの処理やら、「シンプルにDX版相当のライドウォッチを作りたい」という方からすれば邪魔でしかないコードが含まれたものを説明することになってしまいます。そのため、ここでは「シンプルにDX版相当のライドウォッチの動作を再現するためのプログラム」を説明することにします。フォームチェンジ用のリードスイッチは使いませんし、フルカラーLEDも使いません。それらを使った私の万丈ライドウォッチのソースコードは、次回のハード編のときに参考としてご紹介します。

はじめに注意事項ですが、ここでご紹介するプログラムは、ジクウドライバーのLスロット側(=通常、レジェンドライダーのライドウォッチがセットされる側)にセットするライドウォッチを想定しています。Rスロット側(=通常、ジオウとゲイツのライドウォッチがセットされる側)のライドウォッチを作りたい場合は、少しソースコードの修正が必要になります。それはまた最後にご説明いたします。

さて、ライドウォッチのプログラムを書く上で避けて通れないのが、ライドウォッチの内部の状態遷移の理解です。オリジナルガシャットを作ったときも、初めは内部の状態遷移の調査からスタートしました

以下では、説明の便宜上、ライドウォッチの3つのスイッチに上記のような名前をつけます。これらのスイッチをどのように操作すれば、内部でどのように状態が変化し、その変化に伴い何が起こるのか?以下はビルドライドウォッチを題材に、自分なりにスイッチを色々触りながら調査した結果をまとめた状態遷移図になります。

上図は自分が独自に調査した結果であり、玩具の状態遷移を完全に再現しているものではありませんのでご注意ください。例えば、実際の玩具では「時間が経てば初期状態に戻る」という状態遷移が発生すると思いますが、上記の図ではその線は含んでいません。あくまで、自分がプログラムを作る上で必要な情報のみをまとめたものが上図になります。

スイッチの数としてはガシャットのときと同じですが、

  • 片方のスイッチを押しながら片方のスイッチを2回押すことで状態遷移が発生する
  • ある状態からある状態に遷移するのに2つのルート(ライダータイム&アーマータイム)が存在する

ということがあり、ガシャットのときよりも数段ややこしく感じました。

さて、以上がライドウォッチの状態遷移の基本ですが、ここから実際にプログラムを書こうと思うと、もうひと頑張りしなくてはいけません。「どの状態のときに、スイッチの組み合わせが何から何に変わると、どの状態に変わるのか」を一つ一つ確認していかないと、プログラムとして書き起こせないからです。これは相当に面倒だったので、詳細を知りたい方だけ、以下のPDFファイルを開いて拡大表示してみてください。

ライドウォッチ状態遷移整理

これは理解するのはかなりキツイ、という方は、理解しなくても大丈夫です。最終的には、以下で掲載するソースコードをまるっとコピペしてもらえば済む話になります。

以上でプログラムを書くための準備は終了です。お疲れ様でした。ここからは実際にソースコードを解説していきますが、最初にどこに何が書いてあるかを示しておいた方が何かと理解しやすいと思いますので、最初にプログラムの全体構造を示しておきます。

上から順にこんな感じで記述していっています。以下にソースコードを載せますが、詳細説明は上図の番号順に行なっていきます。

#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>

SoftwareSerial ss_mp3_player(2, 3); // RX, TX
#define LED_PIN  5
#define SW_C_PIN 6
#define SW_1_PIN 7
#define SW_2_PIN 8

#define LOOP_INTERVAL_MS 20

#define ON  LOW
#define OFF HIGH
#define N_BUTTON 3
const uint8_t OFF_OFF_OFF[] = {OFF, OFF, OFF};
const uint8_t ON_OFF_OFF[]  = {ON,  OFF, OFF};
const uint8_t OFF_ON_OFF[]  = {OFF, ON,  OFF};
const uint8_t OFF_OFF_ON[]  = {OFF, OFF, ON };
const uint8_t ON_ON_OFF[]   = {ON,  ON,  OFF};
const uint8_t ON_OFF_ON[]   = {ON,  OFF, ON };
const uint8_t OFF_ON_ON[]   = {OFF, ON,  ON };
const uint8_t ON_ON_ON[]    = {ON,  ON,  ON };

#define STATE_SINGLE_A 1
#define STATE_SINGLE_B 2
#define STATE_READY_WP 3
#define STATE_WEAPON   4
#define STATE_READY_CH 5
#define STATE_CHANGED  6
#define STATE_READY_CR 7
#define STATE_CRITICAL 8

#define SIDE_NONE 0
#define SIDE_ARMOR_TIME 1
#define SIDE_RIDER_TIME 2

uint8_t prev_state = STATE_SINGLE_A;
uint8_t state      = STATE_SINGLE_A;
uint8_t prev_side  = SIDE_NONE;
uint8_t side       = SIDE_NONE;
uint8_t rider_time_counter = 0; 
uint8_t armor_time_counter = 0;

uint8_t prev_sw[]  = {OFF, OFF, OFF};
uint8_t sw[] = {OFF, OFF, OFF};

//--------------------------------------------------------------------------//

// 効果音処理

#define SOUND_SINGLE_A       1
#define SOUND_SINGLE_B       2
#define SOUND_RIDER_TIME     3
#define SOUND_ARMOR_TIME     4
#define SOUND_WEAPON         5
#define SOUND_CRITICAL_READY 6
#define SOUND_CRITICAL       7
#define SOUND_EJECT          8

#define ARMOR_TIME_WAIT 6500
#define RIDER_TIME_WAIT 1000
#define CRITICAL_WAIT_SHORT 1000
#define CRITICAL_WAIT_LONG  2000

unsigned long sound_wait_start_time = 0;
boolean is_armor_time_waiting = false;
boolean is_rider_time_waiting = false;
boolean is_armor_time_critical_waiting = false;
boolean is_rider_time_critical_waiting = false;

DFRobotDFPlayerMini mp3_player;

void play_sound(){
  if(prev_state != state){
    switch(state){
    case STATE_SINGLE_A:
      switch(prev_state){
      case STATE_SINGLE_B:
        mp3_player.playMp3Folder(SOUND_SINGLE_A);
        break;
      case STATE_READY_WP:
      case STATE_WEAPON:
      case STATE_READY_CH:
      case STATE_CHANGED:
      case STATE_READY_CR:
      case STATE_CRITICAL:
        mp3_player.playMp3Folder(SOUND_EJECT);
        break;
      default:
        ;
      }
      break;
    case STATE_SINGLE_B:
      mp3_player.playMp3Folder(SOUND_SINGLE_B);
      break;
    case STATE_READY_WP:
      if(prev_state != STATE_WEAPON){
        mp3_player.pause();
      }
      break;
    case STATE_WEAPON:
      mp3_player.playMp3Folder(SOUND_WEAPON);
      break;
    case STATE_READY_CH:
      mp3_player.pause();
      break;
    case STATE_CHANGED:
      if(side == SIDE_RIDER_TIME){
        sound_wait_start_time = millis();
        is_rider_time_waiting = true;
      }else if(side == SIDE_ARMOR_TIME){
        sound_wait_start_time = millis();
        is_armor_time_waiting = true;
      }
      break;
    case STATE_READY_CR:
      mp3_player.playMp3Folder(SOUND_CRITICAL_READY);
      break;
    case STATE_CRITICAL:
      if(side == SIDE_RIDER_TIME){
        sound_wait_start_time = millis();
        is_rider_time_critical_waiting = true;
      }else if(side == SIDE_ARMOR_TIME){
        sound_wait_start_time = millis();
        is_armor_time_critical_waiting = true;
      }
      break;
    default:
      ;
    }
  }else{
    unsigned long now = millis();
    if(is_rider_time_waiting){
      if(now - sound_wait_start_time >= RIDER_TIME_WAIT){
          mp3_player.playMp3Folder(SOUND_RIDER_TIME);
          is_rider_time_waiting = false;
      }
    }else if(is_armor_time_waiting){
      if(now - sound_wait_start_time >= ARMOR_TIME_WAIT){
          mp3_player.playMp3Folder(SOUND_ARMOR_TIME);
          is_armor_time_waiting = false;
      }
    }else if(is_rider_time_critical_waiting){
      if(now - sound_wait_start_time >= CRITICAL_WAIT_LONG){
          mp3_player.playMp3Folder(SOUND_CRITICAL);
          is_rider_time_critical_waiting = false;
      }
    }else if(is_armor_time_critical_waiting){
      if(now - sound_wait_start_time >= CRITICAL_WAIT_SHORT){
          mp3_player.playMp3Folder(SOUND_CRITICAL);
          is_armor_time_critical_waiting = false;
      }
    }
  }
}

//--------------------------------------------------------------------------//

// 発光処理

#define LED_COUNT_MAX 1500 // LOOP_INTERVAL_MSが20msで、30sまでの発光定義を想定
#define LED_BRIGHTNESS 255 // 255が上限値

uint16_t led_counter = LED_COUNT_MAX;
unsigned long blink_time = 0;
unsigned long prev_blink_time = 0;
unsigned long inc_dim_start_time = 0;
boolean is_lighting = false;
boolean is_inc = false;

void led_base_pattern_on(){
  analogWrite(LED_PIN, LED_BRIGHTNESS);
}

void led_base_pattern_off(){
  analogWrite(LED_PIN, 0);
}

void led_base_pattern_blink(int interval_ms){
  unsigned long now = millis();
  if(now - prev_blink_time >= interval_ms){
    if(is_lighting){
      analogWrite(LED_PIN, 0);
    }else{
      analogWrite(LED_PIN, LED_BRIGHTNESS);
    }
    is_lighting = !is_lighting;
    prev_blink_time = now;
  }
}

void led_base_pattern_inc(int interval_ms, uint8_t steps){
  unsigned long now = millis();
  if(inc_dim_start_time == 0){
    inc_dim_start_time = now;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now - inc_dim_start_time) / ms_per_step;
  uint8_t l_step = LED_BRIGHTNESS/steps;
  analogWrite(LED_PIN, l_step*current_step);
  if(now - inc_dim_start_time > interval_ms){
    inc_dim_start_time = 0;
  }
}

void led_base_pattern_dim(int interval_ms, uint8_t steps){
  unsigned long now = millis();
  if(inc_dim_start_time == 0){
    inc_dim_start_time = now;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now - inc_dim_start_time) / ms_per_step;
  uint8_t l_step = LED_BRIGHTNESS/steps;
  analogWrite(LED_PIN, l_step*(steps-current_step));
  if(now - inc_dim_start_time > interval_ms){
    inc_dim_start_time = 0;
  }
}

void led_base_pattern_blink_slowly(int interval_ms, uint8_t steps){
  unsigned long now = millis();
  if(inc_dim_start_time == 0){
    inc_dim_start_time = now;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now - inc_dim_start_time) / ms_per_step;
  uint8_t l_step = LED_BRIGHTNESS/steps;
  if(is_inc){
    analogWrite(LED_PIN, l_step*current_step);
  }else{
    analogWrite(LED_PIN, l_step*(steps-current_step));
  }
  if(now - inc_dim_start_time > interval_ms){
    is_inc = !is_inc;
    inc_dim_start_time = 0;
  }
}


void led_pattern_single_a(uint16_t led_counter_ms){ // 起動音+解説+「ビルドだ!」
  if(led_counter_ms <= 600){                                 led_base_pattern_blink(80);}
  else if(  600 < led_counter_ms && led_counter_ms <=  1600){ led_base_pattern_dim(1000, 10);}
  else if( 1600 < led_counter_ms && led_counter_ms <=  6200){ led_base_pattern_on();}
  else if( 6200 < led_counter_ms && led_counter_ms <=  8600){ led_base_pattern_blink(90);}
  else if( 8600 < led_counter_ms && led_counter_ms <=  9200){ led_base_pattern_off();}
  else if( 9200 < led_counter_ms && led_counter_ms <= 10700){ led_base_pattern_on();}
  else{                                                       led_base_pattern_off();}
}

void led_pattern_single_b(uint16_t led_counter_ms){ // 起動音+起動音+「ビルド!」
  if(led_counter_ms <= 600){                                led_base_pattern_blink(80);}
  else if( 600 < led_counter_ms && led_counter_ms <= 1600){ led_base_pattern_dim(1000, 10);}
  else if(1600 < led_counter_ms && led_counter_ms <= 3200){ led_base_pattern_blink(40);}
  else if(3200 < led_counter_ms && led_counter_ms <= 5000){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_weapon(uint16_t led_counter_ms){ // 「ビルド!」
  if(led_counter_ms <= 300){                               led_base_pattern_blink(40);}
  else if(300 < led_counter_ms && led_counter_ms <= 1800){ led_base_pattern_on();}
  else{                                                    led_base_pattern_off();}
}

void led_pattern_rider_time(uint16_t led_counter_ms){
  if(led_counter_ms <= 1100){                               led_base_pattern_off();}
  else if(1100 < led_counter_ms && led_counter_ms <= 1900){ led_base_pattern_dim(800, 10);}
  else if(1900 < led_counter_ms && led_counter_ms <= 2800){ led_base_pattern_dim(900, 10);}
  else if(2800 < led_counter_ms && led_counter_ms <=11800){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_armor_time(uint16_t led_counter_ms){
  if(led_counter_ms <= 6600){                               led_base_pattern_off();}
  else if(6600 < led_counter_ms && led_counter_ms <= 7400){ led_base_pattern_dim(800, 10);}
  else if(7400 < led_counter_ms && led_counter_ms <= 8600){ led_base_pattern_dim(1200, 10);}
  else if(8600 < led_counter_ms && led_counter_ms <=17600){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_ready_critical(uint16_t led_counter_ms){ // 起動音+「ビルド!」+待機
  if(led_counter_ms <= 600){                                led_base_pattern_blink(80);}
  else if( 600 < led_counter_ms && led_counter_ms <= 1600){ led_base_pattern_dim(1000, 10);}
  else if(1600 < led_counter_ms && led_counter_ms <= 3100){ led_base_pattern_on();}
  else{                                                     led_base_pattern_blink(800);}                                               
}

void led_pattern_critical_long(uint16_t led_counter_ms){ // 「ボルテック!」
  if(led_counter_ms <= 2100){                               led_base_pattern_off();}
  else if(2100 < led_counter_ms && led_counter_ms <= 2600){ led_base_pattern_blink(40);}
  else if(2600 < led_counter_ms && led_counter_ms <= 4100){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}

void led_pattern_critical_short(uint16_t led_counter_ms){ // 「ボルテック!」
  if(led_counter_ms <= 1100){                               led_base_pattern_off();}
  else if(1100 < led_counter_ms && led_counter_ms <= 1600){ led_base_pattern_blink(40);}
  else if(1600 < led_counter_ms && led_counter_ms <= 3100){ led_base_pattern_on();}
  else{                                                     led_base_pattern_off();}
}


void flash_led(){

  if(prev_state != state){
    if(prev_state != STATE_SINGLE_B && state == STATE_SINGLE_A){
      led_counter = LED_COUNT_MAX; // 発光させないように、カウントの上限値にする
    }else if((prev_state == STATE_SINGLE_A || prev_state == STATE_SINGLE_B) && state == STATE_READY_WP){
      led_counter = LED_COUNT_MAX; // 発光させないように、カウントの上限値にする
    }else if(prev_state == STATE_WEAPON && state == STATE_READY_WP){
      ; // 発光を継続させるためにカウントをリセットしない
    }else{
      led_counter = 0; // 基本的に状態遷移が発生するとカウントをリセットする
    }
  }else if(prev_side != side){
    led_counter = 0;
  }

  uint16_t led_counter_ms = led_counter * LOOP_INTERVAL_MS; // LEDはms単位で制御
  
  switch(state){
  case STATE_SINGLE_A:
    led_pattern_single_a(led_counter_ms);
    break;
  case STATE_SINGLE_B:
    led_pattern_single_b(led_counter_ms);
    break;
  case STATE_READY_WP:
  case STATE_WEAPON:
    led_pattern_weapon(led_counter_ms); 
    break;
  case STATE_READY_CH:
    led_base_pattern_blink(800);
    break;
  case STATE_CHANGED:
    if(side == SIDE_NONE){
      led_base_pattern_blink(800);
    }else if(side == SIDE_ARMOR_TIME){
      led_pattern_armor_time(led_counter_ms);
    }else if(side == SIDE_RIDER_TIME){
      led_pattern_rider_time(led_counter_ms);
    }
    break;
  case STATE_READY_CR:
    led_pattern_ready_critical(led_counter_ms);
    break;
  case STATE_CRITICAL:
    if(side == SIDE_ARMOR_TIME){
      led_pattern_critical_short(led_counter_ms);
    }else if(side == SIDE_RIDER_TIME){
      led_pattern_critical_long(led_counter_ms);
    }
    break;
  default:
    ;  
  }

  if(led_counter < LED_COUNT_MAX){
    led_counter++;
  }
}

//--------------------------------------------------------------------------//

// リセット処理

void reset_rider_time_counter(){
  rider_time_counter = 0;
}

void reset_armor_time_counter(){
  armor_time_counter = 0;
}

void reset_all(){
  analogWrite(LED_PIN, 0);
  reset_rider_time_counter();
  reset_armor_time_counter();
  is_armor_time_waiting = false;
  is_rider_time_waiting = false;
  is_armor_time_critical_waiting = false;
  is_rider_time_critical_waiting = false;
  led_counter = LED_COUNT_MAX;
}

//--------------------------------------------------------------------------//

// 起動処理

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  pinMode(SW_C_PIN, INPUT_PULLUP);
  pinMode(SW_1_PIN, INPUT_PULLUP);
  pinMode(SW_2_PIN, INPUT_PULLUP);

  // ---------- 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(10);  //Set volume value (0~30).

  // ---------- 起動エフェクト ----------
  analogWrite(LED_PIN, LED_BRIGHTNESS);
  delay(1500);
  analogWrite(LED_PIN, 0);
}

//--------------------------------------------------------------------------//

// メイン処理

void loop() {
  sw[0] = digitalRead(SW_C_PIN);
  sw[1] = digitalRead(SW_1_PIN);
  sw[2] = digitalRead(SW_2_PIN);

  prev_state = state;
  prev_side  = side;

  if(memcmp(prev_sw, OFF_OFF_OFF, N_BUTTON) == 0){
    if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
      if(prev_state == STATE_SINGLE_A){
        state = STATE_SINGLE_B;
      }else if(prev_state == STATE_SINGLE_B){
        state = STATE_SINGLE_A;
      }
    }else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
      if(prev_state == STATE_SINGLE_A || prev_state == STATE_SINGLE_B){
        state = STATE_READY_WP;
      }
    }else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_OFF,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_ON,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_ON,  N_BUTTON) == 0){
      if(prev_state == STATE_SINGLE_A || prev_state == STATE_SINGLE_B){
        state = STATE_READY_CH;
      }
    }else if(memcmp(sw, ON_ON_ON,   N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, ON_OFF_OFF, N_BUTTON) == 0){
    if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_OFF,  N_BUTTON) == 0){
      if(prev_state == STATE_SINGLE_A || prev_state == STATE_SINGLE_B){
        state = STATE_READY_WP;
      }
    }else if(memcmp(sw, ON_OFF_ON,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_ON,   N_BUTTON) == 0){
      if(prev_state == STATE_SINGLE_A || prev_state == STATE_SINGLE_B){
        state = STATE_READY_CH;
      }
    }else if(memcmp(sw, OFF_ON_ON,  N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0){
    if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0){
      if(prev_state == STATE_CHANGED){
        reset_all();
        state = STATE_READY_CR;
      }else if(prev_state == STATE_READY_CR){
        reset_all();
        prev_state = STATE_CHANGED; // 擬似的に状態変化を起こす
        state = STATE_READY_CR;
      }
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      if(prev_state == STATE_CHANGED || prev_state == STATE_READY_WP || prev_state == STATE_READY_CH || prev_state == STATE_READY_CR){
        reset_all();
        state = STATE_SINGLE_A;
      }
    }else if(memcmp(sw, OFF_ON_ON,   N_BUTTON) == 0){
      if(prev_state == STATE_READY_WP){
        state = STATE_WEAPON;
      }else if(prev_state == STATE_READY_CH || prev_state == STATE_CHANGED){
        rider_time_counter++;
        if(rider_time_counter == 2){
          prev_state = STATE_READY_CH; // 擬似的に状態変化を起こす
          state = STATE_CHANGED;
          side  = SIDE_RIDER_TIME;
        }
      }else if(prev_state == STATE_READY_CR){
        rider_time_counter++;
        if(rider_time_counter == 2){
          state = STATE_CRITICAL;
          side  = SIDE_RIDER_TIME;
        }
      }
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_ON,    N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_OFF_ON,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_ON,   N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
    if(memcmp(sw, ON_OFF_ON, N_BUTTON) == 0){
      if(prev_state == STATE_SINGLE_A){
        state = STATE_SINGLE_B;
      }else if(prev_state == STATE_SINGLE_B){
        state = STATE_SINGLE_A;
      }else if(prev_state == STATE_CHANGED){
        reset_all();
        state = STATE_READY_CR;
      }else if(prev_state == STATE_READY_CR){
        reset_all();
        prev_state = STATE_CHANGED; // 擬似的に状態変化を起こす  
        state = STATE_READY_CR;
      }
    }else if(memcmp(sw, OFF_ON_ON,   N_BUTTON) == 0){
      if(prev_state == STATE_SINGLE_A || prev_state == STATE_SINGLE_B){
        state = STATE_READY_CH;
      }else if(prev_state == STATE_READY_CH || prev_state == STATE_CHANGED){
        armor_time_counter++;
        if(armor_time_counter == 2){
          prev_state = STATE_READY_CH; // 擬似的に状態変化を起こす
          state = STATE_CHANGED;
          side  = SIDE_ARMOR_TIME;
        }
      }else if(prev_state == STATE_READY_CR){
        armor_time_counter++;
        if(armor_time_counter == 2){
          state = STATE_CRITICAL;
          side  = SIDE_ARMOR_TIME;
        }
      }
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      if(prev_state == STATE_WEAPON || prev_state == STATE_CHANGED || prev_state == STATE_READY_CH || prev_state == STATE_READY_CR){
        reset_all();
        state = STATE_SINGLE_A;
      }
    }else if(memcmp(sw, ON_ON_ON,    N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_OFF,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_OFF,   N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, ON_ON_OFF,  N_BUTTON) == 0){
    if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      if(prev_state == STATE_CHANGED || prev_state == STATE_READY_WP || prev_state == STATE_READY_CH || prev_state == STATE_READY_CR){
        reset_all();
        state = STATE_SINGLE_A;
      }
    }else if(memcmp(sw, ON_ON_ON,    N_BUTTON) == 0){
      if(prev_state == STATE_READY_WP){
        state = STATE_WEAPON;
      }else if(prev_state == STATE_READY_CH || prev_state == STATE_CHANGED){
        rider_time_counter++;
        if(rider_time_counter == 2){
          prev_state = STATE_READY_CH; // 擬似的に状態変化を起こす
          state = STATE_CHANGED;
          side  = SIDE_RIDER_TIME;
        }
      }else if(prev_state == STATE_READY_CR){
        rider_time_counter++;
        if(rider_time_counter == 2){
          state = STATE_CRITICAL;
          side  = SIDE_RIDER_TIME;
        }
      }
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_ON,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_ON,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_OFF_ON,  N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, ON_OFF_ON,  N_BUTTON) == 0){
    if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_ON,    N_BUTTON) == 0){
      if(prev_state == STATE_SINGLE_A || prev_state == STATE_SINGLE_B){
        state = STATE_READY_CH;
      }else if(prev_state == STATE_READY_CH || prev_state == STATE_CHANGED){
        armor_time_counter++;
        if(armor_time_counter == 2){
          prev_state = STATE_READY_CH; // 擬似的に状態変化を起こす
          state = STATE_CHANGED;
          side  = SIDE_ARMOR_TIME;
        }
      }else if(prev_state == STATE_READY_CR){
        armor_time_counter++;
        if(armor_time_counter == 2){
          state = STATE_CRITICAL;
          side  = SIDE_ARMOR_TIME;
        }
      }
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      if(prev_state == STATE_WEAPON || prev_state == STATE_CHANGED || prev_state == STATE_READY_CH || prev_state == STATE_READY_CR){
        reset_all();
        state = STATE_SINGLE_A;
      }
    }else if(memcmp(sw, OFF_ON_ON,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_OFF,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_OFF,  N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, OFF_ON_ON,  N_BUTTON) == 0){
    if(memcmp(sw, ON_ON_ON, N_BUTTON) == 0){
      if(prev_state == STATE_CHANGED){
        reset_all();
        state = STATE_READY_CR;
      }else if(prev_state == STATE_READY_CR || prev_state == STATE_CRITICAL){
        reset_all();
        prev_state = STATE_CHANGED; // 擬似的に状態変化を起こす
        state = STATE_READY_CR;
      }
    }else if(memcmp(sw, OFF_OFF_ON,  N_BUTTON) == 0){
      if(prev_state == STATE_CHANGED){
        side = SIDE_NONE;
        if(armor_time_counter == 2){
          reset_armor_time_counter();
        }
      }else if(prev_state == STATE_CRITICAL){
        state = STATE_CHANGED;
        side  = SIDE_NONE;
        if(armor_time_counter == 2){
          reset_armor_time_counter();
        }
      }
    }else if(memcmp(sw, OFF_ON_OFF,  N_BUTTON) == 0){
      if(prev_state == STATE_WEAPON){
        state = STATE_READY_WP;
      }else if(prev_state == STATE_CHANGED){
        side = SIDE_NONE;
        if(rider_time_counter == 2){
          reset_rider_time_counter();
        }
      }else if(prev_state == STATE_CRITICAL){
        state = STATE_CHANGED;
        side  = SIDE_NONE;
        if(rider_time_counter == 2){
          reset_rider_time_counter();
        }
      }
    }else if(memcmp(sw, ON_OFF_ON,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_ON_OFF,   N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      if(prev_state == STATE_WEAPON || prev_state == STATE_CHANGED || prev_state == STATE_READY_CH || prev_state == STATE_READY_CR || prev_state == STATE_CRITICAL){
        reset_all();
        state = STATE_SINGLE_A;
      }
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      ;
    }
  }else if(memcmp(prev_sw, ON_ON_ON,   N_BUTTON) == 0){
    if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_ON,   N_BUTTON) == 0){
      if(prev_state == STATE_CHANGED){
        side = SIDE_NONE;
        if(armor_time_counter == 2){
          reset_armor_time_counter();
        }
      }else if(prev_state == STATE_CRITICAL){
        state = STATE_CHANGED;
        side  = SIDE_NONE;
        if(armor_time_counter == 2){
          reset_armor_time_counter();
        }
      }
    }else if(memcmp(sw, ON_ON_OFF,   N_BUTTON) == 0){
      if(prev_state == STATE_WEAPON){
        state = STATE_READY_WP;
      }else if(prev_state == STATE_CHANGED){
        side = SIDE_NONE;
        if(rider_time_counter == 2){
          reset_rider_time_counter();
        }
      }else if(prev_state == STATE_CRITICAL){
        state = STATE_CHANGED;
        side  = SIDE_NONE;
        if(rider_time_counter == 2){
          reset_rider_time_counter();
        }
      }
    }else if(memcmp(sw, OFF_OFF_ON,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, OFF_ON_OFF,  N_BUTTON) == 0){
      ;
    }else if(memcmp(sw, ON_OFF_OFF,  N_BUTTON) == 0){
      if(prev_state == STATE_WEAPON || prev_state == STATE_CHANGED || prev_state == STATE_READY_CH || prev_state == STATE_READY_CR || prev_state == STATE_CRITICAL){
        reset_all();
        state = STATE_SINGLE_A;
      }
    }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
      ;
    }
  }

  /*
  Serial.print("State: ");
  Serial.println(state);
  Serial.print("rider time counter: ");
  Serial.println(rider_time_counter);
  Serial.print("armor time counter: ");
  Serial.println(armor_time_counter);
  Serial.println("");
  */

  play_sound();
  flash_led();

  prev_sw[0] = sw[0];
  prev_sw[1] = sw[1];
  prev_sw[2] = sw[2];

  delay(LOOP_INTERVAL_MS);
}

⓪ メインの変数定義(1〜46行目)

Arduinoのピン定義、loop()の周期、状態管理管理のための変数などをここで定義しています。

Arduinoのピン定義で一つ注意するところとしては、このプログラムは通常の単色LEDを接続することを前提にしていて、LEDの明るさをプログラムで変更できるようにしています。これはanalogWrite()関数を使って実現しているのですが、analogWrite()関数を使うためには、LEDの接続先のピンがPWMに対応している必要があります。PWMを使えるピンはArduinoごとに違うので、事前にどのピンが使えるかを確認しておきましょう。Arduino Pro miniの場合は5番ピンが使えます。

次に、loop()の周期をLOOP_INTERVAL_MSで指定していますが、ここでは20msにしています。これを100msとかにしてしまうと、ちょっと長過ぎてうまくいかないかもしれません。というのも、このLOOP_INTERVAL_MSの周期でスイッチの状態変化をチェックしているのですが、先で述べたとおり、ライドウォッチはスイッチの2度押しで状態遷移が発生することがあります。ジクウドライバーを勢いよく回転させてしまうと、100ms以内にスイッチの2度押しが発生してしまい、2度押されたものが1度押しとして認識されてしまう可能性があります。かといって、あまり周期を短くし過ぎてしまうと、スイッチの同時押しの認識が難しくなってしまいますので、どの程度の周期に設定すべきかは、多少トライ&エラーが必要かもしれません。

続いて、状態管理のための変数ですが大きく4種類の変数を使います。

  • 以前と今の状態を管理するためのprev_stateとstate
  • 「ライダータイムとアーマータイムのどちらの経路で状態遷移が発生したか」を管理するためのprev_sideとside
  • ライダータイムとアーマータイムのためのスイッチを押された回数を管理するrider_time_counterとarmor_time_counter
  • 3つのスイッチを押されている状態を管理するためのprev_swとsw

ちょっとややこしいですが、これらを②の状態遷移管理・リセット処理の中で変更しながらライドウォッチの状態を管理していきます。

① 起動処理(386〜413行目)

ここはそんなに大したことはしていません。主にArduinoのピンのセットアップと、MP3プレイヤーの初期化処理、音量設定を行なっているだけです。

②状態遷移管理・リセット処理(415〜718行目、363〜384行目)

冒頭の方で説明した状態遷移をプログラムとして書き起こしているのが、この部分になります。興味のある方は、先で紹介した状態遷移のPDFファイルの詳細と、この部分のプログラムの内容を照らし合わせていただくと良いかと思います。

ざっくりとこの部分の説明をしますと、プログラムのこの部分を通過することで、「⓪ メインの変数定義」で紹介したprev_state, state, prev_side, side, rider_time_counter, armor_time_counterの値が変化します。続く「③効果音再生処理呼出」と「④発光処理呼出」は、ここでこれらの変数がどのように変化したのかをチェックすることで、どの音声を再生/停止させるか、どのように発光させるかを判断します。

リセット処理は状態遷移管理部分の要所要所で呼び出していて、その名の通り、状態管理用の変数をまとめて初期化するための関数です。この後で説明する効果音再生処理、発光処理で使用する変数もここで併せて初期化しています。

③効果音再生処理(48〜156行目、729行目)

loop()の729行目で呼び出されているplay_sound()関数の内容が48〜156行目で定義されています。大部分は「prev_stateの状態からstateの状態に変わったら、何番の音を再生する」という内容になっていることがわかると思います。先の状態遷移図と照らし合わせてみるとなおわかりやすいと思います。なお、DFPlayer miniの使い方ですが、このプログラムではSDカードのルートディレクトリに””mp3″という名のフォルダを作り、その中に”0001_single_A.mp3″, “0002_single_B.mp3”, … のような形でファイルを保存することを前提にしています。

ちょっとややこしいのは、ライダータイム用の音声、アーマータイム用の音声、必殺技音声の再生です。状態遷移が発生したルート(←side変数で管理)によって再生する音声が変わることもややこしくなる要因の一つなのですが、それより問題なのは、ジクウドライバーをお持ちの方はよくご存知だと思いますが、ライダータイムの場合は「ベルト回転完了音」→「ライダータイム音」の順、アーマータイムの場合は「ベルト回転完了音」→「ライダータイム音」→「アーマータイム音」の順で音声が再生されます。必殺技(タイムブレイク、タイムバースト)のときも同様です。つまり、「状態が遷移したからといって、すぐに対応する音声が再生されるわけではない」ということになります。解決方法としては、無音部分を含めてmp3ファイルを作成してしまうという方法もありますが、ここでは無音時間の長さをms単位で調整できるように、ARMOR_TIME_WAIT / RIDER_TIME_WAIT / CRITICAL_WAIT_SHORT / CRITICAL_WAIT_LONGという定数と、sound_wait_start_timeという変数を使用しています。ただ、sound_wait_start_timeという変数だけだと、どの効果音の再生待ちをしているのかがわからなくなるので、is_armor_time_waiting / is_rider_time_waiting / is_armor_time_critical_waiting / is_rider_time_critical_waitingというフラグ変数も組み合わせて、再生すべき音声ファイルを特定できるようにしています。

④発光処理(158〜361行目、730行目)

loop()の730行目で呼び出されているflash_led()関数の内容が158〜361行目で定義されています。ここは、ちょっとややこしいです。

まず、play_sound()関数のときと同様、flash_led()関数で、prev_state, state, sideの状態を見て、どの発光パターンでLEDを光らせれば良いかを選択しています。

状態ごとに選択される各発光パターンを定義しているのが、239〜300行目になります。led_pattern_xxx(uint16_t led_counter_ms)という関数が並んでいますが、中身は全て同じような構成になっていて、「何msから何msまでは、この基本発光パターンで光らせる」というのを並べて、その組み合わせで、それぞれの状態のときの固有の発光パターンを定義できるようになっています。

その「基本発光パターン」を定義しているのが、163〜238行目で、led_base_pattern_xxx()という名の関数が6つ並んでいます。

  • led_base_pattern_on … 光る
  • led_base_pattern_off … 消える
  • led_base_pattern_blink … 点滅する(何msごとに、を指定)
  • led_base_pattern_inc … 徐々に光る(何msかけて、何段階で、を指定)
  • led_base_pattern_dim … 徐々に消える(何msかけて、何段階で、を指定)
  • led_base_pattern_blink_slowly … 徐々に点いて徐々に消える(何msごとに、何段階で、を指定)

この6パターンを組み合わせれば、大体の発光パターンは作れるかなと思っています。ガシャットのときは100msごとの色・明るさ定義を地道に記述していたのですが、今回はこんな感じで発光パターンを言うなればプログラムできるようにしたので、発光タイミングの微調整がだいぶ簡単になりました。使用するArduinoのメモリの量も大きく削減されており、動作も安定しやすくなります。

以上が今回のオリジナルライドウォッチのプログラムの全容になります。大変おつかれさまでした。プログラムがかけましたら、実際にブレッドボード上に回路を組んで、プログラムが意図通りに動作するかを確認しましょう。必要な具材については、前回の準備編をご参照ください。

今回組む回路はこんな感じになります。

実際にブレッドボード上に組むとこんな感じで、結構シンプルになります。

無事に動作したでしょうか。「なんか発光と音声がずれるなあ」と感じる方は、発光パターン定義のmsのところをいろいろいじってみてください。

最後に、冒頭で述べた「Rスロット側(=通常、ジオウとゲイツのライドウォッチがセットされる側)のライドウォッチを作りたい場合どうするのか」について、簡単に説明しておきます。

ソースコードをじっくり読まれた方はお気づきかと思いますが、これまで説明してきた「Lスロット側のライドウォッチ」では、SW-1のON/OFFによってアーマータイム、SW-2のON/OFFによってライダータイムがそれぞれ発動するようになっています。これを入れ替えて、SW-1のON/OFFによってライダータイム、SW-2のON/OFFによってアーマータイムがそれぞれ発動するように変更すれば、それで「Rスロット側のライドウォッチ」として動作するようになります。言葉にするとこれだけですが、実際にソースを修正するときは途中でこんがらがってしまわないようご注意ください。

以上、オリジナルライドウォッチ作成のためのソフト(プログラム)の説明でした。冒頭に述べましたとおり、今回ご紹介したプログラムは、「シンプルにDX版相当のライドウォッチの動作を再現するためのプログラム」になります。私が作った万丈ライドウォッチのプログラムは、上記のプログラムに、さらに、フルカラーLEDやフォームチェンジのためのリードスイッチの認識処理を加えたものになります。ただ、今回のプログラムを理解できた方からすればそう難しい変更ではないと思われますので、プログラム自体は次回ご紹介しますが、説明は省略致します。とりあえず音声と発光を差し替えたオリジナルライドウォッチをつくりたい、あるいは自分だけの新機能を追加したライドウォッチを作りたいという方は、今回ご紹介したプログラムをベースに、色々カスタマイズを加えていってもらえればと思います。

次回でいよいよ最終回、実際に遊べる形にするためのハード改造編になります。最後までお付き合い頂けますと幸いです。

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメント一覧 (47件)

  • お久しぶりです。少し時間ができたので、久しぶりに覗いてみました。
    サイトがリニューアルしてから、画像がきれいになりました?
    それはそうと、相変わらずすごい改造ですねw
    オリジナルベルト作成待ってます。

    ちなみに、ガシャット作成のほうは、LED発光までたどり着きました。
    mp3再生ができるようになればほぼ完成です。

    • クオリア様

      ご連絡ありがとうございます。画像の解像度とかは変わっていないと思いますが、全体的にサイトをすっきりさせたせいかもしれません。
      ベルト、特にジクウドライバーは改造難易度がとても高そうなので相変わらず手が出せませんが、ライドウォッチ側の改造をもうちょっと突き詰めて、見た目に面白いものを作れないかなあと色々試しているところです。

      ガシャット作成の方は順調そうで何よりです◎ ぜひ最後までがんばってみてください。

  • コメント失礼致します。
    今回のライドウォッチの記事を読み、私もオリジナルライドウォッチを作成したくなりました!
    そこで、質問なのですが、Arduinoのピンヘッダーはどのようなものをお使いになられましたか?
    あと、当方ラズパイは触ったことがありますが、Arduinoは初めてで、これを機に学んでみようと思ってますが、もし良い参考書などありましたら教えてください

    • 安藤 様

      ご連絡が遅くなり申し訳ありません。いくつかコメントを頂いていたかと思いますが、まとめてこちらで回答させていただきます。

      まずArduinoのピンヘッダですが、基本的には普通のピンヘッダを使っています。このあたりのものですね。
      https://www.switch-science.com/catalog/92/
      あまり高さを出したくないときには、L字型のものを使用します。
      https://www.switch-science.com/catalog/2257/
      リチウムイオンポリマー充電池から電源をとるときなどに、たまに細めのものも使います。
      https://www.switch-science.com/catalog/1280/
      だいたいこんな感じです。基本的にピンヘッダを使うのは、ブレッドボード上でプロトタイピングをするときのみです。ライドウォッチに部品を組み込むときなどは、スペースに余裕がないことが多く、直接半田付けしてしまうことも多いです。

      次に参考書についてですが、すでにラズパイで電子工作の経験がおありであるなら、Arduinoの基礎の本というよりは、「こういうときにはどういう部品を手に入れてどういうソースを書けばいいの?」ということがまとめられたレシピ集のようなものの方が役に立つのではないかと思います。その意味では、オライリー社、小林茂さん著の『Prototyping Lab』などが役立つかなと思います。私も勉強し始めの頃はよく参照していました。

      • お忙しい中アドバイスありがとうございます…!
        大変参考になりました!
        私も頑張ってオリジナルライドウォッチを作成してみようと思います!

        • 安藤 様

          お役に立てたなら良かったです。他の人が作るライドウォッチも見てみたいので、是非是非がんばってください◎

  • はじめまして。
    hidaka様のライドウォッチを見て、私も自作のライドウォッチを作ってみようと思いました。

    現在一通りの動作確認が終わり、自分のオリジナル機構を組み込もうとしています。
    その手始めとして、ボタンを押すとランダムで音声が鳴るようにしたいと思い、76~80行目の

    case STATE_SINGLE_A:
    switch(prev_state)
    case STATE_SINGLE_B:
    mp3_player.playMp3Folder(SOUND_SINGLE_A);
    break;

    case STATE_SINGLE_A:
    switch(prev_state){
    case STATE_SINGLE_B:
    int rand = random(0, 4);
    switch(rand){
    case 0: mp3_player.playMp3Folder(SOUND_SINGLE_A_0); break;
    case 1: mp3_player.playMp3Folder(SOUND_SINGLE_A_1); break;
    case 2: mp3_player.playMp3Folder(SOUND_SINGLE_A_2); break;
    case 3: mp3_player.playMp3Folder(SOUND_SINGLE_A_3); break;
    default:
    ;
    }
    break;
    のように変更してみました。

    しかし、音声は無事ランダムとなったのですが、ライドウォッチを取り外した時の音声[SOUND_EJECT]
    が再生されなくなってしまいました。
    アーマータイムといった他の音声は流れますし、ソースコードを元に戻すと再生されるため、自分が組み込んだ場所に間違いがあると思っています。

    ここ2週間ほどネットなどを見て自力で調べたのですが原因が全く分かりませんでした。
    不躾ですが、アドバイスなどをいただけないでしょうか。

    • Kenneth様

      拝見しましたところ、考え方もソースコードも特に間違いはないように思えます。SOUND_EJECTだけが鳴らないということは、素直に考えれば、

      switch(state){
      case STATE_SINGLE_A:
      switch(prev_state){
      case STATE_SINGLE_B:
      int rand = random(0, 4);
      switch(rand){
      case 0: mp3_player.playMp3Folder(SOUND_SINGLE_A_0); break;
      case 1: mp3_player.playMp3Folder(SOUND_SINGLE_A_1); break;
      case 2: mp3_player.playMp3Folder(SOUND_SINGLE_A_2); break;
      case 3: mp3_player.playMp3Folder(SOUND_SINGLE_A_3); break;
      default: ;
      }
      break;
      case STATE_READY_WP:
      case STATE_WEAPON:
      case STATE_READY_CH:
      case STATE_CHANGED:
      case STATE_READY_CR:
      case STATE_CRITICAL:
      mp3_player.playMp3Folder(SOUND_EJECT);
      break;
      default:
      ;
      }
      break;

      上記のprev_stateについてのswtich文で、”default”のルートに入ってしまっている気がします。つまり、prev_stateが想定外の値になってしまっているということなのですが、他の場所でソースをいじっていない場合、それがなぜ発生しているのかが不明です。すでにご確認頂いているかもしれませんが、このdefaultルートに入ってしまっていないか、またそのときのprev_stateの値がどうなっているかなど、一度print文などでご確認頂き、その結果を教えていただけないでしょうか。

      以上、宜しくお願い致します。

  • DFPlayer miniスピーカー 抵抗器 どれだけ選んだkΩ?
    私は中国人ですが、翻訳器でこれらの話をしました。

    • 陈铭 様

      すみません、私はスピーカーの詳細はわかりません。ただ、スピーカーは他のライドウォッチに組み込まれているものと同じものです。私はそれを直接、抵抗器なしでDFPlayer miniに接続しています。

      Sorry, I don’t know the detail of my speaker. But it’s the same one as which other ridewatches have. I connect it to the DFPlayer mini directly, without any resistor.

  • オリジナルガシャットの動画のほう拝見させていただいたのですが、すごいですね!僕はオリジナル変身ベルトを作ってみたいなと思っているのですが可能なのでしょうか?ヽ(;´ω`)ノパーツ設計についてとプログラムについての意見をお聞きしたいです!イメージとしては最近のギミック盛りだくさんのベルトではなく仮面ライダー1号などのシンプルなベルトを考えています!

    • アオバ様

      ありがとうございます◎ オリジナルの変身ベルトですが、可能か不可能か問われれば、それは可能なはずです。意見を述べさせて頂くにしても、まだあまり完成形が具体化されていないようですので、ちょっと抽象的なコメントになりますがご了承ください。

      私が普段やっていることとして、まず、「こういう機能を持ったこういう玩具を作りたい」というアイデアを、できるだけ具体的に考えていきます。今回のライドウォッチでいうと、「リングを回転させてフォームチェンジをさせる」とか、そういうレベルです。
      次に、そのアイデアが実現可能かどうかを考えます。どう考えても無理なものは早々に諦めますが、「無理というわけではなさそう」と思えるものについては、粘り強く解決策を考えます。今回は、ネオジム磁石とリードスイッチを使えば解決できそう、という方法を思いつきました。
      そして次に、本当に自分が思いついた方法でアイデアを実現できるかを、プロトタイプを作りながら検証し、そこでOKなら実際に制作に入ります。

      そういうわけで、アオバ様も、「仮面ライダー1号などのシンプルなベルト」というコンゼプトから、まずはもう少し機能を具体化するところから始めて、それからそれを実現する方法を考えて行くと良いと思います。例えば、『○○をすると、音がなって、風車が回る』なら、この『○○』は何になるか?変身ポーズをとる?変身ポーズをとることが条件なら、それをどうやってベルトに認識させる?スイッチを押す?どういうスイッチ? … と、こんな感じです。

      • 一番不安なのが音声のプログラムと基盤の作り方なのですが、デザインと音声どちらを先に決めたほうがいいなどというのはありますか?

        • アオバ様

          私の場合、音声の再生はほとんどArduino Pro mini + DFPlayer mini + スピーカー + リチウムイオンポリマー充電池(or 単四電池x3)という組み合わせで行なっておりますので、同じように作る場合は、少なくともこれらの部品が収まるようなベルトのデザインにはしておいた方がよいと思います。ベルトの場合は、ガシャットやライドウォッチと比べて、スペース的にはそこそこ余裕があると思います。

          • なるほど…ありがとうございます!ちなみにこのあとオリジナル変身ベルトの作成などをアップする予定などございますでしょうか?あるようでしたらすこし参考にさせていただきたいですm(__)m

          • アオバ様

            残念ながら、変身ベルトそのものに手を加えるような作品は、今のところ予定していません。ただ、昔にクウガの変身ベルトのミニマム版を作ったことがありますので、それが何かしらの参考にはなるかもしれません。
            https://make-muda.net/2016/09/4628/
            https://make-muda.net/2016/10/4505/
            https://make-muda.net/2016/10/4550/
            https://make-muda.net/2016/10/4615/
            https://make-muda.net/2016/10/4695/
            かなり初期の頃の作品なので、情報が古かったり、色々こなれていないところがあるのはご了承ください。適宜、最近の私の作品のソースなども参考にして頂いた方が良いと思います。

          • ちなみに市販の安い赤外線センサーだと感度はどれくらいなのでしょうか??

          • アオバ様

            すみません、私は赤外線センサはそんなに頻繁には使いませんので、わかりません。。。

          • ちなみに手をかざすたびに音声が鳴ってしまわないようにクウガアークルの場合どうプログラムしたのでしょうか?

          • アオバ様

            私が先にご案内した下記の記事はご確認頂いたでしょうか。こちらに、仕組みもプログラムも全て記載しています。
            https://make-muda.net/2016/10/4695/

            このときは赤外線センサではなくモーションセンサを使用致しましたので、特定のモーションをとったときのみ変身音が鳴るよう、プログラム内で状態遷移を管理しています。

  • はじめまして
    ライドウォッチの改造についてリサーチを行っているときにこの記事にたどり着きました
    私は電子工作等の経験はありませんが段階を踏まえていて改造ライドウォッチの機能の構想について明確化するうえで参考させていただく点が多く得られました
    他の記事についても拝見しているうちに新しく投稿されたカイザフォンXの記事を見て加速度センサで向きを検知する手法を知りました
    そこで素人質問で申し訳ないのですが通常のライドウォッチの場合でも加速度センサを(スペース的に、プログラムの余地的になど)組み込むことが可能であるかについて教えていただきたいです
    私は手先が器用とは言えないのでSW-1,2など微調整が必要そうな部分が減らせたらそれが望ましいと考えていたのですがいかがでしょうか
    どうかご教授お願いします

    • CB缶 様

      > そこで素人質問で申し訳ないのですが通常のライドウォッチの場合でも加速度センサを(スペース的に、プログラムの余地的になど)組み込むことが可能であるかについて教えていただきたいです

      いえ、申し訳なくないので大丈夫です。むしろ質問が明確なので大変答えやすいです、ありがとうございます。

      私自身、通常のライドウォッチに加速度センサを組み込むこと自体はまだ試したことがないのですが、ファイズフォンXに組み込んだ感じですと、おそらく通常のライドウォッチでも加速度センサを組み込むスペースはギリギリ確保できると思います。ただ、そのままだと難しいかもしれませんので、通常SW-1,2を認識するためにあった部分をカットしたり、ライドウォッチの背面に穴を開けたり、スピーカーを小型のものに交換する、などの工夫は必要かもしれません。
      また、プログラムの容量は、全然余裕です。カイザフォンXが容量ギリギリいっぱいのプログラムになってしまったのは、ディスプレイ表示のためのライブラリを使用したからで、加速度センサを使うぐらいであれば全然問題ありません。

      >> 私は手先が器用とは言えないのでSW-1,2など微調整が必要そうな部分が減らせたらそれが望ましいと考えていたのですがいかがでしょうか

      そうですね、できれば私もあの調整は二度とやりたくないので、極力加速度センサを使っていきたいと思っています。ただ、SW-1,2の機能をなくして加速度センサオンリーで判別を行うようにすると、武器(ジカンギレード、ジカンザックス、ライドヘイセイバー)との連動の実装はかなり難しくなってしまうと思いますので、そこはベルト挿し専用のライドウォッチにするなどの割り切りは必要になると思います。

      • なるほど
        回答ありがとうございます
        ジクウドライバーは持っていましたが他の連動武器玩具については未所持でしたので盲点でした
        当初の想定では、ドライバー右サイドのスロットを使ってディケイドのようにジクウドライバーでレジェンドライダーに変身して必殺技再現までできるライドウォッチを想定していたので、最悪武器連動遊びをあきらめることについては妥協できる点ではありますしもう少し考えてみます
        他の玩具との連動について改めて考えて気になった点ですがカイザフォンXの装填認識用ディテクタスイッチはライドウォッチホルダーにはめた時反応しそうに見えるのですがいかがでしょうか
        もう一点確認したいのがリチウムイオン電池の充電は閉じたウォッチ本体を開いて直接充電する形になるという認識で間違いないでしょうか?

        • CB缶 様

          >> カイザフォンXの装填認識用ディテクタスイッチはライドウォッチホルダーにはめた時反応しそうに見えるのですがいかがでしょうか

          ライドウォッチホルダーにはめることは考えていなかったのですが、今カイザフォンXをライドウォッチホルダーにはめてみたら、特に問題なく着脱できました。ギリギリ干渉しない位置にディテクタスイッチがあるようです。

          >> リチウムイオン電池の充電は閉じたウォッチ本体を開いて直接充電する形になるという認識で間違いないでしょうか?

          はい。ただ、それが面倒であれば、充電のたびにウォッチ本体を開けずに済むよう、充電用のコネクタをリード線で伸ばして外から挿せるように露出させるという手もあります。これをやりたい場合は、Arduino Pro miniではなく、その互換機であるGinger Breadを使うと費用も部品点数も少なくて良いと思います。私もカイザフォンXで使用しています。

      • 申し訳ありませんが質問を一つ記載し忘れてしまったのでできればこちらも回答をお願いしたいです
        加速度センサを組み込むための物理的なスペースの確保についてライドウォッチの背面に穴を開けるとの想定ですが内部に収まりきらないパーツを露出させる必要性も考慮しなくてはならないということで合っていますか?

        • >> CB缶 様

          >> 加速度センサを組み込むための物理的なスペースの確保についてライドウォッチの背面に穴を開けるとの想定ですが内部に収まりきらないパーツを露出させる必要性も考慮しなくてはならないということで合っていますか?

          最終的にはそういう手も必要になるかもしれませんが、個人的にはそれはあくまで最終手段にしたいと思っています。内部の余分なパーツを切り取る、スピーカーを小型のものに交換するなどしてスペースをできるだけ確保し、それでもどうにもならない場合に、背面に穴を開けるという手段を取らざるを得ない可能性はあります。ただ、カイザフォンXのときに実際に背面に穴を開けましたが、ギリギリ認識プレートの内側に収まるサイズの穴で済んだので、外見上は背面に穴が空いているようには見えないようになっています。

          • 回答ありがとうございます
            とりあえず音声素材や機材の用意をしつつプログラムや盛り込む機能についてライドウォッチに収納しないテスト段階から色々試しながら詰めていきたいと思います
            今後も記事の内容などを参考にさせていただきます
            よろしくお願いします

  • (1) Rスロット側のライドウォッチを作りたいのですが、説明を読んでも素人の私にはどうすればいいのか、わかりませんでした。何行目をどう修正すればいいのか解説して頂けますか?

    (2) 効果音については以下の認識で合っていますか?
    SOUND_SINGLE_A 1
     (0001_single_A.mp3)=起動
    SOUND_SINGLE_B 2
     (0002_single_B.mp3)=説明
    SOUND_RIDER_TIME 3
     (0003_rider_time.mp3)=ライダータイム
    SOUND_ARMOR_TIME 4
     (0004_armor_time.mp3)=アーマータイム
    SOUND_WEAPON 5
     (0005_weapon.mp3)=武器必殺
    SOUND_CRITICAL_READY 6
     (0006_critical_ready.mp3)=フィニッシュタイム
    SOUND_CRITICAL 7
     (0007_critical.mp3)=タイムブレイク
    SOUND_EJECT 8
     (0008_eject.mp3)=取り外し

    時間があればで結構ですので、お答えして頂けると嬉しいです。

    • ど素人25様

      (1)おそらくですが、ソースコードを変更する必要はなく、配線を一つ入れ替えるだけで済む気がします。7,8行目の定義により、「ドライバー連動部分の上のスイッチを7番、下のスイッチを8番に繋ぐ」という前提になっていますが、これをソースコードをそのままに、「ドライバー連動部分の上のスイッチを8番、下のスイッチを7番に繋ぐ」というふうに配線すれば、多分そのままのソースコードでRスロット側のライドウォッチになるのではないかと思います。一度試してみて頂けますでしょうか。

      (2)はい、ご認識のとおりです。

      • ”最後に、冒頭で述べた~” の部分を見て(ひぇぇ)と思っていたのですが、
        それだけで良かったのですね! 教えて頂き、ありがとうございました!

          • ソースコードをどう書くかによりますが、ここに記載のソースコードと同様にするのであれば、SDカードの階層のトップに”mp3″というフォルダを作って、その中に”0001.mp3″や”0002.mp3″のような名前でファイルをコピーすれば良いです。

  • どのようにプログラムをマイクロUSBに書けばよろしいでしょうか?
    教えてください。

    • ゆうちゃん様

      すみません、「プログラムをマイクロUSBに書く」ということは行なっておらず、プログラムはArduino(Pro mini)というマイコンに書き込んでいます。Arduinoへのプログラムの書き込みについては、たくさんの教本や入門サイトがあるかと思いますので、そちらをご参照頂けますと幸いです。

      • お邪魔しましてすみません(私の日本語は上手でわありませんのでお許してください)、私は貴方説明を読んってそれを参考しオリジナルライドオッチを作って見ったが、素人であるの自分が貴方が提供したソースコードそのままpro miniに入ってきて、説明どうり配線したが、結局ledもdfplayerも作動しなかった。
        それに関しての知識が足りませんの自分が手も足も動くことができません状態になった。
        まさかと思ったが、その”ソースコードそのままpro miniに入って”のが問題の原因なのか。(prominiやdfplayerなどの部品が貴方が使ったのと同じたと思います)
        お解明してくれたらとっても嬉しいです。

        • Dear vacuum

          I’m sorry for my very late reply. Thank you for your comment in Japanese, and I understood you.

          I don’t know why your Arduino doesn’t work well, because I don’t have enough information about your problem. So I’ll tell you my general advise. If you face a problem, you should split it into some small problems, and resolve them one by one.

          For example, if LED doesn’t work, first of all, you should make a very small program only to light it up. And if DFPlayer doesn’t work, you should make a very small program only to play a MP3 file using DFPlayer. Step by Step.

  • はじめまして
    ヒダカさんのyoutubeを見て自分もオリジナルのライドウォッチを作りたくなって、ヒダカさんのライドウォッチを参考にこの外出自粛を機に作ってみました。動画にしたのでよかったら見てください
    https://youtu.be/RbF5zSmFn1c
    スイッチ系は元のライドウォッチのものをそのまま使ったことと電気工作は初めてなため配線が無駄に多くなってしまったことから、蓋が閉まらなくなって食玩仕様のふたになってしまったり、充電を楽にするためにバッテリーのコードがむき出しになってしまっていたりしますが、自分なりに納得できるものができたので満足しています。
    これを機に他にも改造に挑戦してみて腕を磨こうと思います。
    ありがとうございます。

    • motomoto様

      はじめまして。ご連絡ありがとうございます。拝見致しました。こういうマルチ系(オールインワン系)の改造をやった人にしかわからないですが、音声用意するの大変だったでしょう?(笑)
      電子工作が初めてでこれだけのことができていれば、充分すごいと思います。ちなみに「スイッチ系は元のライドウォッチのものをそのまま使った」というのはおそらく手段としては最適で、今は私もこの方式を採用しています(プログライズキーの改造から)。

      ここまでできていれば、あとは応用でいろんなものを作り出せると思います。是非、いろいろチャレンジしてみてください◎

  • 初めまして
    突然の質問で申し訳ありません
    私も、hidaka様の動画を参考にオリジナルのウォッチを作っているのですが
    どうしても、mp3音声がならない状態が続いており、ご相談させていただいた次第です。
    (スピーカー側からは通電しているみたいな音は出ているのですが音自体がでてくれません。またファイル名もhidaka様が本サイトで記載していた通り”sd/mp3/0001.mp3”という形になっています)

    自分の中では原因として、
    ・dfplayerの初期不良⇒追加で同じものを購入し、試しましたが変化なし
    ・インクルードのdfplayerが関数?として認識していない。文字が黒字のまま⇒ライブラリ追加は何度も入れ直ししています。

    以上のことが原因なのではないかと考えています。
    しかし、私は電気工作自体が初めてで、あまり知識がありません。

    ネットなどを見て自力で調べたのですが自分が考える原因が推測でしかなく、他のところのも問題の原因があるかもしれません。
    不躾ですが、アドバイスなどをいただけないでしょうか。

    • さいころ様

      > インクルードのdfplayerが関数?として認識していない。文字が黒字のまま⇒ライブラリ追加は何度も入れ直ししています。

      これが原因の場合、おそらくコンパイルそのものが通らないと思うので、これが原因ではない気がしています。

      > ”sd/mp3/0001.mp3”という形になっています

      これは、トップに”sd”というフォルダ(ディレクトリ)を作ったりはしていないですよね? もし作っていれば、それは不要です。

      他に考えられる可能性は、シンプルな配線ミスか、電源供給が足りていないか、あるいは用意されているSDカード or MP3ファイルが以下のサイトのSpecificationに合っていないか、あたりが疑われます。
      https://wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299

      • ご連絡ありがとうございました。

        sdのファイルにつきましては、フォルダーではなくデバイス名になりますので、mp3ファイルはトップだと思います。

        配線ミスも何度も確認しながら組みましたので多分問題ないかと思われます。

        電源も5vです。

        そう考えると、SDかmp3ファイルの相性の問題かもしれませんね。
        一度違うsdで試してみます。

        ※ちなみに、dfplayer自体はledが光っていますので通電はしていると思います。
        竜頭スイッチを押すとスピーカーからは、一瞬小さい音(耳元まで持ってこないと聞こえないくらいの音)で「ブーン」という音声が鳴ります。SW-1、2ではdfplayerではledが反応するのみとなっています。

        重ね重ねで申し訳ありませんが、アドバイスの程よろしくお願いします

        • さいころ様

          なるほどです。実際の配線を見ていないのでなんとも言えませんが、5V系のArduinoを使っているか3.3V系のArduino系を使っているかで微妙に配線が異なるので、あと思いつくのはそれぐらいですかね。。。

          ちなみに、シンプルに音を鳴らすだけのサンプルコードで音が鳴ることは確認されていますでしょうか。確認していないなら、原因切り分けのため、それが最優先かと思います。

  • arduino pro mini 3.3Vを使用しています。
    サンプルコードを確認してみましたところ、電源モジュールのランプが消えてしまい何も動作しなくなりました。
    現在もhidaka様のお力をお借りしておりますが依然として変化がなくお手上げの状態です。
    (hidaka様のこのページに記載のある作成方法そのままのはずなのですが・・・)
    様々なアドバイスを頂いているにもかかわらず申し訳ありません。
    現状をお見せできれば一番よろしいのですが、その手段がありません。

    回路のほうも今一度hidaka様の画像を基に組み直してみましたが、音だけが出ない状態です。
    本当にあと音さえなれば、ライドウォッチに組み込んで完成なのですが
    考えられるとしたらDFPlayerの初期不良でしょうか?
    しかし、手元に4つあるうちの4つともが同じ状態であるため初期不良は考えにくいです。
    (運もありますが)

    一応hidaka様のつかっていたDFPlayerが現在取り扱っていないという事で以下のリンクの商品を利用しています。
    https://www.amazon.co.jp/HiLetgo%C2%AE-U%E3%83%87%E3%82%A3%E3%82%B9%E3%82%AF%E3%83%9F%E3%83%8B-MP3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%A4%E3%83%BC-%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%83%9C%E3%83%BC%E3%83%89-DFPlay%E3%81%AB%E5%AF%BE%E5%BF%9C/dp/B01D1D0E7Q/ref=pd_aw_sbs_147_1/356-6852472-3891812?_encoding=UTF8&pd_rd_i=B01D1D0E7Q&pd_rd_r=88cb79d4-ad68-451a-862b-cbf0e33fe19d&pd_rd_w=JcJfr&pd_rd_wg=hOV5n&pf_rd_p=bff3a3a6-0f6e-4187-bd60-25e75d4c1c8f&pf_rd_r=9C7Z29B0750BN4E4QP3Q&psc=1&refRID=9C7Z29B0750BN4E4QP3Q

    • さいころ 様

      DFPlayerについては、一応下記ショップで取り扱いがあります。ただ、ちょっとお高いです。
      スイッチサイエンス:https://www.switch-science.com/catalog/4291/
      秋月電子通商:http://akizukidenshi.com/catalog/g/gM-12544/

      HiLetgo製のものは私は使ったことがないので、個体差とかはよくわからないですね。。。
      ちなみ私は最近は大容量のMP3ファイルを扱う案件でもない限りは、以下のMP3ボイスモジュールを使用しています。
      秋月電子通商:http://akizukidenshi.com/catalog/g/gM-13708/

      これ以上はコメントだと多分解決しにくいと思いますので、後ほど別途メールでご連絡させていただきます。

コメントする

目次