2015年11月11日水曜日

「机ドン!」検出デバイス改良@Arduino


■暫定仕様でした■

前バージョンは「とりあえず動けばいいや」で、各軸どれかがボリュームでセットした値を超えたら2段階の明るさでランプを明滅させる、という安易な仕様でした。

これだと一度、例えば閾値ギリギリの信号で点滅が始まるとその後に強い衝撃が来ても無視されてしまいます。幸い、滅多にそういうケースはないのですが、可能性としては排除できません。

■改良版■

まず、加速度を各軸ごとの値ではなくスカラー値として処理します。各軸値自乗の総和の平方根ですね(sqrt(x*x + y*y + z*z))。スカラー値はセンサーの向きが変わっても一定のはずなので、起動時に値を保持しておけば、あとは初期スカラー値と瞬間のスカラー値の差をとれば、どの方向であれ掛かった加速度の大きさがわかります。なお、255を超える値を自乗した時に整数だとオーバーフローしてしまうので、floatに直しています。

それと、少し簡略版ですが、加速度の最大値でランプのピークの明るさを決めるようにしました。ピークかどうかを検出するのは、一定の時間ごとにサンプルした今回の値が前回の値よりも下がったら前回がピークだった、ということになります。本当ならそのピーク値が前回のピーク値より上かどうかを調べれば、大きな山から下っていくところに出来た小さなピークを誤認することがなくなるのですが、今回は「ドン!」という比較的単純な衝撃の検出なのでそこまで凝ったことはしませんでしたm(_ _)m 

この辺は本当にこり始めるとキリがないですし、味付けというか装置の賢さを感じる要素だったりするんですが…とりあえず週末までに何か考えます。

なお、ハードウェアには一切手を加えていません。

■結果■

以下2点の問題が解決しました。

  • 水平に置いて起動した後、傾けて置いても点滅しっぱなしにならない。
  • 大きな衝撃を与えた場合にきっちり強くランプが点灯する。

■ソース■


#include <Wire.h>
#include <Adafruit_NeoPixel.h>
#include <ADXL345.h>
#include <Time.h>
//
// NeoPixel
//
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN 2
#define NUMPIXELS 1
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
//
// ADXL345
//
ADXL345 adxl; //variable adxl is an instance of the ADXL345 library
#define MaxWaves 64
int wave[MaxWaves];
int aveX, aveY, aveZ, aveScalar;
void setup() {
Serial.begin(115200);
//
// NeoPixels
//
#if defined (__AVR_ATtiny85__)
if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
#endif
// End of trinket special code
pixels.begin(); // This initializes the NeoPixel library.
//
// sine curve
//
for (int i = 0; i < MaxWaves; i++) {
wave[i] = sin((float)i * 3.14159265 / (float)MaxWaves) * 255;
}
//
// ADXL345
//
adxl.powerOn();
adxl.setActivityX(1);
adxl.setActivityY(1);
adxl.setActivityZ(1);
adxl.setInactivityX(1);
adxl.setInactivityY(1);
adxl.setInactivityZ(1);
delay(1000);
int sumX = 0, sumY = 0, sumZ = 0, sumS = 0;
const int times = 10;
for (int i = 0; i < times; i++) {
int x,y,z, scalar;
adxl.readAccel(&x, &y, &z); //read the accelerometer values and store them in variables x,y,z
sumX += x;
sumY += y;
sumZ += z;
sumS += calcScalar(x, y, z);
delay(100);
}
aveX = sumX / times;
aveY = sumY / times;
aveZ = sumZ / times;
aveScalar = sumS / times;
Serial.print("Test calcScalar=");
Serial.println(calcScalar(10, -20, 250));
}
// loop counter
int xStep = -1;
int yStep = -1;
int zStep = -1;
// max loop counter
const int MaxSteps = MaxWaves * 5;
// minimum movement
int sensitive;
// range of brightness
int magX, magY, magZ;
//
// calc brightness by range and step in loop
//
int brightness(int inMag, int inStep) {
int ret = wave[inStep % MaxWaves] * inMag / 100;
if (ret < 0) ret = 0;
if (ret > 255) ret = 255;
return ret;
}
//
// set brightness range
//
int calcMag(int inDiff) {
int ret = 10;
if (inDiff > sensitive * 2) {
ret = 100;
} else if (inDiff > sensitive * 2) {
ret = 20;
}
return ret;
}
// peek of scalar
int maxScalar;
//
// calc scalar
//
int calcScalar(int inX, int inY, int inZ) {
float x = inX;
float y = inY;
float z = inZ;
return sqrt(x*x + y*y + z*z);
}
// average value of volume
int aveVol = 0;
void loop() {
//
// RGB each brightness
//
int red = 0, green = 0, blue = 0;
//
// read volume and set sensitivity
//
aveVol = (aveVol * 9 + analogRead(20) / 8) /10;
sensitive = aveVol / 10;
//
// read accelometer and detect movement
//
int x,y,z, scalar;
adxl.readAccel(&x, &y, &z); //read the accelerometer values and store them in variables x,y,z
scalar = calcScalar(x, y, z);
aveX = (aveX * 9 + x) / 10;
aveY = (aveY * 9 + y) / 10;
aveZ = (aveZ * 9 + z) / 10;
int diffX = abs(aveX - x);
int diffY = abs(aveY - y);
int diffZ = abs(aveZ - z);
int diffS = abs(aveScalar - scalar);
//
// if movement is harder than expected, start flashing!
//
if (diffS > maxScalar) {
maxScalar = diffS;
} else if (maxScalar != 0 && maxScalar > diffS) {
Serial.print("ave scalar=");
Serial.print(aveScalar);
Serial.print("diff scalar=");
Serial.print(diffS);
Serial.print("max scalar=");
Serial.print(maxScalar);
Serial.print("sensitive=");
Serial.print(sensitive);
Serial.print("x=");
Serial.print(diffX);
Serial.print("y=");
Serial.print(diffY);
Serial.print("z=");
Serial.println(diffZ);
if (maxScalar > sensitive) {
if (diffX > sensitive) {
// if (xStep == -1) {
xStep = 0;
magX = calcMag(diffX);
// }
}
if (diffY > sensitive) {
// if (yStep == -1) {
yStep = 0;
magY = calcMag(diffY);
// }
}
if (diffZ > sensitive) {
// if (zStep == -1) {
zStep = 0;
magZ = calcMag(diffZ);
// }
}
maxScalar = 0;
}
}
//
// manage flashing
//
if (xStep != -1) {
if (xStep > MaxSteps) {
xStep = -1;
} else {
green = brightness(magX, xStep++);
}
}
if (yStep != -1) {
if (yStep > MaxSteps) {
yStep = -1;
} else {
blue = brightness(magY, yStep++);
}
}
if (zStep != -1) {
if (zStep > MaxSteps) {
zStep = -1;
} else {
red = brightness(magZ, zStep++);
}
}
//
// controll WS2811
//
pixels.setPixelColor(0, pixels.Color(green, red, blue));
pixels.show();
// wait for 5mSec
//
delay(5);
}

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。