【超・超・超回転&発光】トリプルゼッツァー・カスタムをつくる

今回はDXトリプルゼッツァーをシンプルに派手にしてみました。

目次

開発経緯

DXトリプルゼッツァー。カプセムが3つ回って、銃口も回る。見た目に楽しそうな玩具でしたので、珍しく中間武器玩具を買いました。GX-5 (ケルベロス)、CSGで出ないかなあ。

で、実際に遊んでみた感想として、色々惜しい気がしました。まず、光らない。これはまあ、コストを考えるとやむなしでしょう。続いて、ウリの回転ギミック。カプセム3つが一斉に回るのは、楽しいと言えば楽しい。ただ、そんなに長く回り続けるものでもない。で、一番惜しかったのが、銃口の回転ギミック。これが、ギアの噛み合わせの関係で、うまく回らないことがしばしば。

モーターをを使わずにギアだけでこのギミックを実現しているのは素晴らしいけれど、もっとカッコよくできるポイントがいっぱいあるなあというのが正直な感想です。カプセムをセットしたら光るとか、カプセムが順に回転するとか、銃口が光るとか。

思ったからにはやるか、ということで、やってみました。昨年で言うところのベイクマグナムポジションの改造ですね。ただ、思ったより大変でした。

特徴

見た目の特徴として、レバーがグリップに置き換わって、ポンプアクションっぽくなっています。これは改造上の都合で、ここに電源として単四電池3本を入れています。元々のボタン電池×3個では電力が全然足りないため。

電源スイッチもこちらに付いています。

あと、銃口とトリガーはゴールドで塗装しています。元の整形色はややチープな感じがしたので。

続いてギミックです。まず、カプセム非セット状態。トリガーを引くと、銃口が回転&発光します。

トリガーを引き続けると、銃口が回り続けて連射発光。

カプセムをセットすると、セットしたカプセムが一瞬、発光します。

そしてグリップを引くと、必殺待機状態になり、カプセムがゆっくりと発光。

最後にトリガーを引くと、カプセムが激しく点滅し、「ゼゼゼッツ!」に合わせてカプセムが順に回転!

そして最後に銃口も回転!

ちなみにカプセムが1個、2個、3個のどの状態でも適切に発光・回転します。

最後に、デュアルメアカプセムをセット。このときは、両サイドがゆっくりと、七色に発光します。まあ、悪夢っぽく。

そしてグリップを引くと、必殺待機で発光がやや加速します。

最後にトリガーを引いて、さらに発光が加速した後、銃口が回転!

以上。とにかく派手にしてみました。

ハードウェア解説

まずは使用した具材から。

当然ながら、DXトリプルゼッツァーです。

マイコンは今回、Seeeduino XIAOを使用しました。トリプルゼッツァーはスイッチが多いのでピンの数が足りるか心配でしたが、ギリギリ足りました。

ミニモーター。今回は全ての回転を独立制御したかったので、4つ使っています。脳筋的解決手法です。

モータードライバー。これは一つで2個のモーターを制御できるので、2個使用しています。スペック的に、ミニモーターなら余裕で2個動かせます。

フルカラーLED。定番のNeiPixel系。5mmの背の低めのものが良い感じでした。カプセム発光用に3つ使用。

銃口用LED。こちらは単色で、5mmの砲弾型を4つ使用。

電池ボックス。モーターを4つも使うので、単四電池3本のものを使用しています。

銅テープ。別にこれじゃなくても良いかもしれませんが、銃本体から銃口側に電気を通すのに使用しました。

あとは電源スイッチ用のスライドスイッチとかコード類とか。それから、今回は部品を色々作らないといけなかったので、3Dプリンタをめっちゃ使いました。

続いて、具材の配線を示す前に、まずトリプルゼッツァーの中身を見てみます。

こんな感じで、レバーを引くことで、カプセム用の3つのギアが全て同時に噛み合って回転するようになってます。個人的には、カプセムが順に回り出す方が見た目に面白いかなと思いましたので、ここはリッチに、一個一個ミニモーターを独立して設置することにしました。こんなやつ。

丁度良い具合の固定具や回転パーツを3Dプリンタで作るのがなかなかめんどかったです。


続いて銃口側。こっちはレバーを戻したときにギアが噛み合うようになっているのですが、これがなかなか安定しない。

なので、ここもミニモーターで独立制御することにしました。

銃口パーツは少し工夫していて、どうせなら発光させたかったので、LEDを仕込んだ上で、接点を外側に出しています。

