2016年5月15日日曜日

Arduino+ADXL345で地震酔い対策


■地震酔いとは■

東日本大震災では東京はいくつかの施設を除いては被害を免れました。でも、頻繁に震度3-4の揺れを経験していると、そのうちふと揺れを感じているかのような錯覚を覚えることがあります。本当に揺れているのか錯覚なのかわからないんですよね。

これが地震酔いです。熊本では高頻度で強い揺れが続いているので、地震酔いの症状でお困りの方もおられるのではないかと思います。

ということで、Arduinoで「揺れたら点灯」なデバイスを作ってみました。机の上に置いて、「あれ?地震?」と感じた時に点灯していなければそれは気のせい、という感じに動いてくれればいいのですが(作ってから東京で大きな地震がないので未検証です)。

周波数成分ごとに色を変えたのですが、手に持っているとどんなに心を鎮めても赤(ゆっくりした揺れ)が消えてくれません。

アル中検出器を造ってしまったのかもしれません。

なお、「酔い」と言っても色々あります。上記のように「揺れているように感じる」程度なら、物理的に揺れていないことを確認することが一種の認知療法的な効果につながると思いますが、「常時揺れているように感じて気持ちが悪い」という状態であれば、内耳に作用するタイプの乗り物酔い止めが有効です。こういう症状では「どこの医者にかかって良いかわからない」と思うんですが、メマイや酔いなどについては耳鼻咽喉科に相談してみるのがよろしいです。3.11の時、かなり深刻な症状を訴える同僚もいましたが、でも今はみんなすっきり治っています。不快な症状を我慢しないで、医師に相談しましょう。

■ハード■

Arduino nano+加速度センサーADXL345+マイコン内蔵LED WS2811です。以前、「キーボードを強く叩き過ぎたら、警告する」ってのを作りましたが、その流用です。

Arduino接続先
3.3v出力ブレッドボードの+ライン
GNDブレッドボードの-ライン
5v出力マイコン内蔵LEDのVdd
D2マイコン内蔵LEDのDin
A4ADXL345のSCL
A5ADXL345のSDA


ADXL345接続先
SCLArduino A4
SDAArduino A5
SDOGND
CS3.3v
Vs3.3v
GNDGND
Vdd3.3v

■ソフト■

加速度センサーから読み取った値を3軸分合成してからFFTにかけて、ノイズ分を除外した値が一定値を超えたら帯域ごとのパワーにわけてRGB LEDを点灯します。

当初割り込みを使って連続的に計測するプログラムを書いたのですが、インタラプト処理ルーチンでI2Cを読んだ瞬間にフリーズする、という現象を解決できなかったので、諦めて単純な逐次処理にしました。ただ整数処理のFFTを使っているので、計測漏れはそれほど大きくないです。

ADXL345に限らず3軸加速度センサーには重力加速度が常時計測されます。ので、その影響を取り除く必要があります。また、体感する揺れは三軸の合成ベクトルなので、三軸分のスカラー値を求めて、そこから重力加速度分を引き、感度を高めるためにちょっと掛け算してます。read345というルーチンでは、その後、ムダなdual buffer処理をしていますが、これは最初に書いたように当初割り込みで処理しようとしていた名残です。書き換えるの面倒なのでそのままにしています。

ノイズ処理は単に「静かにしていてもFFTからの出力が0-2程度出ているので、2以下はノイズとしてカット。それを超えた分だけ集計して、集計値が一定値を超えたら、有効と判定」というごく単純な処理です。

FFTには整数型FFTを使いました(Topic: Modified 8bit FFT in c)。ありがとうございます。ただ、開発されてから時間が経っているので、いくつか修正が必要です。まず、include文で「WProgram.h」となっている箇所は「Arduino.h」に置換します。また、データタイプの定義が変わっているので、fix_fft.cppの頭の方に
#ifndef prog_int8_t
#define prog_int8_t int8_t
#endif
を貼っておいてください。

ADXL345のライブラリはAdafruitの新しいunifiedライブラリ版を使いましたが、unifiedなインタフェース(センサーが違っても、重力加速度としての値域に変換して返すなど、センサーごとの差異をわりといい感じに吸収してくれるライブラリ群)は使わずgetX, Y, Zで取ってます。FFTが整数ですし。

WS2811は特に問題ないと思います。

