Interfacing MMA8453Q With Arduino

MMA8453Q is a rather inexpensive accelerometer. It is significantly cheaper than many other 3-axis accelerometers (such as the popular LIS3LV02DL) and yet it offers a reasonably high 10 bits resolution and packs a rich set of features that simplifies designs and programming in many different applications.

In this post, I will show you how to interface it with an ATmega328 MCU using Arduino libraries. Because the operating voltage range for MMA8453Q is between 1.95V and 3.6V, you will need to either power your MCU using 3.3V or use an I2C voltage level translator (such as TI’s PCA9517 Level-Translating I2C Bus Repeater) so that MMA8453Q can be powered within its specified voltage range.

MMA8453Q uses the I2C protocol, so I assumed that I could use the default Arduino Wire library. Unfortunately, MMA8453Q uses repeated start condition for the read operations and the default Wire library cannot handle without any modifications. As a result of this, it does not matter which register you try to read from, the returned value is always the content of the status register (at address 0x00) when using the Wire library due to the reset between the write of the register address and the subsequent read. Fortunately, there are alternative I2C libraries available for Arduino that address this issue. And the I2C library I used below is from DSSCircuits.

I mounted my MMA8453Q on a proto-board using my favorite hand-soldering method. And connected it to my customized Arduino experiment board and set the board voltage to 3.3V instead of the usual 5V.

MMA8453Q Breakout Board

MMA8453Q Breakout Board

MMA8453Q I2C Setup

MMA8453Q I2C Setup

The following is some sample code. Note that I only provided a few basic functions and not all the register constants are used in the code. But all the remaining functions can be easily implemented using regRead/regWrite functions and the register definitions listed below:


#include <I2C.h>

const byte REG_STATUS = 0x00; //(R) Real time status
const byte REG_OUT_X_MSB = 0x01; //(R) [7:0] are 8 MSBs of 10-bit sample
const byte REG_OUT_X_LSB = 0x02; //(R) [7:6] are 2 LSBs of 10-bit sample
const byte REG_OUT_Y_MSB = 0x03; //(R) [7:0] are 8 MSBs of 10-bit sample
const byte REG_OUT_Y_LSB = 0x04; //(R) [7:6] are 2 LSBs of 10-bit sample
const byte REG_OUT_Z_MSB = 0x05; //(R) [7:0] are 8 MSBs of 10-bit sample
const byte REG_OUT_Z_LSB = 0x06; //(R) [7:6] are 2 LSBs of 10-bit sample
const byte REG_SYSMOD = 0x0b; //(R) Current system mode
const byte REG_INT_SOURCE = 0x0c; //(R) Interrupt status
const byte REG_WHO_AM_I = 0x0d; //(R) Device ID (0x3A)
const byte REG_XYZ_DATA_CFG = 0xe; //(R/W) Dynamic range settings
const byte REG_HP_FILTER_CUTOFF = 0x0f; //(R/W) cut-off frequency is set to 16Hz @ 800Hz
const byte REG_PL_STATUS = 0x10; //(R) Landscape/Portrait orientation status
const byte REG_PL_CFG = 0x11; //(R/W) Landscape/Portrait configuration
const byte REG_PL_COUNT = 0x12; //(R) Landscape/Portrait debounce counter
const byte REG_PL_BF_ZCOMP = 0x13; //(R) Back-Front, Z-Lock trip threshold
const byte REG_P_L_THS_REG = 0x14; //(R/W) Portrait to Landscape trip angle is 29 degree
const byte REG_FF_MT_CFG = 0x15; //(R/W) Freefall/motion functional block configuration
const byte REG_FF_MT_SRC = 0x16; //(R) Freefall/motion event source register
const byte REG_FF_MT_THS = 0x17; //(R/W) Freefall/motion threshold register
const byte REG_FF_MT_COUNT = 0x18; //(R/W) Freefall/motion debounce counter
const byte REG_TRANSIENT_CFG = 0x1d; //(R/W) Transient functional block configuration
const byte REG_TRANSIENT_SRC = 0x1e; //(R) Transient event status register
const byte REG_TRANSIENT_THS = 0x1f; //(R/W) Transient event threshold
const byte REG_TRANSIENT_COUNT = 0x20; //(R/W) Transient debounce counter
const byte REG_PULSE_CFG = 0x21; //(R/W) ELE, Double_XYZ or Single_XYZ
const byte REG_PULSE_SRC = 0x22; //(R) EA, Double_XYZ or Single_XYZ
const byte REG_PULSE_THSX = 0x23; //(R/W) X pulse threshold
const byte REG_PULSE_THSY = 0x24; //(R/W) Y pulse threshold
const byte REG_PULSE_THSZ = 0x25; //(R/W) Z pulse threshold
const byte REG_PULSE_TMLT = 0x26; //(R/W) Time limit for pulse
const byte REG_PULSE_LTCY = 0x27; //(R/W) Latency time for 2nd pulse
const byte REG_PULSE_WIND = 0x28; //(R/W) Window time for 2nd pulse
const byte REG_ASLP_COUNT = 0x29; //(R/W) Counter setting for auto-sleep
const byte REG_CTRL_REG1 = 0x2a; //(R/W) ODR = 800 Hz, STANDBY mode
const byte REG_CTRL_REG2 = 0x2b; //(R/W) Sleep enable, OS Modes, RST, ST
const byte REG_CTRL_REG3 = 0x2c; //(R/W) Wake from sleep, IPOL, PP_OD
const byte REG_CTRL_REG4 = 0x2d; //(R/W) Interrupt enable register
const byte REG_CTRL_REG5 = 0x2e; //(R/W) Interrupt pin (INT1/INT2) map
const byte REG_OFF_X = 0x2f; //(R/W) X-axis offset adjust
const byte REG_OFF_Y = 0x30; //(R/W) Y-axis offset adjust
const byte REG_OFF_Z = 0x31; //(R/W) Z-axis offset adjust

