FluxSand 1.0
FluxSand - Interactive Digital Hourglass
Loading...
Searching...
No Matches
mpu9250.hpp
1#pragma once
2
3#include <array>
4#include <cassert>
5#include <chrono>
6#include <cmath>
7#include <cstdint>
8#include <format>
9#include <fstream>
10#include <functional>
11#include <iomanip>
12#include <iostream>
13#include <stdexcept>
14#include <thread>
15
16#include "bsp_gpio.hpp"
17#include "bsp_spi.hpp"
18#include "comp_type.hpp"
19
25class Mpu9250 {
26 public:
33 Mpu9250(SpiDevice* spi_device, Gpio* gpio_cs, Gpio* gpio_int)
34 : spi_device_(spi_device), gpio_cs_(gpio_cs), gpio_int_(gpio_int) {
35 assert(spi_device_ && gpio_cs_ && gpio_int_);
36
37 Initialize();
39
40 /* Register interrupt callback */
41 gpio_int_->EnableInterruptRisingEdgeWithCallback([this]() { ReadData(); });
42
43 calibrate_thread_ = std::thread(&Mpu9250::CalibrateThreadTask, this);
44 }
45
46 ~Mpu9250() {
47 if (calibrate_thread_.joinable()) {
48 calibrate_thread_.join();
49 }
50 }
51
52 void CalibrateThreadTask() {
53 while (true) {
54 auto start_time = std::chrono::steady_clock::now();
55 std::chrono::microseconds period_us(1000);
56 std::chrono::steady_clock::time_point next_time =
57 std::chrono::steady_clock::now();
58 uint32_t counter = 0;
59 double gyro_offset_x = 0, gyro_offset_y = 0, gyro_offset_z = 0;
60 static bool cali_done = false;
61
62 if (cali_done) {
63 std::this_thread::sleep_for(std::chrono::seconds(UINT32_MAX));
64 continue;
65 }
66
67 while (true) {
68 if (std::fabsf(gyro_delta_.x) > 0.005 ||
69 std::fabsf(gyro_delta_.y) > 0.005 ||
70 std::fabsf(gyro_delta_.z) > 0.01) {
71 next_time += period_us;
72 std::this_thread::sleep_until(next_time);
73 break;
74 }
75
76 auto now = std::chrono::steady_clock::now();
77 if (now - start_time > std::chrono::seconds(5) &&
78 now - start_time < std::chrono::seconds(30)) {
79 counter++;
80 gyro_offset_x += gyro_.x;
81 gyro_offset_y += gyro_.y;
82 gyro_offset_z += gyro_.z;
83 }
84
85 if (now - start_time > std::chrono::seconds(35) && cali_done == false) {
86 float bias_x =
87 static_cast<float>(gyro_offset_x / static_cast<double>(counter));
88 float bias_y =
89 static_cast<float>(gyro_offset_y / static_cast<double>(counter));
90 float bias_z =
91 static_cast<float>(gyro_offset_z / static_cast<double>(counter));
92
93 if (std::fabsf(bias_x) > 0.005 || std::fabsf(bias_y) > 0.005 ||
94 std::fabsf(bias_z) > 0.005) {
95 gyro_bias_.x += bias_x;
96 gyro_bias_.y += bias_y;
97 gyro_bias_.z += bias_z;
99 std::cout << "Calibration data saved\n";
100 } else {
101 std::cout << "No need to calibrate\n";
102 }
103 cali_done = true;
104 std::cout << "Calibration completed\n";
105 }
106
107 next_time += period_us;
108 std::this_thread::sleep_until(next_time);
109 }
110 }
111 }
112
120 const std::function<void(const Type::Vector3&, const Type::Vector3&)>&
121 callback) {
122 data_callback_ = callback;
123 }
124
131 void Initialize() {
132 /* Reset and wake up the MPU9250 */
133 spi_device_->WriteRegister(gpio_cs_, PWR_MGMT_1, 0x80);
134 std::this_thread::sleep_for(std::chrono::milliseconds(100));
135
136 /* Read and verify WHO_AM_I register */
137 uint8_t who_am_i = spi_device_->ReadRegister(gpio_cs_, WHO_AM_I);
138 std::cout << std::format("MPU9250 initialized. WHO_AM_I: 0x{:02X}\n",
139 who_am_i);
140
141 if (who_am_i != 0x71 && who_am_i != 0x68 && who_am_i != 0x70) {
142 std::perror(
143 std::format("Error: MPU9250 connection failed (WHO_AM_I: 0x{:02X})",
144 who_am_i)
145 .c_str());
146 }
147
148 /* Gyroscope clock source configuration */
149 spi_device_->WriteRegister(gpio_cs_, PWR_MGMT_1, 0x03);
150
151 /* Enable Accelerometer and Gyroscope */
152 spi_device_->WriteRegister(gpio_cs_, PWR_MGMT_2, 0x00);
153
154 /* INT high level, push-pull */
155 spi_device_->WriteRegister(gpio_cs_, INT_PIN_CFG, 0x10);
156 /* enable data ready interrupt */
157 spi_device_->WriteRegister(gpio_cs_, INT_ENABLE, 0x01);
158
159 spi_device_->WriteRegister(gpio_cs_, I2C_MST_CTRL, 0x4D);
160
161 /* Enable I2C Master mode by setting the I2C_MST_EN bit in USER_CTRL. */
162 spi_device_->WriteRegister(gpio_cs_, USER_CTRL, 0x20);
163
164 /* Enable delay for I2C Slave 0 transfers. */
165 spi_device_->WriteRegister(gpio_cs_, I2C_MST_DELAY_CTRL, 0x01);
166
167 /* Enable I2C communication for external sensors. */
168 spi_device_->WriteRegister(gpio_cs_, I2C_SLV0_CTRL, 0x81);
169
170 /* Configure Digital Low-Pass Filter (DLPF). */
171 spi_device_->WriteRegister(gpio_cs_, CONFIG, 3);
172
173 /* Set the gyroscope sampling rate. */
174 spi_device_->WriteRegister(gpio_cs_, SMPLRT_DIV, 0x00);
175
176 /* Configure gyroscope full-scale range to ±2000°/s. */
177 spi_device_->WriteRegister(gpio_cs_, GYRO_CONFIG, 0x18);
178
179 /* Configure accelerometer full-scale range to ±16g. */
180 spi_device_->WriteRegister(gpio_cs_, ACCEL_CONFIG, 0x18);
181
182 /* Configure accelerometer low-pass filter. */
183 spi_device_->WriteRegister(gpio_cs_, ACCEL_CONFIG_2, 0x00);
184
185 /* Reset the AK8963 magnetometer. */
186 WriteMagRegister(AK8963_CNTL2_REG, AK8963_CNTL2_SRST);
187 std::this_thread::sleep_for(std::chrono::milliseconds(10));
188
189 /* Configure AK8963 magnetometer for continuous measurement mode 1. */
191 std::this_thread::sleep_for(std::chrono::milliseconds(10));
192 }
193
197 void ReadData() {
198 std::array<uint8_t, 14> data{};
199
200 spi_device_->ReadRegisters(gpio_cs_, ACCEL_XOUT_H, &data[0], 14);
201
202 constexpr float ACCEL_SCALE = 16.0f / 32768.0f * 9.80665f;
203 constexpr float GYRO_SCALE = 2000.0f / 32768.0f * M_PI / 180.0f;
204
205 uint8_t* accel_data = &data[0];
206 uint8_t* temperature_data = &data[6];
207 uint8_t* gyro_data = &data[8];
208
209 accel_.z = static_cast<float>(
210 static_cast<int16_t>((accel_data[0] << 8) | accel_data[1])) *
211 ACCEL_SCALE;
212 accel_.y = -static_cast<float>(
213 static_cast<int16_t>((accel_data[2] << 8) | accel_data[3])) *
214 ACCEL_SCALE;
215 accel_.x = static_cast<float>(
216 static_cast<int16_t>((accel_data[4] << 8) | accel_data[5])) *
217 ACCEL_SCALE;
218
219 temperature_ = static_cast<float>(static_cast<int16_t>(
220 (temperature_data[0] << 8) | temperature_data[1])) /
221 333.87f +
222 21.0f;
223
224 float gyro_z = static_cast<float>(static_cast<int16_t>((gyro_data[0] << 8) |
225 gyro_data[1])) *
226 GYRO_SCALE -
227 gyro_bias_.z;
228 float gyro_y = -static_cast<float>(static_cast<int16_t>(
229 (gyro_data[2] << 8) | gyro_data[3])) *
230 GYRO_SCALE -
231 gyro_bias_.y;
232 float gyro_x = static_cast<float>(static_cast<int16_t>((gyro_data[4] << 8) |
233 gyro_data[5])) *
234 GYRO_SCALE -
235 gyro_bias_.x;
236
237 gyro_delta_.x = gyro_x - gyro_.x;
238 gyro_delta_.y = gyro_y - gyro_.y;
239 gyro_delta_.z = gyro_z - gyro_.z;
240
241 gyro_.x = gyro_x;
242 gyro_.y = gyro_y;
243 gyro_.z = gyro_z;
244
245 if (data_callback_) {
246 data_callback_(accel_, gyro_);
247 }
248 }
249
253 void DisplayData() {
254 float accel_intensity = std::sqrt(
255 accel_.x * accel_.x + accel_.y * accel_.y + accel_.z * accel_.z);
256 std::cout << std::format(
257 "Acceleration: [X={:+.4f}, Y={:+.4f}, Z={:+.4f} | Intensity={:+.4f}] | "
258 "Gyroscope: [X={:+.4f}, Y={:+.4f}, Z={:+.4f}] | Temperature: {:+.4f} "
259 "\u00b0C\n",
260 accel_.x, accel_.y, accel_.z, accel_intensity, gyro_.x, gyro_.y,
261 gyro_.z, temperature_);
262 }
263
270 void WriteMagRegister(uint8_t reg, uint8_t value) {
271 spi_device_->WriteRegister(gpio_cs_, reg, value);
272 }
273
278 std::ofstream file("cali_data.bin", std::ios::binary);
279 if (!file) {
280 std::cerr << "Error: Unable to open cali_data.bin for writing.\n";
281 return;
282 }
283 file.write(reinterpret_cast<const char*>(&gyro_bias_), sizeof(gyro_bias_));
284 file.close();
285 std::cout << "Calibration data saved successfully.\n";
286 }
287
292 std::ifstream file("cali_data.bin", std::ios::in | std::ios::binary);
293 if (!file) {
294 std::cerr
295 << "MPU9250 calibration file not found. Using default values.\n";
296 return;
297 }
298
299 /* Check file size */
300 file.seekg(0, std::ios::end);
301 std::streamsize file_size = file.tellg();
302 file.seekg(0, std::ios::beg);
303
304 if (file_size != sizeof(gyro_bias_)) {
305 std::cerr
306 << "Error: MPU9250 calibration file size mismatch. Using default "
307 "values.\n";
308 file.close();
309 gyro_bias_ = {0, 0, 0};
310 return;
311 }
312
313 /* Read data */
314 file.read(reinterpret_cast<char*>(&gyro_bias_), sizeof(gyro_bias_));
315 file.close();
316
317 /* Validate data: Ensure gyro_bias values are within a reasonable range */
318 if (std::abs(gyro_bias_.x) > 1.0f || std::abs(gyro_bias_.y) > 1.0f ||
319 std::abs(gyro_bias_.z) > 1.0f || std::isnan(gyro_bias_.x) ||
320 std::isnan(gyro_bias_.y) || std::isnan(gyro_bias_.z) ||
321 std::isinf(gyro_bias_.x) || std::isinf(gyro_bias_.y) ||
322 std::isinf(gyro_bias_.z)) {
323 std::cerr
324 << "Error: MPU9250 invalid calibration data detected. Resetting to "
325 "default values.\n";
326 gyro_bias_ = {0, 0, 0};
327 } else {
328 std::cout << "MPU9250 calibration data loaded successfully: "
329 << "X=" << gyro_bias_.x << ", "
330 << "Y=" << gyro_bias_.y << ", "
331 << "Z=" << gyro_bias_.z << "\n";
332 }
333
334 std::cout << '\n';
335 }
336
338 static constexpr uint8_t WHO_AM_I = 0x75;
339 static constexpr uint8_t PWR_MGMT_1 = 0x6B;
340 static constexpr uint8_t PWR_MGMT_2 = 0x6C;
341 static constexpr uint8_t CONFIG = 0x1A;
342 static constexpr uint8_t SMPLRT_DIV = 0x19;
343 static constexpr uint8_t GYRO_CONFIG = 0x1B;
344 static constexpr uint8_t ACCEL_CONFIG = 0x1C;
345 static constexpr uint8_t ACCEL_CONFIG_2 = 0x1D;
346 static constexpr uint8_t ACCEL_XOUT_H = 0x3B;
347 static constexpr uint8_t GYRO_XOUT_H = 0x43;
348 static constexpr uint8_t USER_CTRL = 0x6A;
349 static constexpr uint8_t INT_PIN_CFG = 0x37;
350 static constexpr uint8_t INT_ENABLE = 0x38;
351 static constexpr uint8_t I2C_MST_CTRL = 0x24;
352 static constexpr uint8_t I2C_MST_DELAY_CTRL = 0x67;
353 static constexpr uint8_t I2C_SLV0_CTRL = 0x27;
354
356 static constexpr uint8_t AK8963_CNTL1_REG = 0x0A;
357 static constexpr uint8_t AK8963_CNTL2_REG = 0x0B;
358 static constexpr uint8_t AK8963_CNTL2_SRST = 0x01;
359
360 SpiDevice* spi_device_; /* SPI device handle */
361 Gpio* gpio_cs_; /* GPIO chip select handle */
362 Gpio* gpio_int_; /* GPIO interrupt handle */
363
364 Type::Vector3 accel_; /* Accelerometer data */
365 Type::Vector3 gyro_; /* Gyroscope data */
366 Type::Vector3 mag_; /* Magnetometer data */
367 Type::Vector3 gyro_delta_; /* Gyroscope delta data */
368
369 Type::Vector3 gyro_bias_ = {0, 0, 0}; /* Gyroscope calibration data */
370
371 float temperature_ = 0; /* Temperature data */
372
373 std::function<void(const Type::Vector3& accel, const Type::Vector3& gyro)>
374 data_callback_; /* Data callback function */
375
376 std::thread calibrate_thread_; /* Thread */
377};
void EnableInterruptRisingEdgeWithCallback(Callback cb)
Definition bsp_gpio.hpp:109
static constexpr uint8_t WHO_AM_I
Definition mpu9250.hpp:338
void WriteMagRegister(uint8_t reg, uint8_t value)
Definition mpu9250.hpp:270
void SaveCalibrationData()
Definition mpu9250.hpp:277
static constexpr uint8_t AK8963_CNTL1_REG
Definition mpu9250.hpp:356
void Initialize()
Definition mpu9250.hpp:131
void ReadData()
Definition mpu9250.hpp:197
void RegisterDataCallback(const std::function< void(const Type::Vector3 &, const Type::Vector3 &)> &callback)
Registers a callback function to be called when new data is available.
Definition mpu9250.hpp:119
Mpu9250(SpiDevice *spi_device, Gpio *gpio_cs, Gpio *gpio_int)
Definition mpu9250.hpp:33
void LoadCalibrationData()
Definition mpu9250.hpp:291
void DisplayData()
Definition mpu9250.hpp:253
uint8_t ReadRegister(Gpio *cs, uint8_t reg)
Definition bsp_spi.hpp:58
void WriteRegister(Gpio *cs, uint8_t reg, uint8_t value)
Definition bsp_spi.hpp:88
Represents a 3D vector.