ゆるく楽しむプログラミング

M5Atom Matrixを使ってみる ~LEDマトリクスと加速度センサー~

M5Atom Matrixの内蔵デバイス、5x5のカラーLEDマトリックスと6軸加速度センサーを使って「なんちゃって水準器」を作ってみました。



カラーLEDマトリックス

M5Atom MatrixのカラーLEDマトリックスに使用されているコントローラは,M5Atom LiteやM5Stamp PICOに使われているのと同じWS1218Bです。

GRBの24Bitを連続して25個書き込めば、LEDマトリクスが操作できます。
が、実際にやってみると、書き込み途中で割り込みが発生する? のか、一部のLEDが操作できません。
サンプルスケッチも動かないので何か根本的な部分で問題があるのだと思われます。

サンプルスケッチのbutton
LEDが全部点灯する筈が、一部のLEDが点灯しない。

購入直後にbuttonスケッチを書き込んだ際には動いていたので、ソフトウエア的な処理で回復できるのではないかと思う。




FastLEDライブラリを使用すれば全部のLEDが操作できるので、ハードウエア的な故障ではない。

25個のLEDを全て点灯すると、消費電力がとんでもない事になって発熱が凄いです。

極度に発熱しないように、明るさを抑えた方が良いです。

FastLEDライブラリでは、LEDの明るさを制限できますので、上限を設定しておくと安心です。

FastLED.setBrightness(明るさ0~255)


※ <>を全角<>に置き換えています。コピペの際は注意してください。
#include <FastLED.h>

#define BTN_PIN 39
const int NUM_LEDS = 25 ;
const int LED_PIN = 27 ;

CRGB leds[NUM_LEDS];
int btnCount = 0 ;
CRGB colors[4] = { CRGB(0x80,0x00,0x00),CRGB(0x00,0x80,0x00),CRGB(0x00,0x00,0x80),CRGB(0x80,0x80,0x80) } ;

void initDisplay() {
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(20); // ★明るさを20/255に設定
}

void setPixelAndShow(int i, CRGB c) {
  leds[i] = c;
  FastLED.show();
}
void setup() {
  pinMode(BTN_PIN,INPUT) ;
  initDisplay() ;
  for (int i=0;i<25;i++) {
    setPixelAndShow(i ,colors[btnCount]) ;
  }
}

void loop() {
  if (digitalRead(BTN_PIN) == LOW) {
    btnCount = (btnCount + 1) % 4 ;
    for (int i=0;i<25;i++) {
      setPixelAndShow(i ,colors[btnCount]) ;
    }
    delay(200) ;
  }
}

加速度センサー

XYZ軸の加速度と角速度を検知する6軸の加速度センサー MPU6886 を内蔵しています

接続ピン
 SDA:G25
 SCL:G21

I2Cアドレス: 0x68

起動時にはスリープモードになっているので
動作モードを設定しないと動きません。

使わない時は止まっているので邪魔にならない仕様です。
レジスタ 内容
0x3B-0x3C X軸加速度 (符号付き16Bit)
0x3D-0x3E Y軸加速度 (符号付き16Bit)
0x3F-0x40 Z軸加速度 (符号付き16Bit)
0x41-0x42 センサーの温度
0x43-0x44 X軸角速度 (符号付き16Bit)
0x45-0x46 Y軸角速度 (符号付き16Bit)
0x47-0x48 Z軸角速度 (符号付き16Bit)
0x1B 角速度計測レンジ
[4-3]:00=±250dps
01=±500dps
10=±1000dps
11=±2000dps
0x1C 加速度計測レンジ
[4-3]:00=±2g
01=±4g
10=±8g
11=±16g
0x6B [7]:リセット
[6]:SLEEP (起動時には1:スリープモード)
[5]:CYCLE
[4]:ジャイロスタンバイ
[3]:温度センサー無効
[2-0]:クロック選択
  0x68に動作モードを設定
 ※加速度と角速度を使用する場合 0 で良い。

