Raspberry Pi Zero WでMPU6050の加速度センサーの補正を行う

2021/12/19

MPU6050 python プログラミング ラズパイ

 前回の記事でMPU6050から出力される加速度のデータにオフセットが掛かっていることが分かりました。今回はこのオフセットを補正しようと思います。
↓前回記事

現在のMPUが出力する加速度データ

まず初めに現状どのようなデータが出力されるか見てみます。
測定条件としては机の上に置いてなるべく水平になるようにしてます。
(そもそも机が傾いている可能性もあるので、本当に水平が出ているかは不明ですが。。。)

図1にz軸の値を示します。
図1:Z軸の加速度センサー値

図1を見ると本来はz軸の値は1にならないといけないのですが、0.1程度マイナス側にオフセットされていることが分かります。(この時のデータの平均は0.884程度でした)

また、値もぶれており最大で0.905程度、最小で0.859程度となってます。

次にx,y軸の値を図2に示します。
図2:X,Y軸の加速度センサー値(青:X、オレンジ:Y)


図2を見るとx,y軸は0であるはずですが、こちらも微妙にずれてます。
このずれは設置場所の平面が出てない可能性がありますが。。。

ただ、平均で見るとX軸で0.040、Y軸で0.025なので、z軸に比べるとオフセットが掛かって無いように思います。

値のブレについてはX軸で最大0.062、最小0.018、Y軸で最大0.046、最小で0.0029でした。

フィルターを使ってデータを平滑化する。

MPU6050のデータシートを見ているとレジスタ1Aの0ビット~2ビットに値を設定するとデジタルローパスフィルターが実装できることが分かります。
図3:レジスタ1Aのレイアウト


設定値とフィルターの関係については図4を参照してください。
図4:フィルターについて
フィルターを強くすると遅延が大きくなってしまうので、ここは実際に使いながら調整してみたいと思います。

まずはフィルターの効果を確認するために、一番強い6番のフィルターを設定して結果を見てみます。

図5にフィルターを掛けた後のZ軸の加速度の値を示します。
図5:フィルターを掛けた場合のZ軸の加速度の値


フィルターを掛けた時の最大値は0.887で最小値は0.881なので、大分ブレが抑えられていることが分かります。

また、フィルターを掛けてもかけなくても平均が0.884だったので、オフセットとして0.116掛かっていることが分かります。

次にフィルターを掛けた後のx,y軸の値を図6に示します。
図6:フィルターを掛けた場合のX,Y軸の加速度の値(X:青、Y:オレンジ)

X軸の最大は0.043、最小は0.037、Y軸の最大は0.030、最小は0.022でこちらも大分ブレが抑えられております。

また、平均はx軸は0.040(フィルター前0.040)、y軸は0.027(フィルター前0.025)です。

オフセットがかかっているのを補正する。

前回の記事でも記載しましたが、私が買ってきたセンサーはZ軸方向に0.1 G程度のオフセットが掛かってます。ここではオフセットの補正をしていきます。

何個か補正方法の記事を見ていたのですが、基準点からの差分の平均を取って求めた値をオフセット値とするのが多く見受けられました。今回は平面にMPU6050を置いて5000点分のデータの平均を取ってどのくらいオフセットが掛かっているかを求め、センサー値に反映します。

図7にX,Y軸のオフセットを考慮した場合の結果、表1,2にそれぞれの最大値/最小値/平均を示します。

図7:X,Y軸のオフセットを考慮した場合と考慮してない場合の比較

表1:元データとフィルター処理/オフセット処理したデータの比較(x軸)
元データフィルター処理したデータフィルター+オフセット処理したデータ
最大0.0620.0620.015
最小0.0180.312-0.0016
平均0.040.0470.00031

表2:元データとフィルター処理/オフセット処理したデータの比較(y軸)
元データフィルター処理したデータフィルター+オフセット処理したデータ
最大0.0460.03120.0086
最小0.0029-0.0097-0.00097
平均0.0250.022-0.0000044
フィルターとオフセット処理をしたことで、x,y軸ともに0に近づいたので補正が出来ました。ちなみに私のセンサーはX軸方向に-703、Y軸方向に-369オフセットが掛かってそうです。(物理値に変換前の値なので、物理値に変換する場合は16384で割ってください)

次に図8にZ軸のオフセットを考慮した場合の結果、表3にそれぞれの最大値/最小値/平均を示します。
図8:Z軸のオフセットを考慮した場合と考慮してない場合の比較

