micro:bit 加速度センサ(micro:bitの傾きの測定)

2022.11.3 2021.10.30 Coskx Lab

1 はじめに

micro:bitには3軸加速度センサが組み込まれています。これを使うとmicro:bitがどのように傾いているかを知ることができます。
この資料では,右手座標系で一般化した加速度センサの働きと,傾きの求め方を示します。
同様に考えれば,鉛直方向に立てられた状態など様々な設置状態でのmicro:bitの傾き角を求めることもできるでしょう。

2 使用環境

3 重力加速度

地球上表面では,重力加速度が加わっています。重力加速度の値はほぼ9.8m/s2となっています。その方向は鉛直方向です。

4 micro:bitの3軸加速度センサ

micro:bitには互いに90度の方向を測定する加速度センサが3つ付いています。
micro:bitのx軸+を天頂に向けるとx軸加速度センサ値は約980cm/s2(最大値),x軸-を天頂に向けると約-980cm/s2(最小値)になるはずです。
同様にy軸+を天頂に向けるとy軸加速度センサ値は約980cm/s2(最大値),y軸-を天頂に向けると約-980cm/s2(最小値)になるはずです。
さらに同様にz軸+を天頂に向けるとz軸加速度センサ値は約980cm/s2(最大値),z軸-を天頂に向けると約-980cm/s2(最小値)になるはずです。



ところが,センサには感度の不均一や0点のずれがあるため,誤差が含まれた値が得られます。各軸の最大値最小値が許容できないほどずれている場合には,補正をするようにします。
実際,手元のmicro:bit v2では次のような値が得られています。
 \(min\)\(max\)
x軸-10801000
y軸-9801030
z軸-9801010
また加速度センサ値にはノイズの混入が多いため,センサ値をそのまま使わずに,平均化した値を使うことが多いです。

【補正方法】
x軸センサを例にとります。
x軸センサ出力の最小値,最大値は\(min_{raw}=-1080, max_{raw}=1000\) になっていますが,本来は \(min_{cmp}=-980, max_{cmp}=980\) になってほしいわけです。
そこで \(s_{raw}\) をセンサ出力値, \(s_{cmp}\) を補正値としたとき,\(s_{cmp}=a(s_{raw}-b)\) の一次変換を行います。
\(a\)は感度補正係数,\(b\)はオフセット補正値です。 \(a=\dfrac{max_{cmp}-min_{cmp}}{max_{raw}-min_{raw}}\)
\(b=\dfrac{min_{raw} max_{cmp}-max_{raw} min_{cmp}}{max_{cmp}-min_{cmp}}\)
とすれば,補正できます。
このx軸センサの例では
\(a=\dfrac{1960}{2080}=0.9423\)
\(b=\dfrac{-1080\times980+1000\times980}{1960}=-\dfrac{80\times980}{1960}=-40\)
となるので,
\(s_{cmp}=0.9423(s_{raw}+40)\)
で補正値が求められます。
この補正式では,\(s_{raw}=-1080\) のとき \(s_{cmp}=-980\) となり,\(s_{raw}=1000\) のとき \(s_{cmp}=980\) となります。

5 micro:bitの姿勢(傾き)測定

飛行機などでは,機体の傾きをロール角とピッチ角で表すので,それに合わせて,micro:bitのロール角とピッチ角を定義します。micro:bitを水平置きにして,y軸+方向を機首と考えています。(micro:bitのy軸+を機首方向と考えるようにしたため,このような定義になりましたが,都合のよいように定義することが可能です。)



micro:bitを傾けたときに,x,y,z各軸の加速度センサ値はどうなるかを考えてみます。

(1)水平置きでまだ傾いていない場合
z軸センサ値が重力加速度\(-G\) で,x軸y軸のセンサ値は0のはずです。



(2)ピッチ角\(\beta\) で傾けた場合
x軸を中心に機首を持ち上げた状態です。 z軸センサ値が\(-G \cos\beta\) で,y軸センサ値が\(G \sin\beta\) になり,x軸センサ値は0のはずです。



(3)ロール角\(\alpha\) で傾けた場合
z軸センサ値が\(-G \cos\alpha\) で,x軸センサ値が\(G \sin\alpha\) になり,y軸センサ値は0のはずです。



