“キング・オブ・ダークネス” EVIL の魔法陣を自作
以前 球体のPOV(Persistent Of Vision=残像)装置をLEDテープでつくろうと思っていたのですが安定した回転装置がうまく作れずそのまま闇に葬ってなかったことにしておりました。
そんな時、昨年 11.3の新日本プロレス 大阪大会を見ていたところ、なんとメインイベントIWGPインターコンチネンタルタイトルマッチの入場時に”キング・オブ・ダークネス “EVILがダークネスな闇の力によって魔法陣を空中に浮き上がらせて登場したのです。
11.3大阪大会!メインイベントIWGPインターコンチネンタル選手権試合 @IAmJericho VS @151012EVIL !
闇の王、EVILが大阪に降臨!
視聴&登録?https://t.co/NxJKtVK94f #njpwworld #njpst #njpw pic.twitter.com/nslTSacNbe? njpwworld (@njpwworld) 2018年11月3日
私は痺れましたよ。そして思ったのです。”俺には闇の力はないけれど、これPOVで再現できんじゃね?”ってね。
もう二度とPOV製作には手をつけないと思っていた私の人生に、一歩踏み出す勇気をまさかのEVIL様が授けてくださったのです。
早速できあがったモノの動作を見てください。
かなりのダークネス感ある魔法陣が再現できて喜んでおります。ちゃっかり内藤哲也選手のマークも表示しております :-P?
鎌も以前自作したものです。
以下、構成や製作過程記載します!
目次
構成
回転部にLEDテープとマイコン、フォトリフレクタを搭載し、LEDテープはDotstarを使用。ワイヤレスチャージモジュールを使って回転部への給電は無線で実施しました。
モータ用に単3電池1個、回転部への無線給電用に単3電池3個使用しました。
部品
- マイコン Adafruit Itsy Bitsy M0 Express
- フォトリフレクタ QTR-1A
- LEDテープ Dotstar
- ワイヤレスチャージモジュール
- マブチモーター RS-540SH
- 単3電池×1 ケース
- 単3電池×3 ケース
-
ミニスライドスイッチ
-
モーター シャフトカップリング ジョイント
Dotstar
以下の特徴を有するLEDテープです。
- SPI入力[CLK, DATA]のため高速に書き込み可能。
入力周波数 30MHzまで対応らしい
- 発光リフレッシュレート19.2 KHzと高速
以上よりPOV向けのLEDセルと言えます。従来ですとLEDにパラレルに信号入力して高速点灯を実現していましたがLEDテープにシリアルに信号入力で高速点灯ができるのは非常に簡単でありがたいです。
非常に親しまれているLEDテープNeopixelも信号線1本で使いやすいですが、800 KHz固定の独自信号入力となるので高速化は厳しいです。リフレッシュレートも400 HzなのでPOVには向きません。
以下でNeopixelとDotstarの比較を実施していますのでご参照ください。
ワイヤレスチャージモジュール
POV製作の際の第一の壁となるのがLED回転部への電源供給です。回転可能な接点をうまいこと作って通電したりするのが見受けられますが加工が大変そうで とても私に作れそうにない。。。
ということで、ここではワイヤレスチャージモジュールを使用しました。これだとほぼ加工なしでモータの回転軸に送受信用コイルを通して向かい合わせに配置するだけです。我ながらナイスアイディアかと。手前味噌ですが 🙄 。
使用したワイヤレスチャージモジュールの詳細は以下に記載しています。
魔法陣POV装置
モータを金具で固定して黒く塗った木材で取っ手をつくり、電池ソケットや電源ON/OFFスライドスイッチを固定しました。
回転部は以下のように完全無線で独立しています。
回転部のフォトリフレクタでモータ側の上部の白いマーカを検知して回転速度を計測しています。
Arduino IDEコード
以下のDotstar用ライブラリを使用しています。
https://github.com/adafruit/Adafruit_DotStar
使用したマイコン Adafruit Itsy Bitsy M0 ExpressのArduino IDE用設定についての詳細は以下を参照ください。
フォトリフレクタがマーカを検出すると出力が低下するのでattachInterruptで検出して割り込み処理で1周するまでの時間を計測します。
1周にかかる時間を250分割してLEDの点滅を切り替えます。250個のLED表示パターンはgraphics.hで配列としてFlashメモリに格納しています。星や四角、文字などのパターンを書き込んでいます。
一定時間ごとに表示を変更しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
#include <Adafruit_DotStar.h> #include <QTRSensors.h> #include <SPI.h> #include "graphics.h" volatile int numRot = 0; int numDiv = 0; int stateDiv = 0; volatile unsigned long rotTime, timeOld, timeNow, opeTime; Adafruit_DotStar strip = Adafruit_DotStar(NUMPIXELS, DOTSTAR_BGR); void setup() { Serial.begin(115200); pinMode(13, OUTPUT); strip.begin(); // Initialize pins for output strip.show(); attachInterrupt(digitalPinToInterrupt(7), RotCount, FALLING ); opeTime = millis(); } void loop() { if(stateDiv == 1 && micros() - timeOld > rotTime / Div * (numDiv)){ stateDiv = 0; } if(stateDiv == 0 && micros() - timeOld < rotTime / Div * (numDiv + 1)){ stateDiv = 1; strip.clear(); if(2000 > millis() - opeTime){ for(int i=numDiv/16;i<numRot/3+numDiv/10; i++){ strip.setPixelColor(i, 0xFF00FF); } } //四角表示 (回転) if(5000 < millis() - opeTime && 20000 > millis() - opeTime){ for(int i=13;i<=19; i++){ int line = numDiv + numRot*4; //if(line >= Div) line -= Div; line = line % Div; if(pgm_read_byte_near(&square2[line][i]) == 1){ strip.setPixelColor(i, 0x0F00FF); } } } //魔法陣表示 if(20000 > millis() - opeTime){ for(int i=8;i<NUMPIXELS; i++){ if(pgm_read_byte_near(&star[numDiv][i]) == 1){ strip.setPixelColor(i, 0xFF00FF); } } } //目表示 if(20000 < millis() - opeTime){ for(int i=5;i<=19; i++){ if(pgm_read_byte_near(&eye[numDiv][i]) == 1){ strip.setPixelColor(i, 0xFF0000); } if(pgm_read_byte_near(&eye[numDiv][i]) == 5){ strip.setPixelColor(i, 0xFFFFFF); } } } if(10000 < millis() - opeTime){ //文字表示 (回転) for(int i=20;i<NUMPIXELS; i++){ int line = numDiv - numRot; if(line < 0) line += Div; if(pgm_read_byte_near(&LIJ[line][i]) == 1){ strip.setPixelColor(i, 0xFF0000); } } } if(26000 < millis() - opeTime) opeTime = millis(); strip.show(); numDiv++; if(numDiv >= Div ) numDiv = 0; } } void RotCount() { timeNow = micros(); rotTime = timeNow - timeOld; timeOld = timeNow; numRot++; if(numRot >= Div ) numRot = 0; } |
1 2 3 4 5 6 7 8 9 10 |
#define NUMPIXELS 29 #define Div 250 const uint8_t PROGMEM star [Div][NUMPIXELS] = { {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1}, //以下省略 }; |
配列にPROGMEMと記載しているがARMマイコンの場合は、変数名の前にconstを追加するだけでフラッシュに保存される。(2019/8/11追記)
表示グラフィックデータ作成手法
Excelの使用
graphics.hに書き込んだ表示する画像の配列は以下のサイトを参考にさせていただきExcelをつかって作成しました。
円に数字を書くと背景の色が紫になるようにして、1周を250分割して展開(29×250)するようにしました。
展開したデータをテキストエディタで加工してgraphics.hに配列としてFlashメモリに格納しました。実際に表示させてみて配列を若干修正したりもしました。
こんな感じにしたい。もう寝ます。#LOSINGOBERNABLESdeJAPON #LED pic.twitter.com/b8v1zQlnpP
? HomeMadeGarbage (@H0meMadeGarbage) 2019年1月12日
Pythonプログラム作成 (19/1/22追記)
Excelだと複雑な画像表示がしんどいのでPythonで表示データ生成プログラムこしらえました。
画像処理ライブラリのopenCVとPillowを利用しています。
画像を取り込んで縮小して極座標変換してLEDの数 (29) ×1周の分割数 (250)の色コードの配列を生成します。確認用に変換後の画像も出力します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
import cv2 import math from PIL import Image NUMPIXELS = 29 #LEDの数 Div = 250 #1周の分割数 #画像データ読み込み imgOrgin = cv2.imread('rock2.jpg') #画像サイズ取得 #参考:https://note.nkmk.me/python-opencv-pillow-image-size/ h, w, _ = imgOrgin.shape print('widthOrgin: ', w) print('heightOrgin:', h) #画像縮小 #参考:https://www.tech-tech.xyz/opecv_resize.html imgRedu = cv2.resize(imgOrgin,(math.floor((NUMPIXELS * 2 -1)/h *w), NUMPIXELS * 2 -1)) cv2.imwrite('img-resize.jpg',imgRedu) #縮小画像中心座標 h2, w2, _ = imgRedu.shape print('widthRedu: ', w2) print('heightRedu:', h2) wC = math.floor(w2 / 2) hC = math.floor(h2 / 2) print('widthCenter: ', wC) print('heightCenter:', hC) #極座標変換画像準備 imgPolar = Image.new('RGB', (NUMPIXELS, Div)) #極座標変換 file = open('PolarConv.txt', 'w') for j in range(0, Div): file.write('{') for i in range(0, hC+1): #座標色取得 #参考:http://peaceandhilightandpython.hatenablog.com/entry/2016/01/03/151320 rP = imgRedu[hC - math.ceil(i * math.cos(2*math.pi/Div*j)), wC + math.ceil(i * math.sin(2*math.pi/Div*j)), 2] gP = imgRedu[hC - math.ceil(i * math.cos(2*math.pi/Div*j)), wC + math.ceil(i * math.sin(2*math.pi/Div*j)), 1] bP = imgRedu[hC - math.ceil(i * math.cos(2*math.pi/Div*j)), wC + math.ceil(i * math.sin(2*math.pi/Div*j)), 0] file.write('0x%02X%02X%02X' % (rP,gP,bP)) if i == hC: file.write('},\n') else: file.write(', ') imgPolar.putpixel((i,j), (int(rP), int(gP), int(bP))) file.close() #変換画像保存 #参考:https://qiita.com/uosansatox/items/4fa34e1d8d95d8783536 imgPolar.save('PolarConv.jpg') |
元画像
変換画像
POV Display Mega Man pic.twitter.com/WmUJyugHlk
? HomeMadeGarbage (@H0meMadeGarbage) 2019年1月22日
製作の流れ
これを製作するにあたり紆余曲折ございましたので、道筋をここに記します。
黎明期
最初マイコンはAdafruit Itsy Bitsy M0 ExpressではなくAdafruit Feather 32u4 Bluefruit LEを使用してました。BLEで表示画像のタイミング信号送りたいなと思っていたので。
しかもフォトリフレクタで回転の視点のマーカを検出を割り込みではなくloop内で検出して回転速度測定していました。
今の手法だと16分割が限界。。出直します。#POV #Dotstar pic.twitter.com/yBVr4CBVtt
? HomeMadeGarbage (@H0meMadeGarbage) 2019年1月12日
この状態だと1周 16分割が限界でした。。。
成長期
DotstarへのSPI入力ピンを普通のデジタルピンを使っていたので、ハードウェアピンのSCKピンとMOSIピンに変更しました。
SPIをハードウェアピンに換えたらあっさり40分割達成。あとはフォトリフレクタの機構変えていい感じに割り込みしたら100分割行けそうな気がするー。#実験 #人生 pic.twitter.com/3rbMcYoNeA
? HomeMadeGarbage (@H0meMadeGarbage) 2019年1月12日
40分割達成!今後SPIはデジタルピンでソフトウェア的に信号入力するのはやめようと思います。こんなに違うとは勉強になりました。
さらにフォトリフレクタのマーカー検出出力でattachInterrupt割り込みで回転速度検出するようにしましたところ
フォトリフレクタ検出で割り込み制御にして128分割達成! 目標の180分割は出来なかったがまぁいいや。こっからは絵だし。さてどうする。#LED #Dotstar #bluefruit #Adafluit pic.twitter.com/OQDL3SdnQI
? HomeMadeGarbage (@H0meMadeGarbage) 2019年1月12日
128分割達成!
Welcome to the Darkness World!!#njpwworld #njpst #njpw #EVIL #LOSINGOBERNABLESdeJAPON pic.twitter.com/9JlTSkIXtn
? HomeMadeGarbage (@H0meMadeGarbage) 2019年1月14日
絵もそれなりに表示できているのですがAdafruit Feather 32u4 Bluefruit LEはBLE通信部以外の動作周波数8MHzだったことが判明。。。
成熟期
そこで動作周波数48MHzのマイコンAdafluit Itsy Bitsy M0 Expressに変更しました。
動作周波数48MHzのマイコンAdafluit Itsy Bitsy M0 Expressに変えたら250分割できた。単純に処理が早くなったからなのかSPIクロック周波数の影響なのかは不明。オシロほしい。。。まぁオールオッケー pic.twitter.com/tWY23lLElf
? HomeMadeGarbage (@H0meMadeGarbage) 2019年1月15日
250分割達成!!やっぱマイコンは高速に限る!ただこの改善がSPIクロック周波数起因なのか単純に動作周波数が上がったからなのかが切り分けられておらず、自宅にオシロがほしい今日このごろです。
終わりに
SPI入力のLEDテープ Dotstarは凄いです!ほんとにPOVができました。これ従来とおりパラレル信号入力でやったらこんなにフランクに実現できないしNeopixelはスピードが足りませんからね。但し、DotstarのテープLEDは国内で手に入りにくいのが玉に瑕でございまさぁね 🙄 。
Does DVD screensaver actually hit the corner?!
DIY Bouncing logo with wireless charging module. ??https://t.co/wdq2eyiupr#doityourself #dvd #retro #module #tutorial #tutoriales #project #proyecto #makersgonnamake #maker #engineering #electronics #arduinoide #programming pic.twitter.com/S98gFdSv5h
? DFRobot (@dfrobotcn) 2019年1月26日
追記
DFRobotの公式ブログで紹介いただきました!(19/1/21)
Adafruitの公式ブログで紹介いただきました!(19/1/23)
PVも作ってしまった (19/2/8)
追記 組み立て概要
モータ固定
1cm幅のL字金具4つ、コの字の金具2個組み合わせてモータを固定。
回転部
回転部の軸にはM2×20のネジを使用。木に穴をあけて通してナットで固定し更にレジンで接着しています。
モータシャフトに固定してモータと連結します。
はじめまして.
興味深く拝見しました.自分でも作ってみようとおもったのですが,次の2点がよくわかりませんでした.可能でしたら,使われている部品や組み立て方法についてご紹介いただけないでしょうか?
1. モータの固定
コの字型の金属プレートを組み合わされているように見えましたが,どういったものになりますでしょうか?
2. 回転部分の固定
モータにシャフトを固定するところまではわかるのですが,LED 部分はどう固定されていますでしょうか?
それなりに高速回転するので,割としっかりと固定されているんだと思いますが,どのような部品を使われていますでしょうか?
以上,よろしくお願いいたします.
コメントありがとうございます。
簡単ではありますが組み立て概要を追加しました。
ご確認よろしくお願い致します。
追記ありがとうございます。
シャフトの長さとモータサイズに合った金具の選定が肝になりそうですね。AliExpressとかだとぴったりのものなさそうですので、ホームセンターで探してみようと思います。
そうなんです。
金具につきましては型番メモし忘れまして申し訳ございません。
ビバホームで購入しました。
分かり次第、追記させていただきます。
コメント失礼します。完成度が高くてすごいですね!
EXCELで画像の極座標を出すのにはマクロを使うのですか?
初心者なのでどうやったか気になりました
コメントありがとうございます!
マクロは一切使っていません。文中の図にも記載した通りOFFSETやROUNDUPなどのExcelの標準的な関数のみで座標変換しています。
本文にもリンクを張っている以下のサイトを参考にしており、Excelファイルも公開されています。
http://www.ze.em-net.ne.jp/~kenken/versawriter/index.html
ご参照ください。
返信ありがとうございます!
POVに興味を持ち始めたので私も作ってみます!
ありがとうございます!! ハード関連の製法は以下に記載しております。
https://homemadegarbage.com/pov-make
参考になれば幸いです。
質問失礼します。プログラムについてなのですが、
imgReduという関数(おそらくRGBデータの取得をしていると考えているのですが)、
ネットで調べても情報が出てこずうずうずしています。
できれば教えていただけないでしょうか。
imgRedu は PolarConv2.pyの19行目の通り
画像サイズを縮小しています。
OpenCV の resize()で画像を指定のサイズに縮小してimgReduに格納しています。
返信ありがとうございます。画像名[y座標,x座標,カラーチャンネル]→画素値へのアクセスということが
理解できてなかったようです。
大変助かりました。
はじめまして!
完成度が高くて自分も参考に作成してみようと思ったのですが、プログラムについて不明点がありご教示いただきたいです。
13行目の
“Adafruit_DotStar strip = Adafruit_DotStar(NUMPIXELS, DOTSTAR_BGR);”の箇所ですが、
“Adafruit_DotStar strip(NUMPIXELS, DATAPIN, CLOCKPIN, DOTSTAR_BRG);”
ではないのはなぜなのでしょうか?(ライブラリの説明を確認しましたが明確な違いを理解できませんでした…)
またDATAとCLKのピンの定義はどこでしているのでしょうか?
よろしければお願いいたします。