0x1Bと0x1Cに計測レンジを設定

符号付き16Bitから加速度(g)への変換は
 センサー値 ÷ (32767÷計測レンジ)

 Ex.計測レンジが±2gの場合
  センサー値÷16384

符号付き16Bitから角速度(dps)への変換は
 センサー値 ÷ (326787÷計測レンジ)

 Ex.計測レンジが±250dpsの場合
  センサー値÷131


加速度からXY平面の傾きを求める

▼の式で求められます、単位はラジアンです。



Deg単位に変換する場合は π÷180 を掛ければよいです。
ArduinoIDEでは π÷180 が変換係数 RAD_TO_DEG で定義されていますので、コレをかけるだけです。

角度は角速度の積分で算出で来ます。
誤差が大きくなりますが簡易的には計測した角速度(dps)に計測間隔(秒)を掛けてれば求められますが・・・
センサーセンサー自体の誤差が大きいのか、静止状態でも緩やかに回転しているようにセンサー値が出てしまい上手く計測できませんでした。

※ <>を全角<>に置き換えています。コピペの際は注意してください。
#include <Wire.h>
#define SDA_PIN 25
#define SCL_PIN 21

#define IMU_ADDR 0x68

// Write to IMU Register
void IMUWrireRegister(uint8_t regAddr,uint8_t data) {
  Wire.beginTransmission(IMU_ADDR);
  Wire.write(regAddr);
  Wire.write(data);
  Wire.endTransmission();
}

// Read Accel Data
void IMUReadAccel(int16_t &accelX,int16_t &accelY,int16_t &accelZ) {
  // Read Address Set
  Wire.beginTransmission(IMU_ADDR);
  Wire.write(0x3B); // Accel register
  Wire.endTransmission();
  
  // 3Word Read
  Wire.requestFrom(IMU_ADDR, 6);
  while(Wire.available()<6);
  accelX = Wire.read()<<8|Wire.read();
  accelY = Wire.read()<<8|Wire.read();
  accelZ = Wire.read()<<8|Wire.read();

  return ;
}

// Read Gyro Data
void IMUReadGyro(int16_t &gyroX,int16_t &gyroY,int16_t &gyroZ) {
  // Read Address Set
  Wire.beginTransmission(IMU_ADDR);
  Wire.write(0x43); // Accel register
  Wire.endTransmission();
  
  // 3Word Read
  Wire.requestFrom(IMU_ADDR, 6);
  while(Wire.available()<6);
  gyroX = Wire.read()<<8|Wire.read();
  gyroY = Wire.read()<<8|Wire.read();
  gyroZ = Wire.read()<<8|Wire.read();
  return ;
}

void setup() {
  Serial.begin(9600);
  Wire.begin(SDA_PIN,SCL_PIN);

  // --- Initail IMU
  IMUWrireRegister(0x6B,0) ;  // PowerMode Initial

  // --- Set Sencer Scale
  IMUWrireRegister(0x1B,0) ;  // gyro ±250deg/s 
  IMUWrireRegister(0x1C,0) ;  // accel ±2g 
}

void loop() {
  float gForceX, gForceY, gForceZ;
  int16_t accelX, accelY, accelZ;
  IMUReadAccel(accelX,accelY,accelZ) ; 
  gForceX = accelX / 16384.0;
  gForceY = accelY / 16384.0; 
  gForceZ = accelZ / 16384.0;
  
  Serial.print(" Accel (g)");
  Serial.print(" X=");
  Serial.print(gForceX);
  Serial.print(" Y=");
  Serial.print(gForceY);
  Serial.print(" Z=");
  Serial.println(gForceZ);

  int16_t gyroX, gyroY, gyroZ;
  float rotX, rotY, rotZ;
  IMUReadGyro(gyroX,gyroY,gyroZ) ; 
  rotX = gyroX / 131.0;
  rotY = gyroY / 131.0; 
  rotZ = gyroZ / 131.0;
  
  Serial.print("Gyro (deg)");
  Serial.print(" X=");
  Serial.print(rotX);
  Serial.print(" Y=");
  Serial.print(rotY);
  Serial.print(" Z=");
  Serial.println(rotZ);

  // ---- calculate the angle
  double roll  = atan2(gForceY, gForceZ) * RAD_TO_DEG;
  double pitch = atan(-gForceX / sqrt(gForceY * gForceY + gForceZ * gForceZ)) * RAD_TO_DEG;

  Serial.print("Angle (deg)");
  Serial.print(" Roll=");
  Serial.print(roll);
  Serial.print(" Pitch=");
  Serial.println(pitch);
}