const byte FULL_SCALE_RANGE_2g = 0x0;
const byte FULL_SCALE_RANGE_4g = 0x1;
const byte FULL_SCALE_RANGE_8g = 0x2;

const byte I2C_ADDR = 0x1c; //SA0=0

/*
  Read register content into buffer.
  The default count is 1 byte. 
  
  The buffer needs to be pre-allocated
  if count > 1
*/
void regRead(byte reg, byte *buf, byte count = 1)
{
  I2c.write(I2C_ADDR, reg);  
  I2c.read(I2C_ADDR, reg, count);

  for (int i = 0; i < count; i++) 
    *(buf+i) = I2c.receive();
}

/*
  Write a byte value into a register
*/
void regWrite(byte reg, byte val)
{
  I2c.write(I2C_ADDR, reg, val);
}

/*
  Put MMA8453Q into standby mode
*/
void standbyMode()
{
  byte reg;
  byte activeMask = 0x01;

  regRead(REG_CTRL_REG1, &reg);
  regWrite(REG_CTRL_REG1, reg & ~activeMask);
}

/* 
  Put MMA8453Q into active mode
*/
void activeMode()
{
  byte reg;
  byte activeMask = 0x01;

  regRead(REG_CTRL_REG1, &reg);
  regWrite(REG_CTRL_REG1, reg | activeMask);
}

/*
  Use fast mode (low resolution mode)
  The acceleration readings will be
  limited to 8 bits in this mode.    
*/
void lowResMode()
{
  byte reg;
  byte fastModeMask = 0x02;

  regRead(REG_CTRL_REG1, &reg);
  regWrite(REG_CTRL_REG1, reg | fastModeMask); 
}

/*
  Use default mode (high resolution mode)
  The acceleration readings will be
  10 bits in this mode.    
*/
void hiResMode()
{
  byte reg;
  byte fastModeMask = 0x02;

  regRead(REG_CTRL_REG1, &reg);
  regWrite(REG_CTRL_REG1,  reg & ~fastModeMask);
}

/*
  Get accelerometer readings (x, y, z)
  by default, standard 10 bits mode is used.
  
  This function also convers 2's complement number to 
  signed integer result.
  
  If accelerometer is initialized to use low res mode,
  isHighRes must be passed in as false.
*/
void getAccXYZ(int *x, int *y, int *z, bool isHighRes=true)
{
  byte buf[6];

  if (isHighRes) {
    regRead(REG_OUT_X_MSB, buf, 6);
    *x = buf[0] << 2 | buf[1] >> 6 & 0x3;
    *y = buf[2] << 2 | buf[3] >> 6 & 0x3;
    *z = buf[4] << 2 | buf[5] >> 6 & 0x3;
  } 
  else {
    regRead(REG_OUT_X_MSB, buf, 3);
    *x = buf[0] << 2;
    *y = buf[1] << 2;
    *z = buf[2] << 2;
  }

  if (*x > 511) *x = *x - 1024;
  if (*y > 511) *y = *y - 1024 ;
  if (*z > 511) *z = *z - 1024;
}

void setup()
{
  I2c.begin(); 
  Serial.begin(9600);

  standbyMode(); //register settings must be made in standby mode
  regWrite(REG_XYZ_DATA_CFG, FULL_SCALE_RANGE_2g);
  hiResMode(); //this is the default setting and can be omitted.
  //lowResMode(); //set to low res (fast mode), must use getAccXYZ(,,,false) to retrieve readings.
  activeMode(); 
  
  byte b;
  regRead(REG_WHO_AM_I, &b);
  Serial.println(b,HEX);
}

