Extended Driver for the MMA8451Q Accelerometer

In “Tutorial: Accelerating the KL25Z Freedom Board” I used the MMA8451Q accelerometer on the FRDM-KL25Z board in a very primitive way: I’m reading directly some low-level registers from the device through an I2C low-level component. No calibrating, no special device feature setting, only raw values. Since then, things have been evolved: In “Tutorial: Creating a Processor Expert Component for an Accelerometer” I started to create a driver for this accelerometer, and since then a lot more functionality has been added.

Tracing Accelerometer Values to the Shell

Traced Accelerometer Values to the Shell

MMA7260Q and MMA8451Q

I’m using a my Processor Expert driver for the Freescale MMA7260Q accelerometer for multiple years in multiple projects. That MMA7260Q is present on many Freescale evaluation boards, including many of the Tower Boards. The MMA7260Q is an accelerometer with an analog interface, and the driver I have created for it is available on GitHub.

So when I was thinking to extend the MMA8451Q driver, I wanted to have the connection to be compatible as much as possible. That way I easily can switch my existing software to the MMA8451Q. The MMA8451Q has a nice digital I2C interface, with a lot of cool features (tap detection, orientation detection) I consider to use.

MMA8451Q Properties

The component properties have been extended with calibration values and Shell support:

MMA8451Q Properties

MMA8451Q Properties

💡 The MMA8451Q is factory calibrated, and has the ability to store calibration values on the device. Right now I store the calibration values in the driver, and not on the device yet.

The optional Shell interface is something I recently have added to the MMA7260Q too: it allows me to inspect/configure the device. More about this later in this post.

💡 I continue to add a command line (Shell) interface to my components, as feedback has been that this is very useful. And I’m using it all the time too: it allows me to work with a device using a command line interface. And with the modular way of Processor Expert components I can enable/disable it on a component by component base.

MMA8451Q Methods

The following picture shows the currently implemented interface:

MMA8451Q Methods

MMA8451Q Methods

The ParseCommand() method is only enabled if the Shell is enabled in the component properties. This is done in the component .CHG (Change) script:

%ifndef Shell
  %set ParseCommand Selection never
%else
  %set ParseCommand Selection always
%endif

I2C

To simplify the communication with I2C to the device, two more methods (WriteByteAddress8() and ReadByteAddress8()) have been added to the GenericI2C component. This makes e.g. the driver method to enable the accelerometer really easy:

byte %'ModuleName'%.%Enable(void)
{
  uint8_t val, res;

  res = inherited.I2C.ReadByteAddress8(MMA8451_I2C_ADDR, MMA8451_CTRL_REG_1, &val);
  if (res!=ERR_OK) {
    return res;
  }
  val |= MMA8451_ACTIVE_BIT_MASK; /* enable device */
  return inherited.I2C.WriteByteAddress8(MMA8451_I2C_ADDR, MMA8451_CTRL_REG_1, val);
}

Little Or Big Endian

