TetsuakiBaba

workshop/ArduinoBasis/LEDs

はじめに:チカチカとふわふわ

Arduinoのサンプルプログラムで最も基本的なものに,13ピンのLEDをブリンク(Blink:点灯消灯の繰り返し)を行うものがあります. まずはこの基本的なプログラムから学習してきます.Arduinoをつなぎ,下記Exampleを実行すると,ボード上のLEDが一秒おきに点滅をくりかえます.

Example-LED-1

int led = 13;
void setup() {                
  pinMode(led, OUTPUT);     // 13番ピンを出力モードに設定
}
void loop() {
  digitalWrite(led, HIGH);  // 13番ピンをHighにしてLEDを点灯
  delay(1000);              // 1秒ここで停止
  digitalWrite(led, LOW);   // 13番ピンをLowにしてLEDを消灯
  delay(1000);              // 1秒ここで停止
}

上記プログラムを利用して,13番ピンのLEDをチカチカ点滅からふわふわ点滅に変えてください.


高機能LED(WS2812B)を使う

近年LED制御でしばしば利用されるモジュールがあります.テープLEDやシリアルLEDなどと呼ばれるもので, マイコン側から電源,GND,制御PINの3つの電極だけで複数のLEDを制御可能なものです.一般的にフルカラーLEDを 制御する場合,PWMピンを3本使うのがマイコンでは一般的ですが,それではたくさんのLEDを制御する場合限界が 生じます.そこでこのような高機能LEDの扱いに慣れておきましょう.ここではWS2812Bと呼ばれるモジュールを複数 利用して実際にLEDを制御してみます.

ライブラリの追加

まずはhttps://github.com/adafruit/Adafruit_NeoPixelから ライブラリをダウンロードし,Arduinoで利用できるようにします.ダウンロードしたライブラリをどのようにするかはREADME.mdに書いてあります. 無事にライブラリの追加ができると,ArduinoのExampleにAdafruit_NeoPixelが追加されます.この中にあるsimpleを開いてみます. 下記にsimple Exampleを簡略化したものを記述しておきます.

Example-LED-2

#include <Adafruit_NeoPixel.h>
#include <avr/power.h>

#define PIN            7
#define NUMPIXELS      16

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
int delayval = 500; // delay for half a second

void setup() {
  pixels.begin(); // This initializes the NeoPixel library.
}

void loop() {
  for(int i=0;i <NUMPIXELS; i++){
    pixels.setPixelColor(i, pixels.Color(0,150,0)); // Moderately bright green color.
    pixels.show(); // This sends the updated pixel color to the hardware.
    delay(delayval); // Delay for a period of time (in milliseconds).
  }
}

上記プログラムを利用して下記動画のようなプログラムに変更してください.

下記動画のようにふわふわとLEDを変更させてください.

下記動画のようにLEDを変更させてください.ただしLEDの色は白色,移動スピードは500msです.

さて,練習を実装するにあたり,これまでのやり方では困難な点に気付いたでしょうか. ふわっとつけてふわっと消すことが出来てもそれを他のLEDも同時に動かさなければなりません. つまり,delay関数を利用すると他の処理ができない(シングルスレッド)の為,実装が困難なわけです. これに対して幾つかの解決法を紹介します.

割り込みTimerを利用する

一般的なマイコンには割り込みと呼ばれる機能があります.例えば,このボタンを押した時だけこの関数を呼び出してほしい, 1秒おきにこの関数を呼び出してほしい.といった場合です.この関数呼び出しが行われている間はメインであるloop()関数の 実行は一時停止になります. miso-engine(川鍋さんのブログ)を参照して, MsTimerライブラリをまず追加し,サンプルを動作させてみましょう.

サンプルを確認できたら,割り込みをつかって練習4を実現してみます.LEDの値設定を割り込みタイマーを利用して,500ms毎に呼び出します. loop()では値を常に減らし続けるコードを記述します.loop()のシングルスレッドだけでは困難と思われるこの処理は,割り込みを 利用することで比較的簡単に記述ができました.下記にそのサンプルコードを記述しておきます.

Example-LED-3

#include <Adafruit_NeoPixel.h>
#include <avr/power.h>
#include <MsTimer2.h>

#define PIN            7
#define NUMPIXELS      10

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
int r[NUMPIXELS],g[NUMPIXELS],b[NUMPIXELS];

void flash() {
  static int count = 0;
  r[count] = 50;
  b[count] = 50;
  g[count] = 50;
  pixels.setPixelColor(count, pixels.Color(r[count],g[count],b[count]));
  count++;
  if( count > NUMPIXELS-1 ){
    count = 0;
  }
 
}

void setup() {
  pixels.begin(); // This initializes the NeoPixel library.
  MsTimer2::set(500, flash); // 500ms period
  MsTimer2::start();
  for( int i = 0; i < NUMPIXELS; i++ ){
    r[i] = g[i] = b[i] = 0;
  }
}

void loop() {
  for(int i=0;i < NUMPIXELS; i++){
    if( r[i] > 0 )r[i] = r[i] - 1;
    if( g[i] > 0 )g[i] = g[i] - 1;
    if( b[i] > 0 )b[i] = b[i] - 1;
    pixels.setPixelColor(i, pixels.Color(r[i],g[i],b[i]));
    pixels.show(); 
    delay(1);
  }
}

関数を利用する