内側が4つのLED共通のマイナス側で、4つ出てきている線が各LEDのプラス側です。銃口を回転させるときに、マイナス側は常に接触しながら回転して、プラス側の電導パーツが接触したタイミングで各銃口のLEDが発光する、という仕組みです。

こっちが本体側、対向の電動パーツになります。銅テープで接点を作っています。実は内側にバネを仕込んでいて、なるべくモーターの回転の妨げにならないようにしつつ、ちゃんと接触するように調整しています。

この接点は、銃口用モーターの制御信号線に繋がっています。つまり、モーターが回転しているときだけ通電するようになっていて、これにより、銃口が回転していない状態で接点が接触していても、LEDが勝手に発光しないようにしています。

ちなみに、似たような改造は過去にギアトリンガーでやりましたが、あのときは砲身にわざわざボタン電池を仕込むやり方にしていて、ちょっと遊び辛かったので、今回は専用の電源は不要で実現できるようにしてみました。

それから、カプセムのセット部。ここは本来LEDがないところですので、

穴を開けて、こんな感じでフルカラーLEDをセットします。

電源については、特徴の方でも述べましたが、元々の玩具のボタン電池ではモーター4つどころかマイコンを駆動させるのも難しいので、別途、単四電池×3本を使用します。中に仕込むのは難しいので、見た目の違和感が小さくなるように、レバーの代わりにグリップを用意し、そこに電源スイッチと一緒に電池ケースを仕込むことにしました。

スイッチ類は元々のトリプルゼッツァーのものを使いますので、元々の基板に影響を与えないようにしながら、配線を引っ張れるようにします。

スイッチを押したときの電圧変化を確認した結果、「+」になっている部分がOFF時は4.5V、ON時は0Vになるようでしたので、ここからマイコン向けに配線を伸ばします。

びろーんと。

配線の全体をまとめると、こんな感じになります。

実際に配線&組み込んだ状態がこちら。なかなかにカオスです。

ソフトウェア解説

状態遷移について。トリプルゼッツァーは大きくは

  • カプセムを未セット
  • 通常カプセムをセット(1〜3個)
  • デュアルメアカプセムをセット

の3モードに分かれていて、かつ、各モードで似たような状態遷移になるので、状態遷移の基本のプログラムは一つとして、モードごとに微妙に動作や遷移を変更させる、という形にしました。全部バラバラに状態を定義する形にすると、カプセム未セット・通常カプセムセット・デュアルメアカプセムセットのそれぞれでSHOTとかRAPID_FIREとかを定義しないといけなくなって面倒&ややこしいので。

ということで、各モードでの状態遷移図は以下のようになります。

設計上の工夫はそれぐらいで、他にプログラムの方で工夫した点はほぼありません。カプセムを1個、あるいは2個セットしたときに、どの位置にセットしても発光やモーターの回転が変にならないように気をつけたぐらいです。それでも760行ぐらいにはなってしまったので、全文は最後に掲載します。

まとめ

以上、トリプルゼッツァー・カスタムのご紹介でした。やりたいことは非常にシンプルながら、中身は3Dプリンタで新規に作らないといけない&調整がシビアなパーツが多くて、けっこう時間がかかってしまいました。けどまあ、遊んでいて楽しい玩具には仕上がったかな、と思います。

ソースコード

ソースコードの全文は以下になります。

////////// 基本定義 ////////////////////////////////////////////////////////////

#define LOOP_DELAY_MS 20

#define PIN_SW_TRIGGER      0
#define PIN_SW_CAPSEM_1     1
#define PIN_SW_CAPSEM_2     2
#define PIN_SW_CAPSEM_3     3
#define PIN_SW_DUALMARE     4
#define PIN_SW_LEVER        5
#define PIN_LED             6
#define PIN_MOTOR_MUZZLE    7
#define PIN_MOTOR_CAPSEM_1  8
#define PIN_MOTOR_CAPSEM_2  9
#define PIN_MOTOR_CAPSEM_3 10

#define ON  LOW
#define OFF HIGH

uint8_t sw_trigger  = OFF;
uint8_t sw_capsem_1 = OFF;
uint8_t sw_capsem_2 = OFF;
uint8_t sw_capsem_3 = OFF;
uint8_t sw_dualmare = OFF;
uint8_t sw_lever    = OFF;

uint8_t prev_sw_trigger  = OFF;
uint8_t prev_sw_capsem_1 = OFF;
uint8_t prev_sw_capsem_2 = OFF;
uint8_t prev_sw_capsem_3 = OFF;
uint8_t prev_sw_dualmare = OFF;
uint8_t prev_sw_lever    = OFF;

