Interfacing IIS2ICLX accelerometer with the Wire.h Library?

Hello,

I'm working on a project where I need to get accurate static tilt measurements. I've been trying & figuring out multiple sensors and refined my requirements. Currently I am trying to work with the IIS2ICLX accelerometer which is outside of the well documented Arduino domain.

The amount of complexity this adds is a bit overwhelming. Their examples are based on their own MCU platforms. They do provide a platform independent driver with an example application (formatted at the bottem of this post). I can't quite figure out if - and how this is adjustable to an Arduino platform.

I am fairly new to all this but would really like to learn. The code has a couple of functions that need to be adjusted to the platform, like:

static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp,
                              uint16_t len)
static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp,
                             uint16_t len)

My general attempts to adjust these to Arduino are as follows;

static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp,
                             uint16_t len)
{
  Wire.beginTransmission(ADDRESS);
  Wire.write(reg);
  Wire.requestFrom((uint8_t)ADDRESS, (uint8_t)len);
  Wire.readBytes(bufp, len);
  Wire.endTransmission();
  delay(1000);

  return 0;
}

Am I on the right track here, or how would I approach this? I'm not sure how to continue and am looking for any information that could help, as I have virtually no experience with this stuff. Thanks in advance!

Full example code:

/*
 ******************************************************************************
 * @file    read_data_simple.c
 * @author  Sensors Software Solution Team
 * @brief   This file show the simplest way to get data from sensor.
 *
 ******************************************************************************
 * @attention
 *
 * <h2><center>&copy; Copyright (c) 2020 STMicroelectronics.
 * All rights reserved.</center></h2>
 *
 * This software component is licensed by ST under BSD 3-Clause license,
 * the "License"; You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at:
 *                        opensource.org/licenses/BSD-3-Clause
 *
 ******************************************************************************
 */

/*
 * This example was developed using the following STMicroelectronics
 * evaluation boards:
 *
 * - STEVAL_MKI109V3 + STEVAL-MKI209V1K
 * - NUCLEO_F411RE + STEVAL-MKI209V1K
 * - DISCOVERY_SPC584B + STEVAL-MKI209V1K
 *
 * and STM32CubeMX tool with STM32CubeF4 MCU Package
 *
 * Used interfaces:
 *
 * STEVAL_MKI109V3    - Host side:   USB (Virtual COM)
 *                    - Sensor side: SPI(Default) / I2C(supported)
 *
 * NUCLEO_STM32F411RE - Host side: UART(COM) to USB bridge
 *                    - Sensor side: I2C(Default) / SPI(supported)
 *
 * DISCOVERY_SPC584B  - Host side: UART(COM) to USB bridge
 *                    - Sensor side: I2C(Default) / SPI(supported)
 *
 * If you need to run this example on a different hardware platform a
 * modification of the functions: `platform_write`, `platform_read`,
 * `tx_com` and 'platform_init' is required.
 *
 */

/* STMicroelectronics evaluation boards definition
 *
 * Please uncomment ONLY the evaluation boards in use.
 * If a different hardware is used please comment all
 * following target board and redefine yours.
 */

//#define STEVAL_MKI109V3  /* little endian */
//#define NUCLEO_F411RE    /* little endian */
//#define SPC584B_DIS      /* big endian */

/* ATTENTION: By default the driver is little endian. If you need switch
 *            to big endian please see "Endianness definitions" in the
 *            header file of the driver (_reg.h).
 */

#if defined(STEVAL_MKI109V3)
/* MKI109V3: Define communication interface */
#define SENSOR_BUS hspi2
/* MKI109V3: Vdd and Vddio power supply values */
#define PWM_3V3 915

#elif defined(NUCLEO_F411RE)
/* NUCLEO_F411RE: Define communication interface */
#define SENSOR_BUS hi2c1

#elif defined(SPC584B_DIS)
/* DISCOVERY_SPC584B: Define communication interface */
#define SENSOR_BUS I2CD1

#endif

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include <stdio.h>
#include "iis2iclx_reg.h"

