ichikawa0701
Published

M5Capsule Persistence of Vision (POV)

I created Persistence of Vision (POV). It detects acceleration with an internal gyro sensor and draws pictures in the air with LEDs.

BeginnerFull instructions provided5 hours52
M5Capsule Persistence of Vision (POV)

Things used in this project

Hardware components

Grove - WS2813 RGB LED Strip Waterproof - 60 LED/m - 1m
Seeed Studio Grove - WS2813 RGB LED Strip Waterproof - 60 LED/m - 1m
×1
M5Stack M5capsule
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Code

POV sauce code

Arduino
This is the source code for writing POV to M5capsule.
#include "M5Capsule.h"
#include <M5Unified.h>
#include <Adafruit_NeoPixel.h>


//IMUの設定
static constexpr const uint8_t calib_value = 64;
static uint8_t calib_countdown = 0;
static int prev_xpos[18];
const int up1 = 5;//シリアルプロッタでのグラフ用下限
const int up2 = 500;//シリアルプロッタでのグラフ用上限
const int gz_treshold_plus = 200;
const int gz_treshold_minus = -200;

//neoPixelの設定
const int DIN_PIN = 15; //データ入力ピン
const int LED_COUNT = 64; //LEDの数
Adafruit_NeoPixel pixels(LED_COUNT, DIN_PIN, NEO_GRB + NEO_KHZ800);

//rainbow関数の設定
unsigned long pre_time = 0; //前回の時間
// unsigned long curr_time = 0; //現在の時間
#define MAX_VAL 10  // 最大輝度の設定をする(暗い←0 ~ 255→明い)
unsigned long off_time= 3000; //自動オフまでの時間

//sleepの設定
unsigned long sleep_time= 60000; //関数実行後、sleepまでの時間 msec
unsigned long lastExecutionTime = 0; // 最後に関数が実行された時間を記録

//画像表示系の設定
extern const PROGMEM uint8_t img_array1[3][64][64];
extern const PROGMEM uint8_t img_array2[3][64][64];
extern const PROGMEM uint8_t img_array3[3][64][64];
extern const PROGMEM uint8_t img_array4[3][64][64];
extern const PROGMEM uint8_t img_array5[3][64][64];
extern const PROGMEM uint8_t img_array6[3][64][64];
extern const PROGMEM uint8_t img_array7[3][64][64];
extern const PROGMEM uint8_t img_array8[3][64][64];
extern const PROGMEM uint8_t img_array9[3][64][64];
extern const PROGMEM uint8_t img_array10[3][64][64];
extern const PROGMEM uint8_t img_array11[3][64][64];

const PROGMEM uint8_t* img_arrays[] = {
    (const PROGMEM uint8_t*)img_array1,
    (const PROGMEM uint8_t*)img_array2,
    (const PROGMEM uint8_t*)img_array3,
    (const PROGMEM uint8_t*)img_array4,
    (const PROGMEM uint8_t*)img_array5,
    (const PROGMEM uint8_t*)img_array6,
    (const PROGMEM uint8_t*)img_array7,
    (const PROGMEM uint8_t*)img_array8,
    (const PROGMEM uint8_t*)img_array9,
    (const PROGMEM uint8_t*)img_array10,
    (const PROGMEM uint8_t*)img_array11,
};

const int MAX_ARRAYS = sizeof(img_arrays) / sizeof(img_arrays[0]);
int track =0;

//点灯の設定
const int val_ratio = 5;//初期10 高いほど暗くなる
const int show_time = 1000;//点灯時間uesc単位
unsigned long func_delayTime = 200; // 次の関数が実行されるまでの所定の時間(ミリ秒)
\

//IMUキャリブレーションの関数。M5unifiedのサンプルコードから引用
void updateCalibration(uint32_t c, bool clear = false)
{
  Serial.println("updateCalibration start");
  calib_countdown = c;

  if (c == 0) {
    clear = true;
  }

  if (clear)
  {
    memset(prev_xpos, 0, sizeof(prev_xpos));

    if (c)
    {
      M5.Imu.setCalibration(calib_value, calib_value, calib_value);
    }
    else
    {
      M5.Imu.setCalibration(0, 0, calib_value);
      M5.Imu.saveOffsetToNVS();
    }
  }
  Serial.println("updateCalibration done");
}

//IMUキャリブレーションの関数。M5unifiedのサンプルコードから引用
void startCalibration(void)
{
  updateCalibration(10, true);
}