表3:元データとフィルター処理/オフセット処理したデータの比較(z軸)
元データフィルター処理したデータフィルター+オフセット処理したデータ
最大0.9050.8941.013
最小0.8590.8750.982
平均0.8840.8920.999


ちょっとノイズが乗ってしましましたが、Z軸についても問題なく補正できていることが確認できました。また、私のセンサーはZ軸方向に1867のオフセットを掛かて補正してます。(こちらも物理値変換前です。)

各方向に傾けた場合の加速度の大きさを見て補正できているか確認

各方向に傾けて静止させた場合に加速度の大きさが1に近づければその方向の補正が出来ていると思います。

この方法で補正がうまくいってるか確認してみました。
軸の定義については図9を参照してください。
図9:軸の定義(引用元)

図10にX軸方向に傾けた場合の加速度の大きさの出力結果を示します。

図10:X軸方向に傾けた場合の加速度の大きさ
図10を見ると0.998 ~ 1.01程度なので、X軸方向は問題なく補正が出来ていると思います。

次に図11にY軸方向に傾けた場合の結果を示します。
図11:Y軸方向に傾けた場合の加速度の大きさ
図11を見ると1.046 ~ 1.057程度となっていますので、ちょっと補正が甘いかもしれません。(-Y方向に傾けた場合は0.095程度となっていたため、0.05程度オフセットが残ってしまってます)

この原因は恐らくキャリブレーションした際の地面が水平では無かったことが原因だと推定します。

次に図12にZ軸方向に傾けた場合(水平に設置した場合)の結果を示します。
図12:Z軸方向に傾けた場合の加速度の大きさ

図12を見ると0.999 ~1.001程度なのでZ軸も問題なく補正が出来てそうです。

ソースコード

動くことを重視した書いたコードなので、あとで修正します。
詳細はコメントをご確認お願いします。


import smbus
import math
from time import sleep
import time
import sys
import traceback
import matplotlib.pyplot as plt
import datetime


#各種レジスタのアドレス
#----------------------------------------------------------------------------------
DEV_ADDR = 0x68
CONFIG = 0x1A
GYRO_CONFIG = 0x1B
ACCEL_CONFIG = 0x1C

ACCEL_XOUT = 0x3b
ACCEL_YOUT = 0x3d
ACCEL_ZOUT = 0x3f
TEMP_OUT = 0x41
GYRO_XOUT = 0x43
GYRO_YOUT = 0x45
GYRO_ZOUT = 0x47

PWR_MGMT_1 = 0x6b
PWR_MGMT_2 = 0x6c
#----------------------------------------------------------------------------------

CAL_SIZE = 5000#キャリブレーションに使うデータ数
IGNORE_DATA_SIZE = 50#キャリブレーションを行う際に最初の方のデータは信頼性が無いため、何個のデータを無視するか設定

#変数/配列の初期化処理
#----------------------------------------------------------------------------------
data_x_list = []#データ格納用の配列作成
data_y_list = []
data_z_list = []

data_x_offset_list = []#データ格納用の配列作成(オフセット後)
data_y_offset_list = []
data_z_offset_list = []

bus = smbus.SMBus(1)
x_offset = 0#オフセット値の計算結果を入れる(計算前は0を入れる)
y_offset = 0#オフセット値の計算結果を入れる(計算前は0を入れる)
z_offset = 0#オフセット値の計算結果を入れる(計算前は0を入れる)

dt_now = datetime.datetime.now()
file = open(str(dt_now)+"calc_data.txt","w",encoding="utf-8")#統計データ格納用のファイル
#----------------------------------------------------------------------------------

#デバイスの初期化処理
#----------------------------------------------------------------------------------
bus.write_byte_data(DEV_ADDR, PWR_MGMT_1, 0x80)#write_byte_data(int addr,char cmd,char val) 1:I2C通信対象のアドレス。今回は加速度センサのみなので68固定,2:レジスタのアドレス,3:値)0x80はデバイスリセット
bus.write_byte_data(DEV_ADDR, PWR_MGMT_1, 0x00)
bus.write_byte_data(DEV_ADDR, PWR_MGMT_2, 0x07)#FIFO/I2C_MST/SIG_CONDのリセット
bus.write_byte_data(DEV_ADDR, PWR_MGMT_2, 0x00)
#----------------------------------------------------------------------------------

#センサーの設定
#----------------------------------------------------------------------------------
# bus.write_byte_data(DEV_ADDR, CONFIG, 0x00)#CONFIG(フィルターなし)
bus.write_byte_data(DEV_ADDR, CONFIG, 0x06)#CONFIG(フィルター最大)
bus.write_byte_data(DEV_ADDR, GYRO_CONFIG, 0x00)#250°/s
bus.write_byte_data(DEV_ADDR, ACCEL_CONFIG, 0x00)#2g
#----------------------------------------------------------------------------------