#define STATE_INIT         0
#define STATE_SHOT         1
#define STATE_RAPID_FIRE   2
#define STATE_RELOAD       3
#define STATE_FINISH_READY 4
#define STATE_FINISH       5
uint8_t state      = STATE_INIT;
uint8_t prev_state = STATE_INIT;

#define TIME_SHOT_TO_RAPID_FIRE_MS   500
#define TIME_RELOAD_TO_INIT_MS       100
#define TIME_FINISH_CAPSEM_1_MS    10000
#define TIME_FINISH_CAPSEM_2_MS    11000
#define TIME_FINISH_CAPSEM_3_MS    12000
#define TIME_FINISH_DUALMARE_MS    11500
#define DELAY_CAPSEM_MS 1000
unsigned long time_finish_ms = 0;


#define TIME_FINISH_NAME_MS    3600
#define TIME_FINISH_ZZZEZTZ_MS 1100
unsigned long state_start_point_ms = 0;

#define MODE_INIT     0
#define MODE_NORMAL   1
#define MODE_DUALMARE 2
uint8_t mode      = MODE_INIT;
uint8_t prev_mode = MODE_INIT;

bool set_capsems[3] = {false, false, false};
bool prev_set_capsems[3] = {false, false, false};
uint8_t count_capsem = 0;

void change_state(uint8_t new_state){
  state = new_state;
  Serial.print(F("STATE: "));
  switch(state){
  case STATE_INIT:         Serial.println(F("INIT"));         break;
  case STATE_SHOT:         Serial.println(F("SHOT"));         break;
  case STATE_RAPID_FIRE:   Serial.println(F("RAPID_FIRE"));   break;
  case STATE_RELOAD:       Serial.println(F("RELOAD"));       break;
  case STATE_FINISH_READY: Serial.println(F("FINISH_READY")); break;
  case STATE_FINISH:       Serial.println(F("FINISH"));       break;
  default: ;
  }
}

void change_mode(uint8_t new_mode){
  mode = new_mode;
  Serial.print(F("MODE: "));
  switch(mode){
  case MODE_INIT:     Serial.println(F("INIT"));     break;
  case MODE_NORMAL:   Serial.println(F("NORMAL"));   break;
  case MODE_DUALMARE: Serial.println(F("DUALMARE")); break;
  default: ;
  }
}

void set_capsem(uint8_t capsem_num){
  Serial.print(F("SET CAPSEM: "));
  switch(capsem_num){
  case 0: Serial.println(1); break;
  case 1: Serial.println(2); break;
  case 2: Serial.println(3); break;
  default: ;
  }
  set_capsems[capsem_num] = true;
  count_capsem++;
}

void unset_capsem(uint8_t capsem_num){
  Serial.print(F("UNSET CAPSEM: "));
  switch(capsem_num){
  case 0: Serial.println(1); break;
  case 1: Serial.println(2); break;
  case 2: Serial.println(3); break;
  default: ;
  }
  set_capsems[capsem_num] = false;
  count_capsem--;
}

 ////////////////////////////////////////////////////////////
#include <Adafruit_NeoPixel.h>
#define N_LED 3

struct color_rgb {
  uint8_t r;
  uint8_t g;
  uint8_t b;  
};

struct color_rgb COLOR_RED   = {255,   0,   0};
struct color_rgb COLOR_BLUE  = {  0,   0, 255};
struct color_rgb COLOR_WHITE = {255, 255, 255};

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(N_LED, PIN_LED, NEO_RGB);


unsigned long led_pattern_start_point_ms = 0;
int prev_interval_ms = 0;
uint8_t prev_steps = 0; 
unsigned long prev_action_point_ms = 0;
unsigned long inc_dim_start_point_ms = 0;
bool is_blink_on = false;
bool is_inc = true;

#define RAINBOW_SPEED_LOW     512
#define RAINBOW_SPEED_MIDDLE 1024
#define RAINBOW_SPEED_HIGH   2048
int rainbow_hue_1 =     0;
int rainbow_hue_3 = 65535;


void led_base_pattern_off(bool capsem_1, bool capsem_2, bool capsem_3){
  if(capsem_1){pixels.setPixelColor(0, pixels.Color(0,0,0));}
  if(capsem_2){pixels.setPixelColor(1, pixels.Color(0,0,0));}
  if(capsem_3){pixels.setPixelColor(2, pixels.Color(0,0,0));}
}

void led_base_pattern_off_single(uint8_t index){
  pixels.setPixelColor(index, pixels.Color(0,0,0));
}

