前回はRGB LEDを使用して、ランダムな色を次々に発光させてみました。
今回は柔らかく色が変化するように制御してみましょう。
回路は前回と全く同じです。
以下のカラーモデル(HSVカラーモデル)を使用して、カラーを0から255の間で変化させます。
コード
/**********************************************************************
Filename : SoftColorfulLight
Description : Colorful light with gradually changing color.
Auther : www.freenove.com
Modification: 2022/10/20
**********************************************************************/
const byte ledPins[] = {38, 39, 40}; //define led pins
const byte chns[] = {0, 1, 2}; //define the pwm channels
void setup() {
for (int i = 0; i < 3; i++) { //setup the pwm channels
ledcSetup(chns[i], 1000, 8);
ledcAttachPin(ledPins[i], chns[i]);
}
}
void loop() {
for (int i = 0; i < 256; i++) {
setColor(wheel(i));
delay(20);
}
}
void setColor(long rgb) {
ledcWrite(chns[0], 255 - (rgb >> 16) & 0xFF);
ledcWrite(chns[1], 255 - (rgb >> 8) & 0xFF);
ledcWrite(chns[2], 255 - (rgb >> 0) & 0xFF);
}
long wheel(int pos) {
long WheelPos = pos % 0xff;
if (WheelPos < 85) {
return ((255 - WheelPos * 3) << 16) | ((WheelPos * 3) << 8);
} else if (WheelPos < 170) {
WheelPos -= 85;
return (((255 - WheelPos * 3) << 8) | (WheelPos * 3));
} else {
WheelPos -= 170;
return ((WheelPos * 3) << 16 | (255 - WheelPos * 3));
}
}
ちょっと長くて複雑に感じるかもしれませんが、一つずつ紐解いていくことで理解できるはずです。まずはsetColor()関数を見てみましょう。
void setColor(long rgb) {
ledcWrite(chns[0], 255 - (rgb >> 16) & 0xFF);
ledcWrite(chns[1], 255 - (rgb >> 8) & 0xFF);
ledcWrite(chns[2], 255 - (rgb >> 0) & 0xFF);
}
setColor() 関数では、RGB の値を表す変数が使われています。そして、色の16進数表現は、0xRRGGBB のような形式で広く使われています。HTMLでは#RRGGBBという表記をします。ここで、RRは赤の値、GGは緑の値、BBは青の値を表します。16進数は0~9とA~Fの計16文字で表します。28を24×24に分割して、16進数2桁で0~255を表現する形式です。変数を使用することで、パラメータの受け渡しがより便利になり、分割時には簡単な演算で各色チャンネルの値を取得することができます。
24行目 16進数の RGB 値を rgb
引数として受け取ります。
25行目 シフト演算 (>>
)とビットマスク(& 0xFF
) を使用して、16進数の rgb
値から、赤、緑、青それぞれの 8 ビット値を抽出します。
シフト演算は、2進数の値をビット単位で左右にシフトさせる演算です。以下の2種類があります。
左シフト演算 (<<): 指定したビット数だけ左にシフトします。シフトされたビットは0で埋められます。
右シフト演算 (>>): 指定したビット数だけ右にシフトします。
ビットマスクは特定のビットのみを操作するために使用されるビットパターンのことです。AND演算子とビットマスクを組み合わせることで、特定のビットを1に設定したり、0に設定したりすることができます。
0xFFというのは、2進数になおすと、11111111のように1が8つ並びます。
それと他の値とのビットANDを計算すると、1のところ(つまり下位8ビット)はそのままで、9ビット目から上は全部0になります。
結果として、下位8ビットだけが取り出せます。
『(rgb >> 16) & 0xFF』の場合、16ビット右シフトしていて、そもそも下位8ビットのみになっているので、理論的には0xFF でビットマスクをする意味は無いです。ただし、可読性の向上という観点や、バグを減らすという観点では書いておいて損はないです。
wheel()関数の箇所は0~255の数値をRRGGBBに引き伸ばしている処理ですね。
long wheel(int pos) {
long WheelPos = pos % 0xff;
if (WheelPos < 85) {
return ((255 - WheelPos * 3) << 16) | ((WheelPos * 3) << 8);
} else if (WheelPos < 170) {
WheelPos -= 85;
return (((255 - WheelPos * 3) << 8) | (WheelPos * 3));
} else {
WheelPos -= 170;
return ((WheelPos * 3) << 16 | (255 - WheelPos * 3));
}
}
ある意味ではwheel()関数がこのプログラムの一番肝になる部分なのですが、HSVカラーモデルの説明を抜きには解説できないので、ここでは単純に引き伸ばしているとだけ理解するのが良いと思います。HSVカラーモデルをご存知の人はもう少し深掘りしてみても良いと思います。
『if』はそのまま『もし~だったら』という条件分岐です。ここまで読み進めてこられた方であれば、雰囲気でわかるようになってきた頃合いじゃないでしょうか。
『WheelPos -= 85;』という書き方は『WheelPosから85を引いた結果をWheelPosに代入する』という意味です。
動作させるとこうなります
色が柔らかく変化するようになりましたね。カラーモデルについては色々な考え方があるので、アルゴリズムを入れ替えながら遊んでみるのも楽しいですね!
次回はマイコン搭載のLEDチップ、WS2812を光らせてみましょう。
コメント