(4)ロール角\(\alpha\) で傾けた後,傾ける前のx軸回りにピッチ角\(\beta\) で傾けた場合
x軸センサ値が\(G \sin\alpha\ \cos\beta\) で,y軸センサ値が\(G \sin\beta\),z軸センサ値は\(-G \cos\alpha \cos\beta\) になります。



一般的には(4)のようになり,3軸のセンサ値よりロール角とピッチ角を求めることができます。


6 最傾斜方向

最傾斜方向は,次の図を参考に,x軸センサ値とy軸センサ値(この2つの値は正しく求められているものとします。)から求めることができるでしょう。
その場合はロール角やピッチ角を求める必要はありません。



実際にはx軸センサ値とy軸センサ値は,「4.micro:bitの3軸加速度センサ」で見られるようにオフセットと感度のずれがあり,補正が必要です。
ただし,5度から10度程度の緩やかな斜面で最傾斜方向を求める場合は,10%程度の感度誤差を無視して,オフセット補正だけでもよい最傾斜方向値が得られます。
オフセット補正のためには,測定する前の準備段階で次の方法でオフセット補正値を取得しておき,測定時にそれぞれのセンサ値から減じて,最傾斜方向の計算に使います。

【簡単なオフセット補正値の取得方法】
カートにmicro:bitが搭載されているものとし,カートの進行方向がy軸+方向とします。
y軸方向を例にとりますが,x軸についても同様に考えることができます。

  1. micro:bitの取り付け角度を含むオフセットが0であれば,水平面にカートを置くと,y軸センサ値は0になり,
    カートを180度方向転換しても,y軸センサ値は0になるはずです。
  2. オフセットが0であれば,傾斜角\(\theta\)[rad]の斜面にカートを置くと,y軸センサ値は\(G\theta\)になるはずです。(\(G=980\))
    カートを180度方向転換すると,y軸センサ値は\(-G\theta\)になるはずです。
  3. もし,micro:bitの取り付け角度を含むオフセットがeであれば,水平面にカートを置くと,y軸センサ値は\(e\)になり,
    カートを180度方向転換しても,y軸センサ値は\(e\)になるはずです。
  4. オフセットが\(e\)のとき,傾斜角\(\theta\)[rad]の斜面にカートを置くと,y軸センサ値は\(s_1=G\theta+e\)になるはずです。
    カートを180度方向転換すると,y軸センサ値は\(s_2=-G\theta+e\)になるはずです。

「d」の斜面で\(s_1\)と\(s_2\)を取得出来れば,
\(e=\dfrac{s_1+s_2}{2}\)
を使うと,斜面の傾斜角\(\theta\)とは無関係にmicro:bitの取り付け角度を含むオフセット\(e\)を得ることができます。
この\(e\)をセンサ値から減ずることで,オフセットの補正が出来ます。
一般に水平面が得られない場合(どれくらい傾いているかわからない斜面の場合でも),この方法を使うと,オフセットの補正値を得ることができます。


7 3軸加速度取得プログラム例

次のプログラムでは3軸加速度センサの値を取得して表示しています。平均化にIIRフィルタという手法を使用しています。
用途によってはこのフィルタは不適切な場合もあるので,採用するかどうかは検討すべきです。
ロール角やピッチ角,最傾斜方向は求めていません。必要に応じて求めてください。

なお,測定値を連続取得する場合(測定値の平均を求める場合など)は,補足の注意事項を参考にしてください。
このプログラムのように「ずっと」ブロック内で連続取得している場合は問題ありません。
「ずっと」ブロックでは舞台裏において20msのpause(一時停止)が行われているためです。




コピペする場合は,次のソースコードをPythonソースとして貼り付ければ完了です。

acc_x = 0
acc_y = 0
acc_z = 0

def on_forever():
    global acc_x, acc_y, acc_z
    acc_x = 0.1 * input.acceleration(Dimension.X) + 0.9 * acc_x
    acc_y = 0.1 * input.acceleration(Dimension.Y) + 0.9 * acc_y
    acc_z = 0.1 * input.acceleration(Dimension.Z) + 0.9 * acc_z
    serial.write_line("" + str(Math.round(acc_x)) + ", " + str(Math.round(acc_y)) + ", " + str(Math.round(acc_z)))
basic.forever(on_forever)

8 姿勢の測定での注意

