こんばんは。
とうとうクリスマスイブになってしまいました。
なんとかギリギリ間に合いました。
今回はかなり即席な記事をサラッとアップします。
M5Stack で Wi-Fi Art-Net DMX レコーダーを作って、パソコンソフト無しで、スタンドアローンで LED Show を再生できるようにしてみました。
1作前、2作前、3作前の記事では、全てパソコンソフトを使って Art-Net DMX で制御して、M5Stack や ESP32 に接続した NeoPixel ( WS2812B ) を動かしていましたが、パソコンとLAN通信環境を占有してしまうものでした。
ですが、今回はパソコンソフトで再生した LED シーンやチェイスパターンの Art-Net データを M5Stack の micro SD カードに保存して、それを再生できるようにしたのです。
約 30秒の記録で、DMX512ch、1universe で、概ね1Mバイトのメモリ程度で済みました。
とりあえず、以下の動画をご覧ください。
どうでしょう?
なかなか忠実に再生してくれていますね。
因みにこれは、1universe ( DMX 512ch )だけ再生するように作りました。
プログラミング次第では複数 universe もいけると思います。
レコーディング中は、できるだけ純粋な Art-Net DMX データをSD カードに書き込むために、NeoPixel LED を消灯しています。
動画にあるように、MIDI コントローラーでシーンを調整しても良いですし、チェイスを組んでシーンを自動再生させても良いと思います。
再生中は、micro SDHC カードの読み込み速度が意外と速いので、もっと再生スピードを上げることもできます。
本当は、データ取りこぼしがあった場合に備えて、 micro SD カードに書き込む時に 目印のフラグを記録したかったんですが、普通に無条件で書き込み、それをただ単に単純に読み込んで再生するだけで、特に問題なく再生できちゃいました。
このプログラムを組む前は、micro SDHC カードの記録が膨大になるのではないかと思って、しばらく敬遠していたのですが、いざ作ってみると、思っていたほどでもなく、再生スピードも速くて拍子抜けしてしまいました。
改めて micro SD カードってスゲーなぁと思いました。
今回のこのプログラムは、クリスマス期間が終わっちゃいそうなので急いで作りました。
ですから動作は一切保証しません。
エラー処理とかも何も考えないで作りました。
とりあえずザッと紹介してみますので、興味ある方は見てみてください。
あと、ご存知かと思いますが、micro SD カードには書込み回数に制限があります。
あまり沢山書き換えをしてしまうと、2度と書き込めなくなることも有り得ますので、ご自分の micro SD カードの性能を確かめておいてください。
ちなみに私は独学素人アマチュアです。
誤り等があるかもしれません。
もし何かお気づきの点があればコメント投稿欄等でお知らせいただけると助かります。
使ったもの
今回は、こちらの記事で使ったものに、ESP32-DevKitC を M5Stack に変えただけです。
M5Stack にはバッテリーオプションを追加しています。
そして、micro SDHC カードは以下の物を使っています。
M5Stack
(追記)
M5Stack Basicは、この記事を書いた当時より格段にバージョンアップしております。
以下のスイッチサイエンスさんの公式サイトをご参照ください。
https://www.switch-science.com/collections/%E5%85%A8%E5%95%86%E5%93%81/products/9010
バッテリーオプション
純正バッテリーのみだと、15分以下しか持たないかも知れません。
このオプションを使うと、使い方によっては4~5時間持つかも知れません。
M5Stack用電池モジュール
micro SDHC カード
当方で動作確認したものは以下のものです。
たまに粗悪品をつかまされることもあるようで、よく吟味してから購入することをお勧めします。
USB-MIDI コントローラ
最初に紹介した動画で使った MIDI-USB コントローラは KORG 社の初代 NANO KONTROL です。
これは生産終了したので、今は以下の物が売っています。
ライブラリや日本語フォント等のインストール
次の2つの記事を参照して、Arduino core for the ESP32 やライブラリ類をインストールしておき、日本語フォントを micro SD カードにインストールしておいてください。
M5Stackと NeoPixel で Art-Net DMX を使った LED エフェクト実験
ESP32 と NeoPixel フルカラー LED テープで Wi-Fi 卓上イルミネーションオブジェを作ってみた
Arduino IDE はver1.8.8 で動作確認しています。
Arduino core for the ESP32 は stable 1.0.0 で動作確認しています。
また、ライブラリは以下の物が必要です。
● ArtnetWifi ライブラリ
● FastLEDライブラリ
● mgo-tec自作ライブラリ
Art-Net制御用パソコンソフトは以下の物を使いました。
●Jinx! – LED Matrix Control ( Windows専用フリーウェア )
接続
以前のこちらの記事のもので、ESP32-DevKitC を M5Stack に変えただけです。
GND の電位差を極力抑えるために、M5Stack USB電源と AC アダプターの電源は同じコンセントから取ってください。
実際の画像はこんな感じです 。
スケッチ(プログラムソースコード)
先ほど述べたように、スケッチ(プログラムソースコード)はかなり即席であまり練らずに作りましたので、動作は保証しません。
もっと改良が必要なところは多々あると思いますので、いろいろ変えてみて下さい。
解説は省略させていただきます。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
#include <WiFi.h>
#include <WiFiUdp.h>
#include <ArtnetWifi.h>
#include <FastLED.h>
#define MGO_TEC_BV1_M5STACK_SD_SKETCH
#include <mgo_tec_bv1_m5stack_sd_simple1.h> //ESP32_mgo_tec library beta ver 1.0.67
const char* ssid = "xxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください
const char* password = "xxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください
const char* utf8sjis_file = "/font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名を記載しておく
const char* shino_full_font_file = "/font/shnmk16.bdf"; //オリジナル東雲全角フォントファイル
const char* shino_half_font_file = "/font/shnm8x16.bdf"; //半角フォントファイル名を定義
ArtnetWifi artnet;
const int numLeds = 144 + 12; // CHANGE FOR YOUR SETUP
const int numberOfChannels = numLeds * 3; // Total number of channels you want to receive (1 led = 3 channels)
const byte dataPin = 21; //ESP32 GPIO pin
CRGB leds[numLeds];
uint8_t dmx[512] = {}; //DMX 1univers : 512ch
File file;
const uint8_t cs = 4;
const char *artnet_read_file = "/artnet.dat"; //SDカードに保存するArt-Net用ファイル名
boolean isStartPlay = false;
boolean isStartRec = false;
int delay_value = 20;
const uint8_t device_universe = 0; //使用するデバイスのUniverseを指定
uint16_t receive_len = numberOfChannels;
//*************************************************
void setup(){
Serial.begin(115200);
SD.begin( cs, SPI, 40000000, "/sd" );
delay(1000);
ConnectWifi();
artnet.begin();
artnet.setArtDmxCallback(onDmxFrame);
mM5.init( utf8sjis_file, shino_half_font_file, shino_full_font_file );
LCD.brightness(255); //LCD LED Full brightness
mM5.font[0].x0 = 0; mM5.font[0].y0 = 0;
mM5.font[0].htmlColorCode( "#FFFFFF" );
mM5.font[0].Xsize = 2, mM5.font[0].Ysize = 2;
mM5.disp_fnt[0].dispText( mM5.font[0], "Art-Net Rec LED Show" );
mM5.font[1].x0 = 0; mM5.font[1].y0 = 48;
mM5.font[1].htmlColorCode( "#FFFFFF" );
mM5.font[1].Xsize = 2, mM5.font[1].Ysize = 3;
mM5.font[2].Xsize = 2, mM5.font[2].Ysize = 2;
mM5.font[2].y0 = 112;
mM5.font[2].htmlColorCode( "#FFFFFF" );
mM5.font[3].Xsize = 2, mM5.font[3].Ysize = 2;
mM5.font[3].x0 = 0;
mM5.font[3].y0 = 144;
mM5.font[3].htmlColorCode( "#FFFFFF" );
mM5.disp_fnt[3].dispText( mM5.font[3], "Speed: " );
mM5.font[4].Xsize = 2, mM5.font[4].Ysize = 2;
mM5.font[4].x0 = 128;
mM5.font[4].y0 = 144;
mM5.font[4].htmlColorCode( "#FFFFFF" );
FastLED.addLeds<WS2812B, dataPin, GRB>(leds, numLeds);
FastLED.setBrightness(20); //※これ以上大きくすると、回路電流が大きすぎて危険注意。(Max 255)
}
//*************************************************
void loop(){
int i;
int ch = 0; //DMX channel number
button_action();
for(i = 0; i < numLeds; i++){
leds[i] = CRGB(dmx[ch], dmx[ch + 1], dmx[ch + 2]);
ch = ch + 3;
}
FastLED.show();
delay(1); //これ重要!これが無いと点灯しない。
if(isStartPlay == true){
readSdArtnet(); //SDカードから再生
delay(delay_value); //ここで Play speed 調整
}else{
artnet.read();
}
}
//***************************************
boolean ConnectWifi(void){
boolean state = true;
int i = 0;
WiFi.begin(ssid, password);
Serial.println("");
Serial.println("Connecting to WiFi");
// Wait for connection
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
if (i > 20){
state = false;
break;
}
i++;
}
if (state){
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("");
Serial.println("Connection failed.");
}
return state;
}
//**************************************************
void readSdArtnet(){
if(isStartPlay == true){
int ch = 0; //DMX channel number
while(file.available()){
dmx[ch++] = file.read();
if(ch == receive_len) return;
}
file.seek(0);
}
}
//**************************************************
void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data){
int ch = 0; //DMX channel number
if(universe == device_universe){
for (ch = 0; ch < length; ch++){
dmx[ch] = data[ch];
ch++;
dmx[ch] = data[ch];
ch++;
dmx[ch] = data[ch];
}
receive_len = length;
if(isStartRec == true) file.write(dmx, length);
}
}
//****************************************
void startPlay(){
file = SD.open(artnet_read_file, FILE_READ);
if(file){
delay(10);
mM5.font[1].htmlColorCode( "#00FF00" );
mM5.font[1].htmlBgColorCode("#000000");
mM5.disp_fnt[1].dispText( mM5.font[1], "Play Start!!" );
LCD.displayClear(0, 112, 319, 143);
mM5.disp_fnt[2].dispText( mM5.font[2], String(file.size()) + " byte" );
LCD.displayClear(128, 144, 319, 176);
mM5.disp_fnt[3].dispText( mM5.font[3], "Speed: " );
mM5.disp_fnt[4].dispText( mM5.font[4], String(50-delay_value) );
isStartPlay = true;
}else{
mM5.font[1].htmlColorCode( "#FFFFFF" );
mM5.disp_fnt[1].dispText( mM5.font[1], "No file " );
isStartPlay = false;
}
}
//****************************************
void stopPlay(){
isStartPlay = false;
if(file) file.close();
delay(10);
mM5.font[1].htmlColorCode( "#FFFFFF" );
mM5.font[1].htmlBgColorCode("#000000");
mM5.disp_fnt[1].dispText( mM5.font[1], "----Stop----" );
for(int i = 0; i < 512; i++){
dmx[i] = 0;
}
}
//****************************************
void startRec(){
isStartPlay = false;
if(file) file.close();
delay(10);
Serial.printf("Deleting file: %s\n", artnet_read_file);
if(SD.remove(artnet_read_file)){
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
delay(10);
for(int i = 0; i < numLeds; i++){
leds[i] = CRGB(0, 0, 0);
}
FastLED.show();
file = SD.open(artnet_read_file, FILE_WRITE);
isStartRec = true;
mM5.font[1].htmlColorCode("#FF0000");
mM5.font[1].htmlBgColorCode("#000000");
mM5.disp_fnt[1].dispText( mM5.font[1], "Recording !!" );
uint32_t blink_time = millis();
while(1){
button_action();
if(isStartRec == false){
break;
}else{
if(millis() - blink_time < 500){
LCD.displayClear(0, 48, 319, 96);
}else{
mM5.disp_fnt[1].dispText( mM5.font[1], "Recording !!" );
if(millis() - blink_time > 1000) blink_time = millis();
}
}
artnet.read();
delay(1); //マルチタスクの場合、これ絶対必要!
}
}
//****************************************
void stopRec(){
isStartRec = false;
if(file) file.close();
delay(10);
file = SD.open(artnet_read_file, FILE_READ);
mM5.font[1].htmlColorCode("#00FFFF");
mM5.font[1].htmlBgColorCode("#000000");
mM5.disp_fnt[1].dispText( mM5.font[1], "----Stop----" );
LCD.displayClear(0, 112, 319, 239);
mM5.disp_fnt[2].dispText( mM5.font[2], String(file.size()) + " byte" );
if(file) file.close();
delay(10);
for(int i = 0; i < 512; i++){
dmx[i] = 0;
}
}
//****************************************
void upPlaySpeed(){
delay_value--;
if(delay_value < 1) delay_value = 0;
LCD.displayClear(128, 144, 319, 176);
mM5.disp_fnt[4].dispText( mM5.font[4], String(50-delay_value) );
}
//****************************************
void downPlaySpeed(){
delay_value++;
if(delay_value > 50) delay_value = 50;
LCD.displayClear(128, 144, 319, 176);
mM5.disp_fnt[4].dispText( mM5.font[4], String(50-delay_value) );
}
//****************************************
void button_action(){
mM5.btnA.buttonAction();
switch( mM5.btnA.ButtonStatus ){
case mM5.btnA.MomentPress:
Serial.println("Button A Moment Press");
startPlay();
break;
case mM5.btnA.ContPress:
Serial.println("-------------Button A Cont Press");
upPlaySpeed();
break;
default:
break;
}
mM5.btnB.buttonAction();
switch( mM5.btnB.ButtonStatus ){
case mM5.btnB.MomentPress:
Serial.println("Button B Moment Press");
stopPlay();
break;
case mM5.btnB.ContPress:
Serial.println("-------------Button B Cont Press");
downPlaySpeed();
break;
default:
break;
}
mM5.btnC.buttonAction();
switch( mM5.btnC.ButtonStatus ){
case mM5.btnC.MomentPress:
Serial.println("Button C Moment Press");
stopRec();
break;
case mM5.btnC.ContPress:
Serial.println("-------------Button C Cont Press");
startRec();
break;
default:
break;
}
}
コンパイル書き込み実行
コンパイル書き込み実行する時は、「Core Debug Level」を「なし」にしてください。
実行するときは、先に紹介した動画のように操作します。
因みに、最初に述べましたが、micro SD カードには書込み回数に制限があります。
あまり沢山書き換えをしてしまうと、2度と書き込めなくなることも有り得ますので、ご自分の micro SD カードの性能を確かめておいてください。
ボタン操作については、下図の通りです。
今回は、急いで作って、あまり練ってプログラミングしていないので、かなり単純な構成です。
ご容赦ください。
いつか、練ってプログラミングしたいと思いますが、このまましないかもしれません。
とりあえず動けばいいや、っていう感じです。
編集後記
どうでしょうか。
いつか作ってみたいと思っていたArt-Net レコーダー がとりあえず出来ちゃいました。
ものすごいメモリを食うかと思ったんですが、意外とそれほどでもなかったです。
なかなか micro SD カードの読み込みは優れ物ですね。
今回は時間がなかったので、かなり省いて記事をアップしてみました。
本当はこんな感じでサクサクアップしていきたいですけどね。
これでやっとクリスマスシーズンの工作やプログラミングは終わりです。
あと年末までもう一つ記事できるかも・・・。
ではまた・・・。
Amazon.co.jp 当ブログのおすすめ


















































コメント
管理人のmgo-tecです。
いつも当ブログの工作を試していただいている juchang さんから、写真の投稿があったので紹介させてください。
3Dプリンターで作ったクリアーカバーに、NeoPixel を貼り付け、光ファイバーを以前より増設させて、この記事の Art-Net レコーダーを試してくださいました。

やっぱり、外側のクリアーカバーが素晴らしく良い効果が出ていますね。
このカバーはなかなか既製品では無いですよ。
この Art-Net レコーダーは、Twitter ではあまり反響が無かったのですが、実際に作ってみると、意外とスゴイんです。
これを応用するといろいろなことができそうですよ!
juchang さんにそれを分かって頂けて、嬉しいですね!
本当にいつもありがとうございます!!!
m(_ _)m
大変素晴らしいです!
独学素人とのことですが、お仕事として複数ユニバースのプログラムを作成していただくことはできないでしょうか?
maeさん
記事をご覧いただき、ありがとうございます。
たいへん嬉しいご依頼ですが、残念ながら現在はプログラミング作業を休止しており、ご要望にお応えできません。
それに、お仕事に耐えうるようなコードとは程遠く、長時間運用すると不意に停止する可能性もあります。
何よりもこれは2018年に作ったもので、ESP32やM5Stack公式ライブラリは既に大幅変更されていて、もはや動かない可能性が大きいです。
そんなわけで個人的にも大変残念な思いですがご容赦ください。