M5Unified
IMU_Class.cpp
Go to the documentation of this file.
1 // Copyright (c) M5Stack. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 #include "../M5Unified.hpp"
5 #include "IMU_Class.hpp"
6 
7 #include "m5unified_common.h"
8 
9 #if defined(ESP_PLATFORM)
10 
11 #include <sdkconfig.h>
12 #include <nvs.h>
13 
14 #include "imu/MPU6886_Class.hpp"
15 #include "imu/SH200Q_Class.hpp"
16 #include "imu/BMI270_Class.hpp"
17 #include "imu/BMM150_Class.hpp"
18 #include "imu/AK8963_Class.hpp"
19 
20 #endif
21 
22 static constexpr char LIBRARY_NAME[] = "M5Unified";
23 
24 namespace m5
25 {
26  static constexpr const char* nvs_key_names[9] = { "ax", "ay", "az", "gx", "gy", "gz", "mx", "my", "mz" };
27 
29  {
30 #if !defined(M5UNIFIED_PC_BUILD)
31  if (i2c)
32  {
33  i2c->begin();
34  }
35 
36  _imu = imu_t::imu_none;
37  _has_sensor_mask = sensor_mask_none;
38 
39  {
40  auto mpu6886 = new MPU6886_Class();
41  auto res = mpu6886->begin(i2c);
42  if (!res) { delete mpu6886; }
43  else
44  {
45  _imu_instance[0].reset(mpu6886);
46  switch (mpu6886->whoAmI())
47  {
49  _imu = imu_t::imu_mpu6050;
50  break;
52  _imu = imu_t::imu_mpu6886;
53  break;
55  _imu = imu_t::imu_mpu9250;
56  break;
57  default:
58  _imu = imu_t::imu_unknown;
59  break;
60  }
61 
62  if (board == m5::board_t::board_M5Atom)
63  { // ATOM Matrix's IMU is oriented differently, so change the setting.
64  _internal_axisorder_fixed[sensor_index_accel] = (internal_axisorder_t)(axis_invert_x | axis_invert_z); // X軸,Z軸反転
65  _internal_axisorder_fixed[sensor_index_gyro ] = (internal_axisorder_t)(axis_invert_x | axis_invert_z); // X軸,Z軸反転
66  }
67  }
68  }
69 
70  if (_imu == imu_t::imu_none)
71  {
72  auto bmi2 = new BMI270_Class();
73  if (!bmi2->begin(i2c)) { delete bmi2; }
74  else
75  {
76  _imu_instance[0].reset(bmi2);
77  _imu = imu_t::imu_bmi270;
78  // CoreS3 BMI270 + BMM150 構成では、地磁気のY軸Z軸をそれぞれ反転する
79  _internal_axisorder_fixed[sensor_index_mag] = (internal_axisorder_t)(axis_invert_y | axis_invert_z); // Y軸,Z軸反転
80  }
81  }
82 
83  if (_imu == imu_t::imu_none)
84  {
85  auto sh200q = new SH200Q_Class();
86  if (!sh200q->begin(i2c)) { delete sh200q; }
87  else
88  {
89  _imu_instance[0].reset(sh200q);
90  _imu = imu_t::imu_sh200q;
91  }
92  }
93 
94  {
95  auto bmm150 = new BMM150_Class();
96  if (!bmm150->begin(i2c)) {
97  delete bmm150;
98  }
99  else
100  {
101  _imu_instance[1].reset(bmm150);
102  if (board == m5::board_t::board_M5Stack)
103  { // M5Stack MPU6886 + BMM150構成では、地磁気のX軸とZ軸をそれぞれ反転する
104  // M5Stack SH200Q + BMM150構成での動作は未確認。(過去に一時期製造されている)
105  _internal_axisorder_fixed[sensor_index_mag] = (internal_axisorder_t)(axis_invert_x | axis_invert_z); // X軸,Z軸反転
106  }
107  }
108  }
109 
110  {
111  auto ak8963 = new AK8963_Class();
112  if (!ak8963->begin(i2c)) {
113  delete ak8963;
114  }
115  else
116  {
117  _imu_instance[1].reset(ak8963);
118  if (_imu == imu_t::imu_mpu9250)
119  { // MPU9250内蔵AK8963は地磁気のX軸とY軸を取り換え、Z軸の向きを反転する
120  _internal_axisorder_fixed[sensor_index_mag ] = (internal_axisorder_t)(axis_order_yxz | axis_invert_z); // Y軸X軸を入替, Z軸反転
121  }
122  }
123  }
124 #endif
125  if (_imu == imu_t::imu_none)
126  {
127  return false;
128  }
129 
130  memset(&_offset_data, 0, sizeof(imu_offset_data_t));
131  _update_convert_param();
132  _update_axis_order();
133 
134  if (!loadOffsetFromNVS())
135  {
136  setCalibration(255, 255, 255);
137  update();
138  for (int i = 0; i < 3; ++i)
139  {
140  for (int j = 0; j < 3; ++j)
141  {
142  int32_t d = _raw_data.sensor[i].value[j] << 16;
143  _offset_data.sensor[i].prev_value[j] = d;
144  _offset_data.sensor[i].avg_value[j] = d;
145  }
146  _offset_data.sensor[i].stillness = 255;
147  _offset_data.sensor[i].calibration();
148  }
149  }
150  setCalibration(0, 0, 0);
151 
152 // debug
153 // for(int i=0;i<3;++i){for(int j=0;j<3;++j){_offset_data.sensor[i].value[j] = 65536*512;}}
154 // for(int i=0;i<3;++i){for(int j=0;j<3;++j){_offset_data.sensor[i].value[j] = 0;}}
155 
156  return true;
157  }
158 
159  void IMU_Class::_update_convert_param(void)
160  {
161  if (_imu_instance[0]) { _imu_instance[0]->getConvertParam(&_convert_param); }
162  if (_imu_instance[1]) { _imu_instance[1]->getConvertParam(&_convert_param); }
163 
164  // 加速度は 1.0G ± 0.000488f の範囲に収まるよう調整
165  _offset_data.accel.radius = 1.0f / _convert_param.accel_res;
166  _offset_data.accel.tolerance = (1.0f / 2048.0f) / _convert_param.accel_res;
167  _offset_data.accel.noise_level = 0.0625f / _convert_param.accel_res;
168  _offset_data.accel.average_shifter = 1;
169 
170  // ジャイロは 誤差 ±2.0度/sec の範囲に収まるよう調整
171  _offset_data.gyro.radius = 0;
172  _offset_data.gyro.tolerance = 0.0f / _convert_param.gyro_res;
173  _offset_data.gyro.noise_level = 2.0f / _convert_param.gyro_res;
174  _offset_data.gyro.average_shifter = 6;
175 
176  // 地磁気は…パラメータ模索中…
177  _offset_data.mag.radius = 384.0f / _convert_param.mag_res;
178  _offset_data.mag.tolerance = 64.0f / _convert_param.mag_res;
179  _offset_data.mag.noise_level = 96.0f / _convert_param.mag_res;
180  _offset_data.mag.average_shifter = 1;
181  }
182 
183  void IMU_Class::setCalibration(uint8_t accel, uint8_t gyro, uint8_t mag)
184  {
185  _offset_data.accel.strength = accel;
186  _offset_data.gyro.strength = gyro;
187  _offset_data.mag.strength = mag;
188  _calibration_flg = (sensor_mask_t)((accel ? sensor_mask_accel : 0)
189  | (gyro ? sensor_mask_gyro : 0)
190  | (mag ? sensor_mask_mag : 0));
191  }
192 
193  bool IMU_Class::setAxisOrder(axis_t axis0, axis_t axis1, axis_t axis2)
194  { // データの取り出し順序を指定する
195  uint_fast8_t result;
196  switch ((axis0 >> 1) + ((axis1 >> 1) << 2) + ((axis2 >> 1) << 4))
197  {
198  case 0 << 0 | 1 << 2 | 2 << 4: result = axis_order_xyz; break;
199  case 0 << 0 | 2 << 2 | 1 << 4: result = axis_order_xzy; break;
200  case 1 << 0 | 0 << 2 | 2 << 4: result = axis_order_yxz; break;
201  case 1 << 0 | 2 << 2 | 0 << 4: result = axis_order_yzx; break;
202  case 2 << 0 | 0 << 2 | 1 << 4: result = axis_order_zxy; break;
203  case 2 << 0 | 1 << 2 | 0 << 4: result = axis_order_zyx; break;
204  default: return false;
205  }
206  result += (axis0 & 1) + ((axis1 & 1) << 1) + ((axis2 & 1) << 2);
207  _internal_axisorder_user = (internal_axisorder_t)result;
208  _update_axis_order();
209  return true;
210  }
211 
212  void IMU_Class::_update_axis_order(void)
213  { // システムの軸設定とユーザの軸設定を反映したデータの取り出し順序を求める
214  static constexpr const uint8_t internal_axisorder_table[6] =
215  {//X idx | Y idx | Z idx
216  0 << 0 | 1 << 2 | 2 << 4, // axis_order_xyz
217  0 << 0 | 2 << 2 | 1 << 4, // axis_order_xzy
218  1 << 0 | 0 << 2 | 2 << 4, // axis_order_yxz
219  1 << 0 | 2 << 2 | 0 << 4, // axis_order_yzx
220  2 << 0 | 0 << 2 | 1 << 4, // axis_order_zxy
221  2 << 0 | 1 << 2 | 0 << 4, // axis_order_zyx
222  };
223 
224  std::uint32_t result = 0;
225  std::uint32_t bitshift = 0;
226  auto axis_order_user = _internal_axisorder_user;
227  auto order_user_tbl = internal_axisorder_table[axis_order_user >> axis_order_shift];
228  for (std::size_t sensor = 0; sensor < 3; ++sensor)
229  {
230  auto axis_order_fixed = _internal_axisorder_fixed[sensor];
231  auto order_fixed_tbl = internal_axisorder_table[axis_order_fixed >> axis_order_shift];
232  for (std::size_t j = 0; j < 3; ++j, bitshift += 3)
233  { // ユーザが要求している軸順と反転指定を求める
234  std::uint_fast8_t axis_user_index = (order_user_tbl >> (j << 1)) & 3;
235  bool invert_user = axis_order_user & (1 << j);
236 
237  // システムで設定された軸順テーブルを、ユーザーが要求している軸順で取得する
238  std::uint_fast8_t axis_fixed_index = (order_fixed_tbl >> (axis_user_index << 1)) & 3;
239  bool invert_fixed = axis_order_fixed & (1 << axis_user_index);
240 
241  // 3ビットでx=0b000,y=b010,z=0b100 | 反転フラグ0b001 を表現する。
242  // これを3軸×3センサの9個分を並べて保持する。合計3bit×9の27ビットが使用される。
243  result |= ((axis_fixed_index << 1) + (invert_user != invert_fixed ? 1 : 0)) << bitshift;
244 // printf("user_index:%d invert_user:%d fixed_index:%d invert_fixed:%d res:%08x\n", axis_user_index, invert_user, axis_fixed_index, invert_fixed, result);
245  }
246  }
247  _axis_order_3bit_x9 = result;
248  }
249 
250  // 指定された2軸を元に、残りの1軸を求める (右手系)
251  static IMU_Class::axis_t getAxis2(IMU_Class::axis_t axis0, IMU_Class::axis_t axis1)
252  {
253  std::uint_fast8_t axis2 = (1 << (axis0 >> 1) | 1 << (axis1 >> 1)) ^ 0b111;
254  axis2 &= 0b110;
255  std::uint_fast8_t ax0 = axis2 ? (axis2 - 2) : 4;
256  axis2 += (bool)((axis0 & 0b110) == ax0) == (bool)((axis0 & 1) == (axis1 & 1));
257  return (IMU_Class::axis_t)axis2;
258  }
259 
261  {
262  return setAxisOrder(axis0, axis1, getAxis2(axis0, axis1));
263  }
264 
266  {
267  // 右手系で求めた軸に反転フラグを設定し、左手系とする。
268  auto axis2 = getAxis2(axis0, axis1) ^ 1;
269  return setAxisOrder(axis0, axis1, (axis_t)axis2);
270  }
271 
273  { // NVSへオフセット値を保存する
274 #if !defined(M5UNIFIED_PC_BUILD)
275  std::uint32_t nvs_handle = 0;
276  if (ESP_OK != nvs_open(LIBRARY_NAME, NVS_READWRITE, &nvs_handle))
277  {
278  return false;
279  }
280  size_t index = 0;
281  for (size_t i = 0; i < 3; ++i)
282  {
283  for (size_t j = 0; j < 3; ++j, ++index)
284  {
285  int32_t val = _offset_data.sensor[i].value[j];
286  nvs_set_i32(nvs_handle, nvs_key_names[index], val);
287  M5_LOGV("%s:%d", nvs_key_names[index], val);
288  }
289  }
290  nvs_close(nvs_handle);
291 #endif
292  return true;
293  }
294 
296  { // NVSからオフセット値を読み込む
297 #if !defined(M5UNIFIED_PC_BUILD)
298  std::uint32_t nvs_handle = 0;
299  if (ESP_OK != nvs_open(LIBRARY_NAME, NVS_READONLY, &nvs_handle))
300  {
301  return false;
302  }
303  size_t index = 0;
304  for (size_t i = 0; i < 3; ++i)
305  {
306  for (size_t j = 0; j < 3; ++j, ++index)
307  {
308  int32_t val = 0;
309  nvs_get_i32(nvs_handle, nvs_key_names[index], &val);
310  _offset_data.sensor[i].value[j] = val;
311  M5_LOGD("%s:%d", nvs_key_names[index], val);
312  }
313  }
314  nvs_close(nvs_handle);
315 #endif
316  return true;
317  }
318 
320  {
321  size_t index = 0;
322  for (size_t i = 0; i < 3; ++i)
323  {
324  for (size_t j = 0; j < 3; ++j, ++index)
325  {
326  _offset_data.sensor[i].value[j] = 0;
327  }
328  }
329  }
330 
331  void IMU_Class::setOffsetData(size_t index, int32_t value)
332  {
333  if (index < 9) {
334  _offset_data.sensor[index / 3].value[index % 3] = value;
335  }
336  }
337 
338  int32_t IMU_Class::getOffsetData(size_t index)
339  {
340  return index < 9 ? _offset_data.sensor[index / 3].value[index % 3] : 0;
341  }
342 
343  int16_t IMU_Class::getRawData(size_t index)
344  {
345  return index < 9 ? _raw_data.value[index] : 0;
346  }
347 
349  {
350  sensor_mask_t res = (sensor_mask_t)0;
351  for (size_t i = 0; i < 2; ++i)
352  {
353  if (_imu_instance[i].get())
354  {
355  uint_fast8_t t = _imu_instance[i]->getImuRawData(&_raw_data);
356  if (t)
357  {
358  res = (sensor_mask_t)(res | t);
359  }
360  if (i == 0)
361  {
362  _latest_micros = m5gfx::micros();
363  }
364  }
365  }
366  if (res)
367  {
368  std::uint_fast8_t mask_flg = _calibration_flg & res;
369  // キャリブレーション処理
370  for (size_t i = 0; mask_flg && i < 3; ++i, mask_flg >>= 1)
371  {
372  // if ((mask_flg & 1) && (16 < _offset_data.sensor[i].updateStillness(_raw_data.sensor[i])))
373  if (mask_flg & 1)
374  {
375  auto st = _offset_data.sensor[i].updateStillness(_raw_data.sensor[i]);
376  if (16 < st)
377  {
378  _offset_data.sensor[i].calibration();
379 // if (i == 1) { printf("Ok %d\n" ,st); }
380  }
381 // else
382 // {
383 // if (i == 1) { printf(" NG %d\n", st); }
384 // }
385  }
386  }
387  }
388  return res;
389  }
390 
392  {
393  data->usec = _latest_micros;
394  auto &raw = _raw_data;
395  // data->temp = raw.temp * _convert_param.temp_res + _convert_param.temp_offset;
396  auto &offset = _offset_data;
397  auto order = _axis_order_3bit_x9;
398  for (int i = 0; i < 3; ++i)
399  {
400  float resolution = _convert_param.value[i] * (1.0f / 65536.0f);
401  for (int j = 0; j < 3; ++j)
402  {
403  auto axis_index = (order >> 1) & 3;
404  int32_t value = (raw.sensor[i].value[axis_index] << 16) - offset.sensor[i].value[axis_index];
405  if (order & 1) { value = -value; }
406  data->sensor[i].value[j] = resolution * value;
407  order >>= 3;
408  }
409  }
410  }
411 
412  bool IMU_Class::getAccel(float *x, float *y, float *z)
413  {
414  bool res = true;
415  uint32_t us = m5gfx::micros();
416  if (us - _latest_micros > 256)
417  {
418  res = update();
419  }
420  imu_data_t data;
421  getImuData(&data);
422  *x = data.accel.x;
423  *y = data.accel.y;
424  *z = data.accel.z;
425  return res;
426  }
427 
428  bool IMU_Class::getGyro(float *x, float *y, float *z)
429  {
430  bool res = true;
431  uint32_t us = m5gfx::micros();
432  if (us - _latest_micros > 256)
433  {
434  res = update();
435  }
436  imu_data_t data;
437  getImuData(&data);
438  *x = data.gyro.x;
439  *y = data.gyro.y;
440  *z = data.gyro.z;
441  return res;
442  }
443 
444  bool IMU_Class::getMag(float* x, float* y, float* z)
445  {
446  bool res = true;
447  uint32_t us = m5gfx::micros();
448  if (us - _latest_micros > 256)
449  {
450  res = update();
451  }
452  imu_data_t data;
453  getImuData(&data);
454  *x = data.mag.x;
455  *y = data.mag.y;
456  *z = data.mag.z;
457  return res;
458  }
459 
460  bool IMU_Class::getTemp(float *t)
461  {
462  int16_t temp;
463  if (_imu_instance[0].get() && _imu_instance[0]->getTempAdc(&temp))
464  {
465  _raw_data.temp = temp;
466  *t = temp * _convert_param.temp_res + _convert_param.temp_offset;
467  return true;
468  }
469  return false;
470  }
471 
473  {
474  return _imu_instance[0].get() && _imu_instance[0]->setINTPinActiveLogic(level);
475  }
476 
477 
479  { // 前回の座標情報との差が許容範囲以内か調べる
480  int32_t res = stillness + 1;
481 
482  int32_t maxdiff = 0;
483  for (int i = 0; i < 3; ++i)
484  {
485  int32_t d = dst.value[i] << 16;
486  int32_t pv = prev_value[i];
487  int32_t diff = d - pv;
488  prev_value[i] = d;
489  maxdiff = std::max(maxdiff, abs(diff));
490 
491  int32_t av = avg_value[i];
492  diff = d - av;
493  maxdiff = std::max(maxdiff, abs(diff));
494 
495  diff = (diff + (1 << (average_shifter - 1))) >> average_shifter;
496  avg_value[i] = av + diff;
497  // maxdiff = std::max(maxdiff, abs(diff) << (average_shifter));
498  }
499 
500  int32_t rt = noise_level << 8;
501  maxdiff >>= 8;
502  if (rt > maxdiff)
503  {
504  maxdiff = rt - (maxdiff + 1);
505  maxdiff = (maxdiff << 8) / rt;
506  }
507  else
508  {
509  maxdiff = 0;
510  }
511  res = std::min(res, maxdiff);
512 
513  stillness = res;
514  return res;
515  }
516 
518  {
519  if (stillness * strength == 0) return;
520  float distance = 0;
521  float diffs[3];
522  for (int i = 0; i < 3; ++i)
523  {
524  // int p = ((average_shifter) ? avg_value : prev_value)[i];
525  auto p = prev_value[i];
526  // auto p = avg_value[i];
527  // auto p = (prev_value[i] + avg_value[i]) >> 1;
528  float f = p - value[i];
529  diffs[i] = f;
530  distance += f * f;
531  }
532  distance = sqrtf(distance * (1.0f / (65536.0f * 65536.0f)));
533  if (distance < 1.0f) { return; }
534 
535  // 誤差を求める (目的の半径と現在の距離の差)
536  float measure_error = distance - radius;
537  // if (signbit(measure_error)) { return; }
538  // float tole_half = tolerance;
539  float force = fabsf(measure_error);
540  // if (force <= 0.0f) { return; }
541  if (force <= tolerance) { return; }
542  // if (force > tolerance) { force = tolerance; }
543  if (measure_error < 0.0f) { force = -force; }
544  float fk = force * (stillness * strength) / (distance * (255.0f * 255.0f));
545  for (int i = 0; i < 3; ++i)
546  {
547  value[i] += roundf(diffs[i] * fk);
548  }
549  return;
550  }
551 }
int16_t temp
Definition: IMU_Base.hpp:3
int16_t value[3]
Definition: IMU_Base.hpp:0
point3d_i16_t accel
Definition: IMU_Base.hpp:0
int16_t z
Definition: IMU_Base.hpp:5
point3d_i16_t mag
Definition: IMU_Base.hpp:2
int16_t x
Definition: IMU_Base.hpp:3
int16_t y
Definition: IMU_Base.hpp:4
point3d_i16_t gyro
Definition: IMU_Base.hpp:1
uint16_t noise_level
Definition: IMU_Class.hpp:32
uint8_t average_shifter
Definition: IMU_Class.hpp:33
float radius
Definition: IMU_Class.hpp:30
uint8_t stillness
Definition: IMU_Class.hpp:34
int32_t avg_value[3]
Definition: IMU_Class.hpp:22
void calibration(void)
uint8_t strength
Definition: IMU_Class.hpp:35
float tolerance
Definition: IMU_Class.hpp:31
std::uint_fast8_t updateStillness(const IMU_Base::point3d_i16_t &dst)
int32_t prev_value[3]
Definition: IMU_Class.hpp:12
#define M5_LOGD(format,...)
Output Debug log with source info.
Definition: Log_Class.hpp:33
#define M5_LOGV(format,...)
Output Verbose log with source info.
Definition: Log_Class.hpp:36
bool begin(i2c_port_t port_num, int pin_sda, int pin_scl)
Definition: I2C_Class.cpp:21
bool getMag(float *mx, float *my, float *mz)
Definition: IMU_Class.cpp:444
bool begin(I2C_Class *i2c=nullptr, board_t board=board_t::board_unknown)
Definition: IMU_Class.cpp:28
bool setAxisOrderRightHanded(axis_t axis0, axis_t axis1)
Definition: IMU_Class.cpp:260
bool saveOffsetToNVS(void)
Definition: IMU_Class.cpp:272
int16_t getRawData(size_t index)
Definition: IMU_Class.cpp:343
sensor_mask_t update(void)
Definition: IMU_Class.cpp:348
bool loadOffsetFromNVS(void)
Definition: IMU_Class.cpp:295
bool getTemp(float *t)
Definition: IMU_Class.cpp:460
bool setAxisOrderLeftHanded(axis_t axis0, axis_t axis1)
Definition: IMU_Class.cpp:265
void clearOffsetData(void)
Definition: IMU_Class.cpp:319
void setCalibration(uint8_t accel_strength, uint8_t gyro_strength, uint8_t mag_strength)
Definition: IMU_Class.cpp:183
bool getGyro(float *gx, float *gy, float *gz)
Definition: IMU_Class.cpp:428
bool setINTPinActiveLogic(bool level)
Definition: IMU_Class.cpp:472
bool getAccel(float *ax, float *ay, float *az)
Definition: IMU_Class.cpp:412
const imu_data_t & getImuData(void)
Definition: IMU_Class.hpp:89
void setOffsetData(size_t index, int32_t value)
Definition: IMU_Class.cpp:331
bool setAxisOrder(axis_t axis0, axis_t axis1, axis_t axis2)
Definition: IMU_Class.cpp:193
int32_t getOffsetData(size_t index)
Definition: IMU_Class.cpp:338
static constexpr const std::uint8_t DEV_ID_MPU9250
static constexpr const std::uint8_t DEV_ID_MPU6050
static constexpr const std::uint8_t DEV_ID_MPU6886
Definition: M5Unified.cpp:48
m5gfx::board_t board_t
Definition: M5Unified.hpp:23
@ imu_none
Definition: IMU_Class.hpp:14
@ imu_bmi270
Definition: IMU_Class.hpp:20
@ imu_mpu6886
Definition: IMU_Class.hpp:18
@ imu_sh200q
Definition: IMU_Class.hpp:16
@ imu_unknown
Definition: IMU_Class.hpp:15
@ imu_mpu6050
Definition: IMU_Class.hpp:17
@ imu_mpu9250
Definition: IMU_Class.hpp:19
point3d_i16_t sensor[3]
Definition: IMU_Base.hpp:41