ESP32を使ってクリスマスイルミネーションを無線プログラムした

メリークリスマス!(唐突)

クリスマスも間近なので、家の前をちょっとだけにぎやかに
イルミネーションしてみました。
去年のイルミネーション制作記がこちら。
Arduino Nanoを使ってクリスマスイルミネーションを作る
去年はArduino NANOを使ったんですが、時間制御とプログラムの書き換えが面倒!
あと面倒な割に白の単色であんまり派手にならない。
今年はその辺をすべて解決すべくシステムを刷新しました!

去年からの変更点

LED 防水白色LEDテープ → WS2812Bで全灯個別フルカラー制御
制御 Arduino NANO → ESP32で時刻制御、無線による書き換え(OTA)に対応
加えてところどころ3Dプリントしたネタをおいてアクセントに。

この変更により去年よりも派手に、扱いは楽になりました。

去年の回路図

今年の回路図

ハードの部分

といっても、回路図もめちゃくちゃシンプルですし、
特に難しいことはなかった…のですが、つまづいた点はありました。
意図しない所でのLEDが光ることがあって、それを回避するために
いろいろやった結果、WS2812Bの5VとGNDの間に1000uFの電解コンデンサを
挟むことで直りました、詳しくはわかりませんが…。
ちなみに5VはESP32からとっているので、1000uFなんか入れたら
突入電流がやばい!(コンデンサが充電されるまでは5V-GNDがショート状態)
というアドバイスもいただきましたが…これは壊れたら対処することにしましょう。

ESP32の信号レベルが3.3V、WS2812B自体の電源が5Vなので、
それによってなんか微妙になっているというアドバイスもいただきました。


今回適当設計をモットーにやっていたのでこれは変更しませんでしたが、
(はんだ付けやり直すのが面倒だったとも言う)
来年も同じシステムでやるとしたらレベルシフタで信号の3.3Vを5Vに
上げて動かしてみようと思います。


LEDの電源はESP32の5V端子から取っています。
この5VピンはmicroUSBからの5Vをダイオードを通って出てきているのですが、
ダイオードの定格が1Aなのでそれまでしか使えません。
ちなみにWS2812BのLEDの数は150個です。
全部フルで点灯すると10A近く使います。
というわけで、出力は全体の1/10にソフト側で抑えて使うことにしました。
一度プログラムを間違えてフル出力させてしまいましたが、
ダイオードがぶっ壊れる前に3Aまで供給できるACアダプターが根を上げて沈黙しました。壊れなくてよかった。

ともあれ、無事動きました。

ソフトの部分

開発はArduino IDEでやりました。

#define span 40

#include 
#include 
#include 
#include 
#include "time.h"

const char* ssid = "あなたのSSID";
const char* password = "あなたのパスワード";

#define JST     3600* 9

#include 
const uint16_t PixelCount = 150;
const uint8_t PixelPin = 14;

#define colorSaturation 80
#define NUMPIXELS      150
NeoPixelBus strip(PixelCount, PixelPin);

void setup() {

  Serial.begin(115200);
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }
  configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");

  // Port defaults to 3232
  // ArduinoOTA.setPort(3232);

  // Hostname defaults to esp3232-[MAC]
  // ArduinoOTA.setHostname("myesp32");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA
  .onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH)
      type = "sketch";
    else // U_SPIFFS
      type = "filesystem";

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    Serial.println("Start updating " + type);
  })
  .onEnd([]() {
    Serial.println("\nEnd");
  })
  .onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  })
  .onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });

  ArduinoOTA.begin();

  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  strip.Begin();
  strip.Show();

}

void loop() {
  ArduinoOTA.handle();
  time_t t;
  struct tm *tm;
  t = time(NULL);
  tm = localtime(&t);

  if (tm->tm_hour < 23 && tm->tm_hour >= 17 )
  {
    fadeinout(10);
    fadecolor(10);
    slide(50);
    rndslide(30);
    rainbowslide(30);
  }

  if (tm->tm_hour < 6 || tm->tm_hour >= 23 )
  {
    nightmode(20);
  }

}