//rainbow点灯パターン関数
uint32_t WheelRainbow(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
    return pixels.Color(map(WheelPos * 3, 0, 255, 0, MAX_VAL), map(255 - WheelPos * 3, 0, 255, 0, MAX_VAL), 0);
  } else if(WheelPos < 170) {
    WheelPos -= 85;
    return pixels.Color(map(255 - WheelPos * 3, 0, 255, 0, MAX_VAL), 0, map(WheelPos * 3, 0, 255, 0, MAX_VAL));
  } else {
    WheelPos -= 170;
    return pixels.Color(0, map(WheelPos * 3, 0, 255, 0, MAX_VAL), map(255 - WheelPos * 3, 0, 255, 0, MAX_VAL));
  }
}
//青色グラデーション点灯パターン関数
uint32_t WheelBlueCyan(byte WheelPos) {
  if (WheelPos < 128) {
    return pixels.Color(0, WheelPos * 2, 255);
  } else {
    WheelPos -= 128;
    return pixels.Color(0, (255 - WheelPos * 2), 255);
  }
}
//点灯パターンを格納する配列
typedef uint32_t (*WheelFuncPtr)(byte);
WheelFuncPtr wheelFunctions[] = {
  WheelRainbow,
  WheelBlueCyan,
};
//点灯パターンのリセット関数
uint32_t Wheelreset() {
  return pixels.Color(0,0,0);
}

//点灯パターンの実行関数
void LED_cycle(int currentFunction){
  Serial.println("LED_cycle start");
  for (int i = 0; i < pixels.numPixels(); i++) {
    pixels.setPixelColor(i, Wheelreset());

  }
  delay(100);
  pre_time = millis();
  Serial.print("pre_time:"+String(pre_time));
  bool flag = true;
  while(flag){
    for (int j = 0; j < 256 * 5; j++) {
        for (int i = 0; i < pixels.numPixels(); i++) { 
        pixels.setPixelColor(i, wheelFunctions[currentFunction]
        ((((pixels.numPixels() - 1 - i) * 256 / pixels.numPixels()) + j) & 255));
        }
        pixels.show();
        if(millis() - pre_time > off_time){
            Serial.println("LED cycle中に一定時間経過しました。");
            pixels.clear();
            pixels.show();
            flag = false;
            delay(100);
            break;
        }    
        delay(10);
    }    
  }   

  Serial.println("LED_cycle end");
}


//画像表示関数 順振り時
void led_line(){
  Serial.println("led_line start");
  const PROGMEM uint8_t (*selected_array)[64][64] = (const PROGMEM uint8_t (*)[64][64])img_arrays[track];
  for (int y = 0; y < 64; y++) {
    // Serial.print("{");
    for (int i = 0; i < LED_COUNT; i++) {
      int r=selected_array[0][y][i]/val_ratio;
      int g=selected_array[1][y][i]/val_ratio;
      int b=selected_array[2][y][i]/val_ratio;
      pixels.setPixelColor(LED_COUNT-i-1, pixels.Color(r, g, b));
      // Serial.print("{" + String(r) + "," + String(g) + "," + String(b)+"}");
    }
    // Serial.print("},");
    pixels.show();
    // Serial.println("y position:" + String(y));
    delayMicroseconds(show_time);  // 適切な遅延を設定
  }  
  pixels.clear();
  pixels.show();
  lastExecutionTime = millis(); // 最後に関数が実行された時間を記録
  Serial.println("led_line end");
}

//画像表示関数 逆振り時
void led_line_r(){
  Serial.println("led_line_r start");
  const PROGMEM uint8_t (*selected_array)[64][64] = (const PROGMEM uint8_t (*)[64][64])img_arrays[track];
  for (int y = 63; y >= 0; y--) {
    // Serial.print("{");
    for (int i = 0; i < LED_COUNT; i++) {
      int r=selected_array[0][y][i]/val_ratio;
      int g=selected_array[1][y][i]/val_ratio;
      int b=selected_array[2][y][i]/val_ratio;
      pixels.setPixelColor(LED_COUNT-i-1, pixels.Color(r, g, b));
      // Serial.print("{" + String(r) + "," + String(g) + "," + String(b)+"}");
    }
    // Serial.print("},");
    pixels.show();
    // Serial.println("y position:" + String(y));
    delayMicroseconds(show_time);  // 適切な遅延を設定
  }  
  pixels.clear();
  pixels.show();
  lastExecutionTime = millis(); // 最後に関数が実行された時間を記録
  Serial.println("led_line_r end");
}