void led_base_pattern_on(struct color_rgb *color, bool capsem_1, bool capsem_2, bool capsem_3){
  if(capsem_1){pixels.setPixelColor(0, pixels.Color(color->r,color->g,color->b));}
  if(capsem_2){pixels.setPixelColor(1, pixels.Color(color->r,color->g,color->b));}
  if(capsem_3){pixels.setPixelColor(2, pixels.Color(color->r,color->g,color->b));}
}

void led_base_pattern_on_single(struct color_rgb *color, uint8_t index){
  pixels.setPixelColor(index, pixels.Color(color->r,color->g,color->b));
}

void led_base_battern_rainbow(int rainbow_speed){
  // いずれもオーバーフローを許可する
  rainbow_hue_1 += rainbow_speed; // 256の倍数で設定
  rainbow_hue_3 -= rainbow_speed; // 256の倍数で設定
  pixels.setPixelColor(0, pixels.ColorHSV(rainbow_hue_1));
  pixels.setPixelColor(2, pixels.ColorHSV(rainbow_hue_3));
}

void led_base_pattern_inc(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps, bool capsem_1, bool capsem_2, bool capsem_3){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }
  
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;

  if(capsem_1){pixels.setPixelColor(0, pixels.Color(r_step*current_step, g_step*current_step, b_step*current_step));}
  if(capsem_2){pixels.setPixelColor(1, pixels.Color(r_step*current_step, g_step*current_step, b_step*current_step));}
  if(capsem_3){pixels.setPixelColor(2, pixels.Color(r_step*current_step, g_step*current_step, b_step*current_step));}
}

void led_base_pattern_dim(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps, bool capsem_1, bool capsem_2, bool capsem_3){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }
  
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;
  if(capsem_1){pixels.setPixelColor(0, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));}
  if(capsem_2){pixels.setPixelColor(1, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));}
  if(capsem_3){pixels.setPixelColor(2, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));}

}

void led_base_pattern_dim_single(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps, uint8_t index){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }
  
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;
  pixels.setPixelColor(index, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));
}

void led_base_pattern_blink(struct color_rgb *color, unsigned long now_ms, int inverval_ms,  bool capsem_1, bool capsem_2, bool capsem_3){
  if(now_ms - prev_action_point_ms >= inverval_ms){
    if(is_blink_on){
      if(capsem_1){pixels.setPixelColor(0, pixels.Color(0,0,0));}
      if(capsem_2){pixels.setPixelColor(1, pixels.Color(0,0,0));}
      if(capsem_3){pixels.setPixelColor(2, pixels.Color(0,0,0));}
    }else{
      if(capsem_1){pixels.setPixelColor(0, pixels.Color(color->r,color->g,color->b));}
      if(capsem_2){pixels.setPixelColor(1, pixels.Color(color->r,color->g,color->b));}
      if(capsem_3){pixels.setPixelColor(2, pixels.Color(color->r,color->g,color->b));}
    }
    is_blink_on = !is_blink_on;
    prev_action_point_ms = now_ms;
  }
}

void led_base_pattern_blink_slowly(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps, bool capsem_1, bool capsem_2, bool capsem_3){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;

  if(is_inc){
    if(capsem_1){pixels.setPixelColor(0, pixels.Color(r_step*current_step, g_step*current_step, b_step*current_step));}
    if(capsem_2){pixels.setPixelColor(1, pixels.Color(r_step*current_step, g_step*current_step, b_step*current_step));}
    if(capsem_3){pixels.setPixelColor(2, pixels.Color(r_step*current_step, g_step*current_step, b_step*current_step));}
  }else{
    if(capsem_1){pixels.setPixelColor(0, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));}
    if(capsem_2){pixels.setPixelColor(1, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));}
    if(capsem_3){pixels.setPixelColor(2, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));}
  }
  
  if(now_ms - inc_dim_start_point_ms >= interval_ms){
    is_inc = !is_inc;
    inc_dim_start_point_ms = 0;
  }    
}

int8_t set_capsem_index = -1;

