#include <M5Atom.h>
#include <Wire.h>
#include <Preferences.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <math.h>
const char DEVICE_NAME[] = "ATOM-MPU6886";
const char SERVICE_UUID[] = "7f1b0001-8e7a-4d76-9f47-7b5a9e9d0001";
const char DATA_UUID[] = "7f1b0002-8e7a-4d76-9f47-7b5a9e9d0001";
const char COMMAND_UUID[] = "7f1b0003-8e7a-4d76-9f47-7b5a9e9d0001";
// ESP32-S3版と同じく、センサー読み出し・姿勢演算・BLE通知を100 Hzで行う。
// 200 Hzで融合演算しても送信は100 Hzのため、ATOMではBLE処理との競合が増える。
const uint32_t SAMPLE_INTERVAL_US = 10000;
const uint32_t BLE_INTERVAL_US = 10000;
const uint32_t CALIBRATION_MAGIC = 0x6886A701;
const uint8_t FAST_IMU_ADDRESS = 0x68;
const uint8_t FAST_IMU_ACCEL_XOUT_H = 0x3B;
BLECharacteristic *dataCharacteristic = nullptr;
Preferences preferences;
volatile bool connected = false, startRequested = false, yawResetRequested = false;
bool streaming = false;
float biasX = 0, biasY = 0, biasZ = 0;
float qw = 1, qx = 0, qy = 0, qz = 0, yawOffset = 0;
// yaw補正用の固定クォータニオン。送信のたびにcosf()/sinf()を呼ばない。
float yawCW = 1.0f, yawSZ = 0.0f;
static void updateYawRotation() {
const float h = -yawOffset * 0.5f;
yawCW = cosf(h);
yawSZ = sinf(h);
}
static bool readImuFast(float &ax, float &ay, float &az,
float &gx, float &gy, float &gz) {
uint8_t raw[14];
Wire1.beginTransmission(FAST_IMU_ADDRESS);
Wire1.write(FAST_IMU_ACCEL_XOUT_H);
if (Wire1.endTransmission(false) != 0) return false;
if (Wire1.requestFrom((int)FAST_IMU_ADDRESS, (int)sizeof(raw)) != sizeof(raw)) return false;
for (uint8_t i = 0; i < sizeof(raw); ++i) {
if (!Wire1.available()) return false;
raw[i] = Wire1.read();
}
const int16_t axRaw = (int16_t)((raw[0] << 8) | raw[1]);
const int16_t ayRaw = (int16_t)((raw[2] << 8) | raw[3]);
const int16_t azRaw = (int16_t)((raw[4] << 8) | raw[5]);
const int16_t gxRaw = (int16_t)((raw[8] << 8) | raw[9]);
const int16_t gyRaw = (int16_t)((raw[10] << 8) | raw[11]);
const int16_t gzRaw = (int16_t)((raw[12] << 8) | raw[13]);
ax = axRaw * (2.0f / 32768.0f); ay = ayRaw * (2.0f / 32768.0f); az = azRaw * (2.0f / 32768.0f);
gx = gxRaw * (250.0f / 32768.0f); gy = gyRaw * (250.0f / 32768.0f); gz = gzRaw * (250.0f / 32768.0f);
return true;
}
class ServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *) override { connected = true; }
void onDisconnect(BLEServer *) override {
connected = false; streaming = false; BLEDevice::startAdvertising();
}
};
class CommandCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *c) override {
String value = c->getValue();
for (size_t i = 0; i < value.length(); ++i) {
if (value[i] == 'r') startRequested = true;
if (value[i] == 'c') yawResetRequested = true;
}
}
};
void calibrateOnce() {
float b[3];
if (preferences.getUInt("magic", 0) == CALIBRATION_MAGIC &&
preferences.getBytesLength("bias") == sizeof(b)) {
preferences.getBytes("bias", b, sizeof(b));
biasX = b[0]; biasY = b[1]; biasZ = b[2];
Serial.println("Gyro calibration loaded");
return;
}
Serial.println("Keep ATOM still: calibrating for 5 seconds...");
double sx = 0, sy = 0, sz = 0;
for (int i = 0; i < 1000; ++i) {
float x, y, z;
M5.IMU.getGyroData(&x, &y, &z);
sx += x; sy += y; sz += z;
delay(5);
}
biasX = sx / 1000.0; biasY = sy / 1000.0; biasZ = sz / 1000.0;
b[0] = biasX; b[1] = biasY; b[2] = biasZ;
preferences.putBytes("bias", b, sizeof(b));
preferences.putUInt("magic", CALIBRATION_MAGIC);
Serial.println("Gyro calibration saved");
}
void updateFusion(float gx, float gy, float gz, float ax, float ay, float az, float dt) {
// ESP32-S3版と同じMahony系処理:加速度LPFと信頼度重み付けを使用する。
static float axf = 0.0f, ayf = 0.0f, azf = 0.0f;
const float alpha = 0.10f;
axf += alpha * (ax - axf);
ayf += alpha * (ay - ayf);
azf += alpha * (az - azf);
float n = sqrtf(axf * axf + ayf * ayf + azf * azf);
if (n < 0.01f) return;
const float accError = fabsf(n - 1.0f);
float accWeight = 1.0f - (accError - 0.12f) / 0.13f;
accWeight = constrain(accWeight, 0.0f, 1.0f);
ax = axf / n; ay = ayf / n; az = azf / n;
float vx = 2 * (qx * qz - qw * qy);
float vy = 2 * (qw * qx + qy * qz);
float vz = qw * qw - qx * qx - qy * qy + qz * qz;
// Mahony型の加速度補正。加速度からヨー(Z軸)は補正しない。
float ex = ay * vz - az * vy;
float ey = az * vx - ax * vz;
gx += 3.8f * accWeight * ex;
gy += 9.0f * accWeight * ey;
float h = 0.5f * dt;
float dw = (-qx * gx - qy * gy - qz * gz) * h;
float dx = ( qw * gx + qy * gz - qz * gy) * h;
float dy = ( qw * gy - qx * gz + qz * gx) * h;
float dz = ( qw * gz + qx * gy - qy * gx) * h;
qw += dw; qx += dx; qy += dy; qz += dz;
n = sqrtf(qw*qw + qx*qx + qy*qy + qz*qz);
qw /= n; qx /= n; qy /= n; qz /= n;
}
int16_t q14(float v) { return (int16_t)lroundf(constrain(v, -1.0f, 1.0f) * 16384.0f); }
void setup() {
M5.begin(true, true, true);
M5.IMU.Init();
Wire1.setClock(400000UL);
M5.IMU.SetGyroFsr(MPU6886::GFS_250DPS);
M5.IMU.SetAccelFsr(MPU6886::AFS_2G);
preferences.begin("atom-imu", false);
calibrateOnce();
BLEDevice::init(DEVICE_NAME);
BLEServer *server = BLEDevice::createServer();
server->setCallbacks(new ServerCallbacks());
BLEService *service = server->createService(SERVICE_UUID);
dataCharacteristic = service->createCharacteristic(DATA_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
dataCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic *command = service->createCharacteristic(COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
command->setCallbacks(new CommandCallbacks());
service->start();
BLEAdvertising *advertising = BLEDevice::getAdvertising();
advertising->addServiceUUID(SERVICE_UUID);
advertising->setScanResponse(true);
BLEDevice::startAdvertising();
}
void loop() {
M5.update();
if (startRequested) { startRequested = false; streaming = true; }
if (yawResetRequested) {
yawResetRequested = false;
yawOffset = atan2f(2 * (qw*qz + qx*qy), 1 - 2 * (qy*qy + qz*qz));
updateYawRotation();
}
if (!connected || !streaming) { delay(1); return; }
static uint32_t nextUs = 0, lastBleUs = 0;
uint32_t now = micros();
if (!nextUs) nextUs = now;
if ((int32_t)(now - nextUs) < 0) return;
nextUs += SAMPLE_INTERVAL_US;
if ((int32_t)(now - nextUs) > 50000) nextUs = now;
static uint32_t previousUs = 0;
float dt = previousUs ? (now - previousUs) * 1e-6f : SAMPLE_INTERVAL_US * 1e-6f;
previousUs = now;
if (dt <= 0.0f || dt > 0.02f) dt = SAMPLE_INTERVAL_US * 1e-6f;
float ax, ay, az, gx, gy, gz;
if (!readImuFast(ax, ay, az, gx, gy, gz)) return;
updateFusion((gx-biasX)*DEG_TO_RAD, (gy-biasY)*DEG_TO_RAD, (gz-biasZ)*DEG_TO_RAD, ax, ay, az, dt);
now = micros();
if ((uint32_t)(now - lastBleUs) < BLE_INTERVAL_US) return;
lastBleUs = now;
const float cw = yawCW, sz = yawSZ;
const float w = cw*qw - sz*qz, x = cw*qx + sz*qy;
const float y = cw*qy - sz*qx, z = cw*qz + sz*qw;
uint8_t packet[14] = {'$', 0x02, 0,0,0,0,0,0,0,0,0,0,'\r','\n'};
int16_t v[4] = {q14(w), q14(x), q14(y), q14(z)};
for (int i = 0; i < 4; ++i) { packet[2+i*2] = v[i] >> 8; packet[3+i*2] = v[i]; }
dataCharacteristic->setValue(packet, sizeof(packet));
dataCharacteristic->notify();
}