みなさん、こんにちは。
シリアル通信で制御するLEDとESP32でイルミネーションライトを製作しましたのでご紹介します。題名の通り、今回は初回編としてスタンドアロンで動作するものを製作しました。応用編としてWi-Fi経由でスマホから操作できる環境も製作します。こちらは完成次第ご紹介します。今回はスタンドアロン編ということで、製作物単体でイルミネーションライトになるものをご紹介します。
Arduinoのソースコードも掲載していますが独学のためヘタクソです。ご留意くださいませ。
使用部品
まずは今回使用した部品についてご説明します。
No | 名称 | 型格 | メーカー名 | 備考 |
1 | テープLED | BTF-5V-030L-B | (WORLDSEMI) | WS2812B(WORLDSEMI)が実装 |
2 | ESP-DevlitC | ESP-DevlitC | (Espressif) | ESP-WROOM-32が実装 |
3 | ESP-DevlitC用基板 | ― | でんゆう | ― |
テープLED
こちらのテープLEDはアマゾンで購入しました。
実装されているチップLEDはWS2812B(WORLDSEMI)というもので、ひとつひとつにマイコンが搭載されており、RGBのフルカラー点灯することができいます。そのためシリアル通信で各色の制御が可能になっています。
簡単にチップLED単体のスペックを確認しておきます。
パラメータ | 特性値 | |
電源電圧 | +3.7V~+5.3V | |
通信電圧 | 最大定格 | VDD-0.3V~VDD+0.3V |
VIH | +2.7V~VDD+0.7V | |
VIL | -0.3V~0.7V | |
消費電流 | データシートに記載無し |
電源電圧についてはUSBなどから5Vを入力すればOKです。通信電圧に関しても5Vでも大丈夫ですが、Arduinoや今回使用するESP32のような3.3Vレベルでも通信可能です。消費電力についてはデータシートに記載がありませんでしたが、メーカーHPを見ると各色12mAと記載がありました。LED1個あたり36mA消費する計算になります。
電流値以外で注意すべきことはありません。ハサミで必要な分だけカットして手軽に使用できる便利なフルカラーLEDです。
ESP-DevkitC
ESP-DevkitCとは、ESP-WROOM-32(Espressif)が実装された開発基板です。
ESP-WROOM-32はSoCであるESP32を搭載した無線モジュールでWi-FiやBlutoothが利用可能です。また書き込むファームウェアはArduino IDEでコンパイルできるため非常に扱いやすい無線モジュール開発基板となっています。
冒頭にも記載した通り、次の取り組みではWi-Fi経由でスマホから操作したいため、無線モジュールであるESP-DevkitCを使用します。
ESP-DevkitC用基板
ESP-DevkitCを手っ取り早く使用したかったため専用の基板を利用しました。
こちらは私が制作し、ラクマやメルカリで販売しております。必要があればご購入お願いいたします!
製作できたもの
上記で説明した部品で製作したものがこちらです。
ざっくり回路図はこちらです。デバッグ用LEDを付けていますが無くても問題ありません。
LEDはSPI通信で制御
LEDのシリアル通信について
自分に必要なデータ以外はそのまま出力
WS2812Bにはデータ入力端子と出力端子があります。テープLED状態では隣のLED同士が入出力端子で繋がっています。
WS2812Bデータシート|WORLDSEMI
この数珠つなぎの1本の通信ラインですべてのLEDを制御することができます。出力元からは全LEDの点灯情報を一気に送信します。
図のとおり、それぞれのLEDは自分に必要なデータだけ消化し、以降のLEDが必要としているデータは以降のLEDにそのままリレーしています。こうすることで直列に接続されたLEDをそれぞれ制御することが可能になります。
注意点として、LEDの接続数は把握しておく必要があります。例えばLEDが3個接続されているのに2個分のLEDデータだけ出力すると、3個目のLEDは点灯しないことになります。ファームウェアを作成する際はLEDの個数を間違えないようにしましょう。
1つのLEDに必要な情報は24bit(8bit×3)
前項において自分のデータを消化していくと説明しました。このデータ容量はひとつのLEDあたり24bitです。R・G・Bそれぞれ8bitずつで合計24bitとなります。LEDを複数繋げて使用する場合は24bit×LED個数分のデータが必要となります。
通信方式は独自:High時間とLow時間の組み合わせで1または0を表現
通信方式は一般的なシリアル通信と違い、High時間とLow時間の組み合わせで1または0を表現します。それぞれの時間規定は以下の通りです。
こちらの表現で各色0x00~0xFFまで表現します。おおよそ1bitあたり1250nsつまり800kbps程度の通信速度となります。
ポイントとなるのはHigh時間で380nsまでのHighであれば0と判定し、580ns以上のHighであれば1と判定されます。
例えば1つのLEDを約30%で黄色に光らせたいとします。各色の配色は以下の通りです。
- G(緑):0x50
- R(赤):0x50
- B(青):0x00
注意が必要なのが、24bitデータの構成としてG→R→Bの順番であることです。
各bitの0または1に対して時間規定どおりの波形を出力できればLEDは黄色に発行します。
SPI通信で簡単に制御
前項で説明した通りnsオーダーの制御が必要になりますが、Arduinoで簡易的に使用できるdelay関数ではusオーダーとなります。タイマーを利用すれば実現できると思いますが、今回はSPIを駆使して簡易的に実現しました。
SPIでの通信方法
SPIでの通信方法を解説します。まず時間規定に沿って以下の通りそれぞれの時間を決定します。
1bitあたり1200nsとなり833.3kbpsの通信となります。これを1bit 300nsのシリアル通信に置き換えます。
シリアル通信としては333.3kHzの通信で0x8または0xCのどちらかの出力となります。バイナリ1bitに対してSPIは4bit使うので、1色あたり32bit必要になります。
バイナリから送信データを作る
バイナリから送信データを作成する際は以下の関数を使用しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/////binary2trans_data///// //説明:1バイトのカラーデータ(0~255)を送信用データに変換する //引数:バイナリカラーデータ(0~255)[1byte] //戻り値:送信用カラーデータ[4byte]・・・最大0xCCCCCCCC、最小0x88888888 unsigned long binary2trans_data(byte binary_data) { unsigned long trans_data = 0x00000000; //送信用データ格納変数 byte count0 = 0x00; //カウンタ変数 do{ //LSBよりチェックする if((binary_data>>count0) & 0x01 == 0x01) //バイナリデータを右にカウント分(0~7)ビットシフトして1or0を判定する { trans_data += 0x0000000C << count0*4; //任意のビットが1の場合、'C'を左にビットシフトして代入 } else { trans_data += 0x00000008 << count0*4; //任意のビットが0の場合、'8'を左にビットシフトして代入 } count0++; }while(count0 < 8); //1byte分ループ return(trans_data); //送信用カラーデータを戻す } |
バイナリデータを1bitずつ見ていき、0なら0x8を1なら0xCを格納していきます。次のデータで上書きされないように最後は4bit分ビットシフトさせます。
この方法で先程例に挙げた黄色(G(緑):0x50、R(赤):0x50、B(青):0x00)を点灯させてみます。
書き込んだソースコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
#include <SPI.h> //PIN Assignment// #define DBG_LED 17 #define SCK 18 //SCK|SPI #define MISO 19 //MISO|SPI #define FULL_LED 23 //MOSI|SPI #define SS 5 //SS|SPI byte binary_data_GRB[3] = {0x50,0x50,0x00}; //送信バイナリ(1bit/処理 ずつ設定バイナリに近づけていく) byte reset = 0x00; void spi_config() { SPI.begin(SCK, MISO, FULL_LED, SS); SPISettings set0(3333333,MSBFIRST,SPI_MODE0); SPI.beginTransaction(set0); } /////setup///// void setup() { pinMode(DBG_LED,OUTPUT); pinMode(FULL_LED,OUTPUT); digitalWrite(FULL_LED,LOW); spi_config(); //SPIの設定を行う } /////binary2trans_data///// //説明:1バイトの送信バイナリ(0~255)を送信用データに変換する //引数:送信バイナリ(0~255)[1byte] //戻り値:送信用カラーデータ[4byte]・・・最大0xCCCCCCCC、最小0x88888888 unsigned long binary2trans_data(byte binary_data) { unsigned long trans_data = 0x00000000; //送信用データ格納変数 byte count0 = 0x00; //カウンタ変数 do{ //LSBよりチェックする if((binary_data>>count0) & 0x01 == 0x01) //送信バイナリを右にカウント分(0~7)ビットシフトして1or0を判定する { trans_data += 0x0000000C << count0*4; //任意のビットが1の場合、'C'を左にビットシフトして代入 } else { trans_data += 0x00000008 << count0*4; //任意のビットが0の場合、'8'を左にビットシフトして代入 } count0++; }while(count0 < 8); //1byte分ループ return(trans_data); //送信用データを戻す } /////main///// void loop() { unsigned long trans_data_GRB[3] = {0x88888888,0x88888888,0x88888888}; //送信用データ byte color_count = 0x00; //カラー数カウンタ(0x03までカウント[GRB]) do{ //バイナリデータを送信データに変換×3色分// trans_data_GRB[color_count] = binary2trans_data(binary_data_GRB[color_count]); color_count++; }while(color_count < 0x03); //3色分実行するために3回ループする //各色32bitずつ送信する// SPI.transfer32(trans_data_GRB[0]); //Green データ(24bit)送信 SPI.transfer32(trans_data_GRB[1]); //Red データ(24bit)送信 SPI.transfer32(trans_data_GRB[2]); //Blue データ(24bit)送信 //LEDごとに色を変える場合はここに処理を追加 SPI.transfer(reset); //リセットコマンド送信 delay(50); //ここを変更することでじんわり度を変更できる } |
1個目のLEDを最低限光らせるためのコードです。binary_data_GRB[3]の設定値を0x00~0xFFの範囲で変えることで色を変えることができます。
イルミネーション化
最終的にイルミネーション機能を盛り込んだソースがこちらです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
#include <SPI.h> #define LEDs 3 //LED数量定義 //PIN Assignment// #define DBG_LED 17 #define SCK 18 //SCK|SPI #define MISO 19 //MISO|SPI #define FULL_LED 23 //MOSI|SPI #define SS 5 //SS|SPI byte binary_data_GRB[3] = {0x00,0x00,0x00}; //送信バイナリ(1bit/処理 ずつ設定バイナリに近づけていく) byte binary_data_GRB_set0[3] = {0xFF,0xFF,0xFF}; //設定バイナリ(任意に設定) byte reset = 0x00; byte color_fixed_flag = 0x00; //カラーフィックスフラグ(送信バイナリが3色ともに設定バイナリに到達した際に0x03になる) byte Gmin = 0x20; byte Gmax = 0x40; byte Rmin = 0x20; byte Rmax = 0x40; byte Bmin = 0x00; byte Bmax = 0x10; void spi_config() { SPI.begin(SCK, MISO, FULL_LED, SS); SPISettings set0(3333333,MSBFIRST,SPI_MODE0); SPI.beginTransaction(set0); } /////setup///// void setup() { pinMode(DBG_LED,OUTPUT); pinMode(FULL_LED,OUTPUT); digitalWrite(FULL_LED,LOW); spi_config(); //SPIの設定を行う } /////binary2trans_data///// //説明:1バイトの送信バイナリ(0~255)を送信用データに変換する //引数:送信バイナリ(0~255)[1byte] //戻り値:送信用カラーデータ[4byte]・・・最大0xCCCCCCCC、最小0x88888888 unsigned long binary2trans_data(byte binary_data) { unsigned long trans_data = 0x00000000; //送信用データ格納変数 byte count0 = 0x00; //カウンタ変数 do{ //LSBよりチェックする if((binary_data>>count0) & 0x01 == 0x01) //送信バイナリを右にカウント分(0~7)ビットシフトして1or0を判定する { trans_data += 0x0000000C << count0*4; //任意のビットが1の場合、'C'を左にビットシフトして代入 } else { trans_data += 0x00000008 << count0*4; //任意のビットが0の場合、'8'を左にビットシフトして代入 } count0++; }while(count0 < 8); //1byte分ループ return(trans_data); //送信用データを戻す } /////sequential_transition///// //説明:設定バイナリに向かって送信バイナリを1ずつ近づけていく(例:設定0xFF 送信0x66→0x67) //引数:送信バイナリ(0~255)[1byte],カラー番号(0:G、1:R、2:B) //戻り値:元の送信バイナリから設定バイナリに向かって1ずつ近づけた送信バイナリ byte sequential_transition(byte binary,byte num) { if(binary_data_GRB_set0[num] > binary) //設定バイナリが現在の送信バイナリ以上のとき、送信バイナリを+1する { binary++; } else if(binary_data_GRB_set0[num] < binary) //設定バイナリが現在の送信バイナリ未満のとき、送信バイナリを-1する { binary--; } else if(binary_data_GRB_set0[num] == binary) //設定バイナリ=送信バイナリのとき、カラーフィックスフラグを+1する { color_fixed_flag++; } return(binary); } /////main///// void loop() { unsigned long trans_data_GRB[3] = {0x88888888,0x88888888,0x88888888}; //送信用データ byte leds_count = 0x00; //LEDの個数カウンタ(LEDsまでカウント) byte color_count = 0x00; //カラー数カウンタ(0x03までカウント[GRB]) if(color_fixed_flag == 0x03) //3色ともに設定カラーに到達した場合(設定バイナリ=送信バイナリ) { //各色の設定カラーを再設定する// binary_data_GRB_set0[0] = random(Gmin,Gmax); //下限値~上限値の間で設定カラーを再設定する。 binary_data_GRB_set0[1] = random(Rmin,Rmax); //上限、下限を設定することで例えば赤系のイルミネーションなど binary_data_GRB_set0[2] = random(Bmin,Bmax); //調色範囲を限定して設定できる。 color_fixed_flag = 0x00; //カラーフィックスフラグをクリアする delay(100); } else //3色ともに設定カラーに到達していない場合(設定バイナリ≠送信バイナリ) { color_fixed_flag = 0x00; //カラーフィックスフラグをクリアする do{ //設定バイナリに向かって送信バイナリを1ずつ近づけていく×3色分// binary_data_GRB[color_count] = sequential_transition(binary_data_GRB[color_count],color_count); color_count++; }while(color_count < 0x03); color_count = 0x00; do{ //LEDの個数分ループする(LED個数は定数LEDsで定義) do{ //バイナリデータを送信データに変換×3色分// trans_data_GRB[color_count] = binary2trans_data(binary_data_GRB[color_count]); color_count++; }while(color_count < 0x03); //3色分実行するために3回ループする //各色32bitずつ送信する// SPI.transfer32(trans_data_GRB[0]); //Green データ(24bit)送信 SPI.transfer32(trans_data_GRB[1]); //Red データ(24bit)送信 SPI.transfer32(trans_data_GRB[2]); //Blue データ(24bit)送信 //LEDごとに色を変える場合はここに処理を追加 leds_count++; }while(leds_count < LEDs); SPI.transfer(reset); //リセットコマンド送信 delay(50); //ここを変更することでじんわり度を変更できる } } |
イルミネーション化した際のポイントはこちらです。
設定値に1bitずつ近づけてじんわり変化させていく
カラー設定値目がけて1処理ごとに1bitずつ近づけていきます。こうすることでLEDがなめらかに変色していきます。また処理ごとにdelay時間をもたせることで変化速度を調節できます。下図でいうとカメの歩く速度です。
ランダム関数を使用することで不規則なイルミネーションが可能
ランダムに整数を生成するランダム関数を使用することで、変化色が不規則なイルミネーションができます。
またランダム整数に範囲をもたせることで色系を定めたイルミネーションにすることも可能です。
SPIでの出力値をシリアル出力してみました。(HEX表記)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
---------------------- CC888C88 CC888C88 CC888C88 ---------------------- CC888C8C CC888C8C CC888C8C ---------------------- CC888CC8 CC888CC8 CC888CC8 ---------------------- CC888CCC CC888CCC CC888CCC ---------------------- CC88C888 CC88C888 CC88C888 ---------------------- |
Cと8で1bitずつ変化しているのがわかります。
まとめ
今回はシリアル制御LEDをSPIで制御しイルミネーションを製作しました。次はスマホで色系統を設定できる仕組みを作りたいと思っていますのでお読みいただければと思います!
また、LEDをシーケンシャルに光らせて見たいのでいずれやります!
お読みいただきありがとうございました!