在宅ウェブ会議用LEDライト作ってみた

## 背景
昨今は、在宅勤務を余儀なくされている方々が多いと思います。
嫁さんが「ウェブ会議で映る自分の顔が暗い」と言ってたので
たまたま家にあったLEDリングライトとArduino互換ボードで
小型照明を作ってみました。

![LEDコントローラ01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247233/b7985f2f-a653-e918-8a3b-8a8a32709f48.jpeg)

ハードウエアなど詳細については、[別のブログ記事](https://shk-maker.hatenablog.jp/entry/2020/04/06/233240)に書いています。
こちらでは、電気回路とコード内容について書きました。

## 部品構成
|アイテム|数量|備考|
|---|:-:|---|
|Arduino互換ボード|1個| [わずか80円！謎のArduino互換機を動かしてみた](https://qiita.com/SamOnodera/items/fd614066a46c56865530:title) |
|LEDリングライト24灯|1個||
|ジョイスティック|1個||
|タクトスイッチ|3個|お好きなだけ|
|LEDカバー|1個|3Dプリンタ品|

SAMさん情報で買っていた謎の激安ボードを採用しました。
使い方はコチラ、ともさんのQiita記事
[LGT8f328P Arduino互換ボードを使ってみる](https://qiita.com/SamOnodera/items/fd614066a46c56865530)
を参考にしました。この情報が無ければ使えなかったでしょう、感謝！

## 電気回路図
次のブレッドボード図を参考お願いします。

![LED-controler01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247233/7bd42186-e491-3ae1-d1d6-839c34e1a969.jpeg)

図は本家のArduino MINIで描いていますので、読み替えお願いします。
MINIと同様にUSBシリアル変換を使って、ArduinoIDEでスケッチ書き込みできました。

## スケッチ
Arduino IDE 1.8.11 を使用しました。

以下、コードを少し区切って説明します。
順番に全てを繋げれば動くはずですが、必要に応じて適時修正お願いします。

```cpp
#include <FastLED.h>

#define NUM_LEDS 24
#define DATA_PIN 8           // Neopixel data pin
CRGB leds[NUM_LEDS];

const int xAxis = A0;         // joystick X axis
const int yAxis = A1;         // joystick Y axis
const int posButton = 13;     // input pin for position mode
const int clrButton = 12;     // input pin for color mode
const int briButton = 11;     // input pin for bright mode
int threshold = 1000000;        // resting threshold

int modeState = 0;            // effective mode state
int modeStated = 0;           // previous mode state
int resetModes = 3;           // countdown to reset
int resetMode = resetModes;
int freetimes = 100;          // countdown to mode cancel
int freetime = freetimes;

int posLed = 0;               // LED ON position
int posLeds[24] = {};
int hueLed = 64;              // LED color type, yellow
int satLed = 128;             // 255 = pure color, 0 = plain white
int valLed = 128;             // LED brightness

void setup() {
  Serial.begin(9600);
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
}
```

冒頭は、I/Oピン定義、各変数の定義です。
全ての変数の意味は、すみませんが説明省略します。
setupにて、neopixelのライブラリ定義。以降は、並んでいるLEDの番号ごとに
色や明るさを指定してから FastLED.show(); の命令で点灯。とても楽ちん。

```cpp
void loop() {
  pinMode(posButton, INPUT_PULLUP);
  pinMode(clrButton, INPUT_PULLUP);
  pinMode(briButton, INPUT_PULLUP);
  long xReading = (long)readAxis(xAxis) * -1;
  long yReading = (long)readAxis(yAxis);
  long rReading = (xReading * xReading) + (yReading * yReading);
  long degReading = _atan2(yReading , xReading);

  // mode select
  int posMode = digitalRead(posButton);
  int clrMode = digitalRead(clrButton);
  int briMode = digitalRead(briButton);
  if (posMode + clrMode + briMode == 0) modeState = 1;
  else if (posMode + clrMode + briMode == 2 and posMode == 0){
    modeState = 2;
    for(int j = 2; j < 8; j += 2){
      for(int i = 0; i < NUM_LEDS; i += 1) {    // mode effect & reset position
        if(i % j == 0) leds[i] = CHSV(0, 0, 128);
        else          leds[i] = CHSV(0, 0, 0);
        posLeds[i] = 0;
      }
      FastLED.show();
      delay(200);
    }
    if(modeState == modeStated) resetMode -= 1;
  }else if (posMode + clrMode + briMode == 2 and clrMode == 0){
    modeState = 3;
    for(int j = 0; j < 256; j += 32){
      for(int i = 0; i < NUM_LEDS; i += 1) {      // mode effect
        if(i > 15 and i < 21) leds[i] = CHSV(0, 0, 0);
        else          leds[i] = CHSV(j, 128, 128);
      }
      FastLED.show();
      delay(100);
    }
    satLed = 128;             //　reset saturation
    if(modeState == modeStated) resetMode -= 1;
  }else if (posMode + clrMode + briMode == 2 and briMode == 0){
    modeState = 4;
    for(int j = 0; j < 256; j += 32){
      for(int i = 0; i < NUM_LEDS; i += 1) {      // mode effect
        leds[i] = CHSV(0, 0, j);
      }
      FastLED.show();
      delay(100);
    }
    if(modeState == modeStated) resetMode -= 1;
  }else{
    if(resetMode < 0){
      switch (modeStated){
        case 2:       //long press posB = all turn on
        for(int i = 0; i < NUM_LEDS; i += 1) {
          posLeds[i] = 1;
        }
        break;
        case 3:       //long press clrB = plain white
        satLed = 0;
        break;
        case 4:       //long press briB = half lit
        valLed = 128;
        break;
      }
    }
    resetMode = resetModes;
    // long time hands-free = mode cancel
    if(rReading < threshold) freetime -= 1;
    if(freetime < 0) modeState = 0;
  }
  if(modeState != modeStated) freetime = freetimes;
```

ブレッドボード図の各種ボタンを押してモード切り替えします。
私は3ボタンにしたので、モードは3パターン。さらに長押しにも意味を持たせています。

|ボタン|モード|長押し|
|:-:|---|---|
| 右 |光る場所を指定|全周点灯| 
| 中央 |色を設定|無色(白)に設定| 
| 左 |明るさと彩度を変更|彩度を50%に設定| 

ボタン押している間は、役割に応じてアニメーション点灯する事で
何のモードに入ったか分かるようにしています。
例えば中央「色を設定」ならば、リングライトが「C」の文字に光ります。

```cpp
  // reset & all black
  switch (modeState){
    case 1:
    for(int i = 0; i < NUM_LEDS; i += 1){
      leds[i] = CHSV(0, 0, 0);
    }
    modeStated = 1;
    delay(1000);
    break;

  // change position mode
    case 2:
    if(rReading > threshold){
      posLed = map(degReading, 0, 36000, 0, 24);
      if(posLeds[posLed] == 0) posLeds[posLed] = 1;
    }
    modeStated = 2;
    break;

  // change color mode
    case 3:
    if(rReading > threshold){
      hueLed = map(degReading, 0, 36000, 0, 256);
    }
    modeStated = 3;
    break;

  // change bright mode
    case 4:
    if (xReading > threshold/100) satLed -= 4;
    else if(xReading < -threshold/100) satLed += 4;
    if (yReading > threshold/100) valLed -= 4;
    else if(yReading < -threshold/100) valLed += 4;
    if(satLed > 255) satLed = 255;
    else if(satLed < 0) satLed = 0;
    if(valLed > 255) valLed = 255;
    else if(valLed < 0) valLed = 0;    
    modeStated = 4;
    break;
  }

  // LED set & turn on
    for(int i = 0; i < NUM_LEDS; i += 1) {
      if(posLeds[i] == 1) leds[i] = CHSV(hueLed, satLed, valLed);
      else                leds[i] = CHSV(0, 0, 0);
    }
  FastLED.show();
  delay(100);
}

```

各種モードに入った後、ジョイスティックを動かした時の挙動を定義しています。
LEDの色や明るさを変数で設定してから、その通りに点灯させる、を繰り返してます。

```cpp
// read the analog input
int readAxis(int thisAxis) {
  int reading = analogRead(thisAxis);
  int distance = reading - 2000;       //adjusted center
  return distance;
}

// convert xy => degree(0 to 36000)
long _atan2(long _y, long _x) {
  long x = abs(_x);
  long y = abs(_y);
  float   z;
  bool    c;

  c = y < x;
  if (c)z = (float)y / x;
  else  z = (float)x / y;

  long a;
  //a = z * (-1556 * z + 6072);                     //2次曲線近似
  //a = z * (z * (-448 * z - 954) + 5894);          //3次曲線近似
  a = z * (z * (z * (829 * z - 2011) - 58) + 5741); //4次曲線近似

  if (c) {
    if (_x > 0) {
      if (_y < 0)a = 36000 - a;
    }
    if (_x < 0) {
      if (_y > 0)a = 18000 - a;
      if (_y < 0)a = 18000 + a;
    }
  }
  if (!c) {
    if (_x > 0) {
      if (_y > 0) a = 9000 - a;
      if (_y < 0) a = 27000 + a;
    }
    if (_x < 0) {
      if (_y > 0) a = a + 9000;
      if (_y < 0) a = 27000 - a;
    }
  }
  return a;
}
```

ジョイスティックのXY軸入力から、ベクトル角度に換算します。
次の記事を参考させていただきました。
[近似式でatan2の処理を高速化してみる](https://garchiving.com/approximation-atan2/)

## まとめ
今回も先人の方々の知恵や情報がなければ完成できませんでした。感謝感謝です。
この記事が少しでも参考になれば幸いです。