//
void fadeinout(int count) {  //フェードイン・アウト↓
  int br =  20;//最大輝度
  for ( int l = 0; l < count; l ++) {
    int r = random(br);
    //    int r = 0;
    int g = random(br);
    int b = random(br);
    int rr, gg, bb;
    for ( int i = 0; i < br ; i ++ ) {
      rr = map(i, 0, br, 0, r);
      gg = map(i, 0, br, 0, g);
      bb = map(i, 0, br, 0, b);
      for ( int j = 0; j < 150 ; j ++)
      {
        RgbColor c(rr, gg, bb);
        strip.SetPixelColor(j, c);
      }
      RgbColor c(rr * 2, gg * 2, bb * 2);
      strip.SetPixelColor(6, c);//球1
      strip.SetPixelColor(26, c);//球2
      strip.SetPixelColor(46, c);//球3
      strip.SetPixelColor(66, c);//球4
      for ( int k = 77; k < 87; k ++) {
        strip.SetPixelColor(k, c);  //猫
      }
      for ( int k = 105; k < 150; k ++) {
        strip.SetPixelColor(k, c);  //ツリー
      }
      strip.Show();
      ArduinoOTA.handle();
      delay(span);
    }

    for ( int i = br; i >= 0 ; i -- ) {
      rr = map(i, 0, br, 0, r);
      gg = map(i, 0, br, 0, g);
      bb = map(i, 0, br, 0, b);
      for ( int j = 0; j < 150 ; j ++)
      {
        RgbColor c(rr, gg, bb);
        strip.SetPixelColor(j, c);
      }
      RgbColor c(rr * 2, gg * 2, bb * 2);
      strip.SetPixelColor(6, c);//球1
      strip.SetPixelColor(26, c);//球2
      strip.SetPixelColor(46, c);//球3
      strip.SetPixelColor(66, c);//球4
      for ( int k = 77; k < 87; k ++) {
        strip.SetPixelColor(k, c);  //猫
      }
      for ( int k = 105; k < 150; k ++) {
        strip.SetPixelColor(k, c);  //ツリー
      }
      strip.Show();
      delay(span);
      ArduinoOTA.handle();
    }
  }
}       //フェードイン・アウト↑

void fadecolor(int count) {  //フェード変化↓
  int br = 20;//最大輝度

  //フェードイン
  int r = random(br);
  int g = random(br);
  int b = random(br);
  int rr, gg, bb;
  for ( int i = 0; i < br ; i ++ ) {
    rr = map(i, 0, br, 0, r);
    gg = map(i, 0, br, 0, g);
    bb = map(i, 0, br, 0, b);
    for ( int j = 0; j < 150 ; j ++)
    {
      RgbColor c(rr, gg, bb);
      strip.SetPixelColor(j, c);
    }
    RgbColor c(rr * 2, gg * 2, bb * 2);
    strip.SetPixelColor(6, c);//球1
    strip.SetPixelColor(26, c);//球2
    strip.SetPixelColor(46, c);//球3
    strip.SetPixelColor(66, c);//球4
    for ( int k = 77; k < 87; k ++) {
      strip.SetPixelColor(k, c);  //猫
    }
    for ( int k = 105; k < 150; k ++) {
      strip.SetPixelColor(k, c);  //ツリー
    }
    strip.Show();
    ArduinoOTA.handle();
    delay(span);
  }

  for ( int l = 0 ; l < count ; l ++) {
    //フェード
    int oldr = rr;
    int oldg = gg;
    int oldb = bb;
    int r = random(br);
    int g = random(br);
    int b = random(br);
    for ( int i = 0; i < br ; i ++ ) {
      rr = map(i, 0, br, oldr, r);
      gg = map(i, 0, br, oldg, g);
      bb = map(i, 0, br, oldb, b);
      for ( int j = 0; j < 150 ; j ++)
      {
        RgbColor c(rr, gg, bb);
        strip.SetPixelColor(j, c);
      }
      RgbColor c(rr * 2, gg * 2, bb * 2);
      strip.SetPixelColor(6, c);//球1
      strip.SetPixelColor(26, c);//球2
      strip.SetPixelColor(46, c);//球3
      strip.SetPixelColor(66, c);//球4
      for ( int k = 77; k < 87; k ++) {
        strip.SetPixelColor(k, c);  //猫
      }
      for ( int k = 105; k < 150; k ++) {
        strip.SetPixelColor(k, c);  //ツリー
      }
      strip.Show();
      ArduinoOTA.handle();
      delay(span);
    }
  }

  //フェードアウト
  int oldr = rr;
  int oldg = gg;
  int oldb = bb;
  for ( int i = br; i >= 0 ; i -- ) {
    rr = map(i, 0, br, 0, oldr);
    gg = map(i, 0, br, 0, oldg);
    bb = map(i, 0, br, 0, oldb);
    for ( int j = 0; j < 150 ; j ++)
    {
      RgbColor c(rr, gg, bb);
      strip.SetPixelColor(j, c);
    }
    RgbColor c(rr * 2, gg * 2, bb * 2);
    strip.SetPixelColor(6, c);//球1
    strip.SetPixelColor(26, c);//球2
    strip.SetPixelColor(46, c);//球3
    strip.SetPixelColor(66, c);//球4
    for ( int k = 77; k < 87; k ++) {
      strip.SetPixelColor(k, c);  //猫
    }
    for ( int k = 105; k < 150; k ++) {
      strip.SetPixelColor(k, c);  //ツリー
    }
    strip.Show();
    delay(span);
    ArduinoOTA.handle();
  }
}  //フェード変化↑