LEDマトリクスと加速度センサーで作る「なんちゃって水準器」

5x5のマトリクスで表示出来る解像度では「なんちゃって」にしかなりませんが、水平をLEDで表示してみました。
LEDマトリクスと加速度センサーの利用サンプルくらいの感じで実用性はありません。

※ <>を全角<>に置き換えています。コピペの際は注意してください。
#include <Wire.h>
#include <FastLED.h>

#define SDA_PIN 25
#define SCL_PIN 21
#define IMU_ADDR 0x68

// ----- MATRIX LED -----
int posX = 2 ;
int posY = 2 ;
int pos = 12 ;
const int NUM_LEDS = 25;
const int LED_PIN = 27;
CRGB leds[NUM_LEDS];

const int FIFO_NUM = 10 ;
int fifoPos = 0 ;
double fifoR[FIFO_NUM] ;
double fifoP[FIFO_NUM] ;

// INIT MATRIX
void initDisplay() {
  FastLED.addLeds<WS2812, LED_PIN>(leds, NUM_LEDS);
  FastLED.setBrightness(20);
}

// SHOW MATRIX
void setPixelAndShow(int i, uint32_t c) {
  leds[i] = c;
  FastLED.show();
}

// ----- IMU -----
// Write to IMU Register
void IMUWrireRegister(uint8_t regAddr,uint8_t data) {
  Wire.beginTransmission(IMU_ADDR);
  Wire.write(regAddr);
  Wire.write(data);
  Wire.endTransmission();
}

// Read Accel Data
void IMUReadAccel(int16_t &accelX,int16_t &accelY,int16_t &accelZ) {
  // Read Address Set
  Wire.beginTransmission(IMU_ADDR);
  Wire.write(0x3B); // Accel register
  Wire.endTransmission();
  
  // 3Word Read
  Wire.requestFrom(IMU_ADDR, 6);
  while(Wire.available()<6);
  accelX = Wire.read()<<8|Wire.read();
  accelY = Wire.read()<<8|Wire.read();
  accelZ = Wire.read()<<8|Wire.read();

  return ;
}

// Read Gyro Data
void IMUReadGyro(int16_t &gyroX,int16_t &gyroY,int16_t &gyroZ) {
  // Read Address Set
  Wire.beginTransmission(IMU_ADDR);
  Wire.write(0x43); // Accel register
  Wire.endTransmission();
  
  // 3Word Read
  Wire.requestFrom(IMU_ADDR, 6);
  while(Wire.available()<6);
  gyroX = Wire.read()<<8|Wire.read();
  gyroY = Wire.read()<<8|Wire.read();
  gyroZ = Wire.read()<<8|Wire.read();

  return ;
}