この解説に示した方法では,micro:bitが静止しているときは,うまく測定できます。
しかし,モータで動く模型カーなどに搭載されたmicro:bitで,移動しながら姿勢測定すると,振動の影響が加速度センサに大きく作用するため,正しい姿勢が得られません。
正しく姿勢を測定するには,模型カーを止めて測定する,測定平均回数を増やすなどの工夫が必要になります。
どのくらい測定値が暴れるのか観察して対策を練ってから姿勢測定を行う必要があります。

9 まとめ

右手座標系で一般的な加速度センサの使い方を示しました。 micro:bitの3軸加速度センサの値と,micro:bitの姿勢(傾き角)をの関係を求めました。
micro:bitの3軸加速度センサの値から最傾斜方向を求める方法を示しました。
これらの作業は,水平置きしたmicro:bitだけでなく鉛直に立てて保持された場合など様々な設置方法に対しても座標系を考慮すれば応用できます。

注意 3軸加速度センサを連続使用するときは,最低でも20msは間隔をとる必要があります。micro:bit側の測定値更新が20msほどかかるので,それ以下の間隔での測定値取得では,同じ値を取り込んでしまうことになります。特に測定値の平均化のときは,平均化が無意味になってしまうので注意が必要です。(「補足 短インターバル繰り返し測定」参照)



補足 短インターバル繰り返し測定(x軸加速度,y軸加速度,z軸加速度)

加速度センサは,内部でAD変換・I2C通信しており,その結果をプログラムで加速度センサ値として受け取っています。
AD変換・I2C通信は一定の時間を必要とするため,短インターバル繰り返しで加速度センサ値を受け取ろうとすると,思い通りの測定値が得られないことがあります。(実は舞台裏でセンサ値の更新周期が設定されています。)
micro;bitを絶えず動かしながら(取得されるべきセンサ値は不規則に変化するはず)
 (1)インターバル無し
 (2)インターバル10ms
 (3)インターバル20ms
で16回連続測定し,得られた加速度センサ値を観察してみました。

(1)インターバル無し(測定間隔ほぼ0)
16回の測定するときは,時間のかかる測定値表示をせず,配列に測定値を保存して,最速で測定します。測定終了後に,配列に保存されていた測定値をコンソールに表示する手法をとりました。




その結果は次の通りになりました。
16回の取得データはすべて同じ値でした。
AD変換・I2C通信によるデータの更新が間に合わず,同じデータを受け取り続けていたと考えられます。 これでは使えません。

started
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016
-104, 160, -1016

(2)インターバル10ms(測定間隔10ms)
16回の測定するときは,時間のかかる測定値表示をせず,配列に測定値を保存して,10msのpause(一時停止)を行っています。測定終了後に,配列に保存されていた測定値をコンソールに表示する手法をとりました。




その結果は次の通りになりました。
ところどころで,同じデータが2回続けて得られています。
AD変換・I2C通信によるデータの更新が完全には間に合わず,同じデータを受け取ってしまうことがあると考えられます。 これでは使えません。

started
-60, 168, -1008
-64, 176, -988
-64, 176, -988  ←
-52, 160, -1008
-52, 160, -1008  ←
-56, 168, -988
-52, 164, -1016
-52, 164, -1016  ←
-56, 172, -996
-56, 172, -996  ←
-40, 164, -1036
-52, 168, -1004
-52, 168, -1004  ←
-48, 148, -1000
-48, 148, -1000  ←
-48, 156, -1004

(3)インターバル20ms(測定間隔20ms)
16回の測定するときは,時間のかかる測定値表示をせず,配列に測定値を保存して,20msのpause(一時停止)を行っています。測定終了後に,配列に保存されていた測定値をコンソールに表示する手法をとりました。




その結果は次の通りになりました。
同じデータが繰り返し取得されることなく,測定できています。

started
240, -204, -1048
124, -244, -1016
404, -104, -964
348, -140, -1056
540, -108, -916
284, -148, -944
220, -48, -972
256, -28, -980
392, 16, -856
396, 8, -860
232, 88, -944
292, 72, -944
356, 148, -916
404, 56, -932
332, 92, -1048
360, 116, -1184

このテストから,加速度センサでの測定には,20msほどのインターバルが必要なことがわかりました。
なお,「ずっと」ブロックでは舞台裏において20msのpause(一時停止)が行われているため,「ずっと」ブロック中ではプログラム中にpause(一時停止)を書かなくても最新の測定値を得ることができます。
参考 「ずっと」ブロックの動作