#if defined(NUCLEO_F411RE)
#include "stm32f4xx_hal.h"
#include "usart.h"
#include "gpio.h"
#include "i2c.h"

#elif defined(STEVAL_MKI109V3)
#include "stm32f4xx_hal.h"
#include "usbd_cdc_if.h"
#include "gpio.h"
#include "spi.h"
#include "tim.h"

#elif defined(SPC584B_DIS)
#include "components.h"
#endif

/* Private macro -------------------------------------------------------------*/
#define    BOOT_TIME            20 //ms

/* Private variables ---------------------------------------------------------*/
static int16_t data_raw_acceleration[2];
static int16_t data_raw_temperature;
static float acceleration_mg[2];
static float temperature_degC;
static uint8_t whoamI, rst;
static uint8_t tx_buffer[1000];

/* Extern variables ----------------------------------------------------------*/

/* Private functions ---------------------------------------------------------*/

/*
 *   WARNING:
 *   Functions declare in this section are defined at the end of this file
 *   and are strictly related to the hardware platform used.
 *
 */
static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp,
                              uint16_t len);
static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp,
                             uint16_t len);
static void tx_com( uint8_t *tx_buffer, uint16_t len );
static void platform_delay(uint32_t ms);
static void platform_init(void);

/* Main Example --------------------------------------------------------------*/
void example_main_iis2iclx(void)
{
  stmdev_ctx_t dev_ctx;
  /* Initialize mems driver interface */
  dev_ctx.write_reg = platform_write;
  dev_ctx.read_reg = platform_read;
  dev_ctx.handle = &SENSOR_BUS;
  /* Init test platform */
  platform_init();
  /* Wait sensor boot time */
  platform_delay(BOOT_TIME);
  /* Set Bus mode */
  iis2iclx_bus_mode_set(&dev_ctx, IIS2ICLX_SEL_BY_HW);
  /* Check device ID */
  iis2iclx_device_id_get(&dev_ctx, &whoamI);

  if (whoamI != IIS2ICLX_ID)
    while (1);

  /* Restore default configuration */
  iis2iclx_reset_set(&dev_ctx, PROPERTY_ENABLE);

  do {
    iis2iclx_reset_get(&dev_ctx, &rst);
  } while (rst);

  /* Enable Block Data Update */
  iis2iclx_block_data_update_set(&dev_ctx, PROPERTY_ENABLE);
  /* Set Output Data Rate */
  iis2iclx_xl_data_rate_set(&dev_ctx, IIS2ICLX_XL_ODR_12Hz5);
  /* Set full scale */
  iis2iclx_xl_full_scale_set(&dev_ctx, IIS2ICLX_2g);
  /* Configure filtering chain(No aux interface)
   * Accelerometer - LPF1 + LPF2 path
   */
  iis2iclx_xl_hp_path_on_out_set(&dev_ctx, IIS2ICLX_LP_ODR_DIV_100);
  iis2iclx_xl_filter_lp2_set(&dev_ctx, PROPERTY_ENABLE);

  /* Read samples in polling mode (no int) */
  while (1) {
    uint8_t reg;
    /* Read output only if new xl value is available */
    iis2iclx_xl_flag_data_ready_get(&dev_ctx, &reg);

    if (reg) {
      /* Read acceleration field data */
      memset(data_raw_acceleration, 0x00, 2 * sizeof(int16_t));
      iis2iclx_acceleration_raw_get(&dev_ctx, data_raw_acceleration);
      acceleration_mg[0] =
        iis2iclx_from_fs2g_to_mg(data_raw_acceleration[0]);
      acceleration_mg[1] =
        iis2iclx_from_fs2g_to_mg(data_raw_acceleration[1]);
      sprintf((char *)tx_buffer, "Acceleration [mg]:%4.2f\t%4.2f\r\n",
              acceleration_mg[0], acceleration_mg[1]);
      tx_com(tx_buffer, strlen((char const *)tx_buffer));
    }

    iis2iclx_temp_flag_data_ready_get(&dev_ctx, &reg);

    if (reg) {
      /* Read temperature data */
      memset(&data_raw_temperature, 0x00, sizeof(int16_t));
      iis2iclx_temperature_raw_get(&dev_ctx, &data_raw_temperature);
      temperature_degC = iis2iclx_from_lsb_to_celsius(data_raw_temperature);
      sprintf((char *)tx_buffer,
              "Temperature [degC]:%6.2f\r\n", temperature_degC);
      tx_com(tx_buffer, strlen((char const *)tx_buffer));
    }
  }
}

