GithubHelp home page GithubHelp logo

stmicroelectronics / lis2dw12-pid Goto Github PK

View Code? Open in Web Editor NEW
9.0 9.0 4.0 89 KB

lis2dw12 platform independent driver based on Standard C language and compliant with MISRA standard

License: BSD 3-Clause "New" or "Revised" License

HTML 2.12% CSS 31.26% C 66.62%
mems-sensors stmicroelectronics

lis2dw12-pid's Introduction

latest tag

1 - Introduction

Sensor driver for LIS2DW12 sensor written in C programming language. This repository contains the sensor driver files (.h and .c) to be included, or linked directly as a git submodule, in your project. The driver is MISRA compliant and the documentation can be generated using the Doxygen tool.

In order to clone the complete content of the repository folder, use the command:

git clone https://github.com/STMicroelectronics/LIS2DW12-PID/

Some examples of driver usage can be found here.


2 - Integration details

The driver is platform-independent, you only need to define two functions for read and write transactions from the sensor hardware bus (ie. SPI or I²C) and an optional one to implement a delay of millisecond granularity. A few devices integrate an extra bit in the communication protocol in order to enable multi read/write access, this bit must be managed in the read and write functions defined by the user. Please refer to the read and write implementation in the reference examples.

2.a Source code integration

  • Include in your project the driver files of the sensor (.h and .c)
  • Define in your code the read and write functions that use the I²C or SPI platform driver like the following:
/** Please note that is MANDATORY: return 0 -> no Error.**/
int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp, uint16_t len)
int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp, uint16_t len)

/** Optional (may be required by driver) **/
void platform_delay(uint32_t millisec)
  • Declare and initialize the structure of the device interface:
xxxxxxx_ctx_t dev_ctx; /** xxxxxxx is the used part number **/
dev_ctx.write_reg = platform_write;
dev_ctx.read_reg = platform_read;
dev_ctx.mdelay = platform_delay;

  • If needed by the platform read and write functions, initialize the handle parameter:
dev_ctx.handle = &platform_handle;

Some integration examples can be found here.

2.b Required properties

  • A standard C language compiler for the target MCU
  • A C library for the target MCU and the desired interface (ie. SPI, I²C)

More Information: http://www.st.com

Copyright (C) 2021 STMicroelectronics

lis2dw12-pid's People

Contributors

avisconti avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lis2dw12-pid's Issues

Bug in lis2dw12_acceleration_raw_get

The function "lis2dw12_acceleration_raw_get" is giving incorrect sensor results along X-, Y- and Z-axis since it doesn't ignore 2 Least significant bits on OUT_X_L (28h), OUT_Y_L (2Ah) and OUT_Z_L (2Ch).

****** Current Implementation ******:
val[0] = (int16_t)buff[1];
val[0] = (val[0] * 256) + (int16_t)buff[0];
val[1] = (int16_t)buff[3];
val[1] = (val[1] * 256) + (int16_t)buff[2];
val[2] = (int16_t)buff[5];
val[2] = (val[2] * 256) + (int16_t)buff[4];

****** Actual implementation must be ******
val[0] = (int16_t)buff[1];
val[0] = (int16_t)((val[0] << 8) + buff[0]);
val[0] = (int16_t)(val[0] >> 2);

val[1] = (int16_t)buff[3];
val[1] = (int16_t)((val[1] << 8) + buff[2]);
val[1] = (int16_t)(val[1] >> 2);

val[2] = (int16_t)buff[5];
val[2] = (int16_t)((val[2] << 8) + buff[4]);
val[2] = (int16_t)(val[2] >> 2);

Incorrect orientation with LIS2DW12TR

Hello,

In our product we are using the accelerometer LIS2DW12TR.
We have issue whereby we sometimes get orientation different from ACCEL_ORIENTATION_UPRIGHT in our driver, even though the accelerometer is upright. It may have moved (probably has moved actually), but definitely stayed upright.

I looked at the file STMems_Standard_C_drivers/lis2dw12_STdC/examples/lis2dw12_orientation.c at master · STMicroelectronics/STMems_Standard_C_drivers · GitHub, and noticed that there is the following line:
lis2dw12_6d_feed_data_set(&dev_ctx, LIS2DW12_LPF2_FEED);
This seems to set the bit LPASS_ON6D in register CTRL7. In our driver we have that bit cleared.

Do you think setting that bit might fix our issue?

Another thought I have, is that whenever we get any kind of interrupt from the LIS2DW12TR, we read the orientation register SIXD_SRC, and update our accel_app->data.orientation. Could it be that the data in that register is sometimes incorrect, and maybe the other bits in that register are correct only if the 6D_IA bit is set? But if so, how can we know what the orientation is at boot-up of our device?

I’ve attached our driver in case you’d like to take a look. The following function converts the SIXD_SRC register to orientation.

