NeoPixelとPro Microで3Dプリンタエンクロージャー内の照明を作ってみた|シリアルLED WS2812B

NeoPixelとPro Microで3Dプリンタエンクロージャー内の照明を作ってみた

NeoPixelとPro Microで、3Dプリンタエンクロージャー内の照明を作ってみました。ロータリーエンコーダーで色をコントロール可能なRGBライトになっています。

ゴール

NeoPixel(シリアルLED WS2812B)とPro Microを組み合わせてRGBライトを制作します。過去にもArduinoとシリアルLED(WS2812B)ArduinoとシリアルLEDでRGBライトを作ってみたでNeoPixelを制御してきましたが、今回はローターリースイッチを使ってHSV色空間のHueを変更することで色を調整できるようにします。ですからインターフェースはロータリーエンコーダひとつのみでさまざまな色の表現が可能です。

NeoPixelを連結させる
NeoPixelを連結させる

制作したRGBライトは3Dプリンタエンクロージャー内の照明に使います。

3Dプリンタのエンクロージャー内
3Dプリンタのエンクロージャー内

3Dプリンタのエンクロージャー内
3Dプリンタのエンクロージャー内

3Dプリンタのエンクロージャー内
3Dプリンタのエンクロージャー内

下の写真のようにエンクロージャーの支柱に結束バンドで輪っかを作り、制作したRGBライトを通して固定しています。

結束バンドで固定
結束バンドで固定

この記事で制作するRGBライトはYouTubeの アイデアノート channel で公開中です。ぜひこちらの動画をご覧ください。

つかうもの

この記事でつかうものをご紹介いたします。

NeoPixel(シリアルLED WS2812B)

NeoPixelというシリアルLEDを使います。型番WS2812BのシリアルLEDが60個ついています。

シリアルLEDは、以前に制作したRGBライトから部品取りして再利用しました。

Pro Micro

KEYESTUDIOさんから販売されているこちらのPro Microを使用しました。

▼ 他のArduinoやESP32を使ってもらってもも構いません。こちらの記事で色々な種類のArduinoをご紹介していますので、ご参考になさってみてください。

ロータリーエンコーダ

RGBの色相をコントロールするためにロータリーエンコーダを使います。秋月電子通商さんで販売されている ロータリーエンコーダ を使いました。Amazonでも入手可能ですのでご参考になさってみてください。

3Dプリンタ

3DプリンタはEnder3 V2を使っています。温度管理の難しいABSフィラメントを印刷したくて、3Dプリンタが丸ごと収まるエンクロージャーも購入しました。おかげでABSフィラメントで印刷しても反りがなく、失敗せずに印刷できるようになりました。

▼ ABS樹脂での印刷の関連記事

3Dプリンタ印刷物

廃物のテープライトを利用しましたので、そのままだと細切れで強度的にも不安でした。そこで、3Dプリンタでテープライトのレールガイドを作って強度を上げました。配線のはんだ付け部分は、写真の通り接着剤で補強しています。また、ロータリーエンコーダとPro Microを収納するためのケースも作成しました。NeoPixelとPro Microの電源供給はUSBコード二つを使ってそれぞれ別々にしました。

テープライトのレールガイド
テープライトのレールガイド

ロータリーエンコーダとPro Micro
ロータリーエンコーダとPro Micro

コントローラーのケース
コントローラーのケース

これらの印刷物(STLファイル)は、Thingiverseで公開中ですのでご参考ください。

NeoPixelとPro Microの配線図

こちらがNeoPixelとPro Microの配線図になります。NeoPixelはデータ線がひとつだけで済み、数珠つなぎのように複数つなげることが可能で、しかもそれぞれのLEDの色を独立して制御できます。NeoPixelの仕組みはArduinoとシリアルLED(WS2812B)で詳しく解説してますのでご参考ください。

NeoPixelとPro Microの配線
NeoPixelとPro Microの配線