def read_word(adr):#センサーの値が2バイトであらわされるため、2バイト分のデータを結合する関数
    high = bus.read_byte_data(DEV_ADDR, adr)#上位バイトのデータ
    low = bus.read_byte_data(DEV_ADDR, adr+1)#下位バイトのデータ
    val = (high << 8) + low#上位バイトのデータをビット1バイト分(8bit)左にシフトして、下位バイトのデータと足し合わせて1つのデータにしている。
    return val

def read_word_sensor(adr):
    val = read_word(adr)
    if (val >= 0x8000):  return -((65535 - val) + 1)#0x8000が最上位ビットが立っている状態なので、マイナス。もし、マイナス値だった場合は符号を反転させてる。(ただ反転させるだけではダメなので注意)
    else:  return val

def get_temp():
    temp = read_word_sensor(TEMP_OUT)
    x = temp / 340 + 36.53      # data sheet(register map)記載の計算式.
    return x

def getGyro():
    x = read_word_sensor(GYRO_XOUT)/ 131.0# data sheet(register map)記載の計算式.(250°を選択した場合)
    y = read_word_sensor(GYRO_YOUT)/ 131.0
    z = read_word_sensor(GYRO_ZOUT)/ 131.0
    return [x, y, z]


def getAccel(): 
    x = read_word_sensor(ACCEL_XOUT)/ 16384.0# data sheet(register map)記載の計算式.(2gを選択した場合)
    y = read_word_sensor(ACCEL_YOUT)/ 16384.0
    z = read_word_sensor(ACCEL_ZOUT)/ 16384.0
    return [x, y, z]

def getAccel_offset(): #オフセット分の考慮した場合のデータ
    x = (read_word_sensor(ACCEL_XOUT)+x_offset)/ 16384.0# data sheet(register map)記載の計算式.(2gを選択した場合)
    y = (read_word_sensor(ACCEL_YOUT)+y_offset)/ 16384.0
    z = (read_word_sensor(ACCEL_ZOUT)+z_offset)/ 16384.0
    return [x, y, z]

def calc_offset_acc():#平面に置いた場合のオフセット計算処理
    global x_offset
    global y_offset
    global z_offset
    for i in range(CAL_SIZE):
        if i>IGNORE_DATA_SIZE:#最初のデータは信憑性が無いため、無視
            x_offset += -read_word_sensor(ACCEL_XOUT)#x,yは0が基準なので、0は省略している。
            y_offset += -read_word_sensor(ACCEL_YOUT)
            z_offset += 16384-read_word_sensor(ACCEL_ZOUT)#変換前の値で16384が1なので、16384を基準に計算する。
        else:#捨てデータは空読み
            read_word_sensor(ACCEL_XOUT)
            read_word_sensor(ACCEL_YOUT)
            read_word_sensor(ACCEL_ZOUT)

    x_offset =int(x_offset/(CAL_SIZE-IGNORE_DATA_SIZE)) #データの個数(無視したデータも考慮)で割ってオフセット分の平均を算出
    y_offset =int(y_offset/(CAL_SIZE-IGNORE_DATA_SIZE)) 
    z_offset =int(z_offset/(CAL_SIZE-IGNORE_DATA_SIZE)) 
    print("offset_calc_finish")        
    