void control_led(unsigned long now_ms){

  if(memcmp(prev_set_capsems, set_capsems, sizeof(set_capsems)) != 0){
    // カプセムのセット状態に変化があったときのみ
    set_capsem_index = -1;
    for(uint8_t i=0;i<3;i++){
      set_capsem_index++;
      if(prev_set_capsems[i] == false && set_capsems[i] == true){
        led_pattern_start_point_ms = now_ms;
        inc_dim_start_point_ms= 0;
        break;
      }
    }
  }

  // 状態変化があったとき
  if(prev_state != state){
    led_pattern_start_point_ms = now_ms;
    inc_dim_start_point_ms = 0;
    set_capsem_index = -1; // 上書き
    led_base_pattern_off(true, true, true);
  }

  unsigned long passed_ms = now_ms - led_pattern_start_point_ms;

  if(mode == MODE_INIT){
    led_base_pattern_off(true, true, true);
  }else if(mode == MODE_NORMAL){
    if(state == STATE_INIT && set_capsem_index != -1){
      if(passed_ms < 600){
        led_base_pattern_on_single(&COLOR_WHITE, set_capsem_index);
      }else if(600 <= passed_ms && passed_ms < 1200){
        led_base_pattern_dim_single(&COLOR_WHITE, now_ms, 600, 10, set_capsem_index);
      }else if(1200 <= passed_ms){
        led_base_pattern_off_single(set_capsem_index);
      }
    }else if(state == STATE_FINISH_READY){
      if(passed_ms < 500){
        led_base_pattern_on(&COLOR_WHITE, set_capsems[0], set_capsems[1], set_capsems[2]);
      }else if(400 <= passed_ms && passed_ms < 1000){
        led_base_pattern_dim(&COLOR_WHITE, now_ms, 600, 10, set_capsems[0], set_capsems[1], set_capsems[2]);
      }else if(1000 <= passed_ms){
        led_base_pattern_blink_slowly(&COLOR_WHITE, now_ms, 300, 10, set_capsems[0], set_capsems[1], set_capsems[2]);
      }
    }else if(state == STATE_FINISH){
      if(passed_ms < 1000){
        led_base_pattern_inc(&COLOR_WHITE, now_ms, 1000, 20, set_capsems[0], set_capsems[1], set_capsems[2]);
      }else if(1000 <= passed_ms && passed_ms < time_finish_ms - 2000){
        led_base_pattern_blink(&COLOR_WHITE, now_ms, 60, set_capsems[0], set_capsems[1], set_capsems[2]);
      }else if(time_finish_ms - 2000 <= passed_ms && passed_ms < time_finish_ms){
        led_base_pattern_dim(&COLOR_WHITE, now_ms, 2000, 20, set_capsems[0], set_capsems[1], set_capsems[2]);
      }else if(time_finish_ms <= passed_ms){
        led_base_pattern_off(set_capsems[0], set_capsems[1], set_capsems[2]);
      }
    }
  }else if(mode == MODE_DUALMARE){
    if(state == STATE_INIT || state == STATE_SHOT || state == STATE_RAPID_FIRE){
      led_base_battern_rainbow(RAINBOW_SPEED_LOW);
    }else if(state == STATE_FINISH_READY){
      led_base_battern_rainbow(RAINBOW_SPEED_MIDDLE);
    }else if(state == STATE_FINISH){
      led_base_battern_rainbow(RAINBOW_SPEED_HIGH);
    }
  }

  pixels.show();
}

////////////////////////////////////////////////////////////

#define CAPSEM_SPEED  192
#define MUZZLE_SPEED  255

unsigned long motor_pattern_start_point_ms = 0;

bool is_capsem_1_active = false;
bool is_capsem_2_active = false;
bool is_capsem_3_active = false;
bool is_muzzle_active   = false;

