ハザードトリガーをカッコ良く発光させる

久々の一品モノの電子工作です。今回のお題(?)は『仮面ライダービルド DXハザードトリガー』です。

ただいま(2018/3/2現在)劇中で絶賛活躍中のハザードトリガー。玩具としてのギミックは、音声が鳴る他、ビルドドライバーのハンドル操作と連動してメーターがピョコピョコ動きます。

個人的にはこういうメカニカルなギミックを搭載したおもちゃは好きなのですが、Amazonのレビューとか見ていると評価はイマイチな様子。どうも価格の割には音声が少ない、発光がない、成型色、などがその理由のようです。うーん、みんなおもちゃに求めるハードルが高いなあ。。。私も本業は(別業界ですが)モノづくりなので、こういうレビューはなかなか見ていて胃が痛くなります。

私は工作が趣味の人間なので、こういう「隙がある」玩具を見ると、「ここをこうしたらもっとカッコ良くなる!」と思って喜んでしまいます。そして、ビルドの玩具は全体的に「隙がある」「惜しい」ものが多くて、色々弄りがいがあって楽しいです。。。世間一般的にはこれを「残念」というのかもしれませんが。

まあとにもかくにもこのハザードトリガー、素材としては大変魅力的なので、せっかくなので改造してもっとカッコ良くしてみたいと思います。「さぁ、実験を始めようか。」です。

 

改造ポイントは、やっぱりメーター部分の発光が一番みんなが期待していて、かつ実際カッコ良くなるところだと思います。トライされている方々はやはり既にいらっしゃって、色々探してみた中では、フルボトルの発光改造やフルボトルのマスタープレート作成のときにも参照させて頂いた、特撮玩具好きの部屋のクレーンの丈様の改造が一番完成度が高いと思います。

私もハザードトリガーを入手した直後はこのレベルのものを作ろうとしたのですが、先にやられてしまいましたので、これ以上のものを作らざるを得なくなってしまいました。記事として公開する以上は、何かしら目新しい内容を入れないと、ただの二番煎じになってしまいますので。

 

ということで、自分なりにもう一捻りして完成させたハザートトリガーがこちらです。

th_hazard_trigger_28

パッと見はただのハザードトリガーです。

th_hazard_trigger_25

背面も特に変わりはありません。 ちなみに、一応メタリックレッドのスプレーはふっているのですが、見た目あんまり変わっていません。

th_hazard_trigger_26

底面も、ビルドドライバーとの接続部分に小さい穴が2つほど開いているぐらいで、他に変わりはありません。

th_hazard_trigger_27

正面から見ると、メーター部分が白っぽい色で常に表示された状態になっています。元々のハザードトリガーはこの部分が完全に透明で、ギア連動でピョコピョコとメーターが顔を出すようになっています。一方、この改修版ハザートドリガーでは、ギア連動で動くメーターは取り除いてしまっていて、代わりにフルカラーLEDリングの光の動きでメーターの動きを表現しています。

実際に組み込んだものとはプログラムの内容が異なりますが、動作イメージとしてはこんな感じです。

 

これを実現するために、メーター部分には、内側から透明ラベルに印刷したメーターのデザイン(?)を貼り付けています。

th_hazard_trigger_19

これに対して、裏からLEDリングをあてがうのですが、微妙にサイズが大きくて中に収まってくれません。

th_hazard_trigger_10

そのため、パーツの下部分を大きくカットしてしまって、

th_hazard_trigger_29

こんな感じにすると、上手くはまってくれました。

th_hazard_trigger_11

ちなみに、透明ラベルだけでメーター部分を表現してしまうと、中のLEDが一個一個独立して光っているように見えてあまり綺麗ではないので、

th_hazard_trigger_20

実際にはメーターとLEDリングの間に上のように白い紙を一枚挟んで、光が綺麗に混ざるようにしています。

 

th_hazard_trigger_21

続いて、電子回路部分です。こっちから見ると、元々の状態からあまり変わっていないように見えるかもしれませんが、色々線が増えています。

基板部分を外して裏返してみると、このとおり。

th_hazard_trigger_16

元々ギア連動のメーターパーツが入っていた部分に、小型のマイコン基板(Trinket 5V)を仕込んでいます。

半田付けが空中配線でメチャメチャなのはご了承ください。ここで、LEDリングの発光パターンをプログラミングしています。ソースコードは最後にご紹介します。

今回は、自分としては珍しく、元々玩具に組み込まれている基板やスイッチを極力そのまま活かせるような形で改造しています(←ガシャットのときは中身を総入れ替えしていました)。そのため、事前にこんな感じでテスターでスイッチ部分の電位差を調べました。

th_hazard_trigger_2

th_hazard_trigger_3

テスター(マルチメータ)はある程度工作に慣れてくるとあんまり使わなくなるのですが、配線は合っているはずなのにどうしても動かないとか、今回のように既存製品の挙動を調べる際には役に立ちます。秋月RSコンポーネンツで色々取り扱いがあるので、手頃なものを一つ持っておくと良いと思います。

さて、上記の写真で黒いテスター棒が当たっている方が、通常はGND(0V)に落ちていますが、それぞれスイッチが押されているときだけ、電位が4.3Vぐらいまで上がります。これを先ほど追加したマイコン基板の入力ピンに繋いでやって電圧の変化(HIGH-LOW)を監視してやれば、元々ハザードトリガーに仕込まれているスイッチに連動して、自分が追加したマイコン基板の動作を制御できるようになります。あ、それから元々の音声基板と追加したマイコン基板のGNDを繋いでおくのと、入力ピンにプルダウン抵抗を挟んでおくのを忘れずに。

 

さて、次が個人的には一番気に入っている改造ポイントなのですが、

th_hazard_trigger_30

マイコン(と、マイコンに接続されているLEDリング)の電源ラインはこれですが、 これがどこに伸びているかというと、実はハザードトリガーに元々搭載されているボタン電池x3には繋がっていません。

それじゃあどこに繋がっているのかと言いますと、

th_hazard_trigger_13

ハザードトリガーのビルドドライバーとの接続部の中を通って、ここに繋がっています。これが実は、最初に外観をお見せしたときに開いていた、小さな二つの穴の正体です。

th_hazard_trigger_31

つまり、今回追加したマイコンとLEDリングの電源は、ハザードトリガーのボタン電池x3ではなく、ビルドドライバーの単四電池x3からとっているのです。ハザードトリガーをビルドドライバーに挿すと、自動的にハザードトリガー内の追加マイコンに電力が供給されて、発光ギミックが有効になるという仕組みです。この形にすることで、

  • マイコンとLEDを安定駆動させるための電源をハザードトリガーのどこに仕込むか(※元々あるボタン電池では心許無いため)
  • マイコンON/OFF切り替えのための追加スイッチをどこに仕込むか

という二つの問題を解決することができました。また、「ビルドドライバーに挿すことによって真価を発揮するアイテム」という特徴をより強く出すことができるようになったと思います。

th_hazard_trigger_6

ビルドドライバー側は、こんな感じで穴を開けて、

th_hazard_trigger_32

こんな感じで電源を引っ張ってきています。この位置なら、ややこしそうなギヤ部分には手を触れずに改造可能です。追加したピンの部分は、ハザードトリガーを挿したときに下に押し込まれないように、ホットボンドでガチガチに固めています。

th_hazard_trigger_5

ちなみに、ビルドドライバーを開けるときに、どうしてもこの位置のネジだけ外れてくれなくて、泣く泣くこの部分だけニッパーで切り離しました。。。

 

物理的な改造ポイントは以上です。最後に、どういう発光プログラムになっているかのご紹介です。ソースコードはあんまり綺麗じゃないですが、こんな感じです。

#include <string.h>
#include <Adafruit_NeoPixel.h>

#define PIN_MODE  0  
#define PIN_COUNT 2
#define PIN_LED   3
#define NUM_OF_LED 12
#define NUM_OF_USE_LED 6
#define INTERVAL 30
#define SHORT_INTERVAL 15

#define COUNT_MAX 5
#define RESET_MAX 1000

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUM_OF_LED, PIN_LED, NEO_GRB);

uint8_t last_mode_state = LOW;
uint8_t mode_state      = LOW;
uint8_t last_count_state = LOW;
uint8_t count_state      = LOW;
boolean isMaxHazard = false;
boolean isAttack    = false;
uint8_t count = 0;
int reset_count = 0;

