/**
 @file FaBo9Axis_MPU9250.cpp
 @brief This is a library for the FaBo 9Axis I2C Brick.

   http://fabo.io/202.html

   Released under APACHE LICENSE, VERSION 2.0

   http://www.apache.org/licenses/

 @author FaBo<info@fabo.io>
 */

 #include "FaBo9Axis_MPU9250.h"

/**
 @brief Constructor
 @param [in] addr MPU-9250 I2C slave address
 */
 FaBo9Axis::FaBo9Axis(uint8_t addr) {
  _mpu9250addr = addr;
  Wire.begin();
}

/**
 @brief Begin Device
 @retval true normaly done
 @retval false device error
 */
 bool FaBo9Axis::begin() {
  if (searchDevice()) {
    configMPU9250();
    configAK8963();
    return true;
    } else {
      return false;
    }
  }

/**
 @brief Search Device
 @retval true device connected
 @retval false device error
 */
 bool FaBo9Axis::searchDevice() {
  uint8_t whoami;
  readI2c(_mpu9250addr, MPU9250_WHO_AM_I, 1, &whoami);
  if(whoami == 0x71){
    return true;
    } else{
      return false;
    }
  }

/**
 @brief Configure MPU-9250
 @param [in] gfs Gyro Full Scale Select(default:MPU9250_GFS_250[+250dps])
 @param [in] afs Accel Full Scale Select(default:MPU9250_AFS_2G[2g])
 */
 void FaBo9Axis::configMPU9250(uint8_t gfs, uint8_t afs) {
  switch(gfs) {
    case MPU9250_GFS_250:
    _gres = 250.0/32768.0;
    break;
    case MPU9250_GFS_500:
    _gres = 500.0/32768.0;
    break;
    case MPU9250_GFS_1000:
    _gres = 1000.0/32768.0;
    break;
    case MPU9250_GFS_2000:
    _gres = 2000.0/32768.0;
    break;
  }
  switch(afs) {
    case MPU9250_AFS_2G:
    _ares = 2.0/32768.0;
    break;
    case MPU9250_AFS_4G:
    _ares = 4.0/32768.0;
    break;
    case MPU9250_AFS_8G:
    _ares = 8.0/32768.0;
    break;
    case MPU9250_AFS_16G:
    _ares = 16.0/32768.0;
    break;
  }
  // sleep off
  writeI2c(_mpu9250addr, MPU9250_PWR_MGMT_1, 0x00);
  delay(100);
  // auto select clock source
  writeI2c(_mpu9250addr, MPU9250_PWR_MGMT_1, 0x01);
  delay(200);
  // DLPF_CFG
  writeI2c(_mpu9250addr, MPU9250_CONFIG, 0x03);
  // sample rate divider
  writeI2c(_mpu9250addr, MPU9250_SMPLRT_DIV, 0x04);
  // gyro full scale select
  writeI2c(_mpu9250addr, MPU9250_GYRO_CONFIG, gfs << 3);
  // accel full scale select
  writeI2c(_mpu9250addr, MPU9250_ACCEL_CONFIG, afs << 3);
  // A_DLPFCFG
  writeI2c(_mpu9250addr, MPU9250_ACCEL_CONFIG_2, 0x03);
  // BYPASS_EN
  writeI2c(_mpu9250addr, MPU9250_INT_PIN_CFG, 0x02);
  delay(100);
}

void FaBo9Axis::dumpConfig() {
  uint8_t c;
  readI2c(_mpu9250addr, MPU9250_GYRO_CONFIG, 1, &c);
  Serial.println(c,HEX);
  readI2c(_mpu9250addr, MPU9250_ACCEL_CONFIG, 1, &c);
  Serial.println(c,HEX);
  readI2c(_mpu9250addr, MPU9250_ACCEL_CONFIG_2, 1, &c);
  Serial.println(c,HEX);
  readI2c(AK8963_SLAVE_ADDRESS, AK8963_CNTL1, 1, &c);
  Serial.println(c,HEX);
}