void relay() { //リレー
  int oldi = 0;
  for ( int i = 0; i < 214; i ++) {
    for ( int br = 0 ; br <= 16; br ++) {
      int p = i - br;
      strip.SetPixelColor(i - br, RgbColor(0, 64 - (br * 4) , 64 - (br * 4)));
      strip.SetPixelColor(i - 32 - br, RgbColor(0, 64 - (br * 4) , 64 - (br * 4)));
      strip.SetPixelColor(i - 64 - br, RgbColor(0, 64 - (br * 4) , 64 - (br * 4)));
    }
    strip.Show();
    delay(200);
    ArduinoOTA.handle();
    oldi = i;
  }
}

void nightmode(int count) {  //ナイトモード↓
  int br = 10;//最大輝度
  for ( int l = 0; l < count; l ++) {
    int r = br;
    //    int r = 0;
    int g = br;
    int b = br;
    int rr, gg, bb;
    for ( int i = 0; i < br ; i ++ ) {
      rr = map(i, 0, br, 0, r);
      gg = map(i, 0, br, 0, g);
      bb = map(i, 0, br, 0, b);
      for ( int j = 0; j < 150 ; j ++)
      {
        RgbColor c(rr / 2, gg / 2, bb / 2);
        strip.SetPixelColor(j, c);
      }
      RgbColor c(rr, gg, bb);
      strip.SetPixelColor(6, c);//球1
      strip.SetPixelColor(26, c);//球2
      strip.SetPixelColor(46, c);//球3
      strip.SetPixelColor(66, c);//球4
      for ( int k = 77; k < 87; k ++) {
        strip.SetPixelColor(k, c);  //猫
      }
      for ( int k = 105; k < 150; k ++) {
        strip.SetPixelColor(k, c);  //ツリー
      }
      strip.Show();
      ArduinoOTA.handle();
      delay(40 * 4);
    }

    for ( int i = br; i >= 0 ; i -- ) {
      rr = map(i, 0, br, 0, r);
      gg = map(i, 0, br, 0, g);
      bb = map(i, 0, br, 0, b);
      for ( int j = 0; j < 150 ; j ++)
      {
        RgbColor c(rr / 2, gg / 2, bb / 2);
        strip.SetPixelColor(j, c);
      }
      RgbColor c(rr, gg, bb);
      strip.SetPixelColor(6, c);//球1
      strip.SetPixelColor(26, c);//球2
      strip.SetPixelColor(46, c);//球3
      strip.SetPixelColor(66, c);//球4
      for ( int k = 77; k < 87; k ++) {
        strip.SetPixelColor(k, c);  //猫
      }
      for ( int k = 105; k < 150; k ++) {
        strip.SetPixelColor(k, c);  //ツリー
      }
      strip.Show();
      delay(40 * 4);
      ArduinoOTA.handle();
    }
  }
}       //ナイトモード↑

void slide(int count) { //横にスライド
  int br = 25;
  int rr = random(br);
  int gg = random(br);
  int bb = random(br);
  for (int l = 0; l < count; l ++) {
    for ( int i = 0; i < 3; i ++) {
      for ( int j = 0; j <= 50; j ++) {
        strip.SetPixelColor(3 * j + i , RgbColor(rr, gg, bb));
        strip.SetPixelColor(3 * j + i - 1 , RgbColor(rr / 3, gg / 3, bb / 3));
        strip.SetPixelColor(3 * j + i - 2 , RgbColor(0, 0, 0));
      }
      strip.Show();
      ArduinoOTA.handle();
      delay(span * 3);
      rr = rr + random(5) - 2;
      if (rr > 25) rr = 25;
      if (rr < 0 ) rr = 0;
      gg = gg + random(5) - 2;
      if (gg > 25) gg = 25;
      if (gg < 0 ) gg = 0;
      bb = bb + random(5) - 2;
      if (bb > 25) bb = 25;
      if (bb < 0 ) bb = 0;
      if ( rr + gg + bb < 30 ) {
        rr = rr + 2;
        gg = gg + 2;
        bb = bb + 2;
      }
    }
  }
}