/*
 * @brief  Write generic device register (platform dependent)
 *
 * @param  handle    customizable argument. In this examples is used in
 *                   order to select the correct sensor bus handler.
 * @param  reg       register to write
 * @param  bufp      pointer to data to write in register reg
 * @param  len       number of consecutive register to write
 *
 */
static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp,
                              uint16_t len)
{
#if defined(NUCLEO_F411RE)
  HAL_I2C_Mem_Write(handle, IIS2ICLX_I2C_ADD_L, reg,
                    I2C_MEMADD_SIZE_8BIT, (uint8_t*) bufp, len, 1000);
#elif defined(STEVAL_MKI109V3)
  HAL_GPIO_WritePin(CS_up_GPIO_Port, CS_up_Pin, GPIO_PIN_RESET);
  HAL_SPI_Transmit(handle, &reg, 1, 1000);
  HAL_SPI_Transmit(handle, (uint8_t*) bufp, len, 1000);
  HAL_GPIO_WritePin(CS_up_GPIO_Port, CS_up_Pin, GPIO_PIN_SET);
#elif defined(SPC584B_DIS)
  i2c_lld_write(handle,  IIS2ICLX_I2C_ADD_L & 0xFE, reg, (uint8_t*) bufp, len);
#endif
  return 0;
}

/*
 * @brief  Read generic device register (platform dependent)
 *
 * @param  handle    customizable argument. In this examples is used in
 *                   order to select the correct sensor bus handler.
 * @param  reg       register to read
 * @param  bufp      pointer to buffer that store the data read
 * @param  len       number of consecutive register to read
 *
 */
static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp,
                             uint16_t len)
{
#if defined(NUCLEO_F411RE)
  HAL_I2C_Mem_Read(handle, IIS2ICLX_I2C_ADD_L, reg,
                   I2C_MEMADD_SIZE_8BIT, bufp, len, 1000);
#elif defined(STEVAL_MKI109V3)
  reg |= 0x80;
  HAL_GPIO_WritePin(CS_up_GPIO_Port, CS_up_Pin, GPIO_PIN_RESET);
  HAL_SPI_Transmit(handle, &reg, 1, 1000);
  HAL_SPI_Receive(handle, bufp, len, 1000);
  HAL_GPIO_WritePin(CS_up_GPIO_Port, CS_up_Pin, GPIO_PIN_SET);
#elif defined(SPC584B_DIS)
  i2c_lld_read(handle, IIS2ICLX_I2C_ADD_L & 0xFE, reg, bufp, len);
#endif
  return 0;
}

/*
 * @brief  Write generic device register (platform dependent)
 *
 * @param  tx_buffer     buffer to transmit
 * @param  len           number of byte to send
 *
 */
static void tx_com(uint8_t *tx_buffer, uint16_t len)
{
#if defined(NUCLEO_F411RE)
  HAL_UART_Transmit(&huart2, tx_buffer, len, 1000);
#elif defined(STEVAL_MKI109V3)
  CDC_Transmit_FS(tx_buffer, len);
#elif defined(SPC584B_DIS)
  sd_lld_write(&SD2, tx_buffer, len);
#endif
}

/*
 * @brief  platform specific delay (platform dependent)
 *
 * @param  ms        delay in ms
 *
 */