/**
 @brief Configure AK8963
 @param [in] mode Magneto Mode Select(default:AK8963_MODE_C8HZ[Continous 8Hz])
 @param [in] mfs Magneto Scale Select(default:AK8963_BIT_16[16bit])
 */
 void FaBo9Axis::configAK8963(uint8_t mode, uint8_t mfs) {
  uint8_t data[3];

  switch(mfs) {
    case AK8963_BIT_14:
    _mres = 4912.0/8190.0;
    break;
    case AK8963_BIT_16:
    _mres = 4912.0/32760.0;
    break;
  }

  // set software reset
//   writeI2c(AK8963_SLAVE_ADDRESS, AK8963_CNTL2, 0x01);
//   delay(100);
  // set power down mode
  writeI2c(AK8963_SLAVE_ADDRESS, AK8963_CNTL1, 0x00);
  delay(1);
  // set read FuseROM mode
  writeI2c(AK8963_SLAVE_ADDRESS, AK8963_CNTL1, 0x0F);
  delay(1);
  // read coef data
  readI2c(AK8963_SLAVE_ADDRESS, AK8963_ASAX, 3, data);
  _magXcoef = (float)(data[0] - 128) / 256.0 + 1.0;
  _magYcoef = (float)(data[1] - 128) / 256.0 + 1.0;
  _magZcoef = (float)(data[2] - 128) / 256.0 + 1.0;
  // set power down mode
  writeI2c(AK8963_SLAVE_ADDRESS, AK8963_CNTL1, 0x00);
  delay(1);
  // set scale&continous mode
  writeI2c(AK8963_SLAVE_ADDRESS, AK8963_CNTL1, (mfs<<4|mode));
  delay(1);
}

/**
 @brief Check data ready
 @retval true data is ready
 @retval false data is not ready
 */
 bool FaBo9Axis::checkDataReady() {
  uint8_t drdy;
  readI2c(_mpu9250addr, MPU9250_INT_STATUS, 1, &drdy);
  if ( drdy & 0x01 ) {
    return true;
    } else {
      return false;
    }
  }

/**
 @brief Read accelerometer
 @param [out] ax X-accel(g)
 @param [out] ay Y-accel(g)
 @param [out] az Z-accel(g)
 */
 void FaBo9Axis::readAccelXYZ(float * ax, float * ay, float * az) {
  uint8_t data[6];
  int16_t axc, ayc, azc;
  readI2c(_mpu9250addr, MPU9250_ACCEL_XOUT_H, 6, data);
  axc = ((int16_t)data[0] << 8) | data[1];
  ayc = ((int16_t)data[2] << 8) | data[3];
  azc = ((int16_t)data[4] << 8) | data[5];
  *ax = (float)axc * _ares;
  *ay = (float)ayc * _ares;
  *az = (float)azc * _ares;
}
float FaBo9Axis::readAccelX() {
  uint8_t data[6];
  int16_t axc, ayc, azc;
  readI2c(_mpu9250addr, MPU9250_ACCEL_XOUT_H, 6, data);
  axc = ((int16_t)data[0] << 8) | data[1]; 
  float ax = (float)axc * _ares;
  return ax;
}
float FaBo9Axis::readAccelY() {
  uint8_t data[6];
  int16_t axc, ayc, azc;
  readI2c(_mpu9250addr, MPU9250_ACCEL_XOUT_H, 6, data);
  ayc = ((int16_t)data[2] << 8) | data[3];
  float ay = (float)ayc * _ares;
  return ay;
}
float FaBo9Axis::readAccelZ() {
  uint8_t data[6];
  int16_t axc, ayc, azc;
  readI2c(_mpu9250addr, MPU9250_ACCEL_XOUT_H, 6, data);
  azc = ((int16_t)data[4] << 8) | data[5];
  float az = (float)azc * _ares;
  return az;
}
/**
 @brief Read gyro
 @param [out] gx X-gyro(degrees/sec)
 @param [out] gy Y-gyro(degrees/sec)
 @param [out] gz Z-gyro(degrees/sec)
 */
 void FaBo9Axis::readGyroXYZ(float * gx, float * gy, float * gz) {
  uint8_t data[6];
  int16_t gxc, gyc, gzc;
  readI2c(_mpu9250addr, MPU9250_GYRO_XOUT_H, 6, data);
  gxc = ((int16_t)data[0] << 8) | data[1];
  gyc = ((int16_t)data[2] << 8) | data[3];
  gzc = ((int16_t)data[4] << 8) | data[5];
  *gx = (float)gxc * _gres;
  *gy = (float)gyc * _gres;
  *gz = (float)gzc * _gres;
}
float FaBo9Axis::readGyroX() {
  uint8_t data[6];
  int16_t gxc, gyc, gzc;
  readI2c(_mpu9250addr, MPU9250_GYRO_XOUT_H, 6, data);
  gxc = ((int16_t)data[0] << 8) | data[1];
  float gx = (float)gxc * _gres;
  return gx;
}
float FaBo9Axis::readGyroY() {
  uint8_t data[6];
  int16_t gxc, gyc, gzc;
  readI2c(_mpu9250addr, MPU9250_GYRO_XOUT_H, 6, data);
  gyc = ((int16_t)data[2] << 8) | data[3];
  float gy = (float)gyc * _gres;
  return gy;
}
float FaBo9Axis::readGyroZ() {
  uint8_t data[6];
  int16_t gxc, gyc, gzc;
  readI2c(_mpu9250addr, MPU9250_GYRO_XOUT_H, 6, data);
  gzc = ((int16_t)data[4] << 8) | data[5];
  float gz = (float)gzc * _gres;
  return gz;
}
/**
 @brief Read magneto
 @param [out] mx X-magneto(uT)
 @param [out] my Y-magneto(uT)
 @param [out] mz Z-magneto(uT)
 */
 void FaBo9Axis::readMagnetXYZ(float * mx, float * my, float * mz) {
  uint8_t data[7];
  uint8_t drdy;
  int16_t mxc, myc, mzc;
  readI2c(AK8963_SLAVE_ADDRESS, AK8963_ST1, 1, &drdy);
  if ( drdy & 0x01 ) { // check data ready
    readI2c(AK8963_SLAVE_ADDRESS, AK8963_HXL, 7, data);
    if ( !(data[6] & 0x08) ) { // check overflow
      mxc = ((int16_t)data[1] << 8) | data[0];
      myc = ((int16_t)data[3] << 8) | data[2];
      mzc = ((int16_t)data[5] << 8) | data[4];
      *mx = (float)mxc * _mres * _magXcoef;
      *my = (float)myc * _mres * _magYcoef;
      *mz = (float)mzc * _mres * _magZcoef;
    }
  }
}
float FaBo9Axis::readMagnetX() {
  uint8_t data[7];
  uint8_t drdy;
  int16_t mxc, myc, mzc;
  float mx;
  readI2c(AK8963_SLAVE_ADDRESS, AK8963_ST1, 1, &drdy);
  if ( drdy & 0x01 ) { // check data ready
    readI2c(AK8963_SLAVE_ADDRESS, AK8963_HXL, 7, data);
    if ( !(data[6] & 0x08) ) { // check overflow
      mxc = ((int16_t)data[1] << 8) | data[0];
      mx = (float)mxc * _mres * _magXcoef;
    }
  }
  return mx;
}
float FaBo9Axis::readMagnetY() {
  uint8_t data[7];
  uint8_t drdy;
  float my ;
  int16_t mxc, myc, mzc;
  readI2c(AK8963_SLAVE_ADDRESS, AK8963_ST1, 1, &drdy);
  if ( drdy & 0x01 ) { // check data ready
    readI2c(AK8963_SLAVE_ADDRESS, AK8963_HXL, 7, data);
    if ( !(data[6] & 0x08) ) { // check overflow
     myc = ((int16_t)data[3] << 8) | data[2];
     my = (float)myc * _mres * _magYcoef;
   }
 }
 return my;
}