上記の様に割り込み以外にも別の方法でも実現することは可能です.例えば500msに一度だけtrueを返す関数があったらどうでしょう. こんな関数があれば,割り込みを利用せずとも,500msに一度だけ任意のコードを実行させることができます. では関数名をmetro()として,実装してみます.

Example-LED-4

#include <Adafruit_NeoPixel.h>
#include <avr/power.h>
#include <MsTimer2.h>

#define PIN            7
#define NUMPIXELS      10

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
int r[NUMPIXELS],g[NUMPIXELS],b[NUMPIXELS];

boolean metro()
{
  static unsigned long int t = millis();
  if( (millis()-t) > 500 ){
    t = millis();
    return true;
  }
  return false;
}

void flash(){
  static int count = 0;

  r[count] = 50;
  b[count] = 50;
  g[count] = 50;
  pixels.setPixelColor(count, pixels.Color(r[count],g[count],b[count]));
  count++;
  if( count > NUMPIXELS-1 ){
    count = 0;
  }
}

void setup() {
  pixels.begin(); // This initializes the NeoPixel library.
  for( int i = 0; i < NUMPIXELS; i++ ){
    r[i] = g[i] = b[i] = 0;
  }
}

void loop() {
  if( metro() == true ){
    flash();
  }

  for(int i=0;i < NUMPIXELS; i++){
    if( r[i] > 0 )r[i] = r[i] - 1;
    if( g[i] > 0 )g[i] = g[i] - 1;
    if( b[i] > 0 )b[i] = b[i] - 1;
    pixels.setPixelColor(i, pixels.Color(r[i],g[i],b[i]));
    pixels.show(); 
    delay(1);
  }
}

2つのやり方で目的とする機能を実装しました.では一体どちらを利用するのが良いのか,, これは時と場合によります.まずはそれぞれの長所と短所を理解しておきましょう.

    metro()関数
  • 長所:何個でもタイマーを準備できる
  • 短所:loopの呼び出し周期に依存するので時間が精確でない
    割り込み処理
  • 長所:ハードウェア依存なので数個くらいしかせいぜいタイマーをつくれない
  • 短所:loop呼び出し周期に依存しないため,呼び出し時間が精確

2.5 LEDの減衰具合にロバスト性を持たせる

さて,ここまでのプログラムでテープLEDの発光アニメーションを実装してみました. このプログラムを少しいじると気づくかもしれませんが,それぞれのLED発光値が例えば 150の場合と15の場合では,LEDが消えるまでの時間が大きく変わります.現状のプログラム ではloopごとに-1を行っているため,150よりも15の方が10倍早く光が消えてしまいます. これではプログラムを作成する側から,「こんな感じで減衰してほしい」を的確に実現できて いるとは言いがたいです.ではどのような指定の仕方があると嬉しいでしょうか.

やはり,「このくらいの時間で減衰して光が消えてほしい」というのが使いやすいように感じないでしょうか? ここではそれをどのように実装すれば良いのか考えてみます. 先ほど学習した,時間感覚で一定に呼び出しをするような割り込みや関数を利用してみると うまく出来そうです.それではここれ練習問題です.

割り込みを1msにすることで,1,000msで減衰(発光→消灯)となるプログラムを割り込みで実装し, 1000msでLED点灯を行うプログラムをmetro()関数を利用して実装せよ.ただし割り込み関数の名前はfadeoutと すること.発光色は白であるが,random(51)として0-50までのランダム値を利用すること.

ちょっとこれは難しかったので回答も一緒に


#include <Adafruit_NeoPixel.h>
#include <avr/power.h>
#include <MsTimer2.h>

#define PIN            7
#define NUMPIXELS      10

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
float r[NUMPIXELS],g[NUMPIXELS],b[NUMPIXELS];

float dt[NUMPIXELS];
int count = 0;
void fadeout(){
 for(int i=0;i < NUMPIXELS; i++){
    if( r[i] > 0 )r[i] = r[i] - dt[i];
    if( g[i] > 0 )g[i] = g[i] - dt[i];
    if( b[i] > 0 )b[i] = b[i] - dt[i];
    pixels.setPixelColor(i, pixels.Color(r[i],g[i],b[i]));
  }
}

boolean metro()
{
  static unsigned long int t = millis();
  if( (millis()-t) > 1000 ){
    t = millis();
    return true;
  }
  return false;
}

void setup() {
  pixels.begin(); // This initializes the NeoPixel library.
  MsTimer2::set(1, fadeout); // 500ms period
  MsTimer2::start();
  for( int i = 0; i < NUMPIXELS; i++ ){
    r[i] = g[i] = b[i] = dt[i] = 0;
  }
}

void loop() {
  if( metro() == true ){
    r[count] = b[count] = g[count] = random(51);
    dt[count] = r[count]/1000.0;
    pixels.setPixelColor(count, pixels.Color(r[count],g[count],b[count]));
    count++;
    if( count > NUMPIXELS-1 ){
      count = 0;
    }
  }
  pixels.show(); 
}