void loop()
{
  int x = 0, y = 0, z = 0;

  getAccXYZ(&x, &y, &z); //get accelerometer readings in normal mode (hi res).
  //getAccXYZ(&x, &y, &z, false); //get accelerometer readings in fast mode (low res).
  Serial.print(x); 
  Serial.print(" ");
  Serial.print(y); 
  Serial.print(" ");
  Serial.print(z); 
  Serial.println();

  delay(500);
}

With some modification to the getAccXYZ function, similar code can be used with MMA8450Q, MMA8451Q and MMA8452Q as well.

Be Sociable, Share!

19 Comments

  1. Thanks for compiling this info! It was very helpful for me; I’m using the MMA8453Q as well, and your work helped save me a lot of time pouring over the datasheet. I see a lot of interesting articles in your blog and look forward to seeing more about your hobby work. :D

  2. […] Wong from kerrywong.com shows us how to interface to a MMA8453Q cheap accelerometer using an ATmega328 MCU. There are tons of cool projects out there that are just waiting to be designed using […]

  3. […] Wong from kerrywong.com shows us how to interface to a MMA8453Q cheap accelerometer using an ATmega328 MCU. There are tons of cool projects out there that are just waiting to be designed using […]

  4. […] Wong from kerrywong.com shows us how to interface to a MMA8453Q cheap accelerometer using an ATmega328 MCU. There are tons of cool projects out there that are just waiting to be designed using […]

  5. Erik says:

    Out*of*the*box!
    Kerry, you rock! this saved me SO much time and effort

  6. mark joseph siapno says:

    can i ask for a little help? i need arduino code for my MMA8451Q accelerometer using wire library..can you please help me with this?thanks

  7. priyanka says:

    sir,
    thank you for such great post
    im working on a project using MMA8451Q accelerometer interfacing ARM7 LPC2148, i have understood the data sheet concepts of I2C interfacing LPC2148, but i need guidance of accelerometer interfacing using I2C bus , please help me .
    thank you.

    • kwong says:

      Hi Priyanka,

      I don’t have MMA8451Q on hand so I couldn’t validate. But by looking at the datasheet, everything seemed to be identical except for the data register layout. It appears that the 8 bit mode (low res) is identical and the high resolution mode needs only slight adjustment:

      void getAccXYZ(int *x, int *y, int *z, bool isHighRes=true)
      {
      byte buf[6];

      if (isHighRes) {
      regRead(REG_OUT_X_MSB, buf, 6);
      *x = buf[0] < < 6 | buf[1] >> 2 & 0x3F;
      *y = buf[2] < < 6 | buf[3] >> 2 & 0x3F;
      *z = buf[4] < < 6 | buf[5] >> 2 & 0x3F;
      }
      else {
      regRead(REG_OUT_X_MSB, buf, 3);
      *x = buf[0] < < 2; *y = buf[1] << 2; *z = buf[2] << 2; } if (*x > 8191) *x = *x – 16384;
      if (*y > 8191) *y = *y – 16384 ;
      if (*z > 8191) *z = *z – 16384;
      }

      But again, I don’t have the chip to test. But feel free to update me on how it goes.

  8. kris dee says:

    have you also tried working on LIS331 accelerometer? thanks!

  9. osama says:

    can i use this sensor with a direct contact to a microcontroller without being connected to a board ??

  10. Yusiang says:

    Hi,
    Is it possible to adapt this project for MSP430 using energia?
    (I see that modifications need to be made to the I2c library)
    Thanks

  11. saddu says:

    Hi Kerry,

    Could you please explain the logic for the below part of code ? I was able to understand rest of the code.

    if (isHighRes) {
    regRead(REG_OUT_X_MSB, buf, 6);
    *x = buf[0] <> 6 & 0x3;
    *y = buf[2] <> 6 & 0x3;
    *z = buf[4] <> 6 & 0x3;
    }
    else {
    regRead(REG_OUT_X_MSB, buf, 3);
    *x = buf[0] << 2;
    *y = buf[1] << 2;
    *z = buf[2] < 511) *x = *x – 1024;
    if (*y > 511) *y = *y – 1024 ;
    if (*z > 511) *z = *z – 1024;

    Thanks,
    Saddu

    • kwong says:

      Sure.
      *x = buf[0] < < 2 | buf[1] >> 6 & 0x3;
      *y = buf[2] < < 2 | buf[3] >> 6 & 0x3;
      *z = buf[4] < < 2 | buf[5] >> 6 & 0x3;

      basically assembles what’s in buf[0] and buf[1] (16 bit word) into a 10 bit value. The high eight byte is formed by shifting what’s in buf[0] (and 2, 4) left by 2 bits and the lower two bits are formed by shifting what’s in buf[1] (and 3, 5) right by 6 bits and only taken the lower two bits (hence & 0x3).

      The else portion simply does a 2’s complement conversion.

  12. Keith says:

    how then do you convert X into number of G’s, assuming you set FULL_SCALE_RANGE_8g

    *x = buf[0] < > 6 & 0x3;

Leave a Reply