void control_motor(unsigned long now_ms){

  // 状態変化があったとき
  if(prev_state != state){
    motor_pattern_start_point_ms = now_ms;
  }

  if(prev_state != state && state == STATE_INIT){
    analogWrite(PIN_MOTOR_MUZZLE,   0);
    analogWrite(PIN_MOTOR_CAPSEM_1, 0);
    analogWrite(PIN_MOTOR_CAPSEM_2, 0);
    analogWrite(PIN_MOTOR_CAPSEM_3, 0);
    is_capsem_1_active = false;
    is_capsem_2_active = false;
    is_capsem_3_active = false;
    is_muzzle_active   = false;
  }

  if(prev_state == STATE_INIT && state == STATE_SHOT){
    analogWrite(PIN_MOTOR_MUZZLE,  MUZZLE_SPEED);
  }

  unsigned long passed_ms = now_ms - motor_pattern_start_point_ms;

  if(mode == MODE_NORMAL){
    if(state == STATE_FINISH){
      switch(count_capsem){
      case 1:
        if(passed_ms < TIME_FINISH_NAME_MS){
          ;
        }else if(TIME_FINISH_NAME_MS <= passed_ms && passed_ms < TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS){
          if(!is_capsem_1_active){
            if(set_capsems[0] == true){
              analogWrite(PIN_MOTOR_CAPSEM_1, CAPSEM_SPEED);
            }else if(set_capsems[1] == true){
              analogWrite(PIN_MOTOR_CAPSEM_2, CAPSEM_SPEED);
            }else if(set_capsems[2] == true){
              analogWrite(PIN_MOTOR_CAPSEM_3, CAPSEM_SPEED);
            }
            is_capsem_1_active = true;
          }
        }else if(TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS <= passed_ms && passed_ms < time_finish_ms){
          if(!is_muzzle_active){
            analogWrite(PIN_MOTOR_MUZZLE,  MUZZLE_SPEED);
            is_muzzle_active = true;
          }
        }else if(time_finish_ms < passed_ms){
          ;
        }
        break;
      case 2:
        if(passed_ms < TIME_FINISH_NAME_MS){
          ;
        }else if(TIME_FINISH_NAME_MS <= passed_ms && passed_ms < TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS){
          if(!is_capsem_1_active){
            if(set_capsems[0] == true && set_capsems[1] == true && set_capsems[2] == false){
              analogWrite(PIN_MOTOR_CAPSEM_1, CAPSEM_SPEED); 
            }else if(set_capsems[0] == true && set_capsems[1] == false && set_capsems[2] == true){
              analogWrite(PIN_MOTOR_CAPSEM_1, CAPSEM_SPEED);
            }else if(set_capsems[0] == false && set_capsems[1] == true && set_capsems[2] == true){
              analogWrite(PIN_MOTOR_CAPSEM_2, CAPSEM_SPEED);
            }
            is_capsem_1_active = true;
          }
        }else if(TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS < passed_ms && passed_ms < TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS*2){
          if(!is_capsem_2_active){
            if(set_capsems[0] == true && set_capsems[1] == true && set_capsems[2] == false){
              analogWrite(PIN_MOTOR_CAPSEM_2, CAPSEM_SPEED); 
            }else if(set_capsems[0] == true && set_capsems[1] == false && set_capsems[2] == true){
              analogWrite(PIN_MOTOR_CAPSEM_3, CAPSEM_SPEED);
            }else if(set_capsems[0] == false && set_capsems[1] == true && set_capsems[2] == true){
              analogWrite(PIN_MOTOR_CAPSEM_3, CAPSEM_SPEED);
            }
            is_capsem_2_active = true;
          }
        }else if(TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS*2 <= passed_ms && passed_ms < time_finish_ms){
          if(!is_muzzle_active){
            analogWrite(PIN_MOTOR_MUZZLE,  MUZZLE_SPEED);
            is_muzzle_active = true;
          }
        }else if(time_finish_ms < passed_ms){
          ;
        }
        break;
      case 3:
        if(passed_ms < TIME_FINISH_NAME_MS){
          ;
        }else if(TIME_FINISH_NAME_MS <= passed_ms && passed_ms < TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS){
          if(!is_capsem_1_active){
            analogWrite(PIN_MOTOR_CAPSEM_1, CAPSEM_SPEED);
            is_capsem_1_active = true;
          }
        }else if(TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS < passed_ms && passed_ms < TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS*2){
          if(!is_capsem_2_active){
            analogWrite(PIN_MOTOR_CAPSEM_2, CAPSEM_SPEED);
            is_capsem_2_active = true;
          }
        }else if(TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS*2 < passed_ms && passed_ms < TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS*3){ 
          if(!is_capsem_3_active){
            analogWrite(PIN_MOTOR_CAPSEM_3, CAPSEM_SPEED);
            is_capsem_3_active = true;
          }
        }else if(TIME_FINISH_NAME_MS + TIME_FINISH_ZZZEZTZ_MS*3 <= passed_ms && passed_ms < time_finish_ms){
          if(!is_muzzle_active){
            analogWrite(PIN_MOTOR_MUZZLE,  MUZZLE_SPEED);
            is_muzzle_active = true;
          }
        }else if(time_finish_ms < passed_ms){
          ;
        }
        break;
      default:
        ;
      }
    }

    if(memcmp(prev_set_capsems, set_capsems, sizeof(set_capsems)) != 0){
      // カプセムのセット状態に変化があったときのみ
      if(prev_set_capsems[0] == true && set_capsems[0] == false){
        analogWrite(PIN_MOTOR_CAPSEM_1, 0);
        is_capsem_1_active = false;
      }
      if(prev_set_capsems[1] == true && set_capsems[1] == false){
        analogWrite(PIN_MOTOR_CAPSEM_2, 0);
        is_capsem_2_active = false;
      }
      if(prev_set_capsems[2] == true && set_capsems[2] == false){
        analogWrite(PIN_MOTOR_CAPSEM_3, 0);
        is_capsem_3_active = false;
      }
    }
  }else if(mode == MODE_DUALMARE){
    if(state == STATE_FINISH){
      if(passed_ms < 5000){
        ;
      }else if(5000 <= passed_ms && passed_ms < time_finish_ms){
        if(!is_muzzle_active){
          analogWrite(PIN_MOTOR_MUZZLE, MUZZLE_SPEED);
          is_muzzle_active = true;
        }
      }else if(time_finish_ms < passed_ms){
        ;
      }
    }
  }
}