ライブラリのインストール

シリアルLEDを動かすために、 adafruit/Adafruit_NeoPixel 1.10.7 のライブラリを使用しました。また、ロータリーエンコーダを扱いやすくするために、 mathertel/RotaryEncoder 1.5.3 ライブラリを使用しました。私は普段PlatformIOを使ってArduino開発しているため、ライブラリのソースファイルをダウンロードして直接配置しています。次のようなディレクトリ構造になりましたので、ご参考までに。

.
├── lib
│   ├── RotaryEncoder
│   │   ├── RotaryEncoder.cpp
│   │   └── RotaryEncoder.h
│   └── WS2812B
│       ├── Adafruit_NeoPixel.cpp
│       ├── Adafruit_NeoPixel.h
│       ├── esp.c
│       ├── esp8266.c
│       ├── kendyte_k210.c
│       └── rp2040_pio.h
├── platformio.ini
└── src
    └── main.cpp

NeoPixelとPro Microのプログラミング

次は、NeoPixelとPro Microのプログラミング例です。起動後数秒間、虹色のグラデーションのデモンストレーションを行います。その後、ローターリーエンコーダを回すと、RGBライトになって色を変更可能となります。

/**
 * @file main.cpp
 * @author Toshihiko Arai
 * @brief Neopixel(WS2812B)とPro Microを使ったRGBライト
 * @version 1.0
 * @date 2023-01-02
 *
 * @copyright Copyright (c) 101010.fun 2023
 *
 */
#include <Adafruit_NeoPixel.h>
#include <Arduino.h>
#include <RotaryEncoder.h>

#define NEO_PIN 5
#define NUMPIXELS 60
Adafruit_NeoPixel pixels(NUMPIXELS, NEO_PIN, NEO_GRB + NEO_KHZ800);

/**
 * @brief  ロータリーエンコーダ
 * A相、B相
 */
#define RE_A A2
#define RE_B A1
#define ROTARY_STEPS 4       // 色相Hueのステップ数
#define ROTARY_MIN 0         // 色相Hueの最小
#define ROTARY_MAX 360       // 色相Hueの最大
#define ROTARY_START_POS 90  // 色相Hueの初期値

RotaryEncoder encoder(RE_A, RE_B, RotaryEncoder::LatchMode::TWO03);
int lastPos = ROTARY_START_POS - 2;
int rgb[3];

/**
 * @brief 参考: https://www.codespeedy.com/hsv-to-rgb-in-cpp/
 * 
 * @param H 色相(Hue)
 * @param S 彩度(Saturation)
 * @param V 明度(Value)
 * @param rgb 
 */
void HSVtoRGB(float H, float S, float V, int* rgb) {
    if (H > 360 || H < 0 || S > 100 || S < 0 || V > 100 || V < 0) {
        Serial.println("The givem HSV values are not in valid range");
        return;
    }
    float s = S / 100;
    float v = V / 100;
    float C = s * v;
    float X = C * (1 - abs(fmod(H / 60.0, 2) - 1));
    float m = v - C;
    float r, g, b;
    if (H >= 0 && H < 60) {
        r = C, g = X, b = 0;
    } else if (H >= 60 && H < 120) {
        r = X, g = C, b = 0;
    } else if (H >= 120 && H < 180) {
        r = 0, g = C, b = X;
    } else if (H >= 180 && H < 240) {
        r = 0, g = X, b = C;
    } else if (H >= 240 && H < 300) {
        r = X, g = 0, b = C;
    } else {
        r = C, g = 0, b = X;
    }
    rgb[0] = int((r + m) * 255);
    rgb[1] = int((g + m) * 255);
    rgb[2] = int((b + m) * 255);
}

