IMUセンサ(ICM20948)からPythonにデータを送りCSV保存する方法

センサ


記事の概要

今回はArduinoでシリアルモニタで表示されるデータをPythonに送る方法を紹介いたします. データはICM20948という加速度と角速度と地磁気を計測できるIMUセンサから取得します. それぞれ3軸(X,Y,Z)ずつと時間の軽10個のデータを送ります(SPI通信). マイコンにはSeeeduinoを用いました. Pythonでは受け取った値をリストに格納し, 最後はCSVファイルに入れて出力します. その流れを回路図とコード(ArduinoとPython)を合わせて説明いたします.

使用したセンサとマイコン

今回使用したセンサを図1に示します. データシートに関しては参考文献のリンクをたどっていただければご覧いただけます. 簡単な仕様は表1に示します. I2C, SPIの両方で通信ができるようですが, 今回は, より通信速度が速くノイズ耐性が強いSPI通信を使います.
 センサを制御するためのマイコンにはSeeedunoを用い, 図2に示します. Seeeduinoは小型である点が一番の強みだと思います. 出力は5Vと3.3Vを選ぶことができます.
 センサのI/O電源(Input Output)は1.71~1.95Vで動作し, コア電源は1.71~3.6Vで動作します.そのためSeeeduinoからの出力である3.3VをそのままI/O電源に入れてしまえば破損してしまいます.
そこで図3に示すDCDCコンバータを用いて電圧降下させました. これは1.8Vに降下してくれます.

Fig.1 使用したIMUセンサ ICM20948[1]
ModelICM-20948
CompanyTDK InvenSense
Built-inAccelerationGyroGeomagnetic
Range±16G±2000dps±4900uT
Output Freq.4.5kHz9kHz
Resolution16bit16bit16bit
CommunicationI2C or SPI
Size12.8*10.2*2.7
Supply Voltage3.3V, 1.8V
Price1980
Table1 ICM20948の仕様

Fig.2 使用したマイコンSeeeduino[2]
Fig. 3 DCDCコンバータ[]

回路構成

回路図は図4になります. コンバータは3.3Vから1.8Vに降圧させるために用いました。
(DCDCコンバータを回路図でどう表せばいいのかよくわかりませんでした. )
最初はスイッチング電源で1.8Vに設定し試してみました.

Fig.4 回路図

プログラムコード

Arduino


本来であればICM20948はレジスタマップから制御するほうが望ましいのですが, 今回は以下のライブラリを使用させていただきました.

SparkFun 9DoF IMU (ICM-20948) Breakout Hookup Guide - SparkFun Learn

一度レジスタマップから指定して値を読み取ろうとしたのですが加速度しか読み取ることができませんでした。。。

/****************************************************************
   Example1_Basics.ino
   ICM 20948 Arduino Library Demo
   Use the default configuration to stream 9-axis IMU data
   Owen Lyke @ SparkFun Electronics
   Original Creation Date: April 17 2019

   Please see License.md for the license information.

   Distributed as-is; no warranty is given.
 ***************************************************************/


#include "ICM_20948.h" // Click here to get the library: http://librarymanager/All#SparkFun_ICM_20948_IMU

#define USE_SPI       // Uncomment this to use SPI

#define Serial Serial

#define SPI_PORT SPI // Your desired SPI port.       Used only when "USE_SPI" is defined
#define CS_PIN 1     // Which pin you connect CS to. Used only when "USE_SPI" is defined

#define WIRE_PORT Wire // Your desired Wire port.      Used when "USE_SPI" is not defined
// The value of the last bit of the I2C address.
// On the SparkFun 9DoF IMU breakout the default is 1, and when the ADR jumper is closed the value becomes 0
#define AD0_VAL 1


int16_t AX, AY, AZ;
float ax, ay, az;
byte max_G;

int16_t GX, GY, GZ;
float gx, gy, gz;
uint16_t max_dps;

int16_t MX,MY,MZ;

unsigned long StartTime,CurrentTime,PreTime,dt; 




ICM_20948_SPI myICM; // If using SPI create an ICM_20948_SPI object


void setup()
{
StartTime = millis();
  Serial.begin(128000);
  while (!Serial)
  {
  };


  SPI_PORT.begin();

  myICM.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial

  bool initialized = false;
  while (!initialized)
  {


    myICM.begin(CS_PIN, SPI_PORT);


    Serial.print(F("Initialization of the sensor returned: "));
    Serial.println(myICM.statusString());
    if (myICM.status != ICM_20948_Stat_Ok)
    {
      Serial.println("Trying again...");
      delay(500);
    }
    else
    {
      initialized = true;
    }
  }
}



void loop()
{

  if (myICM.dataReady())
  {
    myICM.getAGMT();         // The values are only updated when you call 'getAGMT'
    savedata(&myICM);
  }
  else
  {
    Serial.println("Waiting for data");
    delay(500);
  }
}

// Below here are some helper functions to print the data nicely!