//Arduino_DetectQuake
//
// Thanks:
// Fix_fft
// by http://forum.arduino.cc/index.php?topic=38153.0
// caution : include WProgram.h is deprecated. change to Arduino.h
// and have to append these codes to avoid error.
// #ifndef prog_int8_t
// #define prog_int8_t int8_t
// #endif
//
// ADXL Library
// https://github.com/adafruit/Adafruit_ADXL345
//
// NeoPixel Library
// https://github.com/adafruit/Adafruit_NeoPixel
//
// 震度と加速度の関係
// http://www.data.jma.go.jp/svd/eqev/data/kyoshin/kaisetsu/comp.htm
//
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_ADXL345_U.h>
#include <Time.h>
#include <fix_fft.h>
const int kSamples = 128;
const int kSamplesHalf = kSamples / 2;
char im[kSamples];
char Buffer1[kSamples], Buffer2[kSamples];
const int kDataNotReady = -1;
const int kToBuffer1 = 1;
const int kToBuffer2 = 2;
int DataCount = 0;
char *FillingTo = Buffer1;
char *FilledIs = NULL;
bool OverRun = false;
// ADXL345
Adafruit_ADXL345_Unified adxl = Adafruit_ADXL345_Unified(345);
void setupADXL345() {
if (!adxl.begin()) {
Serial.println("ADXL345 is not available.");
return;
}
Serial.println("ADXL is ready");
adxl.setRange(ADXL345_RANGE_2_G);
adxl.setDataRate(ADXL345_DATARATE_200_HZ);
}
void read345() {
float x = adxl.getX();
float y = adxl.getY();
float z = adxl.getZ();
int16_t value = sqrt(x * x + y * y + z * z); // 三軸を合成
value = (value - 240) * 4; // 重力加速度分を引いてから、感度を4倍に
if (value >= 127) value = 127; // 8bitの範囲におさまらない値はカット
if (value <= -128) value = -128; // 同じく
if (DataCount < kSamples) {
FillingTo[DataCount++] = value;
}
if (DataCount == kSamples) {
if (FilledIs == NULL) {
FilledIs = FillingTo;
DataCount = 0;
FillingTo = (FillingTo == Buffer1) ? Buffer2 : Buffer1;
} else {
OverRun = true;
}
}
}
// setup WS2811
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN 2
#define NUMPIXELS 1
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
void setupWS2811() {
//#if defined (__AVR_ATtiny85__)
// if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
//#endif
//
pixels.begin();
int green, red, blue;
green = red = blue = 0;
pixels.setPixelColor(0, pixels.Color(green, red, blue));
pixels.show();
}
//
// Arduino Setup
//
void setup() {
Serial.begin(115200);
Serial.println("Start");
setupADXL345();
setupWS2811();
}
//
// Arduino loop
//
void loop() {
char buf[30];
if (adxl.readRegister(ADXL345_REG_INT_SOURCE) & 0x80) {
read345();
}
if (FilledIs == NULL) { // データが揃ってないので何もしない
delay(1);
} else { // FFT処理開始
char *pData = (char *)FilledIs;
// 虚数部をクリア
for (int i = 0; i < kSamples; i++) {
im[i] = 0;
}
// FFT実行
fix_fft(pData, (char *)im, 7, 0);
// パワー
for (int i = 0; i < kSamplesHalf; i++) {
pData[i] = sqrt(pData[i] * pData[i] + im[i] * im[i]);
}
// 帯域ごとに集計
int low, middle, high;
low = middle = high = 0;
for (int i = 1; i < kSamplesHalf; i++) { // [0]はDCなので除外
char data = pData[i];
sprintf(buf, "%2d", data);
Serial.print(buf);
if (data <= 2) data = 0;
if (i < 20) {
low += data;
} else if (i < 40) {
middle += data;
} else {
high += data;
}
}
int sum = low + middle + high;
// if (sum < 10) {
// low = middle = high = 0;
// }
pixels.setPixelColor(0, pixels.Color(middle, low, high)); // green, red, blue
pixels.show();
sprintf(buf, "l=%4d, m=%4d, h=%4d, sum=%5d", low, middle, high, low+middle+high);
Serial.println(buf);
// 処理が終わったのでフラグをクリア
FilledIs = NULL;
}
}

■感謝■

FIX_FFTの作者の方、およびADXL345/WS2811ライブラリを公開してくださったAdafruitに感謝申し上げます。ありがとうございました。

0 件のコメント:

コメントを投稿

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