2017年4月8日土曜日

フルカラーLED WS2812 の使い方

 
おもしろいです WS2812。
ということで、この投稿ではWS2812をArduinoから遊ぶ方法について私なりのまとめというか備忘録として書いておきます。

WS2812というのは、5mm角のパッケージの中にRGBそれぞれのLEDチップとそれを制御するICが乗った、なんといいますか、LEDモジュールですかね。そんなものです。
Google先生にWS2812の画像を探してもらうと、こんなものが出てきます。


どうです?これ見ただけでもなんかわくわくしますよね。長かったり丸かったり面だったりテープだったりと多彩な使われ方をしております。これをArduino とかんたんなスケッチで自在に光らせることができるのです。

WS2812のデータシートはこちらにあります。
Features and Benefitsをちらと見てみますと、やや怪しい英語が(笑

  • Control circuit and RGB chip are integrated in a package of 5050 components, form a complete control of pixelpoint.
  • Built-in signal reshaping circuit, after wave reshaping to the next driver, ensure wave-form distortion not accumulate.
  • Built-in electric reset circuit and power lost reset circuit. 
  • Each pixel of the three primary color can achieve 256 brightness display, completed 16777216 color full colordisplay, and scan frequency not less than 400Hz/s. 
  • Cascading port transmission signal by single line. 
  • Any two point the distance more than 5m transmission signal without any increase circuit. 
  • When the refresh rate is 30fps, low speed model cascade number are not less than 512 points, high speed mode notless than1024 points. 
  • Send data at speeds of 800Kbps. 
  • The color of the light were highly consistent, cost-effective..

ポイントは4番目あたりから下かな。RGBの三原色ともに256段階の輝度変化ができるので、16万色出せるよ。とか、付加回路無しで5m以上のデータ伝送できるよ。(多分そんな意味。英語怪しすぎ)とか、1024個つないでも30fpsで駆動できるんだぜ。といったことが書いてあります。イルミネーションと共に、ディスプレイとしての利用も想定してある感じ、というか30fpsとか書いてあるあたりからディスプレイとして使えよという意気込みが伝わってきます(笑

で、データシートの後ろの方には「んじゃどうやって色を変えるか」が書いてあります。
この辺り。


ざっと書くと、
まず1bitのデータを送る時間を1.25usに決めて、その中でH/Lを変化させる。その時にHの時間が短い時は0、長い時は1と約束する。

で、データ送り出し元に最も近いWS2812をピクセル1(D1)とし、以下離れるにしたがってピクセル2(D2)、ピクセル3(D3).....とする。
データは上に書いてあった通りRGBいずれも256段階の変化なので、それぞれ8bitの情報量。よって、1ピクセルあたり24bitの情報で一つの発光色が決まることになる。
こっからがなかなか荒っぽくて、このピクセル毎の24bitデータを、ヘッダもフッタもチェックサムもなんも無しにとにかく繋いでだーっと流す。
で、流れてきたデータをピクセル1(下の図のD1)が頭の24bit抜いて残りをピクセル2(下のD2)に渡す、ピクセル2は同じように頭の24bitを抜いて次に渡す。といった具合。下の図では24bitx3セットしかデータがないので、D4は何ももらえない。つまり状態が変わらない(=点いてたらその色で点きっぱなし)。データの流れが50usec以上途絶えたらそれまでの話はご破算になって、次にデータが流れて来たらその頭の24bitはピクセル1に対する情報と解釈される。


んで50us以上長いあいだデータが来ないときは「あ、もう終わりなわけね」と話を白紙に戻して最初からやり直すことにする。といった感じです。

ちょっと前の投稿ではテスト用紙配りで説明しましたが、今回はそうめん流しで説明しましょう(笑
そうめん流しの竹に沿ってピクセル君たちがずらっと並びます。先生が上流からそうめんを240本流します。ピクセル1君はその中から24本だけ麺をすくいます。ピクセル2君も24本すくいます。以下同じで、この場合はピクセル10君までがそうめんを食べられます。
これは単純な場合です。面倒な話になると、
先生がそうめんを24000本流します。ピクセル1君はやはり24本すくってそれを食べます。で食べ終わって前を見るとまだそうめんが流れています。24000本も流しますからそう簡単に流れていかないのです。ですが、ピクセル1君はそのそうめんをもう一度すくってはいけません。これはルールです。じゃあいつ次のそうめんをすくってよいのか。それはそうめんが流れ終わってから50usecたった後に再びそうめんが流れてくる場合です。ちょっとくらいそうめんの流れが途切れても、途切れた時間が50usecに満たない場合はそれをすくってはいけません。それは自分より後ろの、たぶんピクセル101君が食べるそうめんなのです。

で、すくったそうめん24本の中身はどうなっているのかというと、こうなっています。


ということで、こうやって見ると非常に面倒な制御が必要に見えますが、これらをまとめてライブラリ化がなされていますので実際に使うときは全く気にする必要がありません。
それがAdafruit_NeoPixelライブラリです。ありがたいことです。

ライブラリをArduino のスケッチで使うためには、インクルードする必要がありますが、IDEからZIPファイル指定するだけなので簡単です。やり方の公式はこちら。他にもGoogle先生に聞けばいくらでも教えてくれます。
Adafruit_NeoPixelライブラリをちらと覗いてみます。keywords.txtにはこういう記載があります。

#######################################
# Methods and Functions
#######################################

setPixelColor KEYWORD2
setPin KEYWORD2
setBrightness KEYWORD2
numPixels KEYWORD2
getPixelColor KEYWORD2
Color KEYWORD2

使える命令はこれにつきます。特にsetPixelColorですね。
説明はAdafruitのここにありますので、ざっと見ておくと良いです。Tips的に役立ちそうなのは、

setBrightness() was intended to be called once, in setup(), 

setBrightnessと言われると「はいはい明るさの設定ね」と思いがちです。いや実際そうなのですが、これは都度都度使うものではなく、スケッチ全体に対して最初に「上限の明るさはこれね」といったことを決める為のものです。そもそもスケッチ内でいつでもRGB各LEDに対して0-255の明るさ設定ができるのですから、Brightnessを別に設定する必要はないのです。考え方として、NeoPixelの使い方は「色と明るさを指定する」のではなく「RGBそれぞれの明るさを決めることで色が決まる」方式であることを理解することです。
他には、

Can I have multiple NeoPixel objects on different pins?


というのも役立ちそうです。
これは「一度に二つ以上のWS2812グループを駆動することができますか」という質問と同義です。答えは「できる」です。
ライブラリの使用を宣言したら、何らかの名前を付けたオブジェクトを作ります。これを複数作ればよいのです。サイバーコンソールではライトバーとUVクリアリングの二つのインスタンスを作っています。一つのスケッチの中で複数のインスタンスを使うことで協調動作をさせることが楽にできます。逆に「バラバラに動いているように見せる」のは難しくなります。

Can I connect multiple NeoPixel strips to the same Arduino pin?


これも参考になります。「Arduinoの一つのピンにたくさんWS2812のグループをぶらさげて同じ動作をさせたいけどできる?」という質問です。
サイバーコンソールではシートサイドのバーを左右2本使っていますが、二本の入力は同じピンに繋いでいて問題なく動作しています。
上記質問に対する答えは「まあ、4つくらいまでならぶら下げてもいいかな」です。さらに親切なことに「4つ以上ぶら下げたいときは、まず一つのピンに4つのピクセル(例えばABCD)をぶら下げて、その4つにそれぞれ4つまでぶら下げればいいよ。」とあります。つまりAの下に4つ、Bの下に4つ....といった具合です。この場合は16グループまで一つのピンにぶら下げられることになります。で、そんなつなぎ方をするときは枝分かれの最初の部分(ABCD)は「バッファとしてのみ使って実際には光らせないといいよ」とあります。ABCDはピンから見ると一番近いピクセル(#0)なので、「最初のピクセルはいつも消灯、それ以降のピクセルはこれこれ、」といった命令を送れとのこと。
あとは「Pixels Gobble RAM」なんて記述もありますが、私がする程度のスケールでは関係ありません。フルHDのディスプレイとか作る人は考えとくべき。

では、今回作った動作について説明していきます。
スケッチを一度に全部書くとわけわからんくなるので、頭から順に切り出していきます。
あと、LCD駆動とか関係ないのも全部落としてWS2812駆動の部分だけにしています。

#include <Adafruit_NeoPixel.h>

何はともあれこれです。「NeoPixel使うんだぜ」宣言です。

Adafruit_NeoPixel SideBar = Adafruit_NeoPixel(20, 8, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel ClearRing = Adafruit_NeoPixel(4, 9, NEO_GRB + NEO_KHZ800);

二つのインスタンスを作ります。サイドバーとUVクリアリングです。サイドバーは「20個のピクセル、8番ピン」UVクリアリングは「4ピクセル、9番ピン」なんとなくわかりますね。

セットアップ部分。ここからはコメントで書いていきます。
void setup() {

//beginはpinMode(pin,mode)みたいなもんです。おまじない。
  SideBar.begin();
  ClearRing.begin();
//show命令でデータが送り出されます。ここではまだデータがセットされていないので、全ピクセルが輝度0、つまり消灯します。初期化動作です。
  SideBar.show();
  ClearRing.show();

}

セットアップはこんだけ。
実際の動作は関数の形で動作ごとにいくつか作りましたので、単純なものから見ていきますが、その前に全体を通じて使っている色指定関数を説明します。それはWheel関数です。Adafruitライブラリについてくるサンプルスケッチの中に入っています。非常に便利なので私もそのまま使わせていただいております。
こちら。

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return SideBar.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return SideBar.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return SideBar.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

面倒そうですが、ビジュアルで見ると結構簡単です。
色の指定には様々方法がありますが、その中の一つに色環による方法があります。
色環とはこれのことです。


Wheelという名称からも想像できるように、この関数は上の色環を回転方向に256分割し、その値で色を決めようというものです。例えば上の絵で12時方向を0として右回りに256(8bit)分割したとすると、0は黄色を、64は緑を、128は紫?かな、192はオレンジ
を、といった具合に決まることになります。
ただ、ここで色が決まっても、これをWS2812のRGBそれぞれ8bitのLEDの輝度に変換してやらないと実際の発色はできないですよね。この変換をやってくれるのがWheel関数なのです。
ということで、Wheelの引数はByte型、つまり8bit=256段階であって、これが上の絵のどれかの色の位置を決めます。するとこの関数は宣言にあるようにunit32_t型で値を返します。これは32bitの値でRの8bit、Gの8bit、Bの8bit、そして実際は使いませんが、Wの8bitからなります。つまり「色を指定すれば、WS2812でその色を出すための各LEDの輝度を教えてくれる」という非常に便利な関数です。
これを使うことで、例えば8bitのランダム値を引数として与えれば「色環ルーレットをくるっと回して止まったところの色を出す」といったようなランダムな色変更が簡単にできるようになりますし、8bitをインクリメントしていけば色環をたどるように順に色を変更することも簡単になります。素晴らしい。

ではいくつかの動作例を見ていきます。まず簡単なColorGlowです。
こんな動作。


スケッチは以下の通り。
発光色の指定はアナログポートに接続したボリュームから読み込む10bitアナログ値をWheel用に8bitに圧縮(map関数)して行っています。

//全ピクセルがPWMで輝度上下する
void ColorGlow(byte red, byte green, byte blue, float Step, int SpeedDelay, int ReturnDelay) {

  //ボリュームから色指定を読み込む
  //ボリュームが0の時は白になる
  if (analogRead(colorSelect) != 0) {
    uint32_t desigColor = Wheel(map(analogRead(colorSelect), LowerRange, UpperRange, 0, 255));
    //輝度変更のためにRGBに分解しておく
    green = lowByte(desigColor);
    blue = lowByte(desigColor >> 8);
    red = lowByte(desigColor >> 16);
  }
  //輝度を上げていく
  for (float i = 0; i < Step + 1 ; i++) {
    //サインカーブで輝度が上下するようにする
    float ru = ((sin(3.14 * 1.6 + (3.14 / Step) * i) + 1) * 127) / 255;
    //SideBarUVクリアリングに色をセット
    for (int j = 0; j < 20 ; j++) {
      SideBar.setPixelColor(j, red * ru, green * ru, blue * ru);
    }
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * ru, green * ru, blue * ru);
    }
    SideBar.show();
    ClearRing.show();
    delay(SpeedDelay);
  }

  //設定時間待って次の動作へ
  delay(ReturnDelay);

  //輝度を下げていく
  for (float i = Step; i > 1 ; i--) {
    float rd = ((sin(3.14 * 1.6 + (3.14 / Step) * i) + 1) * 127) / 255;
    for (int j = 0; j < 20 ; j++) {
      SideBar.setPixelColor(j, red * rd + 1, green * rd + 1, blue * rd + 1);
    }
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * rd + 1, green * rd + 1, blue * rd + 1);
    }
    SideBar.show();
    ClearRing.show();
    delay(SpeedDelay);
  }

  //別に書いている全部消し関数を呼び出す。(ぜんぶに0セットして.showするだけ)
  //  turnOffBar();
  //  turnOffRing();

  //設定時間待って次の動作へ
  delay(ReturnDelay);
}

ポイントは
    //輝度変更のためにRGBに分解しておく
のあたりですかね。
先のWheelは便利な関数ですが、戻り値が32bitでRGBの値が一塊になっています。これはこれで便利なのですが、ColowGlowのように「そのままの色でぼわーっとあかるくしたり暗くしたりする」場合にはどうしてもRGB各LEDの個別制御が必要になりますので、一旦32bitの塊をRGBそれぞれ8bitずつにばらしておきたいわけです。そんなことをやっています。
あとは、
    //サインカーブで輝度が上下するようにする
これ。けっこう試行錯誤した結果ここに落ち着いています。が、まだ満足ではないです。
LEDを「自然な感じに」明るさを上下させるのは結構難しいです。RGBの輝度256段階はPWMのタイミングコントロールですので、光エネルギー的にはリニアな変化をします。一方で人間の感覚は対数的に反応します。これは自然現象の広いダイナミックレンジに対応するための進化の結果ともいえます。ので、対数的な感覚のところにリニアな変化を持ち込むと「急に明るくなる」「明るいまんまで変化が少ない」といった印象を与えることになります。このようなことは別に視覚に限られたことではなく、聴覚でも同じです。ので、ボリュームには抵抗値がリニアに変化するBカーブの他に人間の聴覚感に合わせたAカーブなんてのが存在します。この辺りもGoogle先生がいくらでも教えてくれます。

ということで、このへんの本質ではない部分をそぎ落としていくと、結局WS2812を駆動しているのは、

SideBar.setPixelColor(j, red * ru, green * ru, blue * ru);



SideBar.show();

だけということになります。SideBarはインスタンス名ですのでなんでもよくて、肝の肝は
.setPixelColor(引数); の部分です。これは「引数に従ってRGBの輝度をセットしろ」という命令です。引数の中は(ピクセル番号、R輝度、G輝度、B輝度)になっています。今回のサイドバーのようにピクセルが複数ある場合はfor~nextを回しながらピクセルの数だけRGB輝度をセットしていくことになります。で.showでそれを一気にそうめんにして流し込むというわけです。

もう一つ見てみましょう。OneWayと名付けた動作です。こちら。


void OneWay(byte red, byte green, byte blue, int Step, int SpeedDelay, int SpeedDelay01, int ReturnDelay) {

  //ボリュームから色指定を読み込む
  //ボリュームが0の時は白になる
  if (analogRead(colorSelect) != 0) {
    uint32_t desigColor = Wheel(map(analogRead(colorSelect), LowerRange, UpperRange, 0, 255));
    //輝度変更のためにRGBに分解しておく
    green = lowByte(desigColor);
    blue = lowByte(desigColor >> 8);
    red = lowByte(desigColor >> 16);
  }

  //長さを6灯にする
  //スタートラインとゴールラインをそれぞれ6灯分ずらすことで
  //下から現れて上へ消え去る
  for (int i = -6; i < 20 + 1; i++) {
    //消す
    turnOffBar();
    turnOffRing();

    //先頭ピクセルやや暗く
    SideBar.setPixelColor(i + 5, red / 10, green / 10, blue / 10);
    //メインの輝度
    SideBar.setPixelColor(i + 4, red, green, blue);
    //以下少しずつ落としていく
    SideBar.setPixelColor(i + 3, red / 3, green / 3, blue / 3);
    SideBar.setPixelColor(i + 2, red / 10, green / 10, blue / 10);
    SideBar.setPixelColor(i + 1, red / 30, green / 30, blue / 30);
    SideBar.setPixelColor(i , red / 100, green / 100, blue / 100);
    SideBar.show();
    delay(SpeedDelay01);
  }
//-----------------------------------------------------
  //輝度を上げていく
  for (int i = 0; i < Step + 1 ; i++) {
    //UVクリアリングに色をセット
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * i / Step, green * i / Step, blue * i / Step);
    }
    ClearRing.show();
    delay(SpeedDelay);
  }

  //設定時間待って次の動作へ
  delay(ReturnDelay);

  //輝度を下げていく
  for (int i = Step; i > 1 ; i--) {
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * i / Step, green * i / Step, blue * i / Step);
    }
    ClearRing.show();
    delay(SpeedDelay);
  }

  //一旦全部消して
  turnOffBar();
  turnOffRing();

  delay(ReturnDelay);
}

int=-6 なんてあたりはすごい邪道なことをやっているような気がしますが、ピクセルが20ですからまあお許しください。
ここでのポイントは、//-------------で区切られた上下でSideBarとClearRingという二つのインスタンスが協調しつつも別動作をしているところです。

ランダムな色変更も一例出しておきます。こちら。


//毎回ランダムな色で輝度上下する
//UVクリアリングも同じ動作
void RandomGlow(byte red, byte green, byte blue, int Step, int SpeedDelay, int ReturnDelay) {
  //乱数使うときのおまじない
  randomSeed(analogRead(3));

  //色環からランダムに色を選択
  uint32_t random_c = Wheel(random(0, 256));

  //random_cは32ビットの連続データなので、輝度変更のためにRGBに分解しておく
  //データの並びは下位から8bitずつGBRW
  green = lowByte(random_c);
  blue = lowByte(random_c >> 8);
  red = lowByte(random_c >> 16);

  //輝度を上げていく
  for (float i = 0; i < Step + 1 ; i++) {
    //サインカーブで輝度が上下するようにする
    float ru = ((sin(3.14 * 1.6 + (3.14 / Step) * i) + 1) * 127) / 255;
    //SideBarUVクリアリングに色をセット
    for (int j = 0; j < 20 ; j++) {
      SideBar.setPixelColor(j, red * ru, green * ru, blue * ru);
    }
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * ru, green * ru, blue * ru);
    }
    SideBar.show();
    ClearRing.show();
    delay(SpeedDelay);
  }

  //設定時間待って次の動作へ
  delay(ReturnDelay);

  //輝度を下げていく
  for (float i = Step; i > 1 ; i--) {
    float rd = ((sin(3.14 * 1.6 + (3.14 / Step) * i) + 1) * 127) / 255;
    for (int j = 0; j < 20 ; j++) {
      SideBar.setPixelColor(j, red * rd + 1, green * rd + 1, blue * rd + 1);
    }
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * rd + 1, green * rd + 1, blue * rd + 1);
    }
    SideBar.show();
    ClearRing.show();
    delay(SpeedDelay);
  }

  //一旦全部消して
  //  turnOffBar();
  //  turnOffRing();

  //設定時間待って次の動作へ
  delay(ReturnDelay);
}

お決まりの   randomSeed(analogRead(**)); が増えているだけで、あとは同じです。こんな簡単な一行でランダムな色変更ができるようになるのです。

最後に現状の全スケッチを下につけておきます。LCDの駆動、アナログボリュームによる動作の選定等いろいろ入っていますのでごちゃごちゃしていますが、参考になるところがありましたらお使いくださいませ。

#include <LiquidCrystal.h>
#include <Adafruit_NeoPixel.h>

#define option 0
#define colorSelect 1
#define typeSelect 2
#define switchLED 10

// initialize
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
Adafruit_NeoPixel SideBar = Adafruit_NeoPixel(20, 8, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel ClearRing = Adafruit_NeoPixel(4, 9, NEO_GRB + NEO_KHZ800);

int UpperRange = 1023;
int LowerRange = 0;

void setup() {

  digitalWrite(switchLED, HIGH);

  // define LCD size
  lcd.begin(16, 2);
  // show message
  lcd.print("Cyber Consol");
  // move cursor
  lcd.setCursor(0, 1);
  // show second row
  lcd.print("ilunmination Set");

  SideBar.begin();
  ClearRing.begin();
  SideBar.show();
  ClearRing.show();

  initialize();

}

void loop() {

  lcd.clear();

  switch (analogRead(typeSelect) / 170) {
    case 0:
      lcd.print("OneWay");
      OneWay(0xff, 0xff, 0xff, 50, 20, 100, 500);
      lcd.clear();
      break;
    case 1:
      lcd.print("KnightRider");
      KnightRider(0xff, 0xff, 0xff, 100, 300);
      lcd.clear();
      break;
    case 2:
      lcd.print("Color Glow");
      ColorGlow(0xff, 0xff, 0xff, 100 , 20, 10);
      lcd.clear();
      break;
    case 3:
      lcd.print("Random Glow");
      RandomGlow(0xff, 0xff, 0xff, 100 , 20, 10);
      lcd.clear();
      break;
    case 4:
      lcd.print("Slow Strobe");
      Strobe(0xff, 0xff, 0xff, 1, 500, 500);
      lcd.clear();
      break;
    case 5:
      lcd.print("Fast Strobe");
      Strobe(0xff, 0xff, 0xff, 1, 10, 300);
      lcd.clear();
      break;
    case 6:
      lcd.print("Random Strobe");
      RandomStrobe(0xff, 0xff, 0xff, 1, 200, 200);
      lcd.clear();
      break;
  }

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// RandomGlow
//

//毎回ランダムな色で輝度上下する
//UVクリアリングも同じ動作
void RandomGlow(byte red, byte green, byte blue, int Step, int SpeedDelay, int ReturnDelay) {
  //乱数使うときのおまじない
  randomSeed(analogRead(3));

  //色環からランダムに色を選択
  uint32_t random_c = Wheel(random(0, 256));

  //random_cは32ビットの連続データなので、輝度変更のためにRGBに分解しておく
  //データの並びは下位から8bitずつGBRW
  green = lowByte(random_c);
  blue = lowByte(random_c >> 8);
  red = lowByte(random_c >> 16);

  //輝度を上げていく
  for (float i = 0; i < Step + 1 ; i++) {
    //サインカーブで輝度が上下するようにする
    float ru = ((sin(3.14 * 1.6 + (3.14 / Step) * i) + 1) * 127) / 255;
    //SideBarUVクリアリングに色をセット
    for (int j = 0; j < 20 ; j++) {
      SideBar.setPixelColor(j, red * ru, green * ru, blue * ru);
    }
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * ru, green * ru, blue * ru);
    }
    SideBar.show();
    ClearRing.show();
    delay(SpeedDelay);
  }

  //設定時間待って次の動作へ
  delay(ReturnDelay);

  //輝度を下げていく
  for (float i = Step; i > 1 ; i--) {
    float rd = ((sin(3.14 * 1.6 + (3.14 / Step) * i) + 1) * 127) / 255;
    for (int j = 0; j < 20 ; j++) {
      SideBar.setPixelColor(j, red * rd + 1, green * rd + 1, blue * rd + 1);
    }
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * rd + 1, green * rd + 1, blue * rd + 1);
    }
    SideBar.show();
    ClearRing.show();
    delay(SpeedDelay);
  }

  //一旦全部消して
  //  turnOffBar();
  //  turnOffRing();

  //設定時間待って次の動作へ
  delay(ReturnDelay);
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ColorGlow
//

//全ピクセルがPWMで輝度上下する
void ColorGlow(byte red, byte green, byte blue, float Step, int SpeedDelay, int ReturnDelay) {

  //ボリュームから色指定を読み込む
  //ボリュームが0の時は白になる
  if (analogRead(colorSelect) != 0) {
    uint32_t desigColor = Wheel(map(analogRead(colorSelect), LowerRange, UpperRange, 0, 255));
    //輝度変更のためにRGBに分解しておく
    green = lowByte(desigColor);
    blue = lowByte(desigColor >> 8);
    red = lowByte(desigColor >> 16);
  }
  //輝度を上げていく
  for (float i = 0; i < Step + 1 ; i++) {
    //サインカーブで輝度が上下するようにする
    float ru = ((sin(3.14 * 1.6 + (3.14 / Step) * i) + 1) * 127) / 255;
    //SideBarUVクリアリングに色をセット
    for (int j = 0; j < 20 ; j++) {
      SideBar.setPixelColor(j, red * ru, green * ru, blue * ru);
    }
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * ru, green * ru, blue * ru);
    }
    SideBar.show();
    ClearRing.show();
    delay(SpeedDelay);
  }

  //設定時間待って次の動作へ
  delay(ReturnDelay);

  //輝度を下げていく
  for (float i = Step; i > 1 ; i--) {
    float rd = ((sin(3.14 * 1.6 + (3.14 / Step) * i) + 1) * 127) / 255;
    for (int j = 0; j < 20 ; j++) {
      SideBar.setPixelColor(j, red * rd + 1, green * rd + 1, blue * rd + 1);
    }
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * rd + 1, green * rd + 1, blue * rd + 1);
    }
    SideBar.show();
    ClearRing.show();
    delay(SpeedDelay);
  }

  //一旦全部消して
  //  turnOffBar();
  //  turnOffRing();

  //設定時間待って次の動作へ
  delay(ReturnDelay);
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// OneWay
//


//KnightRiderの上方向だけ
//赤を高速に動かすとemergency mode の表現ができるのでは
void OneWay(byte red, byte green, byte blue, int Step, int SpeedDelay, int SpeedDelay01, int ReturnDelay) {

  //ボリュームから色指定を読み込む
  //ボリュームが0の時は白になる
  if (analogRead(colorSelect) != 0) {
    uint32_t desigColor = Wheel(map(analogRead(colorSelect), LowerRange, UpperRange, 0, 255));
    //輝度変更のためにRGBに分解しておく
    green = lowByte(desigColor);
    blue = lowByte(desigColor >> 8);
    red = lowByte(desigColor >> 16);
  }

  //長さを6灯にする
  //スタートラインとゴールラインをそれぞれ6灯分ずらすことで
  //下から現れて上へ消え去る
  for (int i = -6; i < 20 + 1; i++) {
    //消す
    turnOffBar();
    turnOffRing();

    //先頭ピクセルやや暗く
    SideBar.setPixelColor(i + 5, red / 10, green / 10, blue / 10);
    //メインの輝度
    SideBar.setPixelColor(i + 4, red, green, blue);
    //以下少しずつ落としていく
    SideBar.setPixelColor(i + 3, red / 3, green / 3, blue / 3);
    SideBar.setPixelColor(i + 2, red / 10, green / 10, blue / 10);
    SideBar.setPixelColor(i + 1, red / 30, green / 30, blue / 30);
    SideBar.setPixelColor(i , red / 100, green / 100, blue / 100);
    SideBar.show();
    delay(SpeedDelay01);
  }

  //輝度を上げていく
  for (int i = 0; i < Step + 1 ; i++) {
    //UVクリアリングに色をセット
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * i / Step, green * i / Step, blue * i / Step);
    }
    ClearRing.show();
    delay(SpeedDelay);
  }

  //設定時間待って次の動作へ
  delay(ReturnDelay);

  //輝度を下げていく
  for (int i = Step; i > 1 ; i--) {
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, red * i / Step, green * i / Step, blue * i / Step);
    }
    ClearRing.show();
    delay(SpeedDelay);
  }

  //一旦全部消して
  turnOffBar();
  turnOffRing();

  delay(ReturnDelay);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// KnightRider
//

//光の列がゆっくりと上下する
void KnightRider(byte red, byte green, byte blue, int SpeedDelay, int ReturnDelay) {

  //ボリュームから色指定を読み込む
  //ボリュームが0の時は白になる
  if (analogRead(colorSelect) != 0) {
    uint32_t desigColor = Wheel(map(analogRead(colorSelect), LowerRange, UpperRange, 0, 255));
    //輝度変更のためにRGBに分解しておく
    green = lowByte(desigColor);
    blue = lowByte(desigColor >> 8);
    red = lowByte(desigColor >> 16);
  }

  //長さを6灯にする
  //スタートラインとゴールラインをそれぞれ6灯分ずらすことで
  //一旦ライトバーから消え去るように動作させる
  for (int i = -6; i < 20 + 1; i++) {
    //Bar消す
    turnOffBar();
    //先頭ピクセルやや暗く
    SideBar.setPixelColor(i + 5, red / 10, green / 10, blue / 10);
    //メインの輝度
    SideBar.setPixelColor(i + 4, red, green, blue);
    //以下少しずつ落としていく
    SideBar.setPixelColor(i + 3, red / 3, green / 3, blue / 3);
    SideBar.setPixelColor(i + 2, red / 10, green / 10, blue / 10);
    SideBar.setPixelColor(i + 1, red / 30, green / 30, blue / 30);
    SideBar.setPixelColor(i , red / 100, green / 100, blue / 100);

    //ClearRingは色が変化する
    for (int j = 0; j < 4; j++) {
      ClearRing.setPixelColor(j, Wheel(map(analogRead(colorSelect), LowerRange, UpperRange, 0, 255) + 5 * (i + 6)));
    }
    SideBar.show();
    ClearRing.show();
    delay(SpeedDelay);
  }

  delay(ReturnDelay);

  //往路と逆パターンを作っているだけ
  for (int i = 20 + 1; i > - 6; i--) {
    setAll(0, 0, 0);
    SideBar.setPixelColor(i - 1, red / 10, green / 10, blue / 10);
    SideBar.setPixelColor(i , red, green, blue);
    SideBar.setPixelColor(i + 1, red / 3, green / 3, blue / 3);
    SideBar.setPixelColor(i + 2, red / 10, green / 10, blue / 10);
    SideBar.setPixelColor(i + 3, red / 30, green / 30, blue / 30);
    SideBar.setPixelColor(i + 4, red / 100, green / 100, blue / 100);

    //ClearRing
    for (int j = 0; j < 4; j++) {
      ClearRing.setPixelColor(j, Wheel(map(analogRead(colorSelect), LowerRange, UpperRange, 0, 255) + 260 - 5 * (i + 6)));
    }
    SideBar.show();
    ClearRing.show();
    delay(SpeedDelay);
  }
  delay(ReturnDelay);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Strobe
//
void Strobe(byte red, byte green, byte blue, int StrobeCount, int FlashDelay, int EndPause) {
  //ボリュームから色指定を読み込む
  //ボリュームが0の時は白になる
  if (analogRead(colorSelect) != 0) {
    uint32_t desigColor = Wheel(map(analogRead(colorSelect), LowerRange, UpperRange, 0, 255));
    //輝度変更のためにRGBに分解しておく
    green = lowByte(desigColor);
    blue = lowByte(desigColor >> 8);
    red = lowByte(desigColor >> 16);
  }
  //SideBarUVクリアリングに色をセット
  for (int j = 0; j < 20 ; j++) {
    SideBar.setPixelColor(j, red, green, blue);
  }
  for (int j = 0; j < 4 ; j++) {
    ClearRing.setPixelColor(j, red, green, blue);
  }
  SideBar.show();
  ClearRing.show();

  delay(FlashDelay);
  turnOffBar();
  turnOffRing();
  delay(EndPause);
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// colorWipe
//
// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  for (uint16_t i = 0; i < SideBar.numPixels(); i++) {
    SideBar.setPixelColor(i, c);
    SideBar.show();
    delay(wait);
  }
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Random  Strobe
//
void RandomStrobe(byte red, byte green, byte blue, int StrobeCount, int FlashDelay, int EndPause) {
  //乱数使うときのおまじない
  randomSeed(analogRead(3) % 17);

  //色環からランダムに色を選択
  uint32_t random_c = Wheel(random(0, 256));

  //random_cは32ビットの連続データなので、輝度変更のためにRGBに分解しておく
  //データの並びは下位から8bitずつGBRW
  green = lowByte(random_c);
  blue = lowByte(random_c >> 8);
  red = lowByte(random_c >> 16);

  //SideBarUVクリアリングに色をセット
  for (int j = 0; j < 20 ; j++) {
    SideBar.setPixelColor(j, red, green, blue);
  }
  for (int j = 0; j < 4 ; j++) {
    ClearRing.setPixelColor(j, red, green, blue);
  }
  SideBar.show();
  ClearRing.show();

  delay(FlashDelay);
  turnOffBar();
  turnOffRing();
  delay(EndPause);
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return SideBar.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return SideBar.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return SideBar.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

void showStrip() {
#ifdef ADAFRUIT_NEOPIXEL_H
  // NeoPixel
  SideBar.show();
#endif
#ifndef ADAFRUIT_NEOPIXEL_H
  // FastLED
  FastLED.show();
#endif
}

void setPixel(int Pixel, byte red, byte green, byte blue) {
#ifdef ADAFRUIT_NEOPIXEL_H
  // NeoPixel
  SideBar.setPixelColor(Pixel, SideBar.Color(red, green, blue));
#endif
#ifndef ADAFRUIT_NEOPIXEL_H
  // FastLED
  leds[Pixel].r = red;
  leds[Pixel].g = green;
  leds[Pixel].b = blue;
#endif
}

void setAll(byte red, byte green, byte blue) {
  for (int i = 0; i < 20; i++ ) {
    setPixel(i, red, green, blue);
  }
  showStrip();
}


void turnOffBar() {
  for (int i = 0; i < 20; i++ ) {
    SideBar.setPixelColor(i, 0);
  }
  SideBar.show();
}

void turnOffRing() {
  for (int i = 0; i < 4; i++ ) {
    ClearRing.setPixelColor(i, 0);
  }
  ClearRing.show();
}

void initialize() {
  //SideBarUVクリアリングに色をセット
  for (int j = 0; j < 20 ; j++) {
    SideBar.setPixelColor(j, 0x5f, 0x5f, 0x5f);
  }
  for (int j = 0; j < 4 ; j++) {
    ClearRing.setPixelColor(j, 0xff, 0xff, 0xff);
  }
  SideBar.show();
  ClearRing.show();

  delay(2000);

  for (int j = 0; j < 20 ; j++) {
    SideBar.setPixelColor(j, 0, 0xff, 0);
    SideBar.show();
    delay(500 - j * 20);
  }
  delay(1000);

  for (int j = 0; j < 20 ; j++) {
    SideBar.setPixelColor(j, 0xff, 0, 0);
    SideBar.show();
    delay(60);
  }
  delay(300);
  for (int j = 0; j < 20 ; j++) {
    SideBar.setPixelColor(j, 0xff, 0xff, 0);
    SideBar.show();
    delay(40);
  }
  delay(300);
  for (int j = 0; j < 20 ; j++) {
    SideBar.setPixelColor(j, 0, 0xff, 0xff);
    SideBar.show();
    delay(30);
  }
  delay(300);
  for (int j = 0; j < 20 ; j++) {
    SideBar.setPixelColor(j, 0, 0, 0xff);
    SideBar.show();
    delay(20);
  }
  delay(2000);

  //輝度を下げていく
  for (float i = 100; i > 1 ; i--) {
    float rd = ((sin(3.14 * 1.6 + (3.14 / 100) * i) + 1) * 127) / 255;
    for (int j = 0; j < 20 ; j++) {
      SideBar.setPixelColor(j, 0, 0,  0xff * rd);
    }
    for (int j = 0; j < 4 ; j++) {
      ClearRing.setPixelColor(j, 0xff * rd, 0xff * rd, 0xff * rd);
    }
    SideBar.show();
    ClearRing.show();
    delay(20);
  }
  delay(3000);
  turnOffBar();
  turnOffRing();

  delay(3000);
}


0 件のコメント:

コメントを投稿