Sunday, October 2, 2016

Featherboard M0 and BMI160

Featherboard M0 and BMI160

BMI160 is the 6 axis MEMS inertial (3 axis Accelerometer + 3 axis Gyroscope) that is used for motion detection, gesture recognition, pedometer, orientation tracking, and so on. It has analog-to--digital converter inside and supports digital interface (SPI or I2C) to obtain the inertial data of gravity and/or acceleration as G and angular velocity as DPS (degree per second).
The driver code is available from here.
The breakout board is available from here.

Code

Since I am just using Arduino IDE for the test, I downloaded the driver code from the above link and renamed bmi160.c as bmi160.ino.
I did not add the driver file to the Arduino library folder this time.
My project folder includes bmi160-test.ino, bmi160_support.h, bmi160.ino, and bmi160.h.
bmi160-test.ino is basically referring to the bmi160_support.c which is included in the sample driver files. The functions defined in the bmi160_support.h are defined in the bmi160-test.ino.
Now let's see the each key functions.

I2C write function is listed below.
s8 bmi160_i2c_bus_write(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
  s32 ierror = BMI160_INIT_VALUE;
  Wire.beginTransmission(dev_addr);
  Wire.write((uint8_t)reg_addr);

  for (uint8_t i = 0; i < cnt; i++)
  {
       Wire.write(reg_data[i]);
  }
 
  Wire.endTransmission();
  return (s8)ierror;
}

I2C read functions is like this.
s8 bmi160_i2c_bus_read(u8 dev_addr, u8 reg_addr, u8 *reg_data, u8 cnt)
{
  s32 ierror = BMI160_INIT_VALUE;

  Wire.beginTransmission(dev_addr);
  Wire.write((uint8_t)reg_addr);
  Wire.endTransmission();
  Wire.requestFrom(dev_addr, (byte)cnt);
  for (uint8_t i = 0; i < cnt; i++)
  {
      reg_data[i] = Wire.read();
  }
  return (s8)ierror;
}

The above 2 functions are referred by the "i2c_routine" function and make sure the function name is same as the one referred from this function.

Next is the setup function.
void setup()
{
  Serial.begin(115200);
  Wire.begin();
  while(!Serial);

  bmi160_read_reg(BMI160_USER_CHIP_ID__REG, &id, BMI160_GEN_READ_WRITE_DATA_LENGTH);
  Serial.print("Device ID: ");
  Serial.println(id, HEX);
  delay(1000);
  Serial.println("Start Initialization");
  bmi160_initialize_sensor();
  Serial.println("End Initialization");
}
It is very useful to add "while(!Serial)" before you print out any debug comment. This sentence waits until the serial terminal is connected so you will not miss the debug messages.
The function called "bmi160_initialize_sensor()" is the same as the one defined in the original "bmi160_support.c" file and you can just modify the following line depending on what mode you want to use.

com_rslt += bmi160_config_running_mode(STANDARD_UI_IMU);
 In my case, I am using "STANDARD_UI_IMU" mode and as you can see the comments in the original "bmi160_support.c" file, the following modes are supported in this code.
 *      Description                                                     |  value
 * ----------------------------------------------------------|----------
 *  STANDARD_UI_9DOF_FIFO                         |   0
 *  STANDARD_UI_IMU_FIFO                           |   1
 *  STANDARD_UI_IMU                                      |   2
 *  STANDARD_UI_ADVANCEPOWERSAVE   |   3
 *  ACCEL_PEDOMETER                                     |   4
 *  APPLICATION_HEAD_TRACKING              |   5
 *  APPLICATION_NAVIGATION                        |   6
 *  APPLICATION_REMOTE_CONTROL           |   7
 *  APPLICATION_INDOOR_NAVIGATION       |   8
Next is the loop function.
void loop()
{
  // If data ready bit set, all data registers have new data
  bmi160_read_reg(0x1B, &dr, BMI160_GEN_READ_WRITE_DATA_LENGTH);
  if(dr & 0xC0) {  // check if data ready interrupt  
    bmi160_read_gyro_xyz(&gxyz);
    bmi160_read_accel_xyz(&axyz);
    Now = micros();
  }

    gx = (float)gxyz.x/GYRO_SENS;
    gy = (float)gxyz.y/GYRO_SENS;
    gz = (float)gxyz.z/GYRO_SENS;
    ax = (float)axyz.x/ACC_SENS;
    ay = (float)axyz.y/ACC_SENS;
    az = (float)axyz.z/ACC_SENS;
    deltat = ((Now - lastUpdate)/1000000.0f);
    lastUpdate = Now;
 
    Serial.print("ax = "); Serial.print(ax);
    Serial.print(" ay = "); Serial.print(ay);
    Serial.print(" az = "); Serial.print(az); Serial.println(" g");
    Serial.print("gx = "); Serial.print( gx, 2);
    Serial.print(" gy = "); Serial.print( gy, 2);
    Serial.print(" gz = "); Serial.print( gz, 2); Serial.println(" deg/s");
               
    Serial.print("rate = "); Serial.print((float)1/deltat, 2); Serial.println(" Hz");
    Serial.println("");
}
GYRO_SENS and ACC_SENS are the sensitivity value of Gyroscope and Accelerometer respectively. I am using the default register settings for those 2 parameters, so simply referring to the datasheet and defined the values as below.
ACC_SENS = 16384 (LSB/g)
GYRO_SENS = 16.4 (LSB/dps)
 By dividing the raw data of each sensor values, you can convert unit from the ADC counts to g or dps units.

Test

There are some bias/offset on both Gyroscope and Accelerometer axis. This will cause errors if you need to calculate orientation from the raw values. Therefore, it is required to do the calibration to cancel the initial bias values. Next step is to implement this to see how the output looks like.



                 

No comments:

Post a Comment