中央の NeoPixel Ring および LED テープ用のスケッチ(プログラムソースコード)
次に、中央のイルミネーションオブジェ、ESP32-DevKitC と NeoPixel ( WS2812B )のスケッチを紹介します。
基本的に、前回の記事とほぼ同じなのですが、LED テープの中央部分の 12×4 pixel だけを使う所が異なります。
意外と小難しくなりますね。
16行目で Universe を1としているところに注意してください。
あとは解説を省略させていただきます。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
#include <WiFi.h>
#include <WiFiUdp.h>
#include <ArtnetWifi.h>
#include <FastLED.h>
const char* ssid = "xxxxxxxx"; //ご自分のルーターのSSIDに書き換えてください
const char* password = "xxxxxxxx"; //ご自分のルーターのパスワードに書き換えてください
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];
ArtnetWifi artnet;
const int max_dmx_ch = (48 + 12) * 3;
uint8_t dmx[max_dmx_ch] = {}; //DMX 1univers : 512ch
const uint8_t device_universe = 1;
//*************************************************
void setup(){
Serial.begin(115200);
ConnectWifi();
artnet.begin();
artnet.setArtDmxCallback(onDmxFrame);
FastLED.addLeds<WS2812B, dataPin, GRB>(leds, numLeds);
FastLED.setBrightness(20); //※これ以上大きくすると、回路電流が大きすぎて危険注意。(Max 255)
TaskHandle_t th; //マルチタスクハンドル定義
xTaskCreatePinnedToCore(Task1, "Task1", 8192, NULL, 5, &th, 0); //マルチタスク起動
}
//*************************************************
void loop(){
int i;
int ch = 0; //DMX channel number
for(i = 0; i < numLeds; i++){
if(i >= 0 && i < 12){
leds[11-i] = CRGB(dmx[ch], dmx[ch + 1], dmx[ch + 2]);
ch = ch + 3;
}else if(i >= 24 && i < 36){
leds[i] = CRGB(dmx[ch], dmx[ch + 1], dmx[ch + 2]);
ch = ch + 3;
}else if(i >= 60 && i < 72){
leds[i] = CRGB(dmx[ch], dmx[ch + 1], dmx[ch + 2]);
ch = ch + 3;
}else if(i >= 96 && i < 108){
leds[i] = CRGB(dmx[ch], dmx[ch + 1], dmx[ch + 2]);
ch = ch + 3;
}else if(i >= 132 && i < 144){
leds[i] = CRGB(dmx[ch], dmx[ch + 1], dmx[ch + 2]);
ch = ch + 3;
}else{
leds[i] = CRGB(0, 0, 0);
}
}
FastLED.show();
delay(1); //これ重要!これが無いと点灯しない。
}
//************ マルチタスクループ ******************
void Task1( void *pvParameters ){
while(1){
artnet.read();
delay(1); //マルチタスクの場合、これ絶対必要!
}
}
//***************************************
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 onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data){
int ch; //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];
}
}
}
右側 M5Stack 用のスケッチ(プログラムソースコード)
次に、右側の M5Stack のスケッチです。
先に紹介した左側と殆ど同じですが、18行目で Universe を2番としています。
そして、36行目で、LCD表示を上下逆転させています。
これは、先に述べたように、USBケーブル給電をし易くするためです。
あとは、41-50行で平仮名にしているだけです。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
#include <WiFi.h>
#include <WiFiUdp.h>
#include <ArtnetWifi.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 uint16_t max_pixels = 40; //文字数
const uint16_t max_dmx_ch = max_pixels * 3;
const uint8_t device_universe = 2;
uint8_t dmx[max_dmx_ch] = {};
uint8_t lcd_px[max_dmx_ch] = {};
uint16_t c0, c1, c2;
String str[max_pixels][8];
uint16_t font_width;
uint16_t font_height;
double str_per_dmx = (double)7.0 / 255.0;
//*************************************************
void setup(){
Serial.begin(115200);
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
LCD.dispRotation(2); //上下逆表示
mM5.font[0].Xsize = 2, mM5.font[0].Ysize = 3;
font_width = mM5.font[0].Xsize * 16 + 8;
font_height = mM5.font[0].Ysize * 16;
for(int i = 0; i < max_pixels; i++){
str[i][0] = "め";
str[i][1] = "り";
str[i][2] = "ー";
str[i][3] = "く";
str[i][4] = "り";
str[i][5] = "す";
str[i][6] = "ま";
str[i][7] = "す";
}
TaskHandle_t th; //マルチタスクハンドル定義
xTaskCreatePinnedToCore(Task1, "Task1", 8192, NULL, 10, &th, 0); //マルチタスク起動
}
//*************************************************
void loop(){
int i;
int ch = 0; //DMX channel number
uint16_t x0 = 0, y0 = 0;
int str_num = 0;
for(i = 0; i < max_pixels; i++){
c0 = ch++, c1 = ch++, c2 = ch++;
if((dmx[c0] != lcd_px[c0]) || (dmx[c1] != lcd_px[c1]) || (dmx[c2] != lcd_px[c2])) {
lcd_px[c0] = dmx[c0];
lcd_px[c1] = dmx[c1];
lcd_px[c2] = dmx[c2];
x0 = i * font_width - 320 * (uint16_t)floor((double)i / 8.0);
y0 = font_height * (uint16_t)floor((double)i / 8.0);
str_num = (uint8_t)round( (double)lcd_px[c0] * str_per_dmx );
mM5.font[0].x0 = x0; mM5.font[0].y0 = y0;
mM5.font[0].colorRGB255( lcd_px[c0], lcd_px[c1], lcd_px[c2] );
mM5.disp_fnt[0].dispText( mM5.font[0], str[i][str_num] );
}
}
}
//************ マルチタスクループ ******************
void Task1( void *pvParameters ){
while(1){
artnet.read();
delay(1); //マルチタスクの場合、これ絶対必要!
}
}
//***************************************
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 onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data){
int ch; //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];
}
}
}
コンパイル書き込み実行
では、2台の M5Stack と ESP32-DevKitC をコンパイル書き込み実行させてみてください。
その時、Arduino core for the ESP32 の「Core Debug Level」は「なし」にしておいてください。
そして、Wi-Fiルーターを起動して、ESP32 を接続しておきます。
Jinx ソフトウェアの DMX Patch ( パッチ )
では、Jinx の Art-Net DMX Patch の割り当てがちょっと特殊です。
Patch 方法については、こちらの記事も合わせてご参照ください。
下の図を見てください。
M5Stack と連動させるために、中央のLEDテープ4列の上に NeoPixel Ring 12連を Patch ( パッチ )しました。
こうすると、Jinx ソフトウェアとの相性が良くなり、全体を一画面としてコントロールできます。
光ファイバーを使わず、LED テープを5列にしても良いですし、M5Stack 側を4列にしても良いですし、これは自由に設定してみてください。
Jinx 上の Patch ( パッチ )は以下のような感じです。
まず、Jinx の Matrix Options は以下のように、28×5 pixel とします。
次に、「Output Devices」設定で「Add」をクリックして、以下のように3つのUniverseを作ります。
Universe #1 : DMX 120ch
Universe #2 : DMX 180ch
Universe #3 : DMX 120ch
そうすると、「Output Devices」画面は以下のようになります。
次に、「Output Patch」設定では、まず、左側の M5Stack 用のパッチをします。
以下のように「Fast Patch」をクリックして、Universe #0 で設定します。
「OK」すると、以下のようになります。
同じように、今度は中央の NeoPixel Ring と LED テープのパッチをします。
ここの Patch Area は 12:5 であることに注意してください。
下図の様に Universe #1 で設定します。
すると、下図のようになります。
次に同じように右側 M5Stack 用のパッチを下図のようにします。
すると下図のようになり、全てパッチ完了です。
Jinx ソフトウェア実行
では、Jinx ソフトウェアで、Start Output させてみてください。
最初に紹介した動画のように表示されればOKです。
シーンを記憶して、Chase で各シーンを自動ループ再生すれば、立派なイルミネーションになると思います。
ただ、テキストスクロールについては、LED テープの列がもっと多くないと読み取りは厳しいですね。
ところで、以前、Twitter でツイートしましたが、M5Stack 1台を 40×30 pixel として、2台を横に置いて、80×30 pixel として Jinx で動かしたこともありました。
でも、これってあまり利用する場面が思いつかず、ブログ記事にはしませんでした。
要するに、Art-Net DMX を使えば、いろんな制御ができるということです。
TouchDesigner などを使えばもっといろんなことができそうですね。
編集後記
どうでしょうか。
今回はパソコンで3台の ESP32 を制御して、一つの画面として卓上イルミネーションを作ってみました。
やっぱりスタンドアローンでパソコンなしで動作させるようにしてみたいですね。
いつかチャレンジしてみたいとは頭の隅で思っています。
ということで、今年の私の小さいクリスマスイルミネーション制作はこれで完結とします。
さぁ、そろそろ大掃除しなきゃ。
ではまた・・・。
コメント