//! Analyzes the 6id byte to obtain orientation
void AccelAnalyzeAllSources(accel_app_st_t *accel_app)
{
lis2dw12_sixd_src_t sixd_src = accel_app->all_sources.sixd_src;

if (1 == sixd_src.zh) {
    accel_app->data.orientation = ACCEL_ORIENTATION_UPRIGHT;
} 
else if (1 == sixd_src.zl) {
    accel_app->data.orientation = ACCEL_ORIENTATION_UPSIDE_DOWN;
} 
else if (1 == sixd_src.yh) {
    accel_app->data.orientation = ACCEL_ORIENTATION_ON_RIGHTSIDE;
} 
else if (1 == sixd_src.yl) {
    accel_app->data.orientation = ACCEL_ORIENTATION_ON_LEFTSIDE;
} 
else if (1 == sixd_src.xh) {
    accel_app->data.orientation = ACCEL_ORIENTATION_NOSE_DOWN;
} 
else if (1 == sixd_src.xl) {
    accel_app->data.orientation = ACCEL_ORIENTATION_NOSE_UP;
} 
else {
    accel_app->data.orientation = ACCEL_ORIENTATION_UNKNOWN;
}

Thanks,

Yves Bastiand

[email protected]
Senior Embedded Software Engineer
Systems Engineering
Epocal, a Siemens Healthineers company
855 Brookfield Road, Suite 101
Ottawa, Ontario, K1V 2S5, Canada

Our driver source code:
++++++++++++++++++++++++++++++++++++++Lis2dw12AccelDriver.h:
//! \file
//!
//! \copyright Copyright (c) 2017 by Epocal. All rights reserved.
//!
//! THIS SOURCE CODE CONTAINS CONFIDENTIAL INFORMATION THAT IS OWNED BY EPOCAL,
//! AND MAY NOT BE COPIED, DISCLOSED OR OTHERWISE USED WITHOUT
//! THE EXPRESS WRITTEN CONSENT OF EPOCAL.
//!
//! \brief Accelerometer Driver

// Header guard ------------------------------------------------------------------------------------
#ifndef LIS2DW12ACCELDRIVER_H
#define LIS2DW12ACCELDRIVER_H

// Includes ----------------------------------------------------------------------------------------

#include "AccelModel.h"
#include "I2cArbiterCmn.h"
#include "SehI2cCmn.h"

// Header guard for accel driver type --------------------------------------------------------------
#ifdef ACCEL_LIS2DW12
// Constants ---------------------------------------------------------------------------------------

#define ACCEL_I2C_DEVICE_ADDRESS 0x33U

#define ACCEL_SLEEPSTATE_CONFIG_ON 0x42U
#define ACCEL_SLEEPSTATE_CONFIG_OFF 0x02U

// When accelerometer at rest, acceleration is 9.81 m/s/s for the vertical
// dimension and 0 in other two dimensions
#define ACCEL_NO_ACCELERATION 9.81f

constexpr bool HIGH_PERFORMANCE_MODE = true;
//constexpr bool LOW_POWER_MODE = false;

// Typedefs ----------------------------------------------------------------------------------------

typedef enum {
ACCEL_STATE_UNDEFINED,
ACCEL_STATE_PWR_ON,
ACCEL_STATE_CFG_WRITE,
ACCEL_STATE_CFG_READ,
ACCEL_STATE_READY,
ACCEL_STATE_READ_INTERRUPT,
ACCEL_STATE_READ_ORIENTATION,
ACCEL_STATE_ANALYZE_ORIENTATION,
ACCEL_STATE_READ_FF,
ACCEL_STATE_READ_XYZ,
ACCEL_STATE_ANALYZE_XYZ,
} accel_state_en_t;

typedef enum {
ACCEL_ERR_NONE,
ACCEL_ERR_FAILED_TO_BOOT,
ACCEL_ERR_CONFIG_WRITE,
ACCEL_ERR_INTERRUPT_READ,
ACCEL_ERR_ORIENT_READ,
ACCEL_ERR_FF_READ,
ACCEL_ERR_XYZ_READ,
ACCEL_ERR_ARB_QUEUE_FULL
} accel_error_en_t;

typedef enum {
ACCEL_FULL_SCALE_2G = 0x00,
ACCEL_FULL_SCALE_4G = 0x10,
ACCEL_FULL_SCALE_8G = 0x20,
ACCEL_FULL_SCALE_16G = 0x30
} accel_full_scale_en_t;

typedef enum {
ACCEL_RESOLUTION_LOW_POWER_1 = 0x00,
ACCEL_RESOLUTION_LOW_POWER_2 = 0x01,
ACCEL_RESOLUTION_LOW_POWER_3 = 0x02,
ACCEL_RESOLUTION_LOW_POWER_4 = 0x03,
ACCEL_RESOLUTION_HIGH = 0x04,
ACCEL_SINGLE_DATA_CONVERSION_ON_DEMAND_1 = 0x08,
ACCEL_SINGLE_DATA_CONVERSION_ON_DEMAND_2 = 0x09,
ACCEL_SINGLE_DATA_CONVERSION_ON_DEMAND_3 = 0x0A,
ACCEL_SINGLE_DATA_CONVERSION_ON_DEMAND_4 = 0x0B
} accel_resolution_en_t;

typedef struct
{
uint8_t drdy : 1;
uint8_t ff_ia : 1;
uint8_t _6d_ia : 1;
uint8_t single_tap : 1;
uint8_t double_tap : 1;
uint8_t sleep_state_ia : 1;
uint8_t drdy_t : 1;
uint8_t ovr : 1;
} lis2dw12_status_dup_t;

typedef struct
{
uint8_t z_wu : 1;
uint8_t y_wu : 1;
uint8_t x_wu : 1;
uint8_t wu_ia : 1;
uint8_t sleep_state_ia : 1;
uint8_t ff_ia : 1;
uint8_t not_used_01 : 2;
} lis2dw12_wake_up_src_t;

typedef struct
{
uint8_t z_tap : 1;
uint8_t y_tap : 1;
uint8_t x_tap : 1;
uint8_t tap_sign : 1;
uint8_t double_tap : 1;
uint8_t single_tap : 1;
uint8_t tap_ia : 1;
uint8_t not_used_01 : 1;
} lis2dw12_tap_src_t;

typedef struct
{
uint8_t xl : 1;
uint8_t xh : 1;
uint8_t yl : 1;
uint8_t yh : 1;
uint8_t zl : 1;
uint8_t zh : 1;
uint8_t _6d_ia : 1;
uint8_t not_used_01 : 1;
} lis2dw12_sixd_src_t;

typedef struct
{
uint8_t ff_ia : 1;
uint8_t wu_ia : 1;
uint8_t single_tap : 1;
uint8_t double_tap : 1;
uint8_t _6d_ia : 1;
uint8_t sleep_change_ia : 1;
uint8_t not_used_01 : 2;
} lis2dw12_all_int_src_t;

typedef struct
{
lis2dw12_status_dup_t status_dup;
lis2dw12_wake_up_src_t wake_up_src;
lis2dw12_tap_src_t tap_src;
lis2dw12_sixd_src_t sixd_src;
lis2dw12_all_int_src_t all_int_src;
} lis2dw12_all_sources_t;

static_assert(5 == sizeof(lis2dw12_all_sources_t),
"lis2dw12_all_sources changed size or there is padding");

typedef struct {
accel_state_en_t state;
// This is added to the state, because the config is written to and read back, both at boot-up
// and when changing the sleep state
bool is_booting_up;
accel_error_en_t error;
accel_interrupt_en_t interrupt_type;
accel_data_st_t data;
accel_data_st_t prev_data_buffer;
uint16_t device_addr;

struct device_app_st    *device_app;
i2c_arb_app_st_t        *i2c_arb_app;
i2c_arb_req_st_t        request;

lis2dw12_all_sources_t  all_sources;

int                     write_config_step;
int                     rx_time_0;
uint8_t                 raw_pl_status;
uint8_t                 raw_ff_mt_src;
uint8_t                 raw_xyz_array[2 * ACCEL_NUM_COOR]; // 2 registers per coordinate

} accel_app_st_t;

// Functions ---------------------------------------------------------------------------------------

bool AccelIsBootupComplete(accel_app_st_t *accel_app);
void AccelExtiIsr(accel_app_st_t *accel_app, accel_interrupt_en_t interr_type);
float AccelResolutionToMg(accel_app_st_t *accel_app,
int16_t lsb, accel_full_scale_en_t full_scale,
bool high_performance_mode,accel_resolution_en_t res);
float AccelCalculateMagnitude(const accel_data_st_t *accel_data);
void AccelStartBoot(accel_app_st_t *accel_app);
void AccelWriteConfig(accel_app_st_t *accel_app);
void AccelReadConfig(accel_app_st_t *accel_app);
void AccelReadAllSources(accel_app_st_t *accel_app);
void AccelAnalyzeAllSources(accel_app_st_t *accel_app);
void AccelReadXYZ(accel_app_st_t *accel_app);
void AccelAnalyzeXYZ(accel_app_st_t *accel_app);
void AccelEventHandler(accel_app_st_t *accel_app);
void AccelChangeSleepstate(accel_app_st_t *accel_app, uint8_t sleepstate_config);
void AccelerometerSelfCheck(struct device_app_st *device_app);
void AccelInit(accel_app_st_t *accel_app, i2c_arb_app_st_t *arb,
struct device_app_st *device_app);

#endif
#endif // ACCELDRIVER_H

++++++++++++++++++++++++++++++++++++++Lis2dw12AccelDriver.cpp:
//! \file
//! \author Eric Huang
//! \date May-2022
//! \copyright Copyright (c) 2022 by Epocal. All rights reserved.
//!
//! THIS SOURCE CODE CONTAINS CONFIDENTIAL INFORMATION THAT IS OWNED BY EPOCAL,
//! AND MAY NOT BE COPIED, DISCLOSED OR OTHERWISE USED WITHOUT
//! THE EXPRESS WRITTEN CONSENT OF EPOCAL.
//!
//! \brief Accelerometer Driver

// Includes ----------------------------------------------------------------------------------------

#include "i2c.h"
#include "DeviceModelCmn.h"
#include "DeviceSelfTestController.h"
#include "DeviceTestController.h"
#include "Lis2dw12AccelDriver.h"
#include "I2cArbiterCmn.h"
#include "InterfaceToHost.h"
#include "Ng.Ntf.Accel.h"
#include "SwCommunicationEvents.h"
#include "ExternalInterrupt.h"
#include "DevicePeripheralMonitorController.h"

// Accel Header Guard ------------------------------------------------------------------------------
#ifdef ACCEL_LIS2DW12

// Constants ---------------------------------------------------------------------------------------

#define ACCEL_GRAVITY 0.00981 // ms^2 per mg unit
#define ACCEL_MAX_READ_TIME 100

#define ACCEL_NUM_CONFIG_WRITES 5

#define ACCEL_ON_TIMEOUT 1
#define ACCEL_ON_TRIALS 10
#define ACCEL_REG_SIZE 1
#define ACCEL_UINT8_T_SIZE 1
#define ACCEL_XYZ_ADR 0x01
#define ACCEL_NUM_MAX_I2C_XFER_ATTEMPTS 3

// register locations
#define LIS2DW12_OUT_T_L 0x0DU
#define LIS2DW12_OUT_T_H 0x0EU
#define LIS2DW12_WHO_AM_I 0x0FU
#define LIS2DW12_CTRL1 0x20U
#define LIS2DW12_CTRL2 0x21U
#define LIS2DW12_CTRL3 0x22U
#define LIS2DW12_CTRL4_INT1_PAD_CTRL 0x23U
#define LIS2DW12_CTRL5_INT2_PAD_CTRL 0x24U
#define LIS2DW12_CTRL6 0x25U
#define LIS2DW12_OUT_T 0x26U
#define LIS2DW12_STATUS 0x27U
#define LIS2DW12_OUT_X_L 0x28U
#define LIS2DW12_OUT_X_H 0x29U
#define LIS2DW12_OUT_Y_L 0x2AU
#define LIS2DW12_OUT_Y_H 0x2BU
#define LIS2DW12_OUT_Z_L 0x2CU
#define LIS2DW12_OUT_Z_H 0x2DU
#define LIS2DW12_FIFO_CTRL 0x2EU
#define LIS2DW12_FIFO_SAMPLES 0x2FU
#define LIS2DW12_TAP_THS_X 0x30U
#define LIS2DW12_TAP_THS_Y 0x31U
#define LIS2DW12_TAP_THS_Z 0x32U
#define LIS2DW12_INT_DUR 0x33U
#define LIS2DW12_WAKE_UP_THS 0x34U
#define LIS2DW12_WAKE_UP_DUR 0x35U
#define LIS2DW12_FREE_FALL 0x36U
#define LIS2DW12_STATUS_DUP 0x37U
#define LIS2DW12_WAKE_UP_SRC 0x38U
#define LIS2DW12_TAP_SRC 0x39U
#define LIS2DW12_SIXD_SRC 0x3AU
#define LIS2DW12_ALL_INT_SRC 0x3BU
#define LIS2DW12_X_OFS_USR 0x3CU
#define LIS2DW12_Y_OFS_USR 0x3DU
#define LIS2DW12_Z_OFS_USR 0x3EU
#define LIS2DW12_CTRL_REG7 0x3FU

// Globals -----------------------------------------------------------------------------------------

// singleton inline functions to help choose configurations
inline uint8_t ReturnFullScaleConfig(uint8_t prev_config, accel_full_scale_en_t full_scale) {
return prev_config & 0xCF | (uint8_t)full_scale;
}

inline uint8_t ReturnResolutionConfig(uint8_t prev_config,
accel_resolution_en_t res,
accel_resolution_en_t sleep_power_mode) {
return prev_config & 0xF0 | (uint8_t)res | (uint8_t)sleep_power_mode;
}

// Command arrays to send during config
uint8_t accel_config_cmd_0[] = {
// 0x20: CTRL1 ODR = 400Hz, high performance (necessary for double tap)
// Selected low power mode when accelerometer sleep mode enable
// LP mode 2 saves the most power with a 14 bit resolution
ReturnResolutionConfig(0x74, ACCEL_RESOLUTION_HIGH,
ACCEL_RESOLUTION_LOW_POWER_2),
};

uint8_t accel_config_cmd_1[] = {
0xF8, // 0x23: CTRL4_INT1_PAD_CTRL, allow all interrupts except FIFO + data
0x40, // 0x24: CTRL5_INT2_PAD_CTRL, Enable sleep state change interrupt
};

uint8_t accel_config_cmd_2[] = {
#ifdef ACCEL_TIMING
// for testing the freefall timing, it helps to have a higher tap threshold
// (or else picking it up/dropping it triggers a wake up and tap before freefall)
0x45, // 0x30: TAP_THS_X, set 6D threshold to 60, and x threshold
0xE5, // 0x31: defaults for these registers
0xE5, // 0x32: TAP_THS_Z, enable all taps
#else
0x41, // 0x30: TAP_THS_X, set 6D threshold to 60, and x threshold
0xE1, // 0x31: defaults for these registers
0xE1, // 0x32: TAP_THS_Z, enable all taps
#endif
0x7F, // 0x33: INT_DUR, set to double tap
ACCEL_SLEEPSTATE_CONFIG_ON, // 0x34: Wake up threshold, enable single tap and sleep
0x42, // 0x35: Wake up duration, set it a bit longer to differentiate from taps
0xF8, // 0x36: Free_fall detection, duration 78mS, threshold 156 mG
};

uint8_t accel_sleepstate;

uint8_t accel_config_cmd_3[] = {
0x20 // 0x3f CTRL7: enable interrupts
};

uint8_t accel_config_cmd_4[] = {
// 0x25, CTRL6, at 2g selection
ReturnFullScaleConfig(0x04, ACCEL_FULL_SCALE_2G)
};

// Addresses of target registers for config
uint8_t accel_config_addr[ACCEL_NUM_CONFIG_WRITES] = {
LIS2DW12_CTRL1,
LIS2DW12_CTRL4_INT1_PAD_CTRL,
LIS2DW12_TAP_THS_X,
LIS2DW12_CTRL_REG7,
LIS2DW12_CTRL6,
};

// List of data ararys to send during config
uint8_t *accel_config_cmd[ACCEL_NUM_CONFIG_WRITES] = {
accel_config_cmd_0,
accel_config_cmd_1,
accel_config_cmd_2,
accel_config_cmd_3,
accel_config_cmd_4,
};

// Sizes of data arrays to send during config
uint16_t accel_config_size[ACCEL_NUM_CONFIG_WRITES] = {
sizeof(accel_config_cmd_0),
sizeof(accel_config_cmd_1),
sizeof(accel_config_cmd_2),
sizeof(accel_config_cmd_3),
sizeof(accel_config_cmd_4),
};

uint8_t accel_config_read_0[sizeof(accel_config_cmd_0)];
uint8_t accel_config_read_1[sizeof(accel_config_cmd_1)];
uint8_t accel_config_read_2[sizeof(accel_config_cmd_2)];
uint8_t accel_config_read_3[sizeof(accel_config_cmd_3)];
uint8_t accel_config_read_4[sizeof(accel_config_cmd_4)];

uint8_t *accel_config_read[ACCEL_NUM_CONFIG_WRITES] = {
accel_config_read_0,
accel_config_read_1,
accel_config_read_2,
accel_config_read_3,
accel_config_read_4,
};

// Macro Functions ---------------------------------------------------------------------------------

#define HAS_MAX_READ_TIME_ELAPSED(start_tick) (HAL_GetTick() - start_tick > ACCEL_MAX_READ_TIME)

// Accessors ---------------------------------------------------------------------------------------
bool AccelIsBootupComplete(accel_app_st_t *accel_app) {
return ACCEL_STATE_READY == accel_app->state;
}

// Functions ---------------------------------------------------------------------------------------

//! \brief singleton that generates an i2c request structure
//! \details Will also create a side effect of mutating accel_app's request
// as well as changing the handle to point to itself
//! \retval request generated
static i2c_arb_req_st_t *GenerateRequest(accel_app_st_t *accel_app, i2c_xfer_type_en_t xfer_t,
uint16_t reg_addr, uint8_t reg_size_bytes, uint8_t *data,
uint16_t data_size, i2c_arb_callback_fp_t CallbackFp)
{
i2c_arb_req_st_t *req = &accel_app->request;

req->xfer_type = xfer_t;
req->device_addr = ACCEL_I2C_DEVICE_ADDRESS;
req->reg_addr = reg_addr;
req->reg_size_bytes = reg_size_bytes;
req->data = data;
req->data_size_bytes = data_size;
req->Callback = CallbackFp;
req->driver = (void*)accel_app;
req->num_retry = ACCEL_NUM_MAX_I2C_XFER_ATTEMPTS;

return req;

}

//! Handles EXTI (freefall and orientation) interrupts
//! Begins gathering information to compose the notification message
void AccelExtiIsr(accel_app_st_t *accel_app, accel_interrupt_en_t interr_type)
{
// Only trigger an event if we're ready to process it. Otherwise, ignore
if(ACCEL_STATE_READY != accel_app->state) {
return;
}

// for timing tests with the lis2dw12 accelerometer 

#ifdef CP_UT
#ifdef ACCEL_TIMING
DebugSetTestPin1();
#endif
#endif

// Starts timer for the entire info gathering operation
accel_app->interrupt_type = interr_type;
accel_app->rx_time_0 = HAL_GetTick();
SwCommunicationEventTrigger(EVT_ACCEL);

}

//! Clears accelerometer state after interrupt analysis
static void AccelReset(accel_app_st_t *accel_app)
{
accel_app->data.orientation = ACCEL_ORIENTATION_UNDEFINED;
CLR_ALL_FLAGS(accel_app->data.motions);
accel_app->interrupt_type = ACCEL_INTERRUPT_NONE;
}

//! \brief Converts lsb's based on full scale and resolution to microgravities (signed)
//! \param lsb signed, full_scale and resolution parameters
//! \retval mg's signed of what the lsb represents
float AccelResolutionToMg(accel_app_st_t *accel_app,
int16_t lsb, accel_full_scale_en_t full_scale,
bool high_performance_mode,
accel_resolution_en_t lp_mode) {
float gravity_per_lsb = (accel_app->all_sources.status_dup.sleep_state_ia ||
!high_performance_mode) &&
lp_mode == ACCEL_RESOLUTION_LOW_POWER_1 ?
// 12 bits, 1/2^12 = 0.244, otherwise 14 bits, 1/2^14 = 0.061
0.244f : 0.061f;

// since we have a left aligned 12/14-bit number, shoved in a 16-bit number, 
// we need to shift based on how many by dividing by 2^4 or 2^2
float lsb_16bit_realignment = (accel_app->all_sources.status_dup.sleep_state_ia ||
                                !high_performance_mode) && 
                                lp_mode == ACCEL_RESOLUTION_LOW_POWER_1 ?
                                0.0625f : 0.25f;

float full_scale_gravities;

switch(full_scale) {
    case ACCEL_FULL_SCALE_2G:
        full_scale_gravities = 4;
        break;
    case ACCEL_FULL_SCALE_4G:
        full_scale_gravities = 8;
        break;
    case ACCEL_FULL_SCALE_8G:
        full_scale_gravities = 16;
        break;
    case ACCEL_FULL_SCALE_16G:
        full_scale_gravities = 32;
        break;
    default:
        // set it as 2g
        full_scale_gravities = 4;
        break;
}

return (float)lsb * lsb_16bit_realignment * full_scale_gravities * gravity_per_lsb;

}

float AccelCalculateMagnitude(const accel_data_st_t *accel_data)
{
double sum_of_squares = 0;

for(int i = 0; i < ACCEL_NUM_COOR; i++) {
    sum_of_squares += pow((double)accel_data->xyz_accel[i], 2);
}

return (float)sqrt(sum_of_squares);

}

// State Machine -----------------------------------------------------------------------------------

//! \brief Called after device has been detected on I2C bus
//! \param None
//! \retval None
void AccelDeviceReadyComplete(i2c_arb_req_st_t *req)
{
accel_app_st_t accel_app = (accel_app_st_t)(req->driver);

if(I2C_REQ_STATE_COMPLT == req->state) {
	// Initiate configuration process
    SwCommunicationEventTrigger(EVT_ACCEL);
}
else {
    accel_app->error = ACCEL_ERR_FAILED_TO_BOOT;
    accel_app->state = ACCEL_STATE_READY;
    accel_app->is_booting_up = false;
    SET_FLAG(accel_app->device_app->boot_up_app->err_flags, MC_BOOT_UP_ERR_ACCELEROMETER);
    DeviceBootUpActionComplete(accel_app->device_app);
}

}

void AccelStartBoot(accel_app_st_t *accel_app)
{
accel_app->is_booting_up = true;

// Check that accelerometer is ready, 1 second timeout
i2c_arb_req_st_t *req = GenerateRequest(accel_app, I2C_IS_DEVICE_READY, 0,
                                        0, NULL, 0,  AccelDeviceReadyComplete);

if(!I2cBusSendRequest(accel_app->i2c_arb_app, req)) {
    accel_app->error = ACCEL_ERR_ARB_QUEUE_FULL;
}

}

//! \brief Callback after a transaction on I2C bus has completed
//! \param None
//! \retval None
void AccelWriteConfigComplete(i2c_arb_req_st_t *req)
{
accel_app_st_t accel_app = (accel_app_st_t)(req->driver);

if(I2C_REQ_STATE_COMPLT == req->state) {
    accel_app->write_config_step++; // Go to next step if successful

    // Trigger an event that will write to the next config register
    SwCommunicationEventTrigger(EVT_ACCEL);
    
    // Return to the arbiter and remove the request from the queue
}
else {
    accel_app->error = ACCEL_ERR_CONFIG_WRITE;
    accel_app->state = ACCEL_STATE_READY;
    if(accel_app->is_booting_up){
        accel_app->is_booting_up = false;
        SET_FLAG(accel_app->device_app->boot_up_app->err_flags, MC_BOOT_UP_ERR_ACCELEROMETER);
        DeviceBootUpActionComplete(accel_app->device_app);
    }
}

}

//! Writes configuration data to accelerometer, and triggers an event if I2C bus is busy
void AccelWriteConfig(accel_app_st_t *accel_app)
{
// Attempt to write config data
if(ACCEL_NUM_CONFIG_WRITES > accel_app->write_config_step) {

    i2c_arb_req_st_t *req = GenerateRequest(accel_app, I2C_IT_WRITE,
                                            accel_config_addr[accel_app->write_config_step],
                                            ACCEL_REG_SIZE,
                                            accel_config_cmd[accel_app->write_config_step],
                                            accel_config_size[accel_app->write_config_step],
                                            AccelWriteConfigComplete);
    
    if(!I2cBusSendRequest(accel_app->i2c_arb_app, req)) {
        accel_app->error = ACCEL_ERR_ARB_QUEUE_FULL;
    }
}
else {
    // The PL interrupt is falsely raised during start-up due to the change in conditions
    // The CFG_CLEAR_INT state lets the handler clear this false alarm
    accel_app->write_config_step = 0;
	accel_app->state = ACCEL_STATE_CFG_READ;
    SwCommunicationEventTrigger(EVT_ACCEL);
}

}

//! \brief Callback after a transaction on I2C bus has completed
//! \param None
//! \retval None
void AccelReadConfigComplete(i2c_arb_req_st_t *req)
{
accel_app_st_t accel_app = (accel_app_st_t)(req->driver);

if(I2C_REQ_STATE_COMPLT != req->state) {
    // Check if out of maximum allocated read time
    if(HAS_MAX_READ_TIME_ELAPSED(accel_app->rx_time_0)) {
        accel_app->state = ACCEL_STATE_READY;
    }
    req->state = I2C_REQ_STATE_COMPLT;
    // Return to the arbiter and remove the request from the queue
}

accel_app->write_config_step++;
// Go to analyzing the orientation
SwCommunicationEventTrigger(EVT_ACCEL);

}

void AccelReadConfig(accel_app_st_t *accel_app)
{
// Attempt to read config data
if(ACCEL_NUM_CONFIG_WRITES > accel_app->write_config_step) {

    i2c_arb_req_st_t *req = GenerateRequest(accel_app, I2C_IT_READ,
                                            accel_config_addr[accel_app->write_config_step],
                                            ACCEL_REG_SIZE,
                                            accel_config_read[accel_app->write_config_step],
                                            accel_config_size[accel_app->write_config_step],
                                            AccelReadConfigComplete);
    
    if(!I2cBusSendRequest(accel_app->i2c_arb_app, req)) {
        accel_app->error = ACCEL_ERR_ARB_QUEUE_FULL;
    }
}
else {
    // The PL interrupt is falsely raised during start-up due to the change in conditions
    // The CFG_CLEAR_INT state lets the handler clear this false alarm
    accel_app->write_config_step = 0;
	accel_app->state = ACCEL_STATE_READY;
    if(accel_app->is_booting_up){
        accel_app->is_booting_up = false;
        DeviceBootUpActionComplete(accel_app->device_app);
    }
}  

}

//! \brief Callback after a transaction on I2C bus has completed
//! \param None
//! \retval None
void AccelReadAllSourcesComplete(i2c_arb_req_st_t *req)
{
accel_app_st_t accel_app = (accel_app_st_t)(req->driver);

if(I2C_REQ_STATE_COMPLT != req->state) {
    // Check if out of maximum allocated read time
    if(HAS_MAX_READ_TIME_ELAPSED(accel_app->rx_time_0)) {
        accel_app->all_sources = lis2dw12_all_sources_t {
            0x00, 0x00, 0x00, 0x00, 0x00
        }; // set all to unknown
    }
    
    req->state = I2C_REQ_STATE_COMPLT;
    // Return to the arbiter and remove the request from the queue
}

#ifdef ACCEL_TIMING
// for timing tests with the lis2dw12 accelerometer
DebugClearTestPin1();
#endif

accel_app->state = ACCEL_STATE_ANALYZE_ORIENTATION;
// Trigger an event that will go analyze the orientation info we just got
SwCommunicationEventTrigger(EVT_ACCEL);

}

void AccelReadAllSources(accel_app_st_t *accel_app)
{

i2c_arb_req_st_t *req = GenerateRequest(accel_app, I2C_IT_READ,
                                        LIS2DW12_STATUS_DUP,
                                        ACCEL_REG_SIZE,
                                        (uint8_t *)(&accel_app->all_sources), // cast it to an address
                                        sizeof(lis2dw12_all_sources_t), // read off 5 bytes
                                        AccelReadAllSourcesComplete);
    
if(!I2cBusSendRequest(accel_app->i2c_arb_app, req)) {
    accel_app->error = ACCEL_ERR_ARB_QUEUE_FULL;
}

}

//! Analyzes the 6id byte to obtain orientation
void AccelAnalyzeAllSources(accel_app_st_t *accel_app)
{
// TODO RK 09-May-2022: Fix orientation based on actual board layout (don't know how chip is
// oriented on board, not sure what nose down, nose up, etc. are); Refer to accelerometer
// documentation for info on the SIXD_SRC register's info pg. 24 of
// https://www.st.com/resource/en/application_note/an5038-lis2dw12-alwayson-3d-accelerometer-stmicroelectronics.pdf
// (a) straight up (b) starboard down (c) starboard up (d) upside down (e) nose up (f) nose down

lis2dw12_sixd_src_t sixd_src = accel_app->all_sources.sixd_src;

if (1 == sixd_src.zh) {
    accel_app->data.orientation = ACCEL_ORIENTATION_UPRIGHT;
} 
else if (1 == sixd_src.zl) {
    accel_app->data.orientation = ACCEL_ORIENTATION_UPSIDE_DOWN;
} 
else if (1 == sixd_src.yh) {
    accel_app->data.orientation = ACCEL_ORIENTATION_ON_RIGHTSIDE;
} 
else if (1 == sixd_src.yl) {
    accel_app->data.orientation = ACCEL_ORIENTATION_ON_LEFTSIDE;
} 
else if (1 == sixd_src.xh) {
    accel_app->data.orientation = ACCEL_ORIENTATION_NOSE_DOWN;
} 
else if (1 == sixd_src.xl) {
    accel_app->data.orientation = ACCEL_ORIENTATION_NOSE_UP;
} 
else {
    accel_app->data.orientation = ACCEL_ORIENTATION_UNKNOWN;
}

if (accel_app->interrupt_type == ACCEL_INTERRUPT_2) {
    if(accel_app-> all_sources.status_dup.sleep_state_ia) {
        accel_app->data.power_mode = ACCEL_POWERMODE_LOW;
    }
    else {
        accel_app->data.power_mode = ACCEL_POWERMODE_HIGH;
    }
    
    SendAccelPowerNotification(MasterGetConn(), &accel_app->data);
    accel_app->state = ACCEL_STATE_READY;
    AccelReset(accel_app);
    return;
}

SwCommunicationEventTrigger(EVT_ACCEL); // Trigger event to read freefall interrupt register

}

//! \brief Callback after a transaction on I2C bus has completed
//! \param None
//! \retval None
void AccelReadXYZComplete(i2c_arb_req_st_t *req)
{
accel_app_st_t accel_app = (accel_app_st_t)(req->driver);

if(I2C_REQ_STATE_COMPLT != req->state) {
    // Check if out of maximum allocated read time
    if(HAS_MAX_READ_TIME_ELAPSED(accel_app->rx_time_0)) {
        SET_FLAG(accel_app->data.motions, ACCEL_MOTION_UNKNOWN);
        accel_app->state = ACCEL_STATE_ANALYZE_XYZ;
    }
    req->state = I2C_REQ_STATE_COMPLT;
    // Return to the arbiter and remove the request from the queue
}
// Trigger event to either analyze the XYZ accelerations, or re-attempt read, or move on if
// timed out
SwCommunicationEventTrigger(EVT_ACCEL);

}

//! Reads the X, Y, Z acceleration registers
void AccelReadXYZ(accel_app_st_t *accel_app)
{
// Read acceleration data
i2c_arb_req_st_t req = GenerateRequest(accel_app,
I2C_IT_READ,
LIS2DW12_OUT_X_L,
ACCEL_REG_SIZE,
(uint8_t
)&accel_app->raw_xyz_array,
MEMBER_SIZE(accel_app_st_t, raw_xyz_array),
AccelReadXYZComplete);

if(!I2cBusSendRequest(accel_app->i2c_arb_app, req)) {
    accel_app->error = ACCEL_ERR_ARB_QUEUE_FULL;
}

}

//! Checks current XYZ acceleration values against the freefall-to-pickup threshold
void AccelAnalyzeXYZ(accel_app_st_t *accel_app)
{
if (1 == accel_app->all_sources.tap_src.single_tap){
SET_FLAG(accel_app->data.motions, ACCEL_MOTION_TAP);
}
if (1 == accel_app->all_sources.wake_up_src.wu_ia) {
SET_FLAG(accel_app->data.motions, ACCEL_MOTION_WAKEUP);
}
if (1 == accel_app->all_sources.wake_up_src.ff_ia) {
SET_FLAG(accel_app->data.motions, ACCEL_MOTION_FREEFALL);
}
if (1 == accel_app->all_sources.sixd_src._6d_ia){
SET_FLAG(accel_app->data.motions, ACCEL_MOTION_ORIENTATION_CHANGE);
}
if (ALL_FLAGS_CLEARED == accel_app->data.motions){
SET_FLAG(accel_app->data.motions, ACCEL_MOTION_UNKNOWN);
}

if(accel_app->data.power_mode != ACCEL_POWERMODE_DISABLED) {
    if(accel_app->all_sources.status_dup.sleep_state_ia) {
        accel_app->data.power_mode = ACCEL_POWERMODE_LOW;
    }
    else {
        accel_app->data.power_mode = ACCEL_POWERMODE_HIGH;
    }
}

// If motions was not read, clear acceleration values
if(IS_FLAG_SET(accel_app->data.motions, ACCEL_MOTION_UNKNOWN)) {
    for(int i = 0; i < ACCEL_NUM_COOR; i++) {
        accel_app->data.xyz_accel[i] = 0.0;
    }
}
else {
    // first, we need to concatenate the raw_xyz_array into a separate array (since two registers each)
    int16_t xyz_accel_concat[ACCEL_NUM_COOR];

    for (int i = 0; i < ACCEL_NUM_COOR; ++i) {
        // takes the high register and shifts it by 8 places, then add in low register
        xyz_accel_concat[i] = ((int16_t)accel_app->raw_xyz_array[i*2 + 1] * 256) + (int16_t)accel_app->raw_xyz_array[i*2];
    }

    // convert accelerations from eng. units to mg as per driver example
    float acceleration_mg[ACCEL_NUM_COOR];
    for (int i = 0; i < ACCEL_NUM_COOR; ++i) {
        // 1. the full scale setting (ctrl 6, or accel_config_4)
        // 2. if it's low power (lp) or high performance (ctrl 1, accel_config_cmd_0)
        acceleration_mg[i] = AccelResolutionToMg(accel_app,
                                                 xyz_accel_concat[i], 
                                                 ACCEL_FULL_SCALE_2G, 
                                                 HIGH_PERFORMANCE_MODE,
                                                 ACCEL_RESOLUTION_LOW_POWER_2);
    }
    
    // Convert accelerations to m/s/s
    for(int i = 0; i < ACCEL_NUM_COOR; i++) {
        accel_app->data.xyz_accel[i] = acceleration_mg[i] * ACCEL_GRAVITY;
    }
}

SAFE_MEMCPY(accel_app->prev_data_buffer, accel_app->data);
// RK 07-Nov-2018: The accelerometer interrupt is triggered once while
// booting up, after about 50 to 150 ms. For now, I'll just let the 
// notification go, because the CP doesn't take any action based on it 
// anyways. The accelerometer boots up along with the CP, so we are 
// confident that a false interrupt that occurs during boot-up won't 
// adversely affect a test.
SendAccelMotionNotification(MasterGetConn(), &accel_app->data);
DpmCollectAccelerometerData(accel_app->data);

// Cancel the test after the PMIs have been reported, so that we have them
// in the last PMI packet produced when cancelling the test.
if(DeviceIsTestInProgress(accel_app->device_app->patient_test)) {
    if (1 == accel_app->all_sources.wake_up_src.ff_ia) {            
        DeviceTestCancelDeviceError(accel_app->device_app, 
                                    DEVICE_RC_ACCEL_FREEFALL);
    }
    else if(accel_app->data.orientation != ACCEL_ORIENTATION_UPRIGHT) {
        DeviceTestCancelDeviceError(accel_app->device_app, 
                                    DEVICE_RC_ACCEL_WRONG_ORIENTATION);
    }
}
// for unit test purposes, we need to be able to read the data after it, so we'll skip the reset

#ifndef CP_UT
AccelReset(accel_app);
#else
// we have a timing test for unit tests with lis2dw12 accelerometer
#ifdef ACCEL_TIMING
DebugClearTestPin1();
#endif
#endif
}

//! Handles accelerometer events
void AccelEventHandler(accel_app_st_t *accel_app)
{
switch(accel_app->state) {
case ACCEL_STATE_PWR_ON:
accel_app->state = ACCEL_STATE_CFG_WRITE;
// Fall-through
case ACCEL_STATE_CFG_WRITE:
AccelWriteConfig(accel_app);
break;

case ACCEL_STATE_CFG_READ:
    AccelReadConfig(accel_app);
    break;

case ACCEL_STATE_READY:
    accel_app->state = ACCEL_STATE_READ_INTERRUPT; 
    // fall-through

case ACCEL_STATE_READ_INTERRUPT:
    CLR_ALL_FLAGS(accel_app->data.motions);
    AccelReadAllSources(accel_app);
    break;

case ACCEL_STATE_ANALYZE_ORIENTATION:
    accel_app->state = ACCEL_STATE_READ_XYZ;
    AccelAnalyzeAllSources(accel_app);
    break;
    
case ACCEL_STATE_READ_XYZ:
    accel_app->state = ACCEL_STATE_ANALYZE_XYZ;
    AccelReadXYZ(accel_app);
    break;

case ACCEL_STATE_ANALYZE_XYZ:
    accel_app->state = ACCEL_STATE_READY;
    AccelAnalyzeXYZ(accel_app);
    break;

default: // Do nothing
    break;
}

}

// Manually enable and reenable sleep mode during tests
void AccelChangeSleepstate(accel_app_st_t *accel_app, uint8_t sleepstate_config)
{
accel_sleepstate = sleepstate_config;
i2c_arb_req_st_t *req = GenerateRequest(accel_app,
I2C_IT_WRITE,
LIS2DW12_WAKE_UP_THS,
ACCEL_REG_SIZE,
&accel_sleepstate,
sizeof(uint8_t),
AccelWriteConfigComplete);

accel_app->state = ACCEL_STATE_CFG_READ;
accel_app->is_booting_up = false;
memset(accel_config_read_2, 0, sizeof(accel_config_read_2));

if(accel_sleepstate == ACCEL_SLEEPSTATE_CONFIG_OFF){
    accel_app->data.power_mode = ACCEL_POWERMODE_DISABLED;
}
else {
    accel_app->data.power_mode = ACCEL_POWERMODE_UNKNOWN;
}

if(!I2cBusSendRequest(accel_app->i2c_arb_app, req)) {
    accel_app->error = ACCEL_ERR_ARB_QUEUE_FULL;
}

}

void AccelerometerSelfCheck(device_app_st_t *device_app)
{
if(device_app->main_accel_app->prev_data_buffer.orientation !=
ACCEL_ORIENTATION_UPRIGHT) {
device_app->eqc_app.eqc_results.accelerometer =
ACTION_FAILED_COMPLETED;
}
else {
device_app->eqc_app.eqc_results.accelerometer = ACTION_SUCCESSFUL;
}

DeviceSelfCheckActionComplete(device_app);

}

// Initialization ----------------------------------------------------------------------------------

//! Accelerometer initialization process
void AccelInit(accel_app_st_t *accel_app, i2c_arb_app_st_t *arb,
device_app_st_t *device_app)
{
// Initialize variables
accel_app->state = ACCEL_STATE_PWR_ON;
accel_app->is_booting_up = false;
accel_app->error = ACCEL_ERR_NONE;
accel_app->interrupt_type = ACCEL_INTERRUPT_NONE;
accel_app->data.orientation = ACCEL_ORIENTATION_UNDEFINED;
CLR_ALL_FLAGS(accel_app->data.motions);
accel_app->device_addr = ACCEL_I2C_DEVICE_ADDRESS;
accel_app->i2c_arb_app = arb;
accel_app->device_app = device_app;
accel_app->write_config_step = 0;

AccelExtiIrqCallback.Callback = AccelExtiIsr;
AccelExtiIrqCallback.CallObject = accel_app;  

// Register event
SwCommunicationEventRegister(EVT_ACCEL, (call_routine_fp_t)AccelEventHandler,
                             (call_obj_t)accel_app);

}

#endif

CTRL7 INTERRUPTS_ENABLE missing validation on interrupt route set

lis2dw12_pin_int1_route_set() and lis2dw12_pin_int2_route_set() functions besides configuring interrupt routing also set/reset global interrupts enable flag in CTRL7. The issue is not all interrupts are checked and it is possible to have the a valid interrupt flag for specific case enabled while global interrupts enable will be reset.

Here are the conditions for lis2dw12_pin_int1_route_set()

  if (ret == 0)
  {
    if ((val->int1_tap |
         val->int1_ff |
         val->int1_wu |
         val->int1_single_tap |
         val->int1_6d |
         ctrl5_int2_pad_ctrl.int2_sleep_state |
         ctrl5_int2_pad_ctrl.int2_sleep_chg) != PROPERTY_DISABLE)
    {
      reg.interrupts_enable = PROPERTY_ENABLE;
    }

    else
    {
      reg.interrupts_enable = PROPERTY_DISABLE;
    }

lis2dw12_pin_int2_route_set() :

  if (ret == 0)
  {
    if ((ctrl4_int1_pad_ctrl.int1_tap |
         ctrl4_int1_pad_ctrl.int1_ff |
         ctrl4_int1_pad_ctrl.int1_wu |
         ctrl4_int1_pad_ctrl.int1_single_tap |
         ctrl4_int1_pad_ctrl.int1_6d |
         val->int2_sleep_state | val->int2_sleep_chg) != PROPERTY_DISABLE)
    {
      reg.interrupts_enable = PROPERTY_ENABLE;
    }

    else
    {
      reg.interrupts_enable = PROPERTY_DISABLE;
    }

Consider scenario where INT1 is configured for wake-up (int1_wu) and INT2 for fifo threshold (int2_fth). Now if the user wants to disable int1_wu, the int2_fth won't be checked by the condition in the code snippet above. This results in resetting CTRL7 interrupts enable flag even though there is an interrupt enabled!

In my opinion the interrupts should be disabled only when both CTRL4_INT1_PAD_CTRL or CTRL5_INT2_PAD_CTRL are zero.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.