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!