One thing (again!) I was running into was that the Kinetis/ARM is Little Endian, while my brain is thinking (always? most of the time?) in Big Endian mode :-(. As I want the driver to work both with LE (Little Endian) and BE (Big Endian) cores, I created a macro to the driver code to deal with both worlds:

%if (CPUfamily = "Kinetis")
#define %'ModuleName'_CPU_IS_LITTLE_ENDIAN 1 /* Cpu is little endian */
%else
#define %'ModuleName'_CPU_IS_LITTLE_ENDIAN 0 /* Cpu is big endian */
%endif

❓ Note sure if there is a better way in Processor Expert CDE to know if the CPU is BE or LE?

Then I can use this in my driver code like this:

%-************************************************************************************************************
%-BW_METHOD_BEGIN MeasureGetRawX
%ifdef MeasureGetRawX
%define! RetVal
%include Common\MMA8451QMeasureGetRawX.Inc
word %'ModuleName'%.%MeasureGetRawX(void)
{
  union {
    uint8_t buf[2]; /* value from device is in big endian */
    uint16_t be;
  } val;
  static const uint8_t addr = MMA8451_OUT_X_MSB;

  if(inherited.I2C.ReadAddress(MMA8451_I2C_ADDR, (uint8_t*)&addr, sizeof(addr), &val.buf[0], sizeof(val.buf))!=ERR_OK) {
    return 0; /* failure */
  }
#if %'ModuleName'_CPU_IS_LITTLE_ENDIAN
  return (uint16_t)((val.buf[0]<<8)|val.buf[1]); /* transform into LE value */
#else
  return val.be; /* already in BE */
#endif
}

%endif %- MeasureGetRawX
%-BW_METHOD_END MeasureGetRawX

Shell Interface

The Shell interface offers following commands:

Shell Help Information

Shell Help Information

With the status command I get the following information:

Accelerometer Status

Accelerometer Status

  • raw: the raw (14bit, left shifted) values, both in hexdecimal and signed decimal
  • calibOffset: the calibration offsets (as defined in the component properties or set by the calibration methods).
  • calib 1g: The value the device uses for 1 g acceleration.
  • GetX, Y, Z: the values returned by the GetX(), GetY() and GetZ() methods. These values are compensated with the calibration values
  • mg X, Y, Z: the values returned by GetXmg(), GetYmg() and GetZmg(), in milli-g

As I have added the I2CSpy component as a bonus to my project, it allows me to read/write/dump the I2C memory map of the accelerometer:

MMA8451Q Memory Map

MMA8451Q Memory Map

💡 Using the I2CSpy I can explore the device settings without the need to download/write special code: I can inspect the bits and settings, write to the device and immediately see the impact. Really cool (at least I think this :-))

Sources

As always, things are available on GitHub. This link points to the driver code.

Summary and Outlook

The new Processor Expert driver works very well for me. It does not cover all the features of the device yet (tap detection, low power modes, orientation detection, different g sensitivity levels). So there is still a lot of room for extensions, and this will be added as soon as I find the time :-). But for now, I think it is a good starting point.

Happy Accelerating 🙂

42 thoughts on “Extended Driver for the MMA8451Q Accelerometer

  1. I’m a fan of yours since I’ve started playing with freedom board a couple weeks ago. I’m following along with this article and start noticing when I enable the MMA8451Q; It only allow a selection to create new component with Shell. But I’ve already used FSShell with other components up to now. And I’d really like to access the MMA8451Q with all the above command lines with FSShell. Any suggestion ? Thank you for a great library.

    Like

    • Hi,
      I had to redesign the FSShell, as it had too much lumped into it. The shell part is now in the ‘Shell’ component, while now FatFS has an interface to it. What I suggest is:
      a) disable the FSShell (so you have it as backup)
      b) Add the Shell component. You can link it to your serial interface (e.g. Asynchroserial)
      c) link accelerometer/FatFS/FreeRTOS or whatever you have to the new Shell

      Should be fairly easy. Otherwise let me know.

      Like

  2. Pingback: Tutorial: FreeMASTER Visualization and Run-Time Debugging | MCU on Eclipse

  3. Pingback: USB for the Freescale ARM Kinetis KL46Z and K21D50M | MCU on Eclipse

  4. Pingback: Tutorial: Using the FRDM-KL25Z as Low Power Board | MCU on Eclipse

    • Hi Juan,
      I did a quick data sheet comparison, and it looks the MMA8452 has only different number of A/D bits, and everything else is the same. So you should be able to use the driver with it. Same for the MMA8453.

      Like

      • Hi Erich, I´m using MMA8453QR1 in a custom board of mine and I´d like to know where I can change the resolution (14 to 10 bits from MMA8451 bean) to port this project? Another question is: what is the full-scale value range? Is it set to 8g?

        Like

        • Hi Marcio,
          you would have to use the accelerometer with SetFastMode(FALSE). Setting the ScaleRange requires that the sensor is disabled.
          I have now extended the component with methods to set and get the range (GetScaleRangeG() and SetScaleRangeG()). Or you could use the following code below:

          uint8_t MMA1_GetScaleRangeG(uint8_t *gScale)
          {
          uint8_t val, res;

          res = GI2C1_ReadByteAddress8(MMA1_I2C_ADDR, MMA1_XYZ_DATA_CFG, &val);
          if (res!=ERR_OK) {
          return res;
          }
          switch(val&0x3) { /* check FS1 and FS2 bits */
          case 0: *gScale = 2; /* 00: 2g */
          return ERR_OK;
          case 1: *gScale = 4; /* 01: 4g */
          return ERR_OK;
          case 2: *gScale = 8; /* 10: 8g */
          return ERR_OK;
          default: break;
          } /* switch */
          *gScale = 0; /* error case */
          return ERR_RANGE;
          }

          There is an updated component available on SourceForge too: https://sourceforge.net/projects/mcuoneclipse/files/PEx%20Components/ and an example project using it is on https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/KDS/FRDM-KL25Z/FRDM-KL25Z_Accel

          I hope this helps?
          Erich

          Like

        • Right now I´m getting a ERR_BUSY code when I try to use SetFastMode(FALSE) method. I do that right after I call MMA1_Init() with no error, so unfortunately I couldn´t test GetScaleRangeG() yet : (

          Did you faced this problem before or have some tip to help me to solve it?

          Like

        • I have found that the accelerometer after writing some settings, needs some delay time (because the sensor is busy storing the settings?).
          Try to add a delay after the MMA1_Init(), e.g. with WAIT1_Waitms(10);

          Like

        • In fact, even in the FRDM-KL25Z board I`m getting this ERR_BUSY error and I don´t know why.

          Like

        • With the project I have posted on GitHub, or your own version of it?
          ERR_BUSY means that the device does not respond with an ACK when the driver wants to talk to it.

          Like

        • It happens with your project too, but it seems to work properly when I turn off and then turn on the board again (FRDM-KL25Z or my board).

          Like

        • Hi Marcio,
          yes, I noticed that as well: if the accelerometer somehow internally is screwed up, it only can be brougth back with a power cycle. That’s why in my designs I usually add a FET to the Vcc/supply pin of the accelerometer so the microcontroller can reset it.

          Like

        • Ok Erich. I´d like to use the shell to test the commands, but I´m little confused here: When I enable it on MMA8451Q component I need to choose one default serial, which one? Why I can use the FreeMaster bean that is already on project?

          Like

        • Oh, it´s true! I have forgot this step, sorry about that. I already make this changes and now it´s working, thanks Erich!

          The only problem now it´s about the strange axis values I´m reading here when I´m doing the calibration.

          Like

      • Doing my tests I found some weird (errors?) here: right after calling MMA1_SetFastMode(FALSE) I used MMA1_CalibrateX1g() method and I found that in fact this function is switched with MMA1_CalibrateY1g(), in other words the X and Y axis are inverted and for MMA1_CalibrateZ1g() method positioning the FRDM-KL25Z in horizontal position I got a 0g and turning it up side down I found -2g (near 8192 in decimal). Did you find the same result at your side? Obs: The scale range for your project came with 2g.

        Like

        • Hi Erich,

          Another issue I´m facing here is that it seems I can´t change the scale range using MMA1_SetScaleRangeG method. It was set to 2g and until now I couldn´t change it to 4g or 8g, even with no error codes. I used this code below:

          uint8_t res=ERR_OK, ret, scale;

          res = MMA1_Init();
          WAIT1_Waitms(10);
          res = MMA1_SetFastMode(FALSE);
          WAIT1_Waitms(10);
          ret = MMA1_SetScaleRangeG(8);

          And then I used this code to read the scale range:

          res = MMA1_Init();
          WAIT1_Waitms(10);
          // res = MMA1_SetFastMode(FALSE);
          // WAIT1_Waitms(10);

          ret = MMA1_GetScaleRangeG(&scale);

          Using MMA1_SetFastMode(FALSE) or not doesn´t make difference here and when I call MMA1_GetScaleRangeG, I allways read scale equals to 2. Did you find the same problem there?

          Like

        • Hi Marcio,
          as noted earlier (and in the documentation of the component too) you have to disable the sensor if you want to do change the settings.
          So have you called MMA1_Disable() first?

          Like

        • Hi Marcio,
          as noted in the comments of these functions, you have to place the board with that respective axis in a 1g position. So if you do the Z calibration, you have to put the board flat (the sensor is exposed 1g). In a smilar way, you have to change the board orientation if you do the other calibration functions.
          I hope this helps,
          Erich

          Like

        • Yes, that is correct Erich. I just would like to know if you have tested there this calibrations and got the same results or behavior I´m got here at my side.

          Like

        • Below is the output of my calibration, and that looks ok to me?

          ------------------------
          FRDM-KL25Z Accel
          ------------------------
          Device is enabled, disable it before applying settings...
          Setting G mode to 2 g.
          Enable device.
          X axis calibration (1g for X): place the board with the USB connector pointing to the sky and press a key...

          Y axis calibration (1g for Y): place the board with the right arduino rows down and press a key...

          Z axis calibration (1g for Z): place the board flat on a table and press a key...

          done!
          x: -11 y: -11, z: 3969
          x: 17 y: 21, z: 4005
          x: 21 y: 3, z: 3995
          x: 19 y: -7, z: 4007
          x: 21 y: 17, z: 4007
          x: 19 y: 29, z: 4021
          x: 29 y: 13, z: 4019
          x: 25 y: 15, z: 4003
          x: 19 y: 13, z: 3995
          x: 39 y: -1, z: 4003
          x: -3 y: -7, z: 4017
          x: 9 y: 21, z: 4017
          x: 15 y: 9, z: 4003
          x: 23 y: -1, z: 4021
          x: 17 y: -1, z: 4027
          x: 15 y: 25, z: 4051
          x: 21 y: 25, z: 4025
          x: 23 y: 13, z: 4003
          x: 33 y: 13, z: 4043

          Like

        • For 2g I think is correct. Are you using Shell? Can you please tell me how to set it on this project to do this tests either?

          Like

  5. Hello Erich,
    Thanks for great posts on pretty much a huge number of topics….
    I tried to use the component with the Shell interface but I do not understand
    which API should be called regularly to parse commands entered in the termite shell…
    I did not find any example code using the extended driver (with non OS)…
    Could you please give me a hint ?
    Thanks
    Vince

    Like

    • Hi Vince,
      Just put the code from the Shell task into a loop:

      static void ShellLoop(void) {
      unsigned char buf[48];

      buf[0] = ”;
      (void)CLS1_ParseWithCommandTable((unsigned char*)CLS1_CMD_HELP, CLS1_GetStdio(), CmdParserTable);
      for(;;) {
      (void)CLS1_ReadAndParseWithCommandTable(buf, sizeof(buf), CLS1_GetStdio(), CmdParserTable);
      LEDG_Neg();
      }
      }

      instead of its own loop, call it from your main() loop. There is really nothing special with or without an RTOS.

      Like

  6. Pingback: First Steps with the Freescale TWR-K64F120M | MCU on Eclipse

  7. Sorry for posting on an old topic however I’m struggling with the GenericI2C component in Kinetis Design Studio 3.0.0.

    I’m trying to adapt this code to work for an LSM9DS0 combined acc, mag and gyro device however I’m having difficulty when trying to do multi-byte reads. As an example, reading the 3×16 bit accelerometer values:

    static const uint8_t addr = LSM9DS0_REG_OUT_X_L_A;
    int16_t xyz[3];
    GI2C1_ReadAddress(LSM9DS0_ADDR_XM, (uint8_t*)&addr, sizeof(addr), (uint8_t*)xyz, 6);

    When inspecting the “xyz” array, each element has the same value. The value is different each time the ReadAddress function is called but still the value in each array element is identical.

    Am I using this ReadAddress function incorrectly?

    Thanks in advance,
    Kevin

    Like

    • Hi Kevin,
      your usage looks ok. I think you have a different problem down the wire. Could you use a logic analyzer or scope to check what is sent and received over SCL and SDA and matches what your LSM9DS0 needs? It might be that there is something wrong.
      I hope this helps,
      Erich

      Like

      • Hi Erich,

        Thank you for your quick reply.

        I haven’t used a logic analyzer as of yet as the WHO_AM_I and single-byte reads all seem to work correctly – returning the expected values.

        I shall investigate with a logic analyzer later this evening.

        Kind regards,
        Kevin

        Like

  8. Hi Erich,

    I’ve been working on FRDM kl25z to interface MMA8451q via I2C0.
    My code is something like this
    https://github.com/sunsided/frdm-kl25z-marg-fusion/blob/milestone/mma8451q-serial-stream/frdm-kl25z-acc-uart/Sources/i2c/i2c.c

    As soon as I initialize the I2C0, I’m read WHO_AM_I continuously with 3 seconds delay, but after 1st read, SCL is pulled low forever. SDA seems to be fine.

    What might be the possible problem? Can you please suggest something which I can try?

    Regards,
    Bharath

    Like

    • Hi Bharath,
      can you try the code I have used in that post instead? There are so many things which could go wrong with your code example. What you could try is to do single stepping through your code while watching with a logic analyzer the SDA and SCL line. I hope this helps, good luck!

      Like

      • Hi Erich,

        Yes I’ve tried stepping through the code and the controller wasn’t able to generate the STOP condition hence the SCL remained low. I used the different frdm kl25z board, and things started working. I believe the board I was using has some problem. Thank you for your input.

        Regards,
        Bharath

        Like

  9. Erich

    If I want to use the (GetXmg, GetYmg, GetZmg) and send those values to freemaster I have to change those three lines in “Appliction.c”:
    #if USE_PEX_COMPONENT
    xyz[0] = MMA1_GetX()>>8; to xyz[0] = MMA1_GetXmg()>>8;
    xyz[1] = MMA1_GetY()>>8; to xyz[1] = MMA1_GetYmg()>>8;
    xyz[2] = MMA1_GetZ()>>8; to xyz[2] = MMA1_GetZmg()>>8;

    or do I have to do something more?

    Like

  10. Hi,
    I have used the Generic I2C Component and Accelerometer code downloaded from GitHub for making the LEDs blink according to tilt. This is my main() program.

    /*for(;;)
    {
    LED1_On();
    LED2_On();
    LED3_On();
    res = MMA1_Init();
    while (res==ERR_OK) {
    res = MMA1_GetRaw8XYZ(&xyz[0]);
    LED1_Put(xyz[0]>50);
    LED2_Put(xyz[1]>50);
    LED3_Put(xyz[2]>50);
    }
    LED1_Off();
    LED2_Off();
    LED3_Off();
    }*/

    I am not sure if I need to include other methods on that are available on the accelerometer driver like the writereg8/readreg8 etc. In a previous tutorial where you built separate events for the accelerometer you had used such methods. I would like to get some pointers with regard to that.

    Like

    • The GenericI2C driver is a wrapper to deal with different I2C driver implementations (LDD, non-LDD, bitbanging, etc). Your posted example make sense, and you should not need any other functions. The writereg8/readreg8 methods are there for you to read/write registers directly in case you need them.

      Like

  11. Pingback: McuOnEclipse Components: 30-Sept-2018 Release | MCU on Eclipse

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.