昨日買ったものを動かしてみた話

また、つまらぬ物を買ってしまった…。　...嘘です、面白かったです！

# はじめに
- 昨日、[ダイヤル型入力装置（エンコーダ）](https://docs.m5stack.com/ja/hat/MiniEncoderC%20Hat)と[帯状のLED（stripLED）](http://docs.m5stack.com/ja/unit/rgb_led_strip)を買ったので、組み合わせて動かしてみた。

# 動作
- stripLEDの連続した7つを「白、赤、緑、青、黄色、水色、紫色」で光らせて、スクロールさせた。
- エンコーダも活躍させるため、ダイヤルをひねるとスクロール速度を変化させ、ボタンを押している間、スクロール方向を反転させることとした。
![GIF_20240407_173642_883.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2733720/15e86755-d204-4868-7e6b-c69bb2095d8c.gif)

# 書いたコード(arduinoIDE)
```cpp

#include <M5Unified.h>
#include <Unit_MiniEncoderC.h>
#include <Adafruit_NeoPixel.h>

#define SIGPIN 32  // 信号ピン
#define LEDNUM 15  // stripLEDのLED数

UNIT_MINIENCODERC encoder;

static M5Canvas cv1(&M5.Lcd);//エンコーダのボタン状態、エンコード値表示用
static M5Canvas cv2(&M5.Lcd);//エンコーダ値の増減表示用
static M5Canvas cv3(&M5.Lcd);//stripLEDのループ周期と方向表示用

uint8_t i2c_address = 0;      //エンコーダのアドレス
int32_t last_value = 0;       //最後に取得したエンコード値
uint8_t strip_loop_time = 0;  //stripLEDのループ周期(msec)
bool strip_direction = true;  //stripLEDのループ方向
uint16_t cnt = 0;

uint8_t brt = 30;  //stripLEDの明るさ(0-255)
Adafruit_NeoPixel strip(LEDNUM, SIGPIN, NEO_GRB);

void page_get_encoder(void) {
  int32_t encoder_value = encoder.getEncoderValue();  //1ノッチで、2増減する。ノッチ間で回転を止めると奇数も可。1回転で60。
  bool btn_status = encoder.getButtonStatus();        //通常1,押された状態で0
  Serial.println(encoder_value);

  //増減でエンコーダ裏面のLED色変化
  if (last_value != encoder_value) {
    if (last_value > encoder_value) {
      encoder.setLEDColor(1, 0x000011);  //増加：赤
    } else {
      encoder.setLEDColor(2, 0x110000);  //減少：青
    }
    last_value = encoder_value;
  }

  //エンコーダのボタンでエンコーダ裏面のLED変化
  if (!btn_status) {
    encoder.setLEDColor(0, 0x001100);  //ボタン押下：緑
  }

  cv1.fillScreen(BLACK);
  cv1.setTextColor(WHITE);
  cv1.drawString("ボタン状態:" + String(btn_status), 10, 10);
  cv1.drawString("エンコーダ値:", 10, 40);
  cv1.setCursor(10, 65);
  cv1.setTextColor(GREEN);
  cv1.printf("%d", encoder_value);
  cv1.pushSprite(0, 0);

  //上下限設定
  if (encoder_value < -126) {
    encoder.setEncoderValue(-126);
    encoder_value = -126;
  } else if (encoder_value > 126) {
    encoder.setEncoderValue(126);
    encoder_value = 126;
  }

  //stripLED用の設定
  strip_loop_time = 128 + encoder_value;  //128msを中心に増減
  strip_direction = btn_status;           //順方向1,逆方向0

  //stripLED用の設定表示
  cv3.fillScreen(BLACK);
  cv3.setTextColor(WHITE);
  cv3.drawString("loop方向:" + String(btn_status ? "順" : "逆"), 10, 10);
  cv3.drawString("loop時間(msec):", 10, 40);
  cv3.setCursor(10, 65);
  cv3.setTextColor(GREEN);
  cv3.printf("%d", strip_loop_time);
  cv3.setTextColor(WHITE);
  cv3.pushSprite(0, 140);
}


//7色表示
void stripShow(uint16_t cnt) {
  strip.clear();                                                               //消灯
  strip.setPixelColor((LEDNUM + cnt - 0) % LEDNUM, strip.Color(80, 80, 80));   //白(R+G+B)
  strip.setPixelColor((LEDNUM + cnt - 1) % LEDNUM, strip.Color(240, 0, 0));    //赤(R)
  strip.setPixelColor((LEDNUM + cnt - 2) % LEDNUM, strip.Color(0, 240, 0));    //緑(G)
  strip.setPixelColor((LEDNUM + cnt - 3) % LEDNUM, strip.Color(0, 0, 240));    //青(B)
  strip.setPixelColor((LEDNUM + cnt - 4) % LEDNUM, strip.Color(120, 120, 0));  //黄(R+G)
  strip.setPixelColor((LEDNUM + cnt - 5) % LEDNUM, strip.Color(0, 120, 120));  //水(G+B)
  strip.setPixelColor((LEDNUM + cnt - 6) % LEDNUM, strip.Color(120, 0, 120));  //紫(R+B)
  strip.setPixelColor((LEDNUM + cnt - 7) % LEDNUM, strip.Color(0, 0, 0));      //黒
  strip.setPixelColor((LEDNUM + cnt + 1) % LEDNUM, strip.Color(0, 0, 0));      //黒(逆方向で色を消すのに必要)
  strip.show();                                                                //点灯
}

void setup() {
  M5.begin();

  cv1.createSprite(135, 100);  //エンコーダのボタン状態、エンコード値表示用
  cv2.createSprite(135, 40);   //エンコーダ値の増減表示用
  cv3.createSprite(135, 100);  //stripLEDのループ周期と方向表示用
  cv1.setFont(&lgfxJapanMincho_16);
  cv2.setFont(&lgfxJapanMincho_16);
  cv3.setFont(&lgfxJapanMincho_16);

  encoder.begin(&Wire, MINIENCODERC_ADDR, 0, 26, 100000UL);
  i2c_address = encoder.getI2CAddress();

  strip.begin();             //pixelLED初期化
  strip.setBrightness(brt);  //明るさ(0-255)
}

void loop() {
  M5.update();
  if (M5.BtnA.wasPressed()) { //前回ボタンAを押した時点からの増減値を取得
    cv2.fillScreen(BLACK);
    cv2.setTextColor(WHITE);
    cv2.drawString("増減:", 10, 10);
    cv2.setTextColor(CYAN);
    cv2.setCursor(50, 10);
    cv2.print(encoder.getIncrementValue());  //前回取得時との差異
    cv2.pushSprite(0, 100);

  } else if (M5.BtnB.wasPressed()) { //ボタンBを押すとエンコーダ値リセット
    encoder.resetCounter();  
    cv2.fillScreen(BLACK);
    cv2.drawString("リセット!", 10, 10);
    cv2.pushSprite(0, 100);
  }

  page_get_encoder();
  stripShow(cnt);
  cnt = cnt + (strip_direction ? 1 : -1);  //ボタンを押している間、逆方向にスクロール
  delay(strip_loop_time);
}
```

# コードの内容
- [動作](https://qiita.com/kei2y/items/679e43bbc394e1fea961#%E5%8B%95%E4%BD%9C)に書いたこと以外を書くと、M5stickCPlus2のボタンBを押すと、エンコード値がリセット(0)となり、ボタンAを押すと、前回ボタンAを押したときからのエンコード値の増減を画面に表示する。
- stripLEDのスクロールはloopによるものだが、loop周期(msec)は128を中心とし、エンコード値を加えたものとした。

# コードの解説
- エンコーダのライブラリ<Unit_MiniEncoderC.h>とstripLEDのライブラリ<Adafruit_NeoPixel.h>を利用したので、お手軽に実装できた。
- エンコーダの初期設定は、 `encoder.begin(&Wire, MINIENCODERC_ADDR, 0, 26, 100000UL);` 
- 0はSDA、26はSCLに割り当てたM5stickCPlus2のピン番号。
- 最後の100000ULはspeedのようだが、深く考えず、sampleのスケッチを使い回した。
- 使用したエンコーダのメソッドは、
    -  `getI2CAddress()` I2Cアドレス取得
    -  `getEncoderValue()` エンコーダ値取得
    -  `setEncoderValue(int32_t value)` エンコーダ値設定
    -  `resetCounter()` エンコーダ値リセット
    -  `getIncrementValue()` 前回の増減取得時からの増減を取得
- ダイヤルを一周回すとエンコード値は60増減する（時計回りで増加）。
- 回すとノッチ感があり、１ノッチで2増減する。
- stripLEDの初期設定は、 `Adafruit_NeoPixel strip(LEDNUM, SIGPIN, NEO_GRB);` 
- 最後のNEO_GRBはledの色指定時の並びを定義している模様。
- 使用したstripLEDのメソッドは、
    -  `clear()` 消灯
    -  `show()` 点灯
    -  `setPixelColor(uint16_t n, uint32_t c)` n番目のledの色指定

# 終わりに
- 今日は、サイクルモード東京2024にチャリで行くか、昨日買ったものを動かすプログラムを書くか、迷った末に、プログラムを書くことにした。
- 気がつけば、富士ヒルクライムのエントリー期限も過ぎてたし、このまま運動をサボっているとBMIがK点超えするのも時間の問題。
- そろそろ、プログラムを書くのも控えた方がいいかも。