void rndslide(int count) { //ランダムスライド
  int br = 25;
  int rr[150];
  int gg[150];
  int bb[150];

  for ( int i = 0 ; i < 150 ; i ++) {
    rr[i] = random(br);
    gg[i] = random(br);
    bb[i] = random(br);
    strip.SetPixelColor(i , RgbColor(rr[i], gg[i], bb[i]));
  }
  strip.Show();
  delay(span * 3);
  ArduinoOTA.handle();
  for ( int l = 0 ; l < count * 3 ; l ++) {
    for ( int i = 150 ; i > 0 ; i --) {
      rr[i] = rr[i - 1];
      gg[i] = gg[i - 1];
      bb[i] = bb[i - 1];
      strip.SetPixelColor(i , RgbColor(rr[i], gg[i], bb[i]));
    }
    rr[0] = random(br);
    gg[0] = random(br);
    bb[0] = random(br);
    strip.SetPixelColor(0 , RgbColor(rr[0], gg[0], bb[0]));
    strip.Show();
    delay(span * 3);
    ArduinoOTA.handle();
  }
}

void rainbowslide(int count) { //レインボースライド
  int br = 25;
  int rr[7];
  int gg[7];
  int bb[7];
  rr[0] =     br;  gg[0] =          0;  bb[0] =      0; //赤
  rr[1] =     br;  gg[1] = br * 2 / 3;  bb[1] =      0; //オレンジ
  rr[2] =     br;  gg[2] =         br;  bb[2] =      0; //黄色
  rr[3] =      0;  gg[3] =     br / 2;  bb[3] =      0; //緑
  rr[4] =      0;  gg[4] =         br;  bb[4] =     br; //水色
  rr[5] =      0;  gg[5] =          0;  bb[5] =     br; //青
  rr[6] = br / 2;  gg[6] =          0;  bb[6] = br / 2; //紫

  for (int l = 0; l < count; l ++) {
    for ( int i = 0; i < 7; i ++) {
      for ( int j = 0; j <= 23; j ++) {
        for ( int k = 0; k < 7; k ++) {
          strip.SetPixelColor(7 * j + i + k - 7 , RgbColor(rr[k], gg[k], bb[k]));
        }
      }
          strip.Show();
      delay(span * 3);
      ArduinoOTA.handle();
    }
  }
}

一応ちょっとだけコードを整頓したので
何か間違えて動かなくなっていたらすみません、自力で頑張ってください。
WS2812Bの150個用です。

使用したWS2812B用のライブラリは「NeoPixelBus」というやつです。
NeoPixelのライブラリだとなんかチラつきが直りませんでした。

loopの最初のほうにある

  time_t t;
  struct tm *tm;
  t = time(NULL);
  tm = localtime(&t);

  if (tm->tm_hour < 23 && tm->tm_hour >= 17 )
  {
    fadeinout(10);
    fadecolor(10);
    slide(50);
    rndslide(30);
    rainbowslide(30);
  }

  if (tm->tm_hour < 6 || tm->tm_hour >= 23 )
  {
    nightmode(20);
  }

で時刻による動作制御をしています。
17時になったらメインの演出、
23時になったらナイトモード(おとなしめにふわふわするだけ)
6時に消灯です。省エネのために消灯中はスリープさせようかとも思いましたが、
その間書き換えできないのもなぁと思ってそのまま起こしておきました。

無線での書き換えにはArduinoOTAというライブラリを使ったのですが、
ArduinoOTA.handle()という関数のタイミングで
書き換えの待機をするようで、イルミネーションみたいに
ワンサイクルが長い内容だと先頭に一つだけ入っているだけでは
いつまでたっても書き換えのタイミングが来ずエラーになります。
ならばと割り込みでArduinoOTA.handleを呼び出すと
書き換え中に切断されてエラーになります。
仕方がないので各所にArduinoOTA.handleをちりばめて
豆に呼び出すことにしました。もうちょっとスマートにならんかなぁ。

あとは特別難しいことはしていませんし、
多分強い人たちから見るととても無駄の多い書き方だと思うので
そんな人たちは生暖かく見守っていただけるとありがたいです。

3Dプリンターでオブジェを作る

去年はまだ3Dプリンターを持っていなかったので
テープLED1本で頑張ったわけですが、やはりオブジェがあったほうが映えるでしょう。

とまあ色々苦労はあったのですが…


めちゃくちゃ見栄えはよくなったのでみんなもっと3Dプリントしよう。

そして出来上がったものがこちらになります

もう数本WS2812Bを増やすともっと派手派手しくなっていいかもしれませんね。
まあうち通りに面しているわけじゃないので隣近所の人たちくらいしか見てくれませんけども!
いいの!自己満足だから!

おかげでOTAの使い方を少しは理解したし、
やはり毎年新しい方法にチャレンジすべきだな!
メリークリスマス!


作成者: necobit(ねこびっと)

音楽と電子工作でMIDI寄りものづくりをするユニット。MIDI制御基板の開発・販売、製品作例として自動演奏化楽器の展示・音とメカのパフォーマンスを行なっています。

1件のコメント

  1. 素敵ですう。

    2020年も期待してます。
                   de 野兎

マセヤスオ へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください