前回の記事でMPU6050から出力される加速度のデータにオフセットが掛かっていることが分かりました。今回はこのオフセットを補正しようと思います。
↓前回記事
現在のMPUが出力する加速度データ
まず初めに現状どのようなデータが出力されるか見てみます。
測定条件としては机の上に置いてなるべく水平になるようにしてます。
(そもそも机が傾いている可能性もあるので、本当に水平が出ているかは不明ですが。。。)
図1にz軸の値を示します。
図1:Z軸の加速度センサー値 |
また、値もぶれており最大で0.905程度、最小で0.859程度となってます。
図2を見るとx,y軸は0であるはずですが、こちらも微妙にずれてます。
次にx,y軸の値を図2に示します。
図2:X,Y軸の加速度センサー値(青:X、オレンジ:Y) |
このずれは設置場所の平面が出てない可能性がありますが。。。
ただ、平均で見ると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軸)
表2:元データとフィルター処理/オフセット処理したデータの比較(y軸)
フィルターとオフセット処理をしたことで、x,y軸ともに0に近づいたので補正が出来ました。ちなみに私のセンサーはX軸方向に-703、Y軸方向に-369オフセットが掛かってそうです。(物理値に変換前の値なので、物理値に変換する場合は16384で割ってください)
次に図8にZ軸のオフセットを考慮した場合の結果、表3にそれぞれの最大値/最小値/平均を示します。
図8:Z軸のオフセットを考慮した場合と考慮してない場合の比較 |
表3:元データとフィルター処理/オフセット処理したデータの比較(z軸)
各方向に傾けた場合の加速度の大きさを見て補正できているか確認
各方向に傾けて静止させた場合に加速度の大きさが1に近づければその方向の補正が出来ていると思います。
この方法で補正がうまくいってるか確認してみました。
軸の定義については図9を参照してください。
![]() |
図9:軸の定義(引用元) |
図10にX軸方向に傾けた場合の加速度の大きさの出力結果を示します。
図10:X軸方向に傾けた場合の加速度の大きさ |
次に図11にY軸方向に傾けた場合の結果を示します。
図11:Y軸方向に傾けた場合の加速度の大きさ |
この原因は恐らくキャリブレーションした際の地面が水平では無かったことが原因だと推定します。
次に図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に変更したらちょうどよくなりました。
0 件のコメント:
コメントを投稿