さあ,ここまでやってみていかがでしょうか.段々とプログラムが複雑になってきました. さらにここからLED発光パターンの要求が複雑化する場合,例えば下記のようなものを作る場合, さらにプログラムが複雑になりそうです.

  • 右から左へ光が流れながら色が赤から青へ変化
  • 右から左へ緑色が光りながら進むなか,青色が途中から追い抜く
  • フルカラーで先ほどと同様に,時間指定での減衰効果を実装したい
  • このような問題に対して,頑張れば複雑なプログラムも対応できますが,できれば複雑になるのは 避けたいところです.そこで,これらに対応するためクラスを導入します.

    クラスを用いてより複雑なLED表現をプログラムする

    ハードウェアをソフトウェアで制御する場合,簡単な動作であれば基本的な文法,構文で十分に対応できます. また,複雑なものでも頑張ればそれなりにプログラムも可能です.一方で,クラスで記述すると後々楽になることが たくさんあります.ここではオブジェクト(LEDモジュール)一つに対して一つのクラスで記述することを学習します. プログラム中級者くらいからはこのようなハードをオブジェクト指向で記述するように心掛けましょう.

    なお,クラスに関する説明はこのページでは省いていますので,C++言語におけるクラスに関してまだ知らないようであれば,一度下記リンク等を参照され,クラスの基礎を抑えて置くのをお勧めします.このページでは,プログラムはこうやって書くということを説明はせず,考えながらプログラムを書く(設計する)場合は,こうやろう.ということに焦点を当ててプログラムを記述していきます.プログラム的にはこちらの方がスマート,正しい,というツッコミどころがたくさんありますが,そこには触れずに行きますので,ご理解ください.

    まずは,これまでコーディングしてきた,LEDがスーッと移動するプログラムをクラスを用いて 記述しなおしてみます.それでは最初にどうすればよいかですが,対象となる部品モジュールをよく みてください.みなさんの手元にはLEDが10個ついた部品が見えると思います.これがなにかというと, これを基にプログラムを記述します.つまり,この部品はLEDモジュールが10個あり,それが一つの モジュールになっている,というものです.

    第一段階として,LEDモジュール一つに対するクラスを記述し,最後にそれら10個をまとめるためのクラスを記述 します.言葉で説明してもなかなかピンと来ないと思うので実際にやっていきましょう.なお,ここではクラスの基礎を 書きませんので,各々参考書はウェブサイト等で学習しておいてください. http://www.asahi-net.or.jp/~wv7y-kmr/memo/old/cpp_cls.html

    クラス

    クラス化をするために,ステップ・バイ・ステップで下記にコードを記載していきます.基本的な設計としては,まずLEDひとつを表すクラス(LED)と,複数のLEDをまとめるTapeLEDというクラスの2つを利用します.まずはLEDクラスの簡単な形をつくり,次にTapeLEDクラスをLEDクラスを参照する形で作成します.概念として重要なことは,物理的イメージとクラスを一致させることです.今回の場合,テープLEDという製品を利用していますが,このテープLEDには複数のLEDが含まれるため,まずは単一のLEDを表すクラス.次にそれらをまとめるクラスを記述するわけです.

    まず最初は,指定した時間間隔で,LEDが点滅するだけのコード.初期設定でsetDurationにランダム変数を渡しているため,それぞれのLEDがランダム間隔で点滅を繰り返します.

    #include <Adafruit_NeoPixel.h>
    
    #define PIN            7
    #define NUMPIXELS      10
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    class LED {
      public:
        LED() {};
        ~LED() {};
        void setID(int _id) {
          id = _id;
        }
        void setDuration(int _duration) {
          duration = _duration;
        }
        void update()
        {
          if ( (millis() - t) > duration ) {
            t = millis();
            flg = !flg;
            if ( flg == true )pixels.setPixelColor(id, pixels.Color(50, 50, 50));
            else pixels.setPixelColor(id, pixels.Color(0, 0, 0));
            pixels.show();
          }
        }
        boolean flg = false; // LEDのON/OFFを決めるフラグ
        unsigned long t = millis();
        float r, g, b; // 各LEDの値
        int id; // LEDのID
        int duration; // 減衰時間
    };
    
    LED led[NUMPIXELS];
    
    void setup()
    {
      pixels.begin();
      for ( int i = 0; i < NUMPIXELS; i++ ) {
        led[i].setID(i);
        led[i].setDuration(100+random(1000));
      }
    }
    
    void loop()
    {
      for ( int i = 0; i < NUMPIXELS; i++ ) {
        led[i].update();
      }
    }
    
    

    次は,setup及びloop関数での利用方法が少し煩雑(forループをなくして,一行にしたい)なので,LEDクラスを包括する,TapeLEDクラスを作成する.

    #include <Adafruit_NeoPixel.h>
    
    #define PIN            7
    #define NUMPIXELS      10
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    class LED {
      public:
        LED() {};
        ~LED() {};
        void setID(int _id) {
          id = _id;
        }
        void setDuration(int _duration) {
          duration = _duration;
        }
        void update()
        {
          if ( (millis() - t) > duration ) {
            t = millis();
            flg = !flg;
            if ( flg == true )pixels.setPixelColor(id, pixels.Color(50, 50, 50));
            else pixels.setPixelColor(id, pixels.Color(0, 0, 0));
            pixels.show();
          }
        }
        boolean flg = false;
        unsigned long t = millis();
        float r, g, b; // 各LEDの値
        int id;
        int duration; // 減衰時間
    };
    
    class TapeLED
    {
      public:
        TapeLED() {}
        ~TapeLED() {}
        LED led[NUMPIXELS];
        void setup() {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].setID(i);
            led[i].setDuration(100 + random(1000));
          }
        }
        void update()
        {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].update();
          }
        }
    };
    
    TapeLED tapeled;
    
    LED led[NUMPIXELS];
    
    void setup()
    {
      pixels.begin();
      tapeled.setup();
    }
    
    void loop()
    {
      tapeled.update();
    }
    
    

    グローバル変数だった,LED led[NUMPIXELS]をTapeLEDクラス内に包括する.またLED一つ分だけに関して,LEDに減衰,増幅効果を付与する.

    #include <Adafruit_NeoPixel.h>
    
    
    #define PIN            7
    #define NUMPIXELS      10
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    class LED {
      public:
        LED() {};
        ~LED() {};
        void setID(int _id) {
          id = _id;
        }
        void setDuration(int _duration) {
          duration = _duration;
    
        }
        void update()
        {
        	// 指定した時間間隔に近づくにつれ,1.0に近づく変数
          float v = (Te - millis()) / (float)duration;
          if ( flg == true ) {
            if ( 0.0 <= v && v <= 1.0) {
              pixels.setPixelColor(id, pixels.Color(v * r, v * g, v * b));
              pixels.show();
            }
            else {
              flg = false;
            }
          }
        }
        void setColor( float _r, float _g, float _b) {
          r = _r;
          g = _g;
          b = _b;
          Ts = millis();
          Te = Ts + duration;
          flg = true;
        }
        boolean flg = false;
        unsigned long t = millis();
        unsigned long Ts; // start of time
        unsigned long Te; // end of time
        float r, g, b; // 各LEDの値
        int id;
        int duration; // 減衰時間
    
    };
    
    class TapeLED
    {
      public:
        TapeLED() {}
        ~TapeLED() {}
        LED led[NUMPIXELS];
        void setup() {
          led[0].setID(0);
          led[0].setDuration(1000);
          led[0].setColor(255, 255, 255);
        }
        void update()
        {
          led[0].update();
        }
    };
    
    TapeLED tapeled;
    
    void setup()
    {
      pixels.begin();
      tapeled.setup();
    }
    
    void loop()
    {
      tapeled.update();
    }
    
    

    LED一つ分に関して準備できたので,これをNUMPIXELS分だけTapeLED内で適応させる.

    #include <Adafruit_NeoPixel.h>
    
    #define PIN            7
    #define NUMPIXELS      10
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    class LED {
      public:
        LED() {};
        ~LED() {};
        void setID(int _id) {
          id = _id;
        }
        void setDuration(int _duration) {
          duration = _duration;
    
        }
        void update()
        {
          float v = (Te - millis()) / (float)duration;
          if ( flg == true ) {
            if ( 0.0 <= v && v <= 1.0) {
              pixels.setPixelColor(id, pixels.Color(v * r, v * g, v * b));
              pixels.show();
            }
            else {
              flg = false;
            }
          }
        }
        void setColor( float _r, float _g, float _b) {
          r = _r;
          g = _g;
          b = _b;
          Ts = millis();
          Te = Ts + duration;
          flg = true;
        }
        boolean flg = false;
        unsigned long t = millis();
        unsigned long Ts; // start of time
        unsigned long Te; // end of time
        float r, g, b; // 各LEDの値
        int id;
        int duration; // 減衰時間
    
    };
    
    class TapeLED
    {
      public:
        TapeLED() {}
        ~TapeLED() {}
        LED led[NUMPIXELS];
        void setup() {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].setID(i);
            led[i].setDuration(300);
            led[i].setColor(255, 255, 255);
          }
        }
        void update()
        {
          if ( metro() == true ) {
            led[pos].setColor(100, 100, 100);
            pos++;
            if ( pos > NUMPIXELS - 1 )pos = 0;
          }
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].update();
          }
        }
        boolean metro()
        {
          if ( (millis() - t) > 100 ) {
            t = millis();
            return true;
          }
          return false;
        }
        unsigned long int t = millis();
        int pos = 0;
    };
    
    TapeLED tapeled;
    
    void setup()
    {
      pixels.begin();
      tapeled.setup();
    }
    
    void loop()
    {
      tapeled.update();
    }
    
    

    次はtapeLEDクラスをより拡張性の高いものに変更します.具体的には

    1. LEDの減衰時間
    2. LED流れる速度
    をsetup()メソッドを通じて簡単に設定できるようにします.

    #include <Adafruit_NeoPixel.h>
    
    #define PIN            7
    #define NUMPIXELS      10
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    class LED {
      public:
        LED() {};
        ~LED() {};
        void setID(int _id) {
          id = _id;
        }
        void setDuration(int _duration) {
          duration = _duration;
    
        }
        void update()
        {
          float v = (Te - millis()) / (float)duration;
          if ( flg == true ) {
            if ( 0.0 <= v && v <= 1.0) {
              pixels.setPixelColor(id, pixels.Color(v * r, v * g, v * b));
              pixels.show();
            }
            else {
              flg = false;
            }
          }
        }
        void setColor( float _r, float _g, float _b) {
          r = _r;
          g = _g;
          b = _b;
          Ts = millis();
          Te = Ts + duration;
          flg = true;
        }
        boolean flg = false;
        unsigned long t = millis();
        unsigned long Ts; // start of time
        unsigned long Te; // end of time
        float r, g, b; // 各LEDの値
        int id;
        int duration; // 減衰時間
    
    };
    
    class TapeLED
    {
      public:
        TapeLED() {}
        ~TapeLED() {}
        LED led[NUMPIXELS];
        void setup(int _duration, int _r, int _g, int _b, int _interval_ms) {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].setID(i);
            led[i].setDuration(_duration);
            led[i].setColor(_r, _g, _b);
          }
          interval_ms = _interval_ms;
        }
        void update()
        {
          if ( metro() == true ) {
            led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
            pos++;
            if ( pos > NUMPIXELS - 1 )pos = 0;
          }
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].update();
          }
        }
        boolean metro()
        {
          if ( (millis() - t) > interval_ms ) {
            t = millis();
            return true;
          }
          return false;
        }
        unsigned long int t = millis();
        int pos = 0;
        int interval_ms;
        
    };
    
    TapeLED tapeled;
    
    
    void setup()
    {
      pixels.begin();
      tapeled.setup(100, 255,0,0, 50);
    }
    
    void loop()
    {
      tapeled.update();
    }
    

    では引き続きクラスの拡張性を高めます.これまでの

    1. LEDの減衰時間
    2. LED流れる速度
    3. に加えて
    4. LEDの流れる方向(アニメーションタイプで,backward, forward, NONEを指定)
    5. LEDの位置指定( int _pos)
    を機能追加します.

    #include <Adafruit_NeoPixel.h>
    
    #define PIN            7
    #define NUMPIXELS      10
    
    #define TYPE_MOVE_FORWARD 1
    #define TYPE_MOVE_NO 0
    #define TYPE_MOVE_BACKWARD 2
    #define MAX_INTERVAL 10000
    #define MAX_DURATION 10000
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    class LED {
      public:
        LED() {};
        ~LED() {};
        void setID(int _id) {
          id = _id;
        }
        void setDuration(int _duration) {
          duration = _duration;
    
        }
        void update()
        {
          float v = (Te - millis()) / (float)duration;
          if ( flg == true ) {
            if ( 0.0 <= v && v <= 1.0) {
              if ( duration < MAX_DURATION ) {
                pixels.setPixelColor(id, pixels.Color(v * r, v * g, v * b));
              }
              else {
                pixels.setPixelColor(id, pixels.Color(r, g, b));
              }
              pixels.show();
            }
            else {
              flg = false;
            }
          }
        }
        void setColor( float _r, float _g, float _b) {
          r = _r;
          g = _g;
          b = _b;
          Ts = millis();
          Te = Ts + duration;
          flg = true;
        }
        boolean flg = false;
        unsigned long t = millis();
        unsigned long Ts; // start of time
        unsigned long Te; // end of time
        float r, g, b; // 各LEDの値
        int id;
        int duration; // 減衰時間
    
    };
    
    class TapeLED
    {
      public:
        TapeLED() {}
        ~TapeLED() {}
        LED led[NUMPIXELS];
        void setup(int _duration, int _r, int _g, int _b, int _interval_ms, int _type_animation, int _pos) {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].setID(i);
            led[i].setDuration(_duration);
            led[i].setColor(_r, _g, _b);
          }
          interval_ms = _interval_ms;
          type_animation = _type_animation;
          pos = _pos;
        }
        void update()
        {
          if ( type_animation == TYPE_MOVE_FORWARD ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
              if ( interval_ms < MAX_INTERVAL )pos++;
              if ( pos > NUMPIXELS - 1 )pos = 0;
            }
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              led[i].update();
            }
          }
          else if ( type_animation == TYPE_MOVE_BACKWARD ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
              if ( interval_ms < MAX_INTERVAL )pos--;
              if ( pos < 0 )pos = NUMPIXELS - 1;
            }
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              led[i].update();
            }
          }
          else if ( type_animation == TYPE_MOVE_NO ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
            }
            led[pos].update();
          }
        }
        boolean metro()
        {
          if ( (millis() - t) > interval_ms ) {
            t = millis();
            return true;
          }
          return false;
        }
        void clear() {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].setColor(0, 0, 0);
            led[i].update();
          }
        }
        unsigned long int t = millis();
        int pos = 0;
        int interval_ms;
        int type_animation;
    
    };
    
    TapeLED tapeled;
    
    void setup()
    {
      pixels.begin();
      tapeled.clear();
      tapeled.setup(MAX_DURATION + 1, 255, 0, 0, MAX_INTERVAL + 1, TYPE_MOVE_NO, 3);
    }
    
    void loop()
    {
      tapeled.update();
    }
    

    これまでLEDの減少時間は設定できましたが,このままだとLEDはスーッと消えても,LEDが毎回パッとついてしまいます.じわっとLEDをつけたい場合は,この機能が必要です.

    #include <Adafruit_NeoPixel.h>
    
    #define PIN            7
    #define NUMPIXELS      10
    
    #define TYPE_MOVE_FORWARD 1
    #define TYPE_MOVE_NO 0
    #define TYPE_MOVE_BACKWARD 2
    #define MAX_INTERVAL 10000
    #define MAX_DURATION 10000
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    class LED {
      public:
        LED() {};
        ~LED() {};
        void setID(int _id) {
          id = _id;
        }
        void setDuration(int _inc_duration, int _dec_duration) {
          inc_duration = _inc_duration;
          dec_duration = _dec_duration;
        }
        void update()
        {
          float v = (Te - millis()) / (float)(dec_duration + inc_duration);
          if ( flg == true ) {
            if ( 0.0 <= v && v <= 1.0) {
              if ( (inc_duration+dec_duration) < MAX_DURATION ) {
                pixels.setPixelColor(id, pixels.Color(r * sin(3.14*v),
                                                      g * sin(3.14*v),
                                                      b * sin(3.14*v)));
              }
              else {
                pixels.setPixelColor(id, pixels.Color(r, g, b));
              }
              pixels.show();
            }
            else {
              flg = false;
            }
          }
        }
        void setColor( float _r, float _g, float _b) {
          r = _r;
          g = _g;
          b = _b;
          Ts = millis();
          Te = Ts + dec_duration + inc_duration;
          flg = true;
        }
        boolean flg = false;
        unsigned long t = millis();
        unsigned long Ts; // start of time
        unsigned long Te; // end of time
        float r, g, b; // 各LEDの値
        int id;
        int dec_duration;     // 減衰時間
        int inc_duration; // 増幅時間
    
    };
    
    class TapeLED
    {
      public:
        TapeLED() {}
        ~TapeLED() {}
        LED led[NUMPIXELS];
        void setup(int _inc_duration, int _dec_duration,
                   int _r, int _g, int _b,
                   int _interval_ms, int _type_animation, int _pos) {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].setID(i);
            led[i].setDuration(_inc_duration, _dec_duration);
            led[i].setColor(_r, _g, _b);
          }
          interval_ms = _interval_ms;
          type_animation = _type_animation;
          pos = _pos;
        }
        void update()
        {
          if ( type_animation == TYPE_MOVE_FORWARD ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
              if ( interval_ms < MAX_INTERVAL )pos++;
              if ( pos > NUMPIXELS - 1 )pos = 0;
            }
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              led[i].update();
            }
          }
          else if ( type_animation == TYPE_MOVE_BACKWARD ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
              if ( interval_ms < MAX_INTERVAL )pos--;
              if ( pos < 0 )pos = NUMPIXELS - 1;
            }
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              led[i].update();
            }
          }
          else if ( type_animation == TYPE_MOVE_NO ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
            }
            led[pos].update();
          }
        }
        boolean metro()
        {
          if ( (millis() - t) > interval_ms ) {
            t = millis();
            return true;
          }
          return false;
        }
        void clear() {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].setColor(0, 0, 0);
            led[i].update();
          }
        }
        unsigned long int t = millis();
        int pos = 0;
        int interval_ms;
        int type_animation;
    
    };
    
    TapeLED tapeled;
    
    void setup()
    {
      pixels.begin();
      tapeled.clear();
      tapeled.setup(1000, 1000, 150, 150, 200, 1000, TYPE_MOVE_FORWARD, 0);
    }
    
    void loop()
    {
      tapeled.update();
    }
    

    ここまでである程度の機能を追記できました.当初の目標であったLEDが流れる動作をするだけでなく,本クラスを利用すると副産物的にとの他のLED効果を実現できるようになっています.では,最後にシリアルターミナルから数値を打ち込んでそれぞれのアニメーションパラメータを操作するための準備しましょう.これを利用することで,動的にLED効果を変更することができる他,ProcessingやOpenframeworks等のアプリケーションを通じて自動でアニメーションを変更することができます.

    #include <Adafruit_NeoPixel.h>
    
    #define PIN            7
    #define NUMPIXELS      10
    
    #define TYPE_MOVE_FORWARD 1
    #define TYPE_MOVE_NO 0
    #define TYPE_MOVE_BACKWARD 2
    #define MAX_INTERVAL 10000
    #define MAX_DURATION 10000
    
    #define FORMAT_RED 10001
    #define FORMAT_GREEN 10002
    #define FORMAT_BLUE 10003
    #define FORMAT_MOVE_FORWARD 20001
    #define FORMAT_MOVE_BACKWARD 20002
    #define FORMAT_MOVE_NO 20003
    #define FORMAT_INTERVAL 30001
    #define FORMAT_INC_DURATION 40001
    #define FORMAT_DEC_DURATION 40002
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    class LED {
      public:
        LED() {};
        ~LED() {};
        void setID(int _id) {
          id = _id;
        }
        void setDuration(int _inc_duration, int _dec_duration) {
          inc_duration = _inc_duration;
          dec_duration = _dec_duration;
        }
        void update()
        {
          float v = (Te - millis()) / (float)(dec_duration + inc_duration);
          if ( flg == true ) {
            if ( 0.0 <= v && v <= 1.0) {
              if ( (inc_duration + dec_duration) < MAX_DURATION ) {
                pixels.setPixelColor(id, pixels.Color(r * sin(3.14 * v),
                                                      g * sin(3.14 * v),
                                                      b * sin(3.14 * v)));
              }
              else {
                pixels.setPixelColor(id, pixels.Color(r, g, b));
              }
              pixels.show();
            }
            else {
              flg = false;
            }
          }
        }
        void setColor( float _r, float _g, float _b) {
          r = _r;
          g = _g;
          b = _b;
          Ts = millis();
          Te = Ts + dec_duration + inc_duration;
          flg = true;
        }
        boolean flg = false;
        unsigned long t = millis();
        unsigned long Ts; // start of time
        unsigned long Te; // end of time
        float r, g, b; // 各LEDの値
        int id;
        int dec_duration;     // 減衰時間
        int inc_duration; // 増幅時間
    
    };
    
    class TapeLED
    {
      public:
        TapeLED() {}
        ~TapeLED() {}
        LED led[NUMPIXELS];
        void setup(int _inc_duration, int _dec_duration,
                   int _r, int _g, int _b,
                   int _interval_ms, int _type_animation, int _pos) {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].setID(i);
            led[i].setDuration(_inc_duration, _dec_duration);
            led[i].setColor(_r, _g, _b);
          }
          interval_ms = _interval_ms;
          type_animation = _type_animation;
          pos = _pos;
        }
        void update()
        {
          if ( type_animation == TYPE_MOVE_FORWARD ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
              if ( interval_ms < MAX_INTERVAL )pos++;
              if ( pos > NUMPIXELS - 1 )pos = 0;
            }
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              led[i].update();
            }
          }
          else if ( type_animation == TYPE_MOVE_BACKWARD ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
              if ( interval_ms < MAX_INTERVAL )pos--;
              if ( pos < 0 )pos = NUMPIXELS - 1;
            }
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              led[i].update();
            }
          }
          else if ( type_animation == TYPE_MOVE_NO ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
            }
            led[pos].update();
          }
        }
        boolean metro()
        {
          if ( (millis() - t) > interval_ms ) {
            t = millis();
            return true;
          }
          return false;
        }
        void clear() {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].setColor(0, 0, 0);
            led[i].update();
          }
        }
        void setColor(int _r, int _g, int _b, int pos) {
          led[pos].setColor(_r, _g, _b);
        }
        unsigned long int t = millis();
        int pos = 0;
        int interval_ms;
        int type_animation;
    
    };
    
    TapeLED tapeled;
    
    void setup()
    {
      Serial.begin(9600);
      Serial.setTimeout(5000);
      pixels.begin();
      tapeled.clear();
      tapeled.setup(1000, 1000, 150, 150, 200, 1000, TYPE_MOVE_FORWARD, 0);
    }
    
    void loop()
    {
      if ( Serial.available() > 0 ) {
        if (Serial.peek() == 13 || Serial.peek() == 10) {
          Serial.read();
          return;
        }
    
    
    
        long int value = Serial.parseInt();
        Serial.print("META INFO:");
        Serial.println(value);
        switch ( value )
        {
          case FORMAT_RED:
            value = Serial.parseInt();
            Serial.print("Format_RED:");
            Serial.println(value);
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              tapeled.setColor(value,
                               tapeled.led[tapeled.pos].g,
                               tapeled.led[tapeled.pos].b,
                               i);
            }
            break;
          case FORMAT_GREEN:
            value = Serial.parseInt();
            Serial.print("Format_GREEN:");
            Serial.println(value);
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              tapeled.setColor(tapeled.led[tapeled.pos].r,
                               value,
                               tapeled.led[tapeled.pos].b,
                               i);
            }
            break;
          case FORMAT_BLUE:
            value = Serial.parseInt();
            Serial.print("Format_BLUE:");
            Serial.println(value);
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              tapeled.setColor(tapeled.led[tapeled.pos].r,
                               tapeled.led[tapeled.pos].g,
                               value,
                               i);
            }
            break;
          case FORMAT_MOVE_FORWARD:
            tapeled.type_animation = TYPE_MOVE_FORWARD;
            break;
          case FORMAT_MOVE_BACKWARD:
            tapeled.type_animation = TYPE_MOVE_BACKWARD;
            break;
          case FORMAT_MOVE_NO:
            tapeled.type_animation = TYPE_MOVE_NO;
            break;
    
          case FORMAT_INTERVAL:
            value = Serial.parseInt();
            tapeled.interval_ms = value;
            break;
    
          case FORMAT_INC_DURATION:
            value = Serial.parseInt();
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              tapeled.led[i].setDuration(value,
                                         tapeled.led[i].dec_duration);
            }
            break;
          case FORMAT_DEC_DURATION:
            value = Serial.parseInt();
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              tapeled.led[i].setDuration(tapeled.led[i].inc_duration,
                                         value);
            }
            break;
        }
    
      }
    
      tapeled.update();
    
    }
    

    上記プログラムをArduinoに書き込んだ後,シリアルモニタを開き,20001と打つと,LEDが順方向に流れ,20002とうつとLEDが逆方向に流れます.また20003と打つと,LEDの流れが止まります.

    平成29年12月18日のOFのコード

    全点灯の機能を追記したコード.Serialから50001のメッセージを送ると設定されたLED色で全点灯します.

    #include <Adafruit_NeoPixel.h>
    
    #define PIN            7
    #define NUMPIXELS      10
    
    #define TYPE_MOVE_FORWARD 1
    #define TYPE_MOVE_NO 0
    #define TYPE_MOVE_BACKWARD 2
    #define TYPE_ALLON 3
    
    #define MAX_INTERVAL 10000
    #define MAX_DURATION 10000
    
    #define FORMAT_RED 10001
    #define FORMAT_GREEN 10002
    #define FORMAT_BLUE 10003
    #define FORMAT_MOVE_FORWARD 20001
    #define FORMAT_MOVE_BACKWARD 20002
    #define FORMAT_MOVE_NO 20003
    #define FORMAT_INTERVAL 30001
    #define FORMAT_INC_DURATION 40001
    #define FORMAT_DEC_DURATION 40002
    #define FORMAT_ALLON 50001
    #define FORMAT_ALLOFF 50002
    
    Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
    
    class LED {
      public:
        LED() {};
        ~LED() {};
        void setID(int _id) {
          id = _id;
        }
        void setDuration(int _inc_duration, int _dec_duration) {
          inc_duration = _inc_duration;
          dec_duration = _dec_duration;
        }
        void update()
        {
          float v = (Te - millis()) / (float)(dec_duration + inc_duration);
          if ( flg == true ) {
            if ( 0.0 <= v && v <= 1.0) {
              if ( (inc_duration + dec_duration) < MAX_DURATION ) {
                pixels.setPixelColor(id, pixels.Color(r * sin(3.14 * v),
                                                      g * sin(3.14 * v),
                                                      b * sin(3.14 * v)));
              }
              else {
                pixels.setPixelColor(id, pixels.Color(r, g, b));
              }
              pixels.show();
            }
            else {
              flg = false;
            }
          }
        }
        void setColor( float _r, float _g, float _b) {
          r = _r;
          g = _g;
          b = _b;
          Ts = millis();
          Te = Ts + dec_duration + inc_duration;
          flg = true;
        }
        boolean flg = false;
        unsigned long t = millis();
        unsigned long Ts; // start of time
        unsigned long Te; // end of time
        float r, g, b; // 各LEDの値
        int id;
        int dec_duration;     // 減衰時間
        int inc_duration; // 増幅時間
    
    };
    
    class TapeLED
    {
      public:
        TapeLED() {}
        ~TapeLED() {}
        LED led[NUMPIXELS];
        void setup(int _inc_duration, int _dec_duration,
                   int _r, int _g, int _b,
                   int _interval_ms, int _type_animation, int _pos) {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].setID(i);
            led[i].setDuration(_inc_duration, _dec_duration);
            led[i].setColor(_r, _g, _b);
          }
          interval_ms = _interval_ms;
          type_animation = _type_animation;
          pos = _pos;
        }
        void update()
        {
          if ( type_animation == TYPE_MOVE_FORWARD ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
              if ( interval_ms < MAX_INTERVAL )pos++;
              if ( pos > NUMPIXELS - 1 )pos = 0;
            }
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              led[i].update();
            }
          }
          else if ( type_animation == TYPE_MOVE_BACKWARD ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
              if ( interval_ms < MAX_INTERVAL )pos--;
              if ( pos < 0 )pos = NUMPIXELS - 1;
            }
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              led[i].update();
            }
          }
          else if ( type_animation == TYPE_MOVE_NO ) {
            if ( metro() == true ) {
              led[pos].setColor(led[pos].r, led[pos].g, led[pos].b );
            }
            led[pos].update();
          }
          else if ( type_animation == TYPE_ALLON ) {
            for ( int i = 0; i < NUMPIXELS; i++) {
              pixels.setPixelColor(i,
                                   pixels.Color(led[i].r, led[i].g, led[i].b));
            }
            pixels.show();
          }
        }
        boolean metro()
        {
          if ( (millis() - t) > interval_ms ) {
            t = millis();
            return true;
          }
          return false;
        }
        void clear() {
          for ( int i = 0; i < NUMPIXELS; i++ ) {
            led[i].setColor(0, 0, 0);
            led[i].update();
          }
        }
        void setColor(int _r, int _g, int _b, int pos) {
          led[pos].setColor(_r, _g, _b);
        }
        unsigned long int t = millis();
        int pos = 0;
        int interval_ms;
        int type_animation;
    
    };
    
    TapeLED tapeled;
    
    void setup()
    {
      Serial.begin(9600);
      Serial.setTimeout(5000);
      pixels.begin();
      tapeled.clear();
      tapeled.setup(1000, 1000, 150, 150, 200, 1000, TYPE_MOVE_FORWARD, 0);
    }
    
    void loop()
    {
      if ( Serial.available() > 0 ) {
        if (Serial.peek() == 13 || Serial.peek() == 10) {
          Serial.read();
          return;
        }
    
    
    
        long int value = Serial.parseInt();
        Serial.print("META INFO:");
        Serial.println(value);
        switch ( value )
        {
          case FORMAT_RED:
            value = Serial.parseInt();
            Serial.print("Format_RED:");
            Serial.println(value);
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              tapeled.setColor(value,
                               tapeled.led[tapeled.pos].g,
                               tapeled.led[tapeled.pos].b,
                               i);
            }
            break;
          case FORMAT_GREEN:
            value = Serial.parseInt();
            Serial.print("Format_GREEN:");
            Serial.println(value);
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              tapeled.setColor(tapeled.led[tapeled.pos].r,
                               value,
                               tapeled.led[tapeled.pos].b,
                               i);
            }
            break;
          case FORMAT_BLUE:
            value = Serial.parseInt();
            Serial.print("Format_BLUE:");
            Serial.println(value);
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              tapeled.setColor(tapeled.led[tapeled.pos].r,
                               tapeled.led[tapeled.pos].g,
                               value,
                               i);
            }
            break;
          case FORMAT_MOVE_FORWARD:
            tapeled.type_animation = TYPE_MOVE_FORWARD;
            break;
          case FORMAT_MOVE_BACKWARD:
            tapeled.type_animation = TYPE_MOVE_BACKWARD;
            break;
          case FORMAT_MOVE_NO:
            tapeled.type_animation = TYPE_MOVE_NO;
            break;
    
          case FORMAT_INTERVAL:
            value = Serial.parseInt();
            tapeled.interval_ms = value;
            break;
    
          case FORMAT_INC_DURATION:
            value = Serial.parseInt();
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              tapeled.led[i].setDuration(value,
                                         tapeled.led[i].dec_duration);
            }
            break;
          case FORMAT_DEC_DURATION:
            value = Serial.parseInt();
            for ( int i = 0; i < NUMPIXELS; i++ ) {
              tapeled.led[i].setDuration(tapeled.led[i].inc_duration,
                                         value);
            }
            break;
          case FORMAT_ALLON:
            tapeled.type_animation = TYPE_ALLON;
            break;
        }
    
      }
    
      tapeled.update();
    
    }