jrowberg / i2cdevlib

I2C device library collection for AVR/Arduino or other C++-based MCUs
http://www.i2cdevlib.com
3.95k stars 7.51k forks source link

MPU6050 Tap feature #703

Open kanimaru opened 2 years ago

kanimaru commented 2 years ago

Hi guys what have to be done to get the Tap feature that the datasheet is promoting? Probably another version of dmpMemory but where you get it in the first place? And then I'm confused what to do next? Shouldn't be there a documentation how the DMP communicate these information?

My use case: I have an ESP32 connected to GY512 as a portable device. To save power I just want to activate the device when it get tapped. If there is a better Idea it would be also OK otherwise how the heck this works :).

Much thanks in advance.

Tristan-Day commented 2 years ago

Hi Kanimaru,

I am also looking to get tap detection working. The MPU6050 does support it according to the spec but this library doesn't have that support yet. Did you manage to find any alternatives?

kanimaru commented 2 years ago

Was looking for other modules but didn't find any and currently I have no time for that anymore. Sorry.

asuslennikov commented 9 months ago

You know, although this library is good, it's not the only one, which supports MPU6050. The official driver available almost for 10 years now. You can download it yourself from manufacture's website: https://invensense.tdk.com/developers/software-downloads/#mpu-en

Also, there is several repos you can check as an example:

The SparkFun library already has tap example: https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/examples/MPU9250_DMP_Tap/MPU9250_DMP_Tap.ino. You only need to define the correct sensor version (#define MPU6050)

If for some reasons, you highly coupled with this library, it is possible to reproduce the same behavior here (mix MPU6050_DMP6_using_DMP_V6.12 with SpurkFun example).

const int16_t mpuAddress = 0x68;  //It can be 0x68 or 0x69
MPU6050 mpu(mpuAddress);
bool dmpReady = false;               // set true if DMP init was successful
uint8_t fifoBuffer[64];              // FIFO storage buffer
volatile bool mpuInterrupt = false;  // indicates whether MPU interrupt pin has gone high

void dmpDataReady() {
  mpuInterrupt = true;
}

void enableMpuInterrupts() {
  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
}

void mpuWriteMemory(unsigned short memAddress, unsigned short length, unsigned char *data) {
  mpu.writeMemoryBlock(data, length, memAddress >> 8, memAddress & 0xFF);
}

void setTwoByteValue(int value, unsigned char *data) {
  data[0] = value >> 8;
  data[1] = value & 0xFF;
}

void configureTaps() {
  // Array to store register values
  unsigned char tmp[4];

  // Add taps to FIFO buffer (i.e. DMP output)
  // see dmp_enable_feature method
  // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L1005
  tmp[0] = 0x20;
  mpuWriteMemory(2742, 1, tmp);  // CFG_27

  // Enable taps
  // see dmp_enable_feature method
  // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L1030
  tmp[0] = 0xF8;
  mpuWriteMemory(2224, 1, tmp);  // CFG_20

  // set FIFO rate
  // see dmp_set_fifo_rate method
  // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L660
  setTwoByteValue((DMP_SAMPLE_RATE / 10 - 1), tmp);
  mpuWriteMemory(534, 2, tmp);  // D_0_22
  const unsigned char regs_end[12] = { 0xfe, 0xf2, 0xab, 0xc4, 0xaa, 0xf1, 0xdf, 0xdf, 0xBB, 0xAF, 0xdf, 0xdf };
  mpuWriteMemory(1106, 12, (unsigned char *)regs_end);  // CFG_6

  // Set threshold (0-1600) for each axis, for example 30
  // see dmp_set_tap_thresh method
  // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L727
  // Divide threshold by sample rate (200)
  // And then multuply by current accelerometer LSB (16384, 8192, 4096, 2048)
  // For some reason also save 0.75 value (12288, 6144, 3072, 1536)
  // X-Axis
  setTwoByteValue((30 * 16384 / DMP_SAMPLE_RATE), tmp);
  mpuWriteMemory(468, 2, tmp);  // DMP_TAP_THX
  setTwoByteValue((30 * 12288 / DMP_SAMPLE_RATE), tmp);
  mpuWriteMemory(292, 2, tmp);  // D_1_36
  // Y-Axis
  setTwoByteValue((30 * 16384 / DMP_SAMPLE_RATE), tmp);
  mpuWriteMemory(472, 2, tmp);  // DMP_TAP_THY
  setTwoByteValue((30 * 12288 / DMP_SAMPLE_RATE), tmp);
  mpuWriteMemory(296, 2, tmp);  // D_1_40
  // Z-Axis
  setTwoByteValue((30 * 16384 / DMP_SAMPLE_RATE), tmp);
  mpuWriteMemory(476, 2, tmp);  // DMP_TAP_THZ
  setTwoByteValue((30 * 12288 / DMP_SAMPLE_RATE), tmp);
  mpuWriteMemory(300, 2, tmp);  // D_1_44

  // Set for which axes detect taps X | Y | Z
  // see dmp_set_tap_axes method
  // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L762
  tmp[0] = 0x30 | 0x0C | 0x03;
  mpuWriteMemory(328, 1, tmp);  // D_1_72

  // Set minimal tap count (1-4), for example 1
  // see dmp_set_tap_count method
  // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L780
  tmp[0] = 0;                   // (add -1 to the value)
  mpuWriteMemory(335, 1, tmp);  // D_1_79

  // Set length between valid taps, for example 100 ms
  // see dmp_set_tap_time method
  // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L796
  setTwoByteValue((100 * DMP_SAMPLE_RATE / 1000), tmp);
  mpuWriteMemory(478, 2, tmp);  // DMP_TAPW_MIN

  // Set max time between taps to register as a multi-tap, for example 800 ms
  // see dmp_set_tap_time_multi method
  // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L812
  setTwoByteValue((800 * DMP_SAMPLE_RATE / 1000), tmp);
  mpuWriteMemory(474, 2, tmp);  // D_1_218

  // Set shake rejection threshold. If the DMP detects a gyro sample larger than @e thresh, taps are rejected, for example 190
  // see dmp_set_shake_reject_thresh method
  // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L830
  long thresh = 190 * 46850825LL * 200 / DMP_SAMPLE_RATE / 1000;
  tmp[0] = (unsigned char)(((long)thresh >> 24) & 0xFF);
  tmp[1] = (unsigned char)(((long)thresh >> 16) & 0xFF);
  tmp[2] = (unsigned char)(((long)thresh >> 8) & 0xFF);
  tmp[3] = (unsigned char)((long)thresh & 0xFF);
  mpuWriteMemory(348, 4, tmp);  // D_1_92

  // Set shake rejection time, for example 40 ms
  // see dmp_set_shake_reject_time method
  // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L848
  setTwoByteValue((40 / (1000 / DMP_SAMPLE_RATE)), tmp);
  mpuWriteMemory(346, 2, tmp);  // D_1_90

  // Set shake rejection timeout. For example 10 ms
  // see dmp_set_shake_reject_timeout method
  // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L866
  setTwoByteValue((10 / (1000 / DMP_SAMPLE_RATE)), tmp);
  mpuWriteMemory(344, 2, tmp);  // D_1_88
}

void setup() {
  Serial.begin(115200);
  Wire.begin();
  Wire.setClock(400000);  // 400kHz I2C clock. Comment this line if having compilation difficulties

  Serial.println(F("Initializing I2C devices..."));
  mpu.initialize();
  mpu.setInterruptMode(true);
  mpu.setIntMotionEnabled(true);

  pinMode(INTERRUPT_PIN, INPUT);

  // verify connection
  Serial.println(F("Testing device connections..."));
  Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));

  // wait for ready
  Serial.println(F("\nSend any character to begin DMP programming and demo: "));
  while (Serial.available() && Serial.read())
    ;  // empty buffer
  while (!Serial.available())
    ;  // wait for data
  while (Serial.available() && Serial.read())
    ;  // empty buffer again

  // load and configure the DMP
  Serial.println(F("Initializing DMP..."));
  int8_t devStatus = mpu.dmpInitialize();

  // supply your own gyro offsets here, scaled for min sensitivity
  // *** here ***
  // make sure it worked (returns 0 if so)
  if (devStatus == 0) {
    mpu.CalibrateAccel(6);
    mpu.CalibrateGyro(6);
    Serial.println();
    mpu.PrintActiveOffsets();

    // turn on the DMP, now that it's ready
    Serial.println(F("Enabling DMP..."));
    mpu.setDMPEnabled(true);
    configureTaps();
    enableMpuInterrupts(); 

    // set our DMP Ready flag so the main loop() function knows it's okay to use it
    Serial.println(F("DMP ready! Waiting for first interrupt..."));
    dmpReady = true;
  } else {
    // ERROR!
    // 1 = initial memory load failed
    // 2 = DMP configuration updates failed
    // (if it's going to break, usually the code will be 1)
    Serial.print(F("DMP Initialization failed (code "));
    Serial.print(devStatus);
    Serial.println(F(")"));
  }
  Serial.println(F("Setup finished"));
}