void setup () {
  pinMode(PIN_MODE, INPUT);
  pinMode(PIN_COUNT, INPUT);
  pixels.begin();
  for(int8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
  pixels.show();
}

void loop(){
  mode_state = digitalRead(PIN_MODE);
  if(last_mode_state == LOW && mode_state == HIGH){
    isMaxHazard = !isMaxHazard;
    count = 0;
    reset_count = 0;
    isAttack = false;
    if(isMaxHazard){
      for(int8_t i=0;i<=10;i++){
        led_pattern_max_hazard(6,i); delay(INTERVAL);
      }
      for(int8_t i=10;i>=0;i--){
        led_pattern_max_hazard(6,i); delay(INTERVAL);
      }
    }else{
      for(int8_t i=10;i>=0;i--){
        led_pattern_max_hazard(6,i); delay(INTERVAL);
      }
    }
  }
  last_mode_state = mode_state;

  count_state = digitalRead(PIN_COUNT);
  if(last_count_state == LOW && count_state == HIGH){
    uint8_t lv = isAttack ? 10:5;
    switch(count){
    case 0:    
      if(isMaxHazard){      
        for(uint8_t i=1;i<7;i++){
          led_pattern_max_hazard(i,lv); delay(INTERVAL);
        }
        for(uint8_t i=5;i>0;i--){
          led_pattern_max_hazard(i,lv); delay(INTERVAL);
        }
      }else{
        led_pattern_normal_lv1(lv); delay(INTERVAL);
        led_pattern_normal_lv2(lv); delay(INTERVAL);
        led_pattern_normal_lv3(lv); delay(INTERVAL);
        led_pattern_normal_lv4(lv); delay(INTERVAL);
        led_pattern_normal_lv5(lv); delay(INTERVAL);
        led_pattern_normal_lv6(lv); delay(INTERVAL);
        led_pattern_normal_lv5(lv); delay(INTERVAL);
        led_pattern_normal_lv4(lv); delay(INTERVAL);
        led_pattern_normal_lv3(lv); delay(INTERVAL);
        led_pattern_normal_lv2(lv); delay(INTERVAL);
        led_pattern_normal_lv1(lv); delay(INTERVAL);
      }
      break;
    case 1: 
      if(isMaxHazard){
        for(uint8_t i=2;i<7;i++){
          led_pattern_max_hazard(i,lv); delay(INTERVAL);
        }
        for(uint8_t i=5;i>1;i--){
          led_pattern_max_hazard(i,lv); delay(INTERVAL);
        }
      }else{
        led_pattern_normal_lv2(lv); delay(INTERVAL);
        led_pattern_normal_lv3(lv); delay(INTERVAL);
        led_pattern_normal_lv4(lv); delay(INTERVAL);
        led_pattern_normal_lv5(lv); delay(INTERVAL);
        led_pattern_normal_lv6(lv); delay(INTERVAL);
        led_pattern_normal_lv5(lv); delay(INTERVAL);
        led_pattern_normal_lv4(lv); delay(INTERVAL);
        led_pattern_normal_lv3(lv); delay(INTERVAL);
        led_pattern_normal_lv2(lv); delay(INTERVAL);
      }
      break;
    case 2:
      if(isMaxHazard){
        for(uint8_t i=3;i<7;i++){
          led_pattern_max_hazard(i,lv); delay(INTERVAL);
        }
        for(uint8_t i=5;i>2;i--){
          led_pattern_max_hazard(i,lv); delay(INTERVAL);
        }
      }else{
        led_pattern_normal_lv3(lv); delay(INTERVAL);
        led_pattern_normal_lv4(lv); delay(INTERVAL);
        led_pattern_normal_lv5(lv); delay(INTERVAL);
        led_pattern_normal_lv6(lv); delay(INTERVAL);
        led_pattern_normal_lv5(lv); delay(INTERVAL);
        led_pattern_normal_lv4(lv); delay(INTERVAL);
        led_pattern_normal_lv3(lv); delay(INTERVAL);
      }
      break;
    case 3: 
      if(isMaxHazard){
        for(uint8_t i=4;i<7;i++){
          led_pattern_max_hazard(i,lv); delay(INTERVAL);
        }
        for(uint8_t i=5;i>3;i--){
          led_pattern_max_hazard(i,lv); delay(INTERVAL);
        }
      }else{
        led_pattern_normal_lv4(lv); delay(INTERVAL);
        led_pattern_normal_lv5(lv); delay(INTERVAL);
        led_pattern_normal_lv6(lv); delay(INTERVAL);
        led_pattern_normal_lv5(lv); delay(INTERVAL);
        led_pattern_normal_lv4(lv); delay(INTERVAL);
      }
      break;
    case 4: 
      if(isMaxHazard){
        for(uint8_t i=5;i<7;i++){
          led_pattern_max_hazard(i,lv); delay(INTERVAL);
        }
        for(uint8_t i=5;i>4;i--){
          led_pattern_max_hazard(i,lv); delay(INTERVAL);
        }
      }else{
        led_pattern_normal_lv5(lv); delay(INTERVAL);
        led_pattern_normal_lv6(lv); delay(INTERVAL);
        led_pattern_normal_lv5(lv); delay(INTERVAL);
      }
      break;
    case 5: 
      if(isMaxHazard){
        for(int8_t i=lv;i>=0;i--){
          led_pattern_max_hazard(6,i); delay(SHORT_INTERVAL);
        }
        for(int8_t i=1;i<=lv;i++){
          led_pattern_max_hazard(6,i); delay(SHORT_INTERVAL);
        }      
      }else{
        for(int8_t i=lv;i>=0;i--){
          led_pattern_normal_lv6(i); delay(SHORT_INTERVAL);
        }
        for(int8_t i=1;i<=lv;i++){
          led_pattern_normal_lv6(i); delay(SHORT_INTERVAL);
        }
      }
      break; 
    default: ;
    }

    if(count < COUNT_MAX){
      count++;  
    }
  }
  last_count_state = count_state;

  if(count == COUNT_MAX){
    reset_count++;
    if(reset_count > RESET_MAX){
      count = 0;
      reset_count = 0;

      if(!isAttack){
        isAttack = true;
      }
    }
  }

  delay(10);
}

void led_pattern_normal_lv1(uint8_t lv){
  uint8_t value = 24*lv;
  pixels.setPixelColor(0, pixels.Color(value,0,0));
  for(uint8_t i=1;i<NUM_OF_USE_LED;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
  pixels.show();
}

void led_pattern_normal_lv2(uint8_t lv){
  uint8_t value = 24*lv;
  pixels.setPixelColor(0, pixels.Color(value,0,0));
  pixels.setPixelColor(1, pixels.Color(value,value,0));
  for(uint8_t i=2;i<NUM_OF_USE_LED;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
  pixels.show();
}

void led_pattern_normal_lv3(uint8_t lv){
  uint8_t value = 24*lv;
  pixels.setPixelColor(0, pixels.Color(value,0,0));
  pixels.setPixelColor(1, pixels.Color(value,value,0));
  pixels.setPixelColor(2, pixels.Color(0,value,0));
  for(uint8_t i=3;i<NUM_OF_USE_LED;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
  pixels.show();
}

void led_pattern_normal_lv4(uint8_t lv){
  uint8_t value = 24*lv;
  pixels.setPixelColor(0, pixels.Color(value,0,0));
  pixels.setPixelColor(1, pixels.Color(value,value,0));
  pixels.setPixelColor(2, pixels.Color(0,value,0));
  pixels.setPixelColor(3, pixels.Color(0,value,value));
  for(uint8_t i=4;i<NUM_OF_USE_LED;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
  pixels.show();
}

void led_pattern_normal_lv5(uint8_t lv){
  uint8_t value = 24*lv;
  pixels.setPixelColor(0, pixels.Color(value,0,0));
  pixels.setPixelColor(1, pixels.Color(value,value,0));
  pixels.setPixelColor(2, pixels.Color(0,value,0));
  pixels.setPixelColor(3, pixels.Color(0,value,value));
  pixels.setPixelColor(4, pixels.Color(0,0,value));
  pixels.setPixelColor(5, pixels.Color(0,0,0));
  pixels.show();
}

void led_pattern_normal_lv6(uint8_t lv){
  uint8_t value = 24*lv;
  pixels.setPixelColor(0, pixels.Color(value,0,0));
  pixels.setPixelColor(1, pixels.Color(value,value,0));
  pixels.setPixelColor(2, pixels.Color(0,value,0));
  pixels.setPixelColor(3, pixels.Color(0,value,value));
  pixels.setPixelColor(4, pixels.Color(0,0,value));
  pixels.setPixelColor(5, pixels.Color(0,0,value));
  pixels.show();
}

void led_pattern_max_hazard(uint8_t pattern, uint8_t lv){
  uint8_t value = 24*lv;
  for(uint8_t i=0;i<pattern;i++){
    pixels.setPixelColor(i, pixels.Color(value,0,0));
  }
  for(uint8_t i=pattern;i<NUM_OF_USE_LED;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
  pixels.show();
}

実際の動きとしては、ビルドドライバーのハンドル回転によってボタンが押されるたびに、ちょっとずつゲージが伸びていくような感じにしてみました。

th_hazard_trigger_43

毎度一瞬だけゲージがMaxまで伸びるようになっているのは、元々の玩具のギミックを意識してそうしています。

th_hazard_trigger_44

あとはアレンジとして、「マックスハザードオン!」の状態になったときには、より「ヤベーイ!」感を強くするために、ゲージの色を赤一色に変更してみました。

あと地味に、「ハザードアタック!」と「ハザードフィニッシュ!」のときは、発光の強さを2倍にしています。ちょっとわかりにくいですが。

 

以上が今回の改造の全てになります。結果は記事冒頭の動画をご確認くださいませ。

 

というわけで、ハザードトリガーの発光改造でした。今回はガシャットのときのようなオリジナリティはあまりないですが、発光はかなり綺麗に出すことができたと思いますし、また電力をビルドドライバーから供給することで全体的におもちゃとして上手くまとめられたと思いますので、個人的には結構お気に入りの作品になりました。