float FaBo9Axis::readMagnetZ() {
  uint8_t data[7];
  uint8_t drdy;
  float mz ;
  int16_t mxc, myc, mzc;
  readI2c(AK8963_SLAVE_ADDRESS, AK8963_ST1, 1, &drdy);
  if ( drdy & 0x01 ) { // check data ready
    readI2c(AK8963_SLAVE_ADDRESS, AK8963_HXL, 7, data);
    if ( !(data[6] & 0x08) ) { // check overflow
     mzc = ((int16_t)data[5] << 8) | data[4];
     mz = (float)mzc * _mres * _magZcoef;
   }
 }
 return mz;
}
/**
 @brief Read temperature
 @param [out] temperature temperature(degrees C)
 */
 void FaBo9Axis::readTemperature(float * temperature) {
  uint8_t data[2];
  int16_t tmc;
  readI2c(_mpu9250addr, MPU9250_TEMP_OUT_H, 2, data);
  tmc = ((int16_t)data[0] << 8) | data[1];
  *temperature = ((float)tmc) / 333.87 + 21.0;
}

/////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 @brief Write I2C
 @param [in] address I2C slave address
 @param [in] register_addr register address
 @param [in] data write data
 */
 void FaBo9Axis::writeI2c(uint8_t address, uint8_t register_addr, uint8_t data) {
  Wire.beginTransmission(address);
  Wire.write(register_addr);
  Wire.write(data);
  Wire.endTransmission();
}

/**
 @brief Read I2C
 @param [in] address I2C slave address
 @param [in] register_addr register address
 @param [in] num read length
 @param [out] buffer read data
 */
 void FaBo9Axis::readI2c(uint8_t address, uint8_t register_addr, uint8_t num, uint8_t * buffer) {
  Wire.beginTransmission(address);
  Wire.write(register_addr);
  Wire.endTransmission();
  uint8_t i = 0;
  Wire.requestFrom(address, num);
  while( Wire.available() ) {
    buffer[i++] = Wire.read();
  }
}

