前回からの続きです。
前回は組み立ての作業性が劣悪、電池交換が面倒、断線しやすいなど諸々不具合あったので、
今回は3Dプリンタで筐体設計して実装することにしました。
#目次
1. はじめに
相変わらず鬼滅の刃はそこそこの人気を誇っており、子供からの支持も熱いです。
おもちゃを作ったら遊んでくれそうなので、バージョンアップをしてみることにしました。
今回のコンセプトは
・モードボタン切替で複数キャラクターの音声/発色
・宇髄天元モードを追加
・ベースはダイソー忍者刀(200円)を流用し、魔改造
・柄部分を3Dプリンタで作成する
・電池の充電ができるような構成
という感じで作ってみました。
2. 構成検討
まず全体構成です。
部品はアマゾンやらAliexpressやらダイソーやらで集めました。
ボディ部分に関しては、刀身、鍔は忍者刀を流用し、柄のみを今回は設計することにしました。
スピーカー、スライドSWも剥ぎ取って流用としました。
電池にはマイクロusbで充電可能な9V型電池を使用しました。
マイコンは今回もArduino pro miniの互換品を使用しました。
DFplayerはArduinoからのシリアル通信で制御します。
発色には3色LED×4個をArduinoからのPWM出力で制御することにしました。
CADはFusion360を使用しました。
3Dモデルの設計に加え、回路図の作成や電子部品の配置もできて便利でした。
3. 回路設計
Fritzingで作成した全体回路図です。
電源が9Vなので、レギュレータで5V降圧して使用する形としました。
Arduino内部のレギュレータで9Vは受け入れ可ですが、DFplayer用の5V電源をArduinoの5Vピンから取ると、
出力が不安定になるため、Arudino, Dfplayerの電源をそれぞれ電源からパラで取るようにしました。
LEDは表示上2つになってますが、実際は4つ実装してます。
ArduinoのPWMピンからパラ接続で4つつなげています。
電流制約上、そこまで強い発光はできませんが、目的に適するレベルでの発光はできているので良しとします。
ユニバーサル基板で実装、組み立てを行うので、FUSION360上のEagleで実装部品の回路図を作成しました。
電源、SWに関しては少し離れた場所での実装なのでこちらでは記載ありません。
JP1-5はArduinoのシリアル通信用のポートです。
音声変更によるバージョンアップなどを想定し、書き込みやデバッグ用に設置しました。
同じくEagleでの実装図です。
この図に従ってはんだ付けをしていきます。
作図したものをEagleからFusion360へ3Dプッシュした像です。
こちらを使用して配置検討をしていきます。
なお、前述のJP1-5ピンがオスで表示されてますが、実際はメスで作ってます。
¥
4. 柄の設計
柄はupperとlowerの2部品で構成としました。
M2ボルト用のタップをたててボルト固定するような考え方です。
リブを長手方向に多めに走らせておき、その他構成部品用の溝やスペースを配置してます。
各種部品を配置した状態の3Dモデル図です。
レギュレータやスライドSWの一部が貫通してますが、実際はいい感じに干渉しないように対応させます。
5. ソースコード
Arduinoのソースコードです。
例によって精査不足な部分はご愛嬌です。
╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╮
╏ ソースコードを表示(折りたたみ) ╏
╰╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╯
#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
#include <avr/sleep.h>
//DEFPLAYER設定
SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;
void printDetail(uint8_t type, int value);
//pin設定
int LED_R=3;
int LED_G=5;
int LED_B=6;
int led=13;
int switch_pin=4;
int wake_pin=2;
//変数設定
int click_flag_1 = 1 , click_flag_2 = 1;
int channel = 1;
int track =0;
int cnt=0;
int track_num_tanjiro =12;
int track_num_zenitsu =10;
int track_num_rengoku =10;
int track_num_hinokami =5;
int track_num_uzui =7;
int track_num_music =2;
int ch_max=6;
int vol=30;
int LED_pow=180;
unsigned long sleep_time=180000;
unsigned long timer=0;
unsigned long curr=0;
void setup()
{
mySoftwareSerial.begin(9600);
Serial.begin(115200);
Serial.println(F("pinmode set"));
pinMode(LED_R, OUTPUT);
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(switch_pin, INPUT_PULLUP);
pinMode(wake_pin, INPUT_PULLUP);
digitalWrite(LED_R,LOW);
digitalWrite(LED_G,LOW);
digitalWrite(LED_B,LOW);
//DEFPLAYER初期化
Serial.println();
Serial.println(F("DFRobot DFPlayer Mini Demo"));
Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
if (!myDFPlayer.begin(mySoftwareSerial)) { //Use softwareSerial to communicate with mp3.
Serial.println(F("Unable to begin:"));
Serial.println(F("1.Please recheck the connection!"));
Serial.println(F("2.Please insert the SD card!"));
while(true);
}
Serial.println(F("DFPlayer Mini online."));
//Mode切り替え
if(digitalRead(switch_pin) == 0){
Serial.println("mode_flg=1");
vol=0;
LED_pow=255;
}
//----Set volume----
myDFPlayer.setTimeOut(500); //Set serial communictaion time out 500ms
myDFPlayer.volume(vol/3); //Set volume value (0~30).
myDFPlayer.volumeUp(); //Volume Up
myDFPlayer.volumeDown(); //Volume Down
myDFPlayer.EQ(DFPLAYER_EQ_NORMAL);
myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD);
Serial.println(F("setup comp."));
}
void click_check(){
//SW ONで開始する関数。SWOFFだと動かない
click_flag_1=0;//クリックフラグをゼロリセット
// click_flag_2=0;
delay(200);
for(int i=0; i<10; i++){
if(digitalRead(switch_pin) == 0){ //GND接続なのでpin=0でON, 1でOFF
click_flag_1=1;
Serial.println("click1");
break; //200msec以内にSW ON⇒OFFでフラグ1を立てる
}
delay(20);//20msec×10で200msec繰り返す
}
if(click_flag_1 == 1){ //フラグが立ってたらダブルクリック
Serial.println("Double");
if(channel==ch_max){
channel=1;
}
else{
channel=channel+1; //チャネルを1送る
}
track = 1; //トラック1を指定
click_flag_2 = 1; //フラグをリセット
delay(50);
}
else{//フラグがなければシングルクリック
Serial.println("Single");
track = track +1;//トラックを1進める
}
// }
play();
return 0;
}
void loop(){
if(digitalRead(switch_pin) == 0){ //スイッチ検出
Serial.println("switch check");
timer =0;
click_check();
}
curr=millis();
if(curr-timer>sleep_time){
LED_off();
Serial.println("sleeing・・・");
delay(100);
sleep();
}
}
void play(){
delay(100); //ここの数値を小さくすると上手く動かなくなるので注意
Serial.print("channel:");
Serial.print(channel);
Serial.print(", track:");
Serial.println(track);
if(channel==1){
Serial.println("竈門炭次郎モード");
if(track>=track_num_tanjiro+1){
track=1;//トラック数が曲数より大きくなってたら1に戻す
myDFPlayer.playFolder(channel, track);//チャネル、トラックを指定再生
}
myDFPlayer.volume(vol*5/6);
delay(100);//Vol変更後再生まですこしディレイないとエラーになる
myDFPlayer.playFolder(channel, track);//チャネル、トラックを指定再生
delay(100);
timer=millis();
LED_tanjiro();
}
else if(channel==2){ //善一の動作
Serial.println("我妻善一");
LED_off();
if(track>=track_num_zenitsu+1){
track=1;//トラック数が曲数より大きくなってたら1に戻す
myDFPlayer.playFolder(channel, track);//チャネル、トラックを指定再生
}
myDFPlayer.volume(vol*2/3);
delay(100);//Vol変更後再生まですこしディレイないとエラーになる
myDFPlayer.playFolder(channel, track);//チャネル、トラックを指定再生
delay(100);
timer=millis();
LED_zenitsu();
}
else if(channel==3){ //煉獄さんの動作
Serial.println("煉獄杏寿郎");
LED_off();
if(track>=track_num_rengoku+1){
track=1;//トラック数が曲数より大きくなってたら1に戻す
myDFPlayer.playFolder(channel, track);//チャネル、トラックを指定再生
}
myDFPlayer.volume(vol*5/6);
delay(100);//Votral変更後再生まですこしディレイないとエラーになる
myDFPlayer.playFolder(channel, track);//チャネル、トラックを指定再生
delay(100); timer=millis();
LED_rengoku();
}
else if(channel==4){
Serial.println("ヒノカミ神楽");
if(track>=track_num_hinokami+1){
track=1;//トラック数が曲数より大きくなってたら1に戻す
myDFPlayer.playFolder(channel, track);//チャネル、トラックを指定再生
}
myDFPlayer.volume(vol*2/3);
delay(100);//Vol変更後再生まですこしディレイないとエラーになる
myDFPlayer.playFolder(channel, track);//チャネル、トラックを指定再生
delay(100); timer=millis();
if(track==1){
LED_hinokami1();
}
else if(track==2){
LED_hinokami2();
}
}
else if(channel==5){ //宇髄天元の動作
Serial.println("宇髄天元");
LED_off();
if(track>=track_num_uzui+1){
track=1;//トラック数が曲数より大きくなってたら1に戻す
myDFPlayer.playFolder(channel, track);//チャネル、トラックを指定再生
}
myDFPlayer.volume(vol*5/6);
delay(100);//Votral変更後再生まですこしディレイないとエラーになる
myDFPlayer.playFolder(channel, track);//チャネル、トラックを指定再生
delay(100); timer=millis();
LED_uzui();
}
else if(channel==6){
Serial.println("Music");
if(track>=track_num_music+1){
track=1;//トラック数が曲数より大きくなってたら1に戻す
}
myDFPlayer.volume(vol*5/6);
delay(100);//Vol変更後再生まですこしディレイないとエラーになる
myDFPlayer.playFolder(channel, track);//チャネル、トラックを指定再生
timer=millis();
LED_music();
Serial.print("timer:");
Serial.println(timer);
}
else{
//トラックをループ
channel=1;
track=1;
}
}
void wakeUp()
{
Serial.println("wakeup!");
}
void sleep()
{
// https://www.hiramine.com/physicalcomputing/atmega328/sleepbuttonwakeup.html
byte adcsra_old = ADCSRA;
ADCSRA = 0; // アナログ・デジタル・コンバータ機能の無効にする。
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
noInterrupts(); // 割り込み有効化 cli()でも同じ
sleep_bod_disable();
sleep_enable();
interrupts(); // sei()でも同じ
attachInterrupt(0,wakeUp, LOW); // 割り込み設定。INT0端子(2番ピン)の立ち下がりで割り込み発生。wakeUp関数が呼ばれる。
sleep_cpu();
sleep_disable();
detachInterrupt(0);
ADCSRA = adcsra_old;
}
void LED_off(){
digitalWrite(LED_R,LOW);
digitalWrite(LED_G,LOW);
digitalWrite(LED_B,LOW);
digitalWrite(led,LOW);
Serial.println("LED all OFF");
}
void LED_tanjiro(){
Serial.println("tanjiro LED start");
analogWrite(LED_R,0);
analogWrite(LED_G,0);
analogWrite(LED_B,LED_pow);
}
void LED_zenitsu(){
Serial.println("zenitsu LED start");
analogWrite(LED_R,LED_pow*2/3);
analogWrite(LED_G,LED_pow/3);
analogWrite(LED_B,0);
}
void LED_rengoku(){
Serial.println("rengoku LED start");
analogWrite(LED_R,LED_pow);
analogWrite(LED_G,0);
analogWrite(LED_B,0);
}
void LED_uzui(){
Serial.println("uzui LED start");
analogWrite(LED_R,LED_pow/3);
analogWrite(LED_G,0);
analogWrite(LED_B,LED_pow*2/3);
}
void LED_hinokami1(){
Serial.println("hinokami LED start");
cnt=0;
while(cnt<3000){
analogWrite(LED_R,0);
analogWrite(LED_G,0);
analogWrite(LED_B,LED_pow);
delay(10);
cnt++;
if(digitalRead(switch_pin) == 0){
click_check();
return 0;
}
}
for(int i=0;i<=LED_pow;i++){
analogWrite(LED_R,i);
analogWrite(LED_G,0);
analogWrite(LED_B,LED_pow-i);
delay(30);
}
while(cnt<8000){
analogWrite(LED_R,LED_pow);
analogWrite(LED_G,0);
analogWrite(LED_B,0);
delay(10);
cnt++;
if(digitalRead(switch_pin) == 0){
click_check();
return 0;
}
}
}
void LED_hinokami2(){
Serial.println("hinokami LED start");
analogWrite(LED_R,LED_pow);
analogWrite(LED_G,0);
analogWrite(LED_B,0);
}
void LED_music(){
Serial.println("rainbow LED start");
timer=millis();
curr=timer+10;
bool flg_end=0;
while(curr-timer<100000){
if(flg_end==1){
break;
}
for(int cnt=0;cnt<3;cnt++){
if(flg_end==1){
break;
}
for(int i=0;i<=LED_pow;i++){
if(cnt==0){
analogWrite(LED_R,LED_pow-i);
analogWrite(LED_G,i);
analogWrite(LED_B,0);
}
else if(cnt==1){
analogWrite(LED_R,0);
analogWrite(LED_G,LED_pow-i);
analogWrite(LED_B,i);
}
else if(cnt==2){
analogWrite(LED_R,i);
analogWrite(LED_G,0);
analogWrite(LED_B,LED_pow-i);
}
if(digitalRead(switch_pin) == 0){
click_check();
flg_end=1;
LED_off();
break;
}
delay(10);
}
}
curr=millis();
Serial.println(curr-timer);
}
channel=1;
track=1;
LED_off();
}
6. 作製
柄部分を3Dプリンタで出力しました。
黒のPLAで作製しています。
積層のスジが残ってますが、ある意味模様っぽいので良しとします。
なんとなく重さも測ってみました。
こんなもんです。
100金流用品の鍔とのはめあい確認です。
実測で合わせて作りましたが、ほぼいい感じに固めにハマりました。
樹脂材なのである程度パワープレイでの融通が効きます。
電装系を組み立ててきます。
ユニバーサル基板に各種部品を実装しました。
めっちゃ空中配線でやってます。ちゃんと動けば良し。
スピーカーとかスイッチ基板あたりです。
SW基板はプラ接着剤で柄と固定しました。
今回は配置検討上で楽なので別基板にしましたが、組み立ては1基板でまとめたほうが楽。
LED部です。
LEDの足にエナメル線をはんだ付けするのがちょっと大変でした。
なんとか短絡しないようにがんばります。
この部分は何かしら固定治具があったほうが良さげ。
組み立て後の外観です。
メイン基板は積極的に固定はしてませんが、柄を上下はめた際に固定されるような寸法になってます。
Arduinoやレギュレータあたりに負荷が掛かる感じになってるので、固定はちゃんとしないとダメですね。
電池もはめあいで勝手に固定されるような感じです。
電池はまぁ負荷かけてはめあいになっててもいいかな。
完成後の外観を100金のオリジナルと並べて比較しました。
サイズ感的にはほぼ同じ感じです。
9V電池の存在感により、持った感じは若干重いですが許容範囲。
動作させたみた動画です。
7. おわりに
前に比べて断線はしづらくなり、メンテがやりやすくなりました。
ただ空中配線があったり、LED配線が短絡しないか心配だったり、基板の固定が適当だったりと、
残課題は色々あるので、2023年鬼滅アニメの新クールにむけてその4に続けていこうかなと画策中。
最後にコストの振り返りです。
やっぱり電池は高いですね。
3Dプリンタは外注するとそれだけで5000円くらいかかっちゃいますが、材料実費自体はお安い。