calc_offset_acc()#オフセットの計算をする場合にこの関数をコールする。(一度計算すれば問題無し。計算が終わったらコメントアウト)
print("lecoding")
while True:
    try:
        ax, ay, az = getAccel()#加速度センサーの情報読み出し
        gx, gy, gz = getGyro()#ジャイロセンサーの情報読み出し
        ax_offset,ay_offset,az_offset = getAccel_offset()
        magnitude_a = math.sqrt(ax**2+ay**2+az**2)#加速度の大きさを算出
        temp = get_temp()#温度センサーの情報読み出し
        raw_ax = read_word(ACCEL_XOUT)
        raw_ay = read_word(ACCEL_YOUT)
        raw_az = read_word(ACCEL_ZOUT)
        
        # print ('GYRO:{:4.3f},{:4.3f},{:4.3f},' .format(gx, gy, gz)) 
        # print ('ACC:{:4.3f},{:4.3f},{:4.3f},' .format(ax, ay, az))    
        # print ('ACC_RAW:{:4.3f},{:4.3f},{:4.3f},' .format(raw_ax, raw_ay, raw_az))    
        # print ('ACC_MAG:{:4.3f}'.format(magnitude_a))
        # print ('TEMP:{:4.3f}'.format(temp))
        data_x_list.append(ax)
        data_y_list.append(ay)
        data_z_list.append(az)
        data_x_offset_list.append(ax_offset)
        data_y_offset_list.append(ay_offset)
        data_z_offset_list.append(az_offset)


    except:#ctrl+Cでコードの実行を停止した場合にこちらの処理が実行(将来的にはCAN途絶したらこの処理に入るように変更予定)
        bus.close()
        del data_x_list[:50]#最初の方のデータが0なので除外
        del data_y_list[:50]
        del data_z_list[:50]
        del data_x_offset_list[:50]#最初の方のデータが0なので除外
        del data_y_offset_list[:50]
        del data_z_offset_list[:50]
        
        #現状ctrl+cでこの処理に入るので本当にエラーが起きた時用にエラーメッセージを出力
        #----------------------------------------------------------------------------------        
        print("Error Occur")
        print(traceback.format_exc())
        #----------------------------------------------------------------------------------
        
        
        #統計情報をファイルに出力
        #----------------------------------------------------------------------------------
        print("x_max",file=file)
        print(max(data_x_list),file=file)
        print("x_min",file=file)
        print(min(data_x_list),file=file)
        print("x_ave",file=file)
        print(sum(data_x_list)/len(data_x_list),file=file)
        print("y_max",file=file)
        print(max(data_y_list),file=file)
        print("y_min",file=file)
        print(min(data_y_list),file=file)
        print("y_ave",file=file)
        print(sum(data_y_list)/len(data_y_list),file=file)
        print("z_max",file=file)
        print(max(data_z_list),file=file)
        print("z_min",file=file)
        print(min(data_z_list),file=file)
        print("z_ave",file=file)
        print(sum(data_z_list)/len(data_z_list),file=file)

        print("x_offset_max",file=file)
        print(max(data_x_offset_list),file=file)
        print("x_offset_min",file=file)
        print(min(data_x_offset_list),file=file)
        print("x_offset_ave",file=file)
        print(sum(data_x_offset_list)/len(data_x_offset_list),file=file)
        print("y_offset_max",file=file)
        print(max(data_y_offset_list),file=file)
        print("y_offset_min",file=file)
        print(min(data_y_offset_list),file=file)
        print("y_offset_ave",file=file)
        print(sum(data_y_offset_list)/len(data_y_offset_list),file=file)
        print("z_offset_max",file=file)
        print(max(data_z_offset_list),file=file)
        print("z_offset_min",file=file)
        print(min(data_z_offset_list),file=file)
        print("z_offset_ave",file=file)
        print(sum(data_z_offset_list)/len(data_z_offset_list),file=file)

        print("OFFSET_X",file=file)
        print(x_offset,file=file)
        print("OFFSET_Y",file=file)
        print(y_offset,file=file)
        print("OFFSET_Z",file=file)
        print(z_offset,file=file)
        print("OFFSET_Z_cal",file=file)
        print(z_offset/16384,file=file)
        file.close()
        #----------------------------------------------------------------------------------

        #ロギングしたデータをプロット
        #----------------------------------------------------------------------------------
        time_list = range(len(data_x_list))
        time_list_offset = range(len(data_x_offset_list))        
        plt.plot(time_list,data_x_list,label="x_not_offset")
        plt.plot(time_list,data_y_list,label="y_not_offset")
        plt.plot(time_list_offset,data_x_offset_list,label="x_offset")
        plt.plot(time_list_offset,data_y_offset_list,label="y_offset")
        plt.legend()
        # plt.ylim(-0.015,0.015)
        plt.show()
        plt.plot(time_list,data_z_list,label="z_not_offset")
        plt.plot(time_list_offset,data_z_offset_list,label="z_offset")
        plt.legend()
        # plt.ylim(0.9,1.1)
        plt.show()
        sys.exit()
        #----------------------------------------------------------------------------------    
 

まとめ

今回は加速度センサーの補正を行いました。
補正をする際に地面の水平が出ていなかったため、Y軸方向の補正が甘くなってしまいました。Y軸の計算した補正値は-369でしたが、別途計算してを補正値を+394に変更したらちょうどよくなりました。

関連記事




自己紹介

はじめまして 社会人になってからバイクやプログラミングなどを始めました。 プログラミングや整備の記事を書いていますが、独学なので間違った情報が多いかもしれません。 間違っている情報や改善点がありましたらコメントしていただけると幸いです。

X(旧Twitter)

フォローお願いします!

ラベル

QooQ