void loop() {
  // if programming failed, don't try to do anything
  if (!dmpReady) return;
  // read a packet from FIFO
  if (mpuInterrupt) {
    if (mpu.dmpPacketAvailable() && mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) {
      if (fifoBuffer[29] & 0x01) {
        unsigned char tap = 0x3F & fifoBuffer[31];
        unsigned char direction, count;
        direction = tap >> 3;
        count = (tap % 8) + 1;
        switch (direction) {
          case TAP_X_UP:
            Serial.print("Tap X+ ");
            break;
          case TAP_X_DOWN:
            Serial.print("Tap X- ");
            break;
          case TAP_Y_UP:
            Serial.print("Tap Y+ ");
            break;
          case TAP_Y_DOWN:
            Serial.print("Tap Y- ");
            break;
          case TAP_Z_UP:
            Serial.print("Tap Z+ ");
            break;
          case TAP_Z_DOWN:
            Serial.print("Tap Z- ");
            break;
        }
        Serial.println(count);
      }
    }
    mpuInterrupt = false;
  }
}

Remember, that you also need to update FIFO size in library (MPU6050_6Axis_MotionApps612.cpp):

/*
    dmpPacketSize += 16;//DMP_FEATURE_6X_LP_QUAT
    dmpPacketSize += 6;//DMP_FEATURE_SEND_RAW_ACCEL
    dmpPacketSize += 6;//DMP_FEATURE_SEND_RAW_GYRO
    dmpPacketSize += 4;//DMP_FEATURE_TAP
*/
    dmpPacketSize = 32;

It's a bit low-level, but works just fine.

You also can try to modify dmpMemory:

#define DMP_SAMPLE_RATE (200)       // https://github.com/sparkfun/SparkFun_MPU-9250-DMP_Arduino_Library/blob/master/src/util/inv_mpu_dmp_motion_driver.c#L443
#define MPU6050_DMP_CODE_SIZE 3062  // dmpMemory[]

unsigned char dmpMemory[MPU6050_DMP_CODE_SIZE] = {
// copy content form the library
}

void printDmpMemory() {
  static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
  Serial.print(F("const unsigned char dmpMemory[MPU6050_DMP_CODE_SIZE] PROGMEM = {"));
  for (int i = 0; i <= MPU6050_DMP_CODE_SIZE - 1; i++) {
    if (i % 16 == 0) {
      Serial.println(F(""));
    } else {
      Serial.print(F(" "));
    }
    if (i % 256 == 0) {
      Serial.print(F("/* bank # "));
      Serial.print(i / 256);
      Serial.println(F(" */"));
    }
    Serial.print(F("0x"));
    Serial.print(hex[dmpMemory[i] >> 4]);
    Serial.print(hex[dmpMemory[i] & 0x0F]);
    if (i < MPU6050_DMP_CODE_SIZE - 1) {
      Serial.print(F(","));
    }
  }
  Serial.println(F(""));
  Serial.println(F("};"));
}

void mpuWriteMemory(unsigned short memAddress, unsigned short length, unsigned char *data) {
  // mpu.writeMemoryBlock(data, length, memAddress >> 8, memAddress & 0xFF);
  // instead of actual write to the sensor - just update the array
  for (int i = 0; i <= length; i++) {
    dmpMemory[i + memAddress] = data[i];
  }
}

void configureTaps() {
   // see above
}

void setup() {
  Serial.begin(115200);
  configureTaps();
  printDmpMemory();
}

void loop() {
  // do nothing
}

But I didn't test it. You can produce the dmpMemory output on simulator (), for example https://wokwi.com/projects/new/esp32 (esp32 just for memory requirements)