void demo(float beginHue) {
    for (int i = 0; i < NUMPIXELS; i++) {
        float hue = 360.0 * (float(i) / float(NUMPIXELS)) + beginHue;
        if (hue >= 360.0) {
            hue -= 360.0;
        }

        // Serial.println(hue);
        HSVtoRGB(hue, 100.0, 100.0, rgb);

        int red = rgb[0];
        int green = rgb[1];
        int blue = rgb[2];

        pixels.setPixelColor(i, pixels.Color(red, green, blue));
    }
    pixels.show();

    if (beginHue >= 360.0) {
        Serial.println("Done demo.");
        return;
    } else {
        beginHue += 3.6;
        // Serial.println(beginHue);
        demo(beginHue);
    }
}

void setup(void) {
    Serial.begin(115200);
    pixels.begin();
    encoder.setPosition(ROTARY_START_POS / ROTARY_STEPS);
    pixels.clear();
    demo(0.0);
    demo(0.0);
    demo(0.0);
    demo(0.0);
    demo(0.0);
    demo(0.0);
}

void loop() {
    encoder.tick();  // just call tick() to check the state.

    int newPos = encoder.getPosition() * ROTARY_STEPS;

    if (newPos < ROTARY_MIN) {
        encoder.setPosition(ROTARY_MAX / ROTARY_STEPS);
        newPos = ROTARY_MAX;

    } else if (newPos > ROTARY_MAX) {
        encoder.setPosition(ROTARY_MIN / ROTARY_STEPS);
        newPos = ROTARY_MIN;
    }

    if (lastPos != newPos) {
        Serial.println(lastPos);
        Serial.println(newPos);
        
        lastPos = newPos;
        float hue = float(lastPos);
        HSVtoRGB(hue, 100.0, 100.0, rgb);

        // Serial.println(lastPos);
        // Serial.println(hue);
        //  Serial.println(rgb[0]);
        int red = rgb[0];
        int green = rgb[1];
        int blue = rgb[2];
        // Serial.println(red);
        for (int i = 0; i < NUMPIXELS; i++) {
            pixels.setPixelColor(i, pixels.Color(red, green, blue));
        }
        pixels.show();
        delay(50);
    }
}

プログラムの解説

ロータリーエンコーダでHSV色空間のHue(色相)を変更後、その値をRGBに変換してシリアルLEDの色を変更させています。

ロータリーエンコーダは割り込み処理を使わずに、ポーリングで回転を検知しています。ポーリングで処理する方が変数管理に悩まなくて済みますし、トラブルも少ないです。と言いますか、私には割り込み処理で上手くやるスキルと自信がありません^^; ロータリーエンコーダの詳しい使い方はこちらの記事をご覧ください。

HSVからRGBに変換するプログラムはネットでいくつも見つかります。 こちらの記事 のソースコードを参考に、少し修正して使わせていただきました。

HSV色空間はRGBと違って色相(Hue)、彩度(Saturation)、明度(Value)の三要素で色を表現する仕組みです。画像編集ソフトのカラーピッカーでよく使われます。また、OpenCVを使った画像処理では、HSV処理をうまく使うことで特定の色のみ変更したり、特徴点を取得してくり抜いたりできます。以前にOpenCVで遊んだ経験が、本プロジェクトを進める上でも役立ちました。興味ある方はこれらの記事もご参考になさってみてください。

記事に関するご質問などがあれば、お問い合わせ までご連絡ください。
人気のArduino互換機
Arduinoで人気の周辺パーツ
あると便利な道具
Arduinoのオススメ参考書

▼ Arduino初心者向きの内容となっています。ほかのArduino書籍と比べて図や説明がとてもていねいで、読みやすかったです。Arduinoで一通りのセンサーが扱えるようになります。

▼ 外国人が書いた本を翻訳したものです。この手の書籍は、目からうろこな発見をすることが多いです。

▼ Arduinoの入門書を既に読んでいる方で、次のステップを目指したい人向きの本です。C言語のプログラミングの内容が中心です。ESP32だけでなく、ふつうのArduinoにも役立つ内容でした。

関連記事