センサの取得値を加工してなだらかにする
センサーのデータを取得する際に、ローデータだとどうしても値が暴れます。
その値をならす方法はいろいろあると思いますが、シンプルな移動平均法を書いたので自分用のメモとして残しておきます。
移動平均法とは?
たとえば気温の変化度合いを毎日発表するとした時、その値をグラフにするとかなりギザギザします。
そこで毎日一週間分の平均値を発表するとします。
この場合、明日発表する平均値は、その平均値に明日の気温を足して、もっとも古いデータ、つまり先週の同じ曜日の気温をひいてから7で割って平均かしたものになります。
これが移動平均です。
小さな移動平均
たとえば過去7個までのデータで移動平均を取る場合。
月〜日までの7曜日分のデータを配列とします。
新しいデータを入力する際には、その曜日の枠の値を上書きするイメージです。
更新された一週間分のデータの平均をとればそれが最新の移動平均になります。
小さな数値であれば毎回合計しても負荷はそれほど気にしなくてよいと思います。
具体的には、下記のようなコードはどうでしょう。
int stock_rotation_term = 7;//移動平均の期間(標本数)
int stock_rotation_number = 0;//輪番用のカウント
int stock_data[stock_rotation_term];//データをストックする配列
float new_data;//センサから取得した最新のデータ
void moving_average(float data) {//IMUデータの移動平均値の取得
//二次元配列の0番から輪番で最新のデータを入れていく。指定個数を上回ったら0番に戻す
stock_rotation_number ++;
if (stock_rotation_number > stock_rotation_term) {
stock_rotation_number = 0;
}
stock_data[stock_rotation_number] = data;//ストックの輪番箇所にデータ上書き
float result=0;//結果を初期化
for (int i = 0; i < stock_rotation_term; i++) {//ストックした値を合計する
result += stock_data[i];
}
result = result/stock_rotation_term; //合計値を標本数でを割る
return result;
}
void setup() {
Serial.begin(9600);
}
void loop {
new_data = //<なんらかのセンサ値>
Serial.prlintln(moving_average(new_data));
}
3~4個ぐらいの過去についての移動平均であれば、この方式でいけると思います。
大きな移動平均
100個とか大きめの移動平均の場合は、現在の平均値を利用して結果を出せば計算回数を大幅に減らせてよいでしょう。その場合、ある程度の段取りは必要になると思います。
100個の例で考えてみます。
① 最初の100個のデータについては、配列の輪番枠に順にデータを貯めていく。
② その際の移動平均は、合計値を取得したデータ個数で割って出していく。
③ 100個に達するまで、①②を繰り返す。
④ 101個めからは次のルーチンに入る。
⑤ 現在の平均値から輪番枠のデータを減算する。
⑥ 同時に、最新のデータを輪番枠に上書きする。
⑦ 平均値に最新のデータを加算し、100で割った結果を移動平均として出力する。
⑧ ある程度の期間は⑤〜⑦をループ
⑨ 誤差が積算していくため、定期的に②で平均値をリフレッシュする。
これで移動平均が出せると思います。
コードが長くなっても問題ない場合には、こちらの方法でやれば、4個ぐらいの小さな移動平均でも計算コストを下げることができます。3個の移動平均だと計算コストは小さな移動平均と同じぐらいになると思います
他の方法
ライブラリとかが便利なのかもしれませんが概念としてメモしておきました。
もっとよい方法があればぜひ教えてください。