////////// メイン処理 ////////////////////////////////////////////////////////////

void setup(){  
  Serial.begin(115200);
  pinMode(PIN_SW_TRIGGER,  INPUT_PULLUP);
  pinMode(PIN_SW_CAPSEM_1, INPUT_PULLUP);
  pinMode(PIN_SW_CAPSEM_2, INPUT_PULLUP);
  pinMode(PIN_SW_CAPSEM_3, INPUT_PULLUP);
  pinMode(PIN_SW_DUALMARE, INPUT_PULLUP);
  pinMode(PIN_SW_LEVER,    INPUT_PULLUP);
  pinMode(PIN_MOTOR_MUZZLE,   OUTPUT);
  pinMode(PIN_MOTOR_CAPSEM_1, OUTPUT);
  pinMode(PIN_MOTOR_CAPSEM_2, OUTPUT);
  pinMode(PIN_MOTOR_CAPSEM_3, OUTPUT);

  pixels.begin();
  pixels.clear();

  led_base_pattern_off(true, true, true);
  pixels.show();

  delay(500);

  change_state(STATE_INIT);
  if(digitalRead(PIN_SW_DUALMARE) == ON){
    change_mode(MODE_DUALMARE);
  }else if(digitalRead(PIN_SW_CAPSEM_1) == ON || digitalRead(PIN_SW_CAPSEM_2) == ON || digitalRead(PIN_SW_CAPSEM_2) == ON){
    change_mode(MODE_NORMAL);
    if(digitalRead(PIN_SW_CAPSEM_1) == ON){count_capsem++;}
    if(digitalRead(PIN_SW_CAPSEM_2) == ON){count_capsem++;}
    if(digitalRead(PIN_SW_CAPSEM_3) == ON){count_capsem++;}
  }else{
    change_mode(MODE_INIT);
  }  
}