static void platform_delay(uint32_t ms)
{
#if defined(NUCLEO_F411RE) | defined(STEVAL_MKI109V3)
  HAL_Delay(ms);
#elif defined(SPC584B_DIS)
  osalThreadDelayMilliseconds(ms);
#endif
}

/*
 * @brief  platform specific initialization (platform dependent)
 */
static void platform_init(void)
{
#if defined(STEVAL_MKI109V3)
  TIM3->CCR1 = PWM_3V3;
  TIM3->CCR2 = PWM_3V3;
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
  HAL_Delay(1000);
#endif
}

Much of the code you posted has to do with setting up the ARM processor properly, and has nothing to do with the accelerometer. You don't need to imitate those functions. The accelerometer data sheet will explain how to read the data.

Please explain what do you mean by "accurate".

Getting tilt angles is easy, but MEMS accelerometers are noisy, so lots of averaging is required, and all require careful calibration in order to produce accurate results.

There is probably some Arduino code for it, but I can't find it yet.

Is it allowed to ignore the handle ?

You are mixing reading and writing functions. I made an alternative explanation of the Wire functions.

Do you know if it supports a repeated start ?

static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp, uint16_t len)
{
  Wire.beginTransmission(ADDRESS);
  Wire.write(reg);
  // false for a repeated start, true for a STOP condition.
  int error = Wire.endTransmission( false);    

  if (error != 0)
    return(error);

  int n = Wire.requestFrom(ADDRESS, len);
  if( n != len)
    return 100;      // I2C bus error, return a number, not zero

  Wire.readBytes(bufp, len);

  return 0;       // no error
}

static int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp, uint16_t len)
{
  Wire.beginTransmission(ADDRESS);
  Wire.write(reg);
  for( int i=0; i<len; i++)
    Wire.write(bufp[i]);
  int error = Wire.endTransmission(); 

  return(error);
}

Thanks for the replies!

Interesting, perhaps approaching this from scratch would be a better than adjusting the example code? The accuracy i'm trying to achieve is exactly that- low noise (15 μg/√Hz), temperature drift, etc. I'm hoping to achieve atleast a 0.1 deg accuracy and being able to read slow adjustments.

I will read through your alternative explanation, it seems very helpful! In the datasheet (p. 12) it mentions a repeated start in the I2C slave timing diagram, so it seems to support that. I will try that code as soon as I can.

In the driver it refers to the handle as a "optional pointer", so I assumed it can ignore it;

typedef struct {
  /** Component mandatory fields **/
  stmdev_write_ptr  write_reg;
  stmdev_read_ptr   read_reg;
  /** Customizable optional pointer **/
  void *handle;
} stmdev_ctx_t;

That is what I would do. All the information required to write I2C or SPI code for Arduino is given in the data sheet. The example code you posted doesn't seem helpful.

Good. I read the rest of the I2C interface, and it is a standard I2C interface.

Which Arduino board do you use ?

The Arduino Uno has a buffer for a I2C session of only 32 data bytes.

The first thing to do is to run a I2C Scanner to find the I2C address.
The next thing is trying to read the identifer or who_am_i. This chip has a WHO_AM_I in register 0x0F. Can you try to read that ? Regardless if you use a library or code from scratch, it helps to understand using the I2C interface.

  HAL_GPIO_WritePin(CS_up_GPIO_Port, CS_up_Pin, GPIO_PIN_RESET);
  HAL_SPI_Transmit(handle, &reg, 1, 1000);
  HAL_SPI_Transmit(handle, (uint8_t*) bufp, len, 1000);
  HAL_GPIO_WritePin(CS_up_GPIO_Port, CS_up_Pin, GPIO_PIN_SET);

Alternatively, you can use SPI instead of I2C. It looks easier to implement.

One thing is for certain, writing drivers from scratch for these ST MEMs sensor is like a full time job. The sensor alone has like 60 registers you have to work with.

Hello Markm5,

have you succeeded in getting data from IIS2ICLX accelerometer? In fact, I am working on a projet for measuring the acceeleration from this sensor but I haven't succeeded....

Thank you for your reply.
Minh.