// ----- SETUP -----
void setup() {
//  Serial.begin(9600);
  Wire.begin(SDA_PIN,SCL_PIN);

  // --- Initail IMU
  IMUWrireRegister(0x6B,0) ;  // PowerMode Initial

  // --- Set Sencer Scale
  IMUWrireRegister(0x1B,0) ;  // gyro ±250deg/s 
  IMUWrireRegister(0x1C,0) ;  // accel ±2g 

  // FIFO初期設定
  fifoPos = 0 ;
  for (int i=0;i<FIFO_NUM;i++) {
    float gForceX, gForceY, gForceZ;
    int16_t accelX, accelY, accelZ;
    IMUReadAccel(accelX,accelY,accelZ) ; 
    gForceX = accelX / 16384.0; // 32768 ÷ 2 
    gForceY = accelY / 16384.0; 
    gForceZ = accelZ / 16384.0;
    
    // ---- calculate the angle
    double roll  = atan2(gForceY, gForceZ) * RAD_TO_DEG;
    double pitch = atan(-gForceX / sqrt(gForceY * gForceY + gForceZ * gForceZ)) * RAD_TO_DEG;
    roll += (roll > 0) ? -180.0 : 180.0 ;
    fifoR[fifoPos] = roll ;
    fifoP[fifoPos] = pitch ;
    fifoPos = (fifoPos +1) % FIFO_NUM ;
  }

  // ----- Initial Matrix LED
  initDisplay() ;
  pos = posY * 5 + posX ;
  setPixelAndShow(pos,0x00FF0000) ;
}

// ----- LOOP ----
void loop() {
  float gForceX, gForceY, gForceZ;
  int16_t accelX, accelY, accelZ;
  IMUReadAccel(accelX,accelY,accelZ) ; 
  gForceX = accelX / 16384.0; // 32768 ÷ 2 
  gForceY = accelY / 16384.0; 
  gForceZ = accelZ / 16384.0;
  
  // ---- calculate the angle
  double roll  = atan2(gForceY, gForceZ) * RAD_TO_DEG;
  double pitch = atan(-gForceX / sqrt(gForceY * gForceY + gForceZ * gForceZ)) * RAD_TO_DEG;
  roll += (roll > 0) ? -180.0 : 180.0 ;

  fifoR[fifoPos] = roll ;
  fifoP[fifoPos] = pitch ;
  fifoPos = (fifoPos +1) % FIFO_NUM ;

  roll = 0 ;
  pitch = 0 ;
  for (int i=0;i<FIFO_NUM;i++) {
    roll += fifoR[i] ;
    pitch += fifoP[i] ;
  }
  roll /= FIFO_NUM ;
  pitch /= FIFO_NUM ;

  posX = 2 ;
  if (pitch > 20) { posX = 4 ; } 
  else if (pitch > 10) { posX = 3 ; }
  if (pitch < -20) { posX = 0 ; }
  else if (pitch < -10) { posX = 1 ; }

  posY = 2 ;
  if (roll > 20) { posY = 4 ; } 
  else if (roll > 10) { posY = 3 ; }
  if (roll < -20) { posY = 0 ; }
  else if (roll < -10) { posY = 1 ; }
  
  setPixelAndShow(pos,0x00000000) ;
  pos = posY * 5 + posX ;
  setPixelAndShow(pos,0x00FF0000) ;
}

M5Atom Matrixを接続するとPCが発狂する件

M5Atom Matrixを接続するとPCが操作不能になる現象が発生しましした。
原因を探るべく、最後に書き込んだスケッチを簡略化してみたところ、最終的に▼でもダメでした。

void setup() {
  Serial.begin(9600);
}

void loop() {
  int roll ;
  int pitch ;
  Serial.print(roll);
  Serial.println(pitch);
}

シリアルポートを使うとアウトのようで、Windowsのデバイスマネージャーを見るとM5Atom Matrixがシリアルマウスにご認識されているようで
M5Atom Matrixを接続するとシリアルマウスが増えます。

このご認識されているデバイスを右クリックして
プロパティ画面を表示し[デバイスを無効にする]
をクリックすれば解決します。
マウスの操作が効かないので困難ですが、頑張れば何とかなります。


同じ環境でもM5Atom Liteは平気でしたし、必ず発生する訳でもないようです。
私は試してませんが 【旧記事】【サポート】シリアルポートにデバイスを接続するとマウスが暴走する!
の記事のようにレジストリを書き換えて、シリアルマウスを検出しなくする方法もあるようです。

解説動画