void setup(void)
{
  //m5の初期設定
  auto cfg = M5.config();
  M5Capsule.begin(cfg);
  //IMUの初期設定
  const char* name;
  auto imu_type = M5.Imu.getType();
  switch (imu_type)
  {
  case m5::imu_none:        name = "not found";   break;
  case m5::imu_sh200q:      name = "sh200q";      break;
  case m5::imu_mpu6050:     name = "mpu6050";     break;
  case m5::imu_mpu6886:     name = "mpu6886";     break;
  case m5::imu_mpu9250:     name = "mpu9250";     break;
  case m5::imu_bmi270:      name = "bmi270";      break;
  default:                  name = "unknown";     break;
  };

  //IMUの種類を表示。M5capsule はBMI270
  Serial.println("IMU type: " + String(name));

  if (imu_type == m5::imu_none)
  {
    for (;;) { delay(1); }
  }

  if (!M5.Imu.loadOffsetFromNVS())
  {
    startCalibration();
  }
  M5.Imu.begin();

  //neoPixelの初期設定
  pixels.begin();
  pixels.show(); 
  
  //LEDCycleで起動点灯
  LED_cycle(0);

  Serial.println("setup done");
}



void loop(void)
{
  M5Capsule.update();
  auto imu_update = M5.Imu.update();
  auto data = M5.Imu.getImuData();
  // 振り終わり後は少しだけ待つ
  if (millis() - lastExecutionTime < func_delayTime) {
      Serial.println("loop関数を実行するのを待っています。");
      return; // loop関数を直ちに終了
  }
  //スリープする
  if(millis()-lastExecutionTime > sleep_time){
      Serial.println("一定時間経過しました。");
      esp_deep_sleep_start();
  }

  int ax = data.accel.x;
  int ay = data.accel.y;
  int az = data.accel.z;
  int gx = data.gyro.x;
  int gy = data.gyro.y;
  int gz = data.gyro.z;
  // Serial.println("gz:" + String(gz));
  if(gz > gz_treshold_plus){
      M5Capsule.Speaker.tone(10000, 20);
      led_line();
  }
  else if(gz < gz_treshold_minus){
      M5Capsule.Speaker.tone(10000, 20);
      led_line_r();
  }

  if (M5Capsule.BtnA.wasPressed()) {
      M5Capsule.Speaker.tone(10000, 20);
      track++;
      if (track >= MAX_ARRAYS) {
        track = 0;
      }
      Serial.println("next_track:" + String(track));
  }      
}

Image file conversion source code

Python
This is the source code for converting image files.
Gamma correction has been applied to adjust the actual light intensity.
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt


# 画像ファイルを開く
img = Image.open(r'G:\マイドライブ\****')

# 画像を64x64のサイズにリサイズ
img = img.resize((64, 64))

# ガンマ値
gamma1 = 1.0
gamma2 = 0.4

# 画像データをnumpy配列に変換し、8ビットの整数に変換
img_array = np.array(img, dtype=np.float32) / 255

# ガンマ補正1
img_array1 = np.power(img_array, 1/gamma1)
# 8ビットの整数に変換
img_array1 = (img_array1 * 255).astype(np.uint8)

# ガンマ補正2
img_array2 = np.power(img_array, 1/gamma2)
# 8ビットの整数に変換
img_array2 = (img_array2 * 255).astype(np.uint8)

# 配列をinoファイルに出力
with open(r'G:\マイドライブ\***.ino', 'w') as f:
    f.write("const PROGMEM uint8_t img_array[3][64][64] = {\n")
    for color in range(3):  # RGBの3チャンネル
        f.write("{\n")
        for row in img_array2:
            f.write("{")
            f.write(",".join(str(row[i][color]) for i in range(64)))  # 64ピクセル
            f.write("},\n")
        f.write("},\n")
    f.write("};\n")

# 画像を表示
plt.figure(figsize=(10, 5))

plt.subplot(1, 2, 1)
plt.title(f'Gamma = {gamma1}')
plt.imshow(img_array1)

plt.subplot(1, 2, 2)
plt.title(f'Gamma = {gamma2}')
plt.imshow(img_array2)

plt.show()

Credits

ichikawa0701
1 project • 0 followers

Comments