とりあえずサンプルコードで動かしてみる
今回は ArduinoIDE を用いて実験をします。 M5Stack 公式から出されている UiFlow-IDE を用いても良いのですが、アイコンが使いづらい上処理も遅いので好きじゃありません。 ぱぱっと ArduinoIDE のスケッチ例から IMU.ino を選択し実行。
1// define must ahead #include <M5Stack.h>
2#define M5STACK_MPU6886
3// #define M5STACK_MPU9250
4// #define M5STACK_MPU6050
5// #define M5STACK_200Q
6
7#include <M5Stack.h>
8
9float accX = 0.0F;
10float accY = 0.0F;
11float accZ = 0.0F;
12
13float gyroX = 0.0F;
14float gyroY = 0.0F;
15float gyroZ = 0.0F;
16
17float pitch = 0.0F;
18float roll = 0.0F;
19float yaw = 0.0F;
20
21float temp = 0.0F;
22
23// the setup routine runs once when M5Stack starts up
24void setup(){
25
26 // Initialize the M5Stack object
27 M5.begin();
28 /*
29 Power chip connected to gpio21, gpio22, I2C device
30 Set battery charging voltage and current
31 If used battery, please call this function in your project
32 */
33 M5.Power.begin();
34
35 M5.IMU.Init();
36
37 M5.Lcd.fillScreen(BLACK);
38 M5.Lcd.setTextColor(GREEN , BLACK);
39 M5.Lcd.setTextSize(2);
40}
41
42// the loop routine runs over and over again forever
43void loop() {
44 // put your main code here, to run repeatedly:
45 M5.IMU.getGyroData(&gyroX,&gyroY,&gyroZ);
46 M5.IMU.getAccelData(&accX,&accY,&accZ);
47 M5.IMU.getAhrsData(&pitch,&roll,&yaw);
48 M5.IMU.getTempData(&temp);
49
50 M5.Lcd.setCursor(0, 20);
51 M5.Lcd.printf("%6.2f %6.2f %6.2f ", gyroX, gyroY, gyroZ);
52 M5.Lcd.setCursor(220, 42);
53 M5.Lcd.print(" o/s");
54 M5.Lcd.setCursor(0, 65);
55 M5.Lcd.printf(" %5.2f %5.2f %5.2f ", accX, accY, accZ);
56 M5.Lcd.setCursor(220, 87);
57 M5.Lcd.print(" G");
58 M5.Lcd.setCursor(0, 110);
59 M5.Lcd.printf(" %5.2f %5.2f %5.2f ", pitch, roll, yaw);
60 M5.Lcd.setCursor(220, 132);
61 M5.Lcd.print(" degree");
62 M5.Lcd.setCursor(0, 155);
63 M5.Lcd.printf("Temperature : %.2f C", temp);
64
65 delay(1);
66}
一応、値を見ることができました。
しかしこの degree の yaw 角ドリフトがある上に、回転する速度によって変化する値が結構変わってしまう、、、
yaw 角を求めるプログラムを作る
yaw 角以外の値はぱっと見正しそうなので今回は yaw 角だけ求めるプログラムを自分で作ることにしました。
1#define M5STACK_MPU6886
2#define CALIBCOUNT 10000
3
4#include <M5Stack.h>
5
6float accX = 0.0F;
7float accY = 0.0F;
8float accZ = 0.0F;
9
10float gyroX = 0.0F;
11float gyroY = 0.0F;
12float gyroZ = 0.0F;
13
14float yaw = 0.0F;
15
16float gyroOffsetZ = 0.0;
17
18float preTime = 0.0F;
19float dt = 0.0F;
20
21float pregz = 0.0F;
22float degree = 0;
23
24int cnt = 0;
25
26void calibration()
27{
28 delay(1000);
29 M5.Lcd.printf("...");
30 float gyroSumZ = 0;
31 int count = CALIBCOUNT;
32 for (int i = 0; i < count; i++) {
33 M5.update();
34
35 float gyroZ;
36 M5.IMU.getGyroData(&gyroX, &gyroY, &gyroZ);
37
38 gyroSumZ += gyroZ;
39 if (M5.BtnB.wasPressed())
40 {
41 M5.Lcd.clear();
42 M5.Lcd.setCursor(140, 120);
43 M5.Lcd.printf("Exit");
44 delay(500);
45 return;
46 }
47 }
48 gyroOffsetZ = gyroSumZ / count - 0.02;
49 M5.Lcd.clear();
50 M5.Lcd.setCursor(140, 120);
51 M5.Lcd.printf("Done");
52 delay(500);
53}
54
55void GetGyro()
56{
57 M5.IMU.getGyroData(&gyroX, &gyroY, &gyroZ);
58 M5.IMU.getAccelData(&accX, &accY, &accZ);
59
60 gyroZ -= gyroOffsetZ;
61
62 dt = (micros() - preTime) / 1000000;
63 preTime = micros();
64
65 yaw -= (pregz + gyroZ) * dt / 2;
66 pregz = gyroZ;
67
68 if(yaw > 180)
69 {
70 yaw -= 360;
71 }
72 else if(yaw < -180)
73 {
74 yaw += 360;
75 }
76 delay(10);
77}
78
79void Button()
80{
81 M5.update();
82 if (M5.BtnA.wasPressed())
83 {
84 cnt--;
85 M5.Lcd.clear();
86 }
87
88 if (M5.BtnC.wasPressed())
89 {
90 cnt++;
91 M5.Lcd.clear();
92 }
93}
94
95void ResetGyro()
96{
97 gyroZ = 0.0;
98 pregz = 0.0;
99 yaw = 0.0;
100 M5.Lcd.clear();
101 M5.Lcd.setCursor(120, 120);
102 M5.Lcd.printf("RESET");
103 delay(500);
104 M5.Lcd.clear();
105}
106
107void Main()
108{
109 M5.Lcd.clear();
110 while (true)
111 {
112 M5.update();
113 M5.Lcd.fillCircle(160 + 80 * cos(degree), 120 + 80 * sin(degree), 10, BLACK);
114 M5.Lcd.setCursor(160, 0);
115 degree = (yaw - 90) / (180 / PI);
116 GetGyro();
117 M5.Lcd.drawCircle(160, 120, 80, WHITE);
118 M5.Lcd.fillCircle(160 + 80 * cos(degree), 120 + 80 * sin(degree), 10, GREEN);
119 M5.Lcd.printf("%4.0f", yaw);
120 if (M5.BtnB.wasPressed())
121 {
122 M5.Lcd.clear();
123 break;
124 }
125 }
126}
127
128void setup() {
129
130 M5.begin();
131
132
133 M5.Power.begin();
134
135 M5.IMU.Init();
136
137 M5.Lcd.fillScreen(BLACK);
138 M5.Lcd.setTextColor(WHITE , BLACK);
139 M5.Lcd.setTextSize(2);
140 delay(1);
141}
142
143
144void loop() {
145 Button();
146
147 switch (cnt)
148 {
149 case 0:
150 M5.Lcd.setCursor(140, 120);
151 M5.Lcd.printf("Main");
152 if (M5.BtnB.wasPressed())
153 {
154 Main();
155 }
156 break;
157 case 1:
158 M5.Lcd.setCursor(90, 120);
159 M5.Lcd.printf("Calibration");
160 if (M5.BtnB.wasPressed())
161 {
162 calibration();
163 }
164 break;
165 case 2:
166 M5.Lcd.setCursor(100, 120);
167 M5.Lcd.printf("ResetGyro");
168 if (M5.BtnB.wasPressed())
169 {
170 ResetGyro();
171 }
172 break;
173 default:
174 cnt = 0;
175 break;
176 }
177}
プログラムが長くなってしまいました 💦 時間があったので余計な物まで追加しちゃってます。キャリブレーションと初期方向のリセット、実際の yaw 角の変化の可視化を同一プログラム内でできるようにしました。
キャリブレーションをする関数 Calibration
Calibration では一番上で define した数 CALIBCOUNT 分だけデータを取りその値を平均した値をオフセット値に代入するという関数になっています。キャリブレーション中は IMU を動かさないというのが前提です。
yaw 角の角度変化を求める関数 GetGyro
GetGyro では縦軸角速度(deg/s)、横軸時間(s)からなるグラフの面積を積算することで角度変化を求めるということをしています。(積分には台形による積算の方法を用いています。詳しくはこちらのサイトを参考にさせていただきました)
https://garchiving.com/angular-from-angular-acceleration/ 実際に yaw に入る値は-180~180 になるようにしています。
ジャイロの方向を初期化する関数 ResetGyro
ResetGyro は yaw 角を求めるために必要なパラメーターと yaw 角自体を初期化するという簡単なものです。
最後に、求めた yaw 角の角度変化を可視化する関数 Main
今回最終的にやりたかったことが角度変化を可視化することであったので Main という名をつけました。 この関数では実際に GetGyro で求めた値を円上にプロットすることで可視化するというものです。円上の点の座標は半径を x、θ を yaw 角の角度変化とすると(xcosθ,xsinθ)となります。なので最終的に半径 x の円を画面中央に描画し(円の中心 x 座標+(xcosθ),円の中心 y 座標+(xsinθ))に点を描画することで可視化できました。
しかし一度描画した点は自動的に消えてはくれないので、毎回次のループでその点を黒く塗るということをおこなっています。そのせいで表示と消すという動きを繰り返すため点が点滅してしまい少し見づらいです、、、何かいい方法はないのでしょうか、、