void loop(){
  sw_trigger  = digitalRead(PIN_SW_TRIGGER);
  sw_capsem_1 = digitalRead(PIN_SW_CAPSEM_1);
  sw_capsem_2 = digitalRead(PIN_SW_CAPSEM_2);
  sw_capsem_3 = digitalRead(PIN_SW_CAPSEM_3);
  sw_dualmare = digitalRead(PIN_SW_DUALMARE);
  sw_lever    = digitalRead(PIN_SW_LEVER);

  unsigned long now_ms = millis();

  switch(state){
  case STATE_INIT:
    if(prev_sw_trigger == OFF && sw_trigger == ON){
      change_state(STATE_SHOT);
      state_start_point_ms = now_ms;
    }

    if(prev_sw_capsem_1 == OFF && sw_capsem_1 == ON){
      change_mode(MODE_NORMAL);
      set_capsem(0);
    }
    if(prev_sw_capsem_2 == OFF && sw_capsem_2 == ON){
      change_mode(MODE_NORMAL);
      set_capsem(1);
    }
    if(prev_sw_capsem_3 == OFF && sw_capsem_3 == ON){
      change_mode(MODE_NORMAL);
      set_capsem(2);
    }

    if(prev_sw_capsem_1 == ON && sw_capsem_1 == OFF){
      unset_capsem(0);
    }
    if(prev_sw_capsem_2 == ON && sw_capsem_2 == OFF){
      unset_capsem(1);
    }
    if(prev_sw_capsem_3 == ON && sw_capsem_3 == OFF){
      unset_capsem(2);
    }

    if(prev_sw_dualmare == OFF && sw_dualmare == ON){
      change_mode(MODE_DUALMARE);
    }
    if(prev_sw_dualmare == ON && sw_dualmare == OFF){
      if(mode == MODE_DUALMARE){
        change_mode(MODE_INIT);
      }
    }

    if(prev_sw_lever == OFF && sw_lever == ON){
      switch(mode){
      case MODE_INIT:
        change_state(STATE_RELOAD);
        state_start_point_ms = now_ms;
        break;
      case MODE_NORMAL:
      case MODE_DUALMARE:
        change_state(STATE_FINISH_READY);
        break;
      default:
        ;
      }
    }

    // すべてのカプセムが取り出されていれば、INITモードに遷移
    if(mode == MODE_NORMAL){
      if(set_capsems[0] == false && set_capsems[1] == false && set_capsems[2] == false){
        change_mode(MODE_INIT);
      }
    }
    break;
  case STATE_SHOT:
    if(prev_sw_trigger == ON && sw_trigger == OFF){
      change_state(STATE_INIT);
    }
    if(now_ms - state_start_point_ms >= TIME_SHOT_TO_RAPID_FIRE_MS){
      change_state(STATE_RAPID_FIRE);
    }
    break;
  case STATE_RAPID_FIRE:
    if(prev_sw_trigger == ON & sw_trigger == OFF){
      change_state(STATE_INIT);
    }
    break;
  case STATE_RELOAD:
    if(now_ms - state_start_point_ms >= TIME_RELOAD_TO_INIT_MS){
      change_state(STATE_INIT);
    }
    break;
  case STATE_FINISH_READY:
    if(prev_sw_trigger == OFF & sw_trigger == ON){
      change_state(STATE_FINISH);
      state_start_point_ms = now_ms;
      if(mode == MODE_NORMAL){
        switch(count_capsem){
        case 1: time_finish_ms = TIME_FINISH_CAPSEM_1_MS;
        case 2: time_finish_ms = TIME_FINISH_CAPSEM_2_MS;
        case 3: time_finish_ms = TIME_FINISH_CAPSEM_3_MS;
        default: ;
        }
      }else if(mode == MODE_DUALMARE){
        time_finish_ms = TIME_FINISH_DUALMARE_MS;
      }
    }

    switch(mode){
    case MODE_INIT:
      break;
    case MODE_NORMAL:
      if(prev_sw_capsem_1 == ON && sw_capsem_1 == OFF){
        change_state(STATE_INIT);
        unset_capsem(0);
      }
      if(prev_sw_capsem_2 == ON && sw_capsem_2 == OFF){
        change_state(STATE_INIT);
        unset_capsem(1);
      }
      if(prev_sw_capsem_3 == ON && sw_capsem_3 == OFF){
        change_state(STATE_INIT);
        unset_capsem(2);
      }
      break;
    case MODE_DUALMARE:
      if(prev_sw_dualmare == ON && sw_dualmare == OFF){
        change_state(STATE_INIT);
        change_mode(MODE_INIT);
      }
      break;
    default:
      ;
    }
    break;
  case STATE_FINISH:
    switch(mode){
    case MODE_INIT:
      break;
    case MODE_NORMAL:
      if(prev_sw_capsem_1 == ON && sw_capsem_1 == OFF){
        change_state(STATE_INIT);
        unset_capsem(0);
      }
      if(prev_sw_capsem_2 == ON && sw_capsem_2 == OFF){
        change_state(STATE_INIT);
        unset_capsem(1);
      }
      if(prev_sw_capsem_3 == ON && sw_capsem_3 == OFF){
        change_state(STATE_INIT);
        unset_capsem(2);
      }

      switch(count_capsem){
      case 1:
        if(now_ms - state_start_point_ms >= TIME_FINISH_CAPSEM_1_MS){
          change_state(STATE_INIT);
        }
        break;
      case 2:
        if(now_ms - state_start_point_ms >= TIME_FINISH_CAPSEM_2_MS){
          change_state(STATE_INIT);
        }
        break;
      case 3:
        if(now_ms - state_start_point_ms >= TIME_FINISH_CAPSEM_3_MS){
          change_state(STATE_INIT);
        }
        break;
      default:
        ;
      }
      break;
    case MODE_DUALMARE:
      if(prev_sw_dualmare == ON && sw_dualmare == OFF){
        change_state(STATE_INIT);
        change_mode(MODE_INIT);
      }

      if(now_ms - state_start_point_ms >= TIME_FINISH_DUALMARE_MS){
        change_state(STATE_INIT);
      }
      break;
    default:
      ;
    }

    break;
  default:
    ;
  }

  ///////////////////////////////////////////

  control_motor(now_ms);

  control_led(now_ms);

  ///////////////////////////////////////////

  prev_sw_trigger  = sw_trigger;
  prev_sw_capsem_1 = sw_capsem_1;
  prev_sw_capsem_2 = sw_capsem_2;
  prev_sw_capsem_3 = sw_capsem_3;
  prev_sw_dualmare = sw_dualmare;
  prev_sw_lever    = sw_lever;

  prev_state = state;
  prev_mode = mode;
  memcpy(prev_set_capsems, set_capsems, sizeof(set_capsems));

  delay(LOOP_DELAY_MS);
}

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

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

コメント

コメントする

目次