void savedata(ICM_20948_SPI *sensor)
{

  CurrentTime = millis();  
  dt = CurrentTime - StartTime;


  
  AX = sensor->accX();
  AY = sensor->accY();
  AZ = sensor->accZ();

  gx = sensor->gyrX();
  gy = sensor->gyrY();
  gz = sensor->gyrZ();

  MX = sensor->magX();
  MY = sensor->magY();
  MZ = sensor->magZ();


  Serial.print(AX);
  Serial.print(",");
  Serial.print(AY);
  Serial.print(",");
  Serial.print(AZ);
  Serial.print(",");
  Serial.print(gx);  
  Serial.print(",");
  Serial.print(gy);
  Serial.print(",");
  Serial.print(gz);
  Serial.print(",");
  Serial.print(MX );
  Serial.print(",");
  Serial.print(MY );
  Serial.print(",");
  Serial.print(MZ );
  Serial.print(",");
  
  Serial.print(dt);


  Serial.println();

  }
 

Python

# 10進数で送られてくるためそのままゲットしてCSVに保存

import serial
import csv
import numpy as np  
import os
from datetime import datetime
# ----------
path = os.getcwd() + "/csv"
extens = ".csv"
# -----------------------------


ary_IMU = []
ser = serial.Serial('COM7', 128000, timeout=None)
ary_IMU = np.array(['proc_T,AX,AY,Az,Gx,Gy,Gz,Mx,My,Mz\n'])

# CSVというフォルダを同じディレクリに作る。
def CSV_save(ary, name):
  global path,extens

  filename = datetime.now().strftime("%m_%d__%H_%M_%S")
  File = open(path + "/" \
              + filename + "_" + name \
              + extens, 'w',  encoding="utf_8")
  #writer = csv.writer(File, lineterminator='')

  File.writelines(ary)
  File.close()


  np.savetxt(path + "/"+filename, ary, delimiter=",")

  print("Save Complete!" , " (", name, ")")
  

  return filename
try:
  while True:

    line = ser.readline() #1行のデータを取得するがbyte型
  
    stripped_str = str(line, 'utf-8').strip()# byte型を文字列に変換して前後の空白改行除去
    datas = [float(d) for d in stripped_str.split(',')]  # 整数ではなく浮動小数点数に変換する
    # print(datas)

    if len(datas) == 10:
      Ax = datas[0]
      Ay = datas[1]
      Az = datas[2]
      
      Gx = datas[3]
      Gy = datas[4]
      Gz = datas[5]

      Mx = datas[6]
      My = datas[7]
      Mz = datas[8]


      proc_T = datas[9]*0.001
      
      Ax=Ax*0.01+0.5 #若干ずれているので修正
      Ay=Ay*0.01
      Az=Az*0.01-0.6
      ary_IMU = np.append(ary_IMU,[proc_T,',',Ax,',', Ay,',',Az,',',Gx,',', Gy,',',Gz,',',Mx,',', My,',',Mz, "\n"])

      
      print(ary_IMU)
      # print("\n")
      
    else:
      print('not 10')

except KeyboardInterrupt  :
  CSV_save(ary_IMU,"IMU")
  print('!!FINISH!!')


取得データ

Arduinoで書き込み, その後Pythonで実行すると図5 のようなファイルを取得できました.
サンプリング時間を計算するとおよそ2msでした. 最高でも5msと期待していたよりも早いです.
センサを上げて下げると図6のようなグラフを取得でき、うまく測れていることが確認できました。

Fig 5 取得できたデータ

Fig. 6 加速度のグラフ

課題点

課題点はたくさんあります.
まず大きい点として, 並列処理をしていない点にあります. (これがでかい!!)
並列処理ができていないと, 30秒くらいまでしか計測データをCSVに吐き出せません.

あと時間のスタートに対する処理が必要です.
現状6秒あたりからスタートしていますが, このタイミングがずれる場合があります.

今後並列処理のプログラムや, オイラー角の導出, そしてそのカルマンフィルタの構築や, クォータニオンを用いた処理を行っていきたいと思います。

もっとこうしたほうがいいというご意見がある方はぜひ教えていただければと思います。
質問もあれば可能な限り答えたいと思います。

参考

[1]https://strawberry-linux.com/catalog/items?code=20948
[2] https://wiki.seeedstudio.com/Seeeduino-XIAO/

雑談

以前まではMPU-9250というIMUセンサを用いていましたが生産が中止となっていたためこちらのセンサを用いました。
このセンサは単一電源で動作しましたが今回のICM20948は2つの異なる電圧が必要であったため、降圧型DCDCコンバータを用いて電圧を下げて制御しました。
また、MPU-9250とはレジスタマップが異なっているためその点も注意が必要です。
最初は頑張ってレジスタマップを解読してビットを合わせようとしましたが、加速度しか得られませんでした。。。
そこであきらめてライブラリに頼ることにしました。。。。

ちなみにDCDCコンバータについては以下のサイトがわかりやすいです。

https://club-z.zuken.co.jp/tech-column/20180425_r005.html

コメント

タイトルとURLをコピーしました