記事の概要
今回は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に降下してくれます.
Model | ICM-20948 | ||
Company | TDK InvenSense | ||
Built-in | Acceleration | Gyro | Geomagnetic |
Range | ±16G | ±2000dps | ±4900uT |
Output Freq. | 4.5kHz | 9kHz | – |
Resolution | 16bit | 16bit | 16bit |
Communication | I2C or SPI | ||
Size | 12.8*10.2*2.7 | ||
Supply Voltage | 3.3V, 1.8V | ||
Price | 1980円 |
回路構成
回路図は図4になります. コンバータは3.3Vから1.8Vに降圧させるために用いました。
(DCDCコンバータを回路図でどう表せばいいのかよくわかりませんでした. )
最初はスイッチング電源で1.8Vに設定し試してみました.
プログラムコード
Arduino
本来であればICM20948はレジスタマップから制御するほうが望ましいのですが, 今回は以下のライブラリを使用させていただきました.
一度レジスタマップから指定して値を読み取ろうとしたのですが加速度しか読み取ることができませんでした。。。
/****************************************************************
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のようなグラフを取得でき、うまく測れていることが確認できました。
課題点
課題点はたくさんあります.
まず大きい点として, 並列処理をしていない点にあります. (これがでかい!!)
並列処理ができていないと, 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コンバータについては以下のサイトがわかりやすいです。
コメント