/* * Driver for LIS3DH 3-axes digital accelerometer connected to I2C or SPI. * * This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos) * [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32 * and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux * based systems using a wrapper library for ESP8266 functions. * * --------------------------------------------------------------------------- * * The BSD License (3-clause license) * * Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * The information provided is believed to be accurate and reliable. The * copyright holder assumes no responsibility for the consequences of use * of such information nor for any infringement of patents or other rights * of third parties which may result from its use. No license is granted by * implication or otherwise under any patent or patent rights of the copyright * holder. */ #include #include #include "lis3dh.h" #if defined(LIS3DH_DEBUG_LEVEL_2) #define debug(s, f, ...) printf("%s %s: " s "\n", "LIS3DH", f, ## __VA_ARGS__) #define debug_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "LIS3DH", f, d->bus, d->addr, ## __VA_ARGS__) #else #define debug(s, f, ...) #define debug_dev(s, f, d, ...) #endif #if defined(LIS3DH_DEBUG_LEVEL_1) || defined(LIS3DH_DEBUG_LEVEL_2) #define error(s, f, ...) printf("%s %s: " s "\n", "LIS3DH", f, ## __VA_ARGS__) #define error_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "LIS3DH", f, d->bus, d->addr, ## __VA_ARGS__) #else #define error(s, f, ...) #define error_dev(s, f, d, ...) #endif // register addresses #define LIS3DH_REG_STATUS_AUX 0x07 #define LIS3DH_REG_OUT_ADC1_L 0x08 #define LIS3DH_REG_OUT_ADC1_H 0x09 #define LIS3DH_REG_OUT_ADC2_L 0x0a #define LIS3DH_REG_OUT_ADC2_H 0x0b #define LIS3DH_REG_OUT_ADC3_L 0x0c #define LIS3DH_REG_OUT_ADC3_H 0x0d #define LIS3DH_REG_INT_COUNTER 0x0e #define LIS3DH_REG_WHO_AM_I 0x0f #define LIS3DH_REG_TEMP_CFG 0x1f #define LIS3DH_REG_CTRL1 0x20 #define LIS3DH_REG_CTRL2 0x21 #define LIS3DH_REG_CTRL3 0x22 #define LIS3DH_REG_CTRL4 0x23 #define LIS3DH_REG_CTRL5 0x24 #define LIS3DH_REG_CTRL6 0x25 #define LIS3DH_REG_REFERENCE 0x26 #define LIS3DH_REG_STATUS 0x27 #define LIS3DH_REG_OUT_X_L 0x28 #define LIS3DH_REG_OUT_X_H 0x29 #define LIS3DH_REG_OUT_Y_L 0x2a #define LIS3DH_REG_OUT_Y_H 0x2b #define LIS3DH_REG_OUT_Z_L 0x2c #define LIS3DH_REG_OUT_Z_H 0x2d #define LIS3DH_REG_FIFO_CTRL 0x2e #define LIS3DH_REG_FIFO_SRC 0x2f #define LIS3DH_REG_INT1_CFG 0x30 #define LIS3DH_REG_INT1_SRC 0x31 #define LIS3DH_REG_INT1_THS 0x32 #define LIS3DH_REG_INT1_DUR 0x33 #define LIS3DH_REG_INT2_CFG 0x34 #define LIS3DH_REG_INT2_SRC 0x35 #define LIS3DH_REG_INT2_THS 0x36 #define LIS3DH_REG_INT2_DUR 0x37 #define LIS3DH_REG_CLICK_CFG 0x38 #define LIS3DH_REG_CLICK_SRC 0x39 #define LIS3DH_REG_CLICK_THS 0x3a #define LIS3DH_REG_TIME_LIMIT 0x3b #define LIS3DH_REG_TIME_LATENCY 0x3c #define LIS3DH_REG_TIME_WINDOW 0x3d // register structure definitions struct lis3dh_reg_status { uint8_t XDA :1; // STATUS<0> X axis new data available uint8_t YDA :1; // STATUS<1> Y axis new data available uint8_t ZDA :1; // STATUS<2> Z axis new data available uint8_t ZYXDA :1; // STATUS<3> X, Y and Z axis new data available uint8_t XOR :1; // STATUS<4> X axis data overrun uint8_t YOR :1; // STATUS<5> Y axis data overrun uint8_t ZOR :1; // STATUS<6> Z axis data overrun uint8_t ZYXOR :1; // STATUS<7> X, Y and Z axis data overrun }; #define LIS3DH_ANY_DATA_READY 0x0f // LIS3DH_REG_STATUS<3:0> struct lis3dh_reg_ctrl1 { uint8_t Xen :1; // CTRL1<0> X axis enable uint8_t Yen :1; // CTRL1<1> Y axis enable uint8_t Zen :1; // CTRL1<2> Z axis enable uint8_t LPen :1; // CTRL1<3> Low power mode enable uint8_t ODR :4; // CTRL1<7:4> Data rate selection }; struct lis3dh_reg_ctrl2 { uint8_t HPIS1 :1; // CTRL2<0> HPF enabled for AOI on INT2 uint8_t HPIS2 :1; // CTRL2<1> HPF enabled for AOI on INT2 uint8_t HPCLICK :1; // CTRL2<2> HPF enabled for CLICK uint8_t FDS :1; // CTRL2<3> Filter data selection uint8_t HPCF :2; // CTRL2<5:4> HPF cutoff frequency uint8_t HPM :2; // CTRL2<7:6> HPF mode }; struct lis3dh_reg_ctrl3 { uint8_t unused :1; // CTRL3<0> unused uint8_t I1_OVERRUN :1; // CTRL3<1> FIFO Overrun interrupt on INT1 uint8_t I1_WTM1 :1; // CTRL3<2> FIFO Watermark interrupt on INT1 uint8_t IT_DRDY2 :1; // CTRL3<3> DRDY2 (ZYXDA) interrupt on INT1 uint8_t IT_DRDY1 :1; // CTRL3<4> DRDY1 (321DA) interrupt on INT1 uint8_t I1_AOI2 :1; // CTRL3<5> AOI2 interrupt on INT1 uint8_t I1_AOI1 :1; // CTRL3<6> AOI1 interrupt on INT1 uint8_t I1_CLICK :1; // CTRL3<7> CLICK interrupt on INT1 }; struct lis3dh_reg_ctrl4 { uint8_t SIM :1; // CTRL4<0> SPI serial interface selection uint8_t ST :2; // CTRL4<2:1> Self test enable uint8_t HR :1; // CTRL4<3> High resolution output mode uint8_t FS :2; // CTRL4<5:4> Full scale selection uint8_t BLE :1; // CTRL4<6> Big/litle endian data selection uint8_t BDU :1; // CTRL4<7> Block data update }; struct lis3dh_reg_ctrl5 { uint8_t D4D_INT2 :1; // CTRL5<0> 4D detection enabled on INT1 uint8_t LIR_INT2 :1; // CTRL5<1> Latch interrupt request on INT1 uint8_t D4D_INT1 :1; // CTRL5<2> 4D detection enabled on INT2 uint8_t LIR_INT1 :1; // CTRL5<3> Latch interrupt request on INT1 uint8_t unused :2; // CTRL5<5:4> unused uint8_t FIFO_EN :1; // CTRL5<6> FIFO enabled uint8_t BOOT :1; // CTRL5<7> Reboot memory content }; struct lis3dh_reg_ctrl6 { uint8_t unused1 :1; // CTRL6<0> unused uint8_t H_LACTIVE:1; // CTRL6<1> Interrupt polarity uint8_t unused2 :1; // CTRL6<2> unused uint8_t I2_ACT :1; // CTRL6<3> ? uint8_t I2_BOOT :1; // CTRL6<4> ? uint8_t I2_AOI2 :1; // CTRL6<5> AOI2 interrupt on INT1 uint8_t I2_AOI1 :1; // CTRL6<6> AOI1 interrupt on INT1 uint8_t I2_CLICK :1; // CTRL6<7> CLICK interrupt on INT2 }; struct lis3dh_reg_fifo_ctrl { uint8_t FTH :5; // FIFO_CTRL<4:0> FIFO threshold uint8_t TR :1; // FIFO_CTRL<5> Trigger selection INT1 / INT2 uint8_t FM :2; // FIFO_CTRL<7:6> FIFO mode }; struct lis3dh_reg_fifo_src { uint8_t FFS :5; // FIFO_SRC<4:0> FIFO samples stored uint8_t EMPTY :1; // FIFO_SRC<5> FIFO is empty uint8_t OVRN_FIFO :1; // FIFO_SRC<6> FIFO buffer full uint8_t WTM :1; // FIFO_SRC<7> FIFO content exceeds watermark }; struct lis3dh_reg_intx_cfg { uint8_t XLIE :1; // INTx_CFG<0> X axis below threshold enabled uint8_t XHIE :1; // INTx_CFG<1> X axis above threshold enabled uint8_t YLIE :1; // INTx_CFG<2> Y axis below threshold enabled uint8_t YHIE :1; // INTx_CFG<3> Y axis above threshold enabled uint8_t ZLIE :1; // INTx_CFG<4> Z axis below threshold enabled uint8_t ZHIE :1; // INTx_CFG<5> Z axis above threshold enabled uint8_t SIXD :1; // INTx_CFG<6> 6D/4D orientation detecetion enabled uint8_t AOI :1; // INTx_CFG<7> AND/OR combination of interrupt events }; struct lis3dh_reg_intx_src { uint8_t XL :1; // INTx_SRC<0> X axis below threshold enabled uint8_t XH :1; // INTx_SRC<1> X axis above threshold enabled uint8_t YL :1; // INTx_SRC<2> Y axis below threshold enabled uint8_t YH :1; // INTx_SRC<3> Y axis above threshold enabled uint8_t ZL :1; // INTx_SRC<4> Z axis below threshold enabled uint8_t ZH :1; // INTx_SRC<5> Z axis above threshold enabled uint8_t IA :1; // INTx_SRC<6> Interrupt active uint8_t unused:1; // INTx_SRC<7> unused }; struct lis3dh_reg_click_cfg { uint8_t XS :1; // CLICK_CFG<0> X axis single click enabled uint8_t XD :1; // CLICK_CFG<1> X axis double click enabled uint8_t YS :1; // CLICK_CFG<2> Y axis single click enabled uint8_t YD :1; // CLICK_CFG<3> Y axis double click enabled uint8_t ZS :1; // CLICK_CFG<4> Z axis single click enabled uint8_t ZD :1; // CLICK_CFG<5> Z axis double click enabled uint8_t unused:2; // CLICK_CFG<7:6> unused }; /** Forward declaration of functions for internal use */ static bool lis3dh_reset (lis3dh_sensor_t* dev); static bool lis3dh_is_available(lis3dh_sensor_t* dev); static bool lis3dh_i2c_read (lis3dh_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); static bool lis3dh_i2c_write (lis3dh_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); static bool lis3dh_spi_read (lis3dh_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); static bool lis3dh_spi_write (lis3dh_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); #define msb_lsb_to_type(t,b,o) (t)(((t)b[o] << 8) | b[o+1]) #define lsb_msb_to_type(t,b,o) (t)(((t)b[o+1] << 8) | b[o]) #define lsb_to_type(t,b,o) (t)(b[o]) #define lis3dh_update_reg(dev,addr,type,elem,value) \ { \ struct type __reg; \ if (!lis3dh_reg_read (dev, (addr), (uint8_t*)&__reg, 1)) \ return false; \ __reg.elem = (value); \ if (!lis3dh_reg_write (dev, (addr), (uint8_t*)&__reg, 1)) \ return false; \ } lis3dh_sensor_t* lis3dh_init_sensor (uint8_t bus, uint8_t addr, uint8_t cs) { lis3dh_sensor_t* dev; if ((dev = malloc (sizeof(lis3dh_sensor_t))) == NULL) return NULL; // init sensor data structure dev->bus = bus; dev->addr = addr; dev->cs = cs; dev->error_code = LIS3DH_OK; dev->scale = lis3dh_scale_2_g; dev->fifo_mode = lis3dh_bypass; dev->fifo_first = true; // if addr==0 then SPI is used and has to be initialized if (!addr && !spi_device_init (bus, cs)) { error_dev ("Could not initialize SPI interface.", __FUNCTION__, dev); free (dev); return NULL; } // check availability of the sensor if (!lis3dh_is_available (dev)) { error_dev ("Sensor is not available.", __FUNCTION__, dev); free (dev); return NULL; } // reset the sensor if (!lis3dh_reset(dev)) { error_dev ("Could not reset the sensor device.", __FUNCTION__, dev); free (dev); return NULL; } lis3dh_update_reg (dev, LIS3DH_REG_CTRL4, lis3dh_reg_ctrl4, FS, lis3dh_scale_2_g); lis3dh_update_reg (dev, LIS3DH_REG_CTRL4, lis3dh_reg_ctrl4, BDU, 1); return dev; } bool lis3dh_set_mode (lis3dh_sensor_t* dev, lis3dh_odr_mode_t odr, lis3dh_resolution_t res, bool x, bool y, bool z) { if (!dev) return false; dev->error_code = LIS3DH_OK; dev->res = res; struct lis3dh_reg_ctrl1 reg; uint8_t old_odr; // read current register values if (!lis3dh_reg_read (dev, LIS3DH_REG_CTRL1, (uint8_t*)®, 1)) return false; old_odr = reg.ODR; // set mode reg.Xen = x; reg.Yen = y; reg.Zen = z; reg.ODR = odr; reg.LPen = (res == lis3dh_low_power); lis3dh_update_reg (dev, LIS3DH_REG_CTRL4, lis3dh_reg_ctrl4, HR, (res == lis3dh_high_res)); if (!lis3dh_reg_write (dev, LIS3DH_REG_CTRL1, (uint8_t*)®, 1)) return false; // if sensor was in power down mode it takes at least 100 ms to start in another mode if (old_odr == lis3dh_power_down && odr != lis3dh_power_down) vTaskDelay (15); return false; } bool lis3dh_set_scale (lis3dh_sensor_t* dev, lis3dh_scale_t scale) { if (!dev) return false; dev->error_code = LIS3DH_OK; dev->scale = scale; // read CTRL4 register and write scale lis3dh_update_reg (dev, LIS3DH_REG_CTRL4, lis3dh_reg_ctrl4, FS, scale); return true; } bool lis3dh_set_fifo_mode (lis3dh_sensor_t* dev, lis3dh_fifo_mode_t mode, uint8_t thresh, lis3dh_int_signal_t trigger) { if (!dev) return false; dev->error_code = LIS3DH_OK; dev->fifo_mode = mode; // read CTRL5 register and write FIFO_EN flag lis3dh_update_reg (dev, LIS3DH_REG_CTRL5, lis3dh_reg_ctrl5, FIFO_EN, mode != lis3dh_bypass); struct lis3dh_reg_fifo_ctrl fifo_ctrl = { .FTH = thresh, .TR = trigger, .FM = mode, }; // write FIFO_CTRL register if (!lis3dh_reg_write (dev, LIS3DH_REG_FIFO_CTRL, (uint8_t*)&fifo_ctrl, 1)) return false; return true; } bool lis3dh_new_data (lis3dh_sensor_t* dev) { if (!dev) return false; dev->error_code = LIS3DH_OK; if (dev->fifo_mode == lis3dh_bypass) { struct lis3dh_reg_status status; if (!lis3dh_reg_read (dev, LIS3DH_REG_STATUS, (uint8_t*)&status, 1)) { error_dev ("Could not get sensor status", __FUNCTION__, dev); return false; } return status.ZYXDA; } else { struct lis3dh_reg_fifo_src fifo_src; if (!lis3dh_reg_read (dev, LIS3DH_REG_FIFO_SRC, (uint8_t*)&fifo_src, 1)) { error_dev ("Could not get fifo source register data", __FUNCTION__, dev); return false; } return !fifo_src.EMPTY; } } /** * Scaling factors for the conversion of raw sensor data to floating point g * values. Scaling factors are from mechanical characteristics in datasheet. * * scale/sensitivity resolution * +-1g 1 mg/digit * +-2g 2 mg/digit * +-4g 4 mg/digit * +-16g 12 mg/digit */ const static double LIS3DH_SCALES[4] = { 0.001, 0.002, 0.004, 0.012 }; bool lis3dh_get_float_data (lis3dh_sensor_t* dev, lis3dh_float_data_t* data) { if (!dev || !data) return false; lis3dh_raw_data_t raw; if (!lis3dh_get_raw_data (dev, &raw)) return false; data->ax = LIS3DH_SCALES[dev->scale] * (raw.ax >> 4); data->ay = LIS3DH_SCALES[dev->scale] * (raw.ay >> 4); data->az = LIS3DH_SCALES[dev->scale] * (raw.az >> 4); return true; } uint8_t lis3dh_get_float_data_fifo (lis3dh_sensor_t* dev, lis3dh_float_data_fifo_t data) { if (!dev || !data) return false; lis3dh_raw_data_fifo_t raw; uint8_t num = lis3dh_get_raw_data_fifo (dev, raw); for (int i = 0; i < num; i++) { data[i].ax = LIS3DH_SCALES[dev->scale] * (raw[i].ax >> 4); data[i].ay = LIS3DH_SCALES[dev->scale] * (raw[i].ay >> 4); data[i].az = LIS3DH_SCALES[dev->scale] * (raw[i].az >> 4); } return num; } bool lis3dh_get_raw_data (lis3dh_sensor_t* dev, lis3dh_raw_data_t* raw) { if (!dev || !raw) return false; dev->error_code = LIS3DH_OK; // abort if not in bypass mode if (dev->fifo_mode != lis3dh_bypass) { dev->error_code = LIS3DH_SENSOR_IN_BYPASS_MODE; error_dev ("Sensor is in FIFO mode, use lis3dh_get_*_data_fifo to get data", __FUNCTION__, dev); return false; } // read raw data sample if (!lis3dh_reg_read (dev, LIS3DH_REG_OUT_X_L, (uint8_t*)raw, 6)) { error_dev ("Could not get raw data sample", __FUNCTION__, dev); dev->error_code |= LIS3DH_GET_RAW_DATA_FAILED; return false; } return true; } uint8_t lis3dh_get_raw_data_fifo (lis3dh_sensor_t* dev, lis3dh_raw_data_fifo_t raw) { if (!dev) return 0; dev->error_code = LIS3DH_OK; // in bypass mode, use lis3dh_get_raw_data to return one sample if (dev->fifo_mode == lis3dh_bypass) return lis3dh_get_raw_data (dev, raw) ? 1 : 0; struct lis3dh_reg_fifo_src fifo_src; // read FIFO state if (!lis3dh_reg_read (dev, LIS3DH_REG_FIFO_SRC, (uint8_t*)&fifo_src, 1)) { error_dev ("Could not get fifo source register data", __FUNCTION__, dev); return 0; } // if nothing is in the FIFO, just return with 0 if (fifo_src.EMPTY) return 0; uint8_t samples = fifo_src.FFS + (fifo_src.OVRN_FIFO ? 1 : 0); // read samples from FIFO for (int i = 0; i < samples; i++) if (!lis3dh_reg_read (dev, LIS3DH_REG_OUT_X_L, (uint8_t*)&raw[i], 6)) { error_dev ("Could not get raw data samples", __FUNCTION__, dev); dev->error_code |= LIS3DH_GET_RAW_DATA_FIFO_FAILED; return i; } lis3dh_reg_read (dev, LIS3DH_REG_FIFO_SRC, (uint8_t*)&fifo_src, 1); // if FFS is not 0 after all samples read, ODR is higher than fetching rate if (fifo_src.FFS) { dev->error_code = LIS3DH_ODR_TOO_HIGH; error_dev ("New samples stored in FIFO while reading, " "output data rate (ODR) too high", __FUNCTION__, dev); return 0; } if (dev->fifo_mode == lis3dh_fifo && samples == 32) { // clean FIFO (see app note) lis3dh_update_reg (dev, LIS3DH_REG_FIFO_CTRL, lis3dh_reg_fifo_ctrl, FM, lis3dh_bypass); lis3dh_update_reg (dev, LIS3DH_REG_FIFO_CTRL, lis3dh_reg_fifo_ctrl, FM, lis3dh_fifo); } return samples; } bool lis3dh_enable_int (lis3dh_sensor_t* dev, lis3dh_int_type_t type, lis3dh_int_signal_t signal, bool value) { if (!dev) return false; dev->error_code = LIS3DH_OK; struct lis3dh_reg_ctrl3 ctrl3; struct lis3dh_reg_ctrl6 ctrl6; uint8_t* reg = NULL; uint8_t addr; // determine the addr of the register to change if (type == lis3dh_int_data_ready || type == lis3dh_int_fifo_watermark || type == lis3dh_int_fifo_overrun) { reg = (uint8_t*)&ctrl3; addr = LIS3DH_REG_CTRL3; } else if (signal == lis3dh_int1_signal) { reg = (uint8_t*)&ctrl3; addr = LIS3DH_REG_CTRL3; } else { reg = (uint8_t*)&ctrl6; addr = LIS3DH_REG_CTRL6; } // read the register if (!lis3dh_reg_read (dev, addr, reg, 1)) { error_dev ("Could not read interrupt control registers", __FUNCTION__, dev); dev->error_code |= LIS3DH_CONFIG_INT_FAILED; return false; } // change the register switch (type) { case lis3dh_int_data_ready: ctrl3.IT_DRDY1 = value; break; case lis3dh_int_fifo_watermark: ctrl3.I1_WTM1 = value; break; case lis3dh_int_fifo_overrun: ctrl3.I1_OVERRUN = value; break; case lis3dh_int_event1: if (signal == lis3dh_int1_signal) ctrl3.I1_AOI1 = value; else ctrl6.I2_AOI1 = value; break; case lis3dh_int_event2: if (signal == lis3dh_int1_signal) ctrl3.I1_AOI2 = value; else ctrl6.I2_AOI2 = value; break; case lis3dh_int_click: if (signal == lis3dh_int1_signal) ctrl3.I1_CLICK = value; else ctrl6.I2_CLICK = value; break; default: dev->error_code = LIS3DH_WRONG_INT_TYPE; error_dev ("Wrong interrupt type", __FUNCTION__, dev); return false; } if (!lis3dh_reg_write (dev, addr, reg, 1)) { error_dev ("Could not enable/disable interrupt", __FUNCTION__, dev); dev->error_code |= LIS3DH_CONFIG_INT_FAILED; return false; } return true; } bool lis3dh_get_int_data_source (lis3dh_sensor_t* dev, lis3dh_int_data_source_t* source) { if (!dev || !source) return false; dev->error_code = LIS3DH_OK; struct lis3dh_reg_ctrl3 ctrl3; struct lis3dh_reg_status status; struct lis3dh_reg_fifo_src fifo_src; if (!lis3dh_reg_read (dev, LIS3DH_REG_CTRL3 , (uint8_t*)&ctrl3 , 1) || !lis3dh_reg_read (dev, LIS3DH_REG_STATUS , (uint8_t*)&status , 1) || !lis3dh_reg_read (dev, LIS3DH_REG_FIFO_SRC, (uint8_t*)&fifo_src, 1)) { error_dev ("Could not read source of interrupt INT2 from sensor", __FUNCTION__, dev); dev->error_code |= LIS3DH_INT_SOURCE_FAILED; return false; } source->data_ready = status.ZYXDA & ctrl3.IT_DRDY1; source->fifo_watermark = fifo_src.WTM & ctrl3.I1_WTM1; source->fifo_overrun = fifo_src.OVRN_FIFO & ctrl3.I1_OVERRUN; return true; } bool lis3dh_set_int_event_config (lis3dh_sensor_t* dev, lis3dh_int_event_config_t* config, lis3dh_int_event_gen_t gen) { if (!dev || !config) return false; dev->error_code = LIS3DH_OK; struct lis3dh_reg_intx_cfg intx_cfg; intx_cfg.XLIE = config->x_low_enabled; intx_cfg.XHIE = config->x_high_enabled; intx_cfg.YLIE = config->y_low_enabled; intx_cfg.YHIE = config->y_high_enabled; intx_cfg.ZLIE = config->z_low_enabled; intx_cfg.ZHIE = config->z_high_enabled; bool d4d_int = false; switch (config->mode) { case lis3dh_wake_up : intx_cfg.AOI = 0; intx_cfg.SIXD = 0; break; case lis3dh_free_fall : intx_cfg.AOI = 1; intx_cfg.SIXD = 0; break; case lis3dh_4d_movement : d4d_int = true; case lis3dh_6d_movement : intx_cfg.AOI = 0; intx_cfg.SIXD = 1; break; case lis3dh_4d_position : d4d_int = true; case lis3dh_6d_position : intx_cfg.AOI = 1; intx_cfg.SIXD = 1; break; } uint8_t intx_cfg_addr = (gen == lis3dh_int_event1_gen) ? LIS3DH_REG_INT1_CFG : LIS3DH_REG_INT2_CFG; uint8_t intx_ths_addr = (gen == lis3dh_int_event1_gen) ? LIS3DH_REG_INT1_THS : LIS3DH_REG_INT2_THS; uint8_t intx_dur_addr = (gen == lis3dh_int_event1_gen) ? LIS3DH_REG_INT1_DUR : LIS3DH_REG_INT2_DUR; if (// write the thresholds to registers IG_THS_* !lis3dh_reg_write (dev, intx_ths_addr, &config->threshold, 1) || // write duration configuration to IG_DURATION !lis3dh_reg_write (dev, intx_dur_addr, &config->duration, 1) || // write INT1 configuration to IG_CFG !lis3dh_reg_write (dev, intx_cfg_addr, (uint8_t*)&intx_cfg, 1)) { error_dev ("Could not configure interrupt INT1", __FUNCTION__, dev); dev->error_code |= LIS3DH_CONFIG_INT_FAILED; return false; } if (gen == lis3dh_int_event1_gen) { lis3dh_update_reg (dev, LIS3DH_REG_CTRL5, lis3dh_reg_ctrl5, LIR_INT1, config->latch); lis3dh_update_reg (dev, LIS3DH_REG_CTRL5, lis3dh_reg_ctrl5, D4D_INT1, d4d_int); } else { lis3dh_update_reg (dev, LIS3DH_REG_CTRL5, lis3dh_reg_ctrl5, LIR_INT2, config->latch); lis3dh_update_reg (dev, LIS3DH_REG_CTRL5, lis3dh_reg_ctrl5, D4D_INT2, d4d_int); } return true; } bool lis3dh_get_int_event_config (lis3dh_sensor_t* dev, lis3dh_int_event_config_t* config, lis3dh_int_event_gen_t gen) { if (!dev || !config) return false; dev->error_code = LIS3DH_OK; uint8_t intx_cfg_addr = (gen == lis3dh_int_event1_gen) ? LIS3DH_REG_INT1_CFG : LIS3DH_REG_INT2_CFG; uint8_t intx_ths_addr = (gen == lis3dh_int_event1_gen) ? LIS3DH_REG_INT1_THS : LIS3DH_REG_INT2_THS; uint8_t intx_dur_addr = (gen == lis3dh_int_event1_gen) ? LIS3DH_REG_INT1_DUR : LIS3DH_REG_INT2_DUR; struct lis3dh_reg_intx_cfg intx_cfg; struct lis3dh_reg_ctrl5 ctrl5; if (!lis3dh_reg_read (dev, intx_cfg_addr, (uint8_t*)&intx_cfg, 1) || !lis3dh_reg_read (dev, intx_ths_addr, (uint8_t*)&config->threshold, 1) || !lis3dh_reg_read (dev, intx_dur_addr, (uint8_t*)&config->duration, 1) || !lis3dh_reg_read (dev, LIS3DH_REG_CTRL5, (uint8_t*)&ctrl5, 1)) { error_dev ("Could not read interrupt configuration from sensor", __FUNCTION__, dev); dev->error_code |= LIS3DH_CONFIG_INT_FAILED; return false; } config->x_low_enabled = intx_cfg.XLIE; config->x_high_enabled = intx_cfg.XHIE; config->y_low_enabled = intx_cfg.YLIE; config->y_high_enabled = intx_cfg.YHIE; config->z_low_enabled = intx_cfg.ZLIE; config->z_high_enabled = intx_cfg.ZHIE; bool d4d_int = false; if (gen == lis3dh_int_event1_gen) { config->latch = ctrl5.LIR_INT1; d4d_int = ctrl5.D4D_INT1; } else { config->latch = ctrl5.LIR_INT2; d4d_int = ctrl5.D4D_INT2; } if (intx_cfg.AOI) { if (intx_cfg.SIXD && d4d_int) config->mode = lis3dh_4d_position; else if (intx_cfg.SIXD && !d4d_int) config->mode = lis3dh_6d_position; else config->mode = lis3dh_free_fall; } else { if (intx_cfg.SIXD && d4d_int) config->mode = lis3dh_4d_movement; else if (intx_cfg.SIXD && !d4d_int) config->mode = lis3dh_6d_movement; else config->mode = lis3dh_wake_up; } return true; } bool lis3dh_get_int_event_source (lis3dh_sensor_t* dev, lis3dh_int_event_source_t* source, lis3dh_int_event_gen_t gen) { if (!dev || !source) return false; dev->error_code = LIS3DH_OK; struct lis3dh_reg_intx_cfg intx_cfg; struct lis3dh_reg_intx_src intx_src; uint8_t intx_cfg_addr = (gen == lis3dh_int_event1_gen) ? LIS3DH_REG_INT1_CFG : LIS3DH_REG_INT2_CFG; uint8_t intx_src_addr = (gen == lis3dh_int_event1_gen) ? LIS3DH_REG_INT1_SRC : LIS3DH_REG_INT2_SRC; if (!lis3dh_reg_read (dev, intx_src_addr, (uint8_t*)&intx_src, 1) || !lis3dh_reg_read (dev, intx_cfg_addr, (uint8_t*)&intx_cfg, 1)) { error_dev ("Could not read source of interrupt INT1/INT2 from sensor", __FUNCTION__, dev); dev->error_code |= LIS3DH_INT_SOURCE_FAILED; return false; } source->active = intx_src.IA; source->x_low = intx_src.XL & intx_cfg.XLIE; source->x_high = intx_src.XH & intx_cfg.XHIE; source->y_low = intx_src.YL & intx_cfg.YLIE; source->y_high = intx_src.YH & intx_cfg.YHIE; source->z_low = intx_src.ZL & intx_cfg.ZLIE; source->z_high = intx_src.ZH & intx_cfg.ZHIE; return true; } bool lis3dh_set_int_click_config (lis3dh_sensor_t* dev, lis3dh_int_click_config_t* config) { if (!dev || !config) return false; dev->error_code = LIS3DH_OK; struct lis3dh_reg_click_cfg click_cfg; click_cfg.XS = config->x_single; click_cfg.XD = config->x_double; click_cfg.YS = config->y_single; click_cfg.YD = config->y_double; click_cfg.ZS = config->z_single; click_cfg.ZD = config->z_double; uint8_t click_ths = config->threshold | ((config->latch) ? 0x80 : 0x00); if (!lis3dh_reg_write (dev, LIS3DH_REG_CLICK_CFG , (uint8_t*)&click_cfg, 1) || !lis3dh_reg_write (dev, LIS3DH_REG_CLICK_THS , (uint8_t*)&click_ths, 1) || !lis3dh_reg_write (dev, LIS3DH_REG_TIME_LIMIT , (uint8_t*)&config->time_limit, 1) || !lis3dh_reg_write (dev, LIS3DH_REG_TIME_LATENCY, (uint8_t*)&config->time_latency, 1) || !lis3dh_reg_write (dev, LIS3DH_REG_TIME_WINDOW , (uint8_t*)&config->time_window, 1)) { error_dev ("Could not configure click detection interrupt", __FUNCTION__, dev); dev->error_code |= LIS3DH_CONFIG_CLICK_FAILED; return false; } return true; } bool lis3dh_get_int_click_config (lis3dh_sensor_t* dev, lis3dh_int_click_config_t* config) { if (!dev || !config) return false; dev->error_code = LIS3DH_OK; struct lis3dh_reg_click_cfg click_cfg; uint8_t click_ths; if (!lis3dh_reg_read (dev, LIS3DH_REG_CLICK_CFG , (uint8_t*)&click_cfg, 1) || !lis3dh_reg_read (dev, LIS3DH_REG_CLICK_THS , (uint8_t*)&click_ths, 1) || !lis3dh_reg_read (dev, LIS3DH_REG_TIME_LIMIT , (uint8_t*)&config->time_limit, 1) || !lis3dh_reg_read (dev, LIS3DH_REG_TIME_LATENCY, (uint8_t*)&config->time_latency, 1) || !lis3dh_reg_read (dev, LIS3DH_REG_TIME_WINDOW , (uint8_t*)&config->time_window, 1)) { error_dev ("Could not configure click detection interrupt", __FUNCTION__, dev); dev->error_code |= LIS3DH_CONFIG_CLICK_FAILED; return false; } config->x_single = click_cfg.XS; config->x_double = click_cfg.XD; config->y_single = click_cfg.YS; config->y_double = click_cfg.YD; config->z_single = click_cfg.ZS; config->z_double = click_cfg.ZD; config->threshold= click_ths & 0x7f; config->latch = click_ths & 0x80; return true; } bool lis3dh_get_int_click_source (lis3dh_sensor_t* dev, lis3dh_int_click_source_t* source) { if (!dev || !source) return false; dev->error_code = LIS3DH_OK; if (!lis3dh_reg_read (dev, LIS3DH_REG_CLICK_SRC, (uint8_t*)source, 1)) { error_dev ("Could not read source of click interrupt from sensor", __FUNCTION__, dev); dev->error_code |= LIS3DH_CLICK_SOURCE_FAILED; return false; } return true; } bool lis3dh_config_int_signals (lis3dh_sensor_t* dev, lis3dh_int_signal_level_t level) { if (!dev) return false; dev->error_code = LIS3DH_OK; lis3dh_update_reg (dev, LIS3DH_REG_CTRL6, lis3dh_reg_ctrl6, H_LACTIVE, level); return true; } bool lis3dh_config_hpf (lis3dh_sensor_t* dev, lis3dh_hpf_mode_t mode, uint8_t cutoff, bool data, bool click, bool int1, bool int2) { if (!dev) return false; dev->error_code = LIS3DH_OK; struct lis3dh_reg_ctrl2 reg; reg.HPM = mode; reg.HPCF = cutoff; reg.FDS = data; reg.HPCLICK = click; reg.HPIS1 = int1; reg.HPIS2 = int2; if (!lis3dh_reg_write (dev, LIS3DH_REG_CTRL2, (uint8_t*)®, 1)) { error_dev ("Could not configure high pass filter", __FUNCTION__, dev); dev->error_code |= LIS3DH_CONFIG_HPF_FAILED; return false; } return true; } bool lis3dh_set_hpf_ref (lis3dh_sensor_t* dev, int8_t ref) { if (!dev) return false; dev->error_code = LIS3DH_OK; if (!lis3dh_reg_write (dev, LIS3DH_REG_REFERENCE, (uint8_t*)&ref, 1)) { error_dev ("Could not set high pass filter reference", __FUNCTION__, dev); dev->error_code |= LIS3DH_CONFIG_HPF_FAILED; return false; } return true; } int8_t lis3dh_get_hpf_ref (lis3dh_sensor_t* dev) { if (!dev) return 0; dev->error_code = LIS3DH_OK; int8_t ref; if (!lis3dh_reg_read (dev, LIS3DH_REG_REFERENCE, (uint8_t*)&ref, 1)) { error_dev ("Could not get high pass filter reference", __FUNCTION__, dev); dev->error_code |= LIS3DH_CONFIG_HPF_FAILED; return 0; } return ref; } int8_t lis3dh_enable_adc (lis3dh_sensor_t* dev, bool adc, bool tmp) { if (!dev) return 0; dev->error_code = LIS3DH_OK; uint8_t reg = 0; reg |= (adc) ? 0x80 : 0; reg |= (tmp) ? 0x40 : 0; return lis3dh_reg_write (dev, LIS3DH_REG_TEMP_CFG, (uint8_t*)®, 1); } bool lis3dh_get_adc (lis3dh_sensor_t* dev, uint16_t* adc1, uint16_t* adc2, uint16_t* adc3) { if (!dev) return false; dev->error_code = LIS3DH_OK; uint8_t data[6]; uint8_t temp_cfg; struct lis3dh_reg_ctrl1 ctrl1; if (!lis3dh_reg_read (dev, LIS3DH_REG_OUT_ADC1_L, data, 6) || !lis3dh_reg_read (dev, LIS3DH_REG_CTRL1, (uint8_t*)&ctrl1, 1) || !lis3dh_reg_read (dev, LIS3DH_REG_TEMP_CFG, &temp_cfg, 1)) { error_dev ("Could not get adc data", __FUNCTION__, dev); dev->error_code |= LIS3DH_GET_ADC_DATA_FAILED; return false; } if (adc1) *adc1 = lsb_msb_to_type ( int16_t, data, 0) >> (ctrl1.LPen ? 8 : 6); if (adc2) *adc2 = lsb_msb_to_type ( int16_t, data, 2) >> (ctrl1.LPen ? 8 : 6); // temperature is always 8 bit if (adc3 && temp_cfg & 0x40) *adc3 = (lsb_msb_to_type ( int16_t, data, 4) >> 8) + 25; else if (adc3) *adc3 = lsb_msb_to_type ( int16_t, data, 4) >> (ctrl1.LPen ? 8 : 6); return true; } /** Functions for internal use only */ /** * @brief Check the chip ID to test whether sensor is available */ static bool lis3dh_is_available (lis3dh_sensor_t* dev) { uint8_t chip_id; if (!dev) return false; dev->error_code = LIS3DH_OK; if (!lis3dh_reg_read (dev, LIS3DH_REG_WHO_AM_I, &chip_id, 1)) return false; if (chip_id != LIS3DH_CHIP_ID) { error_dev ("Chip id %02x is wrong, should be %02x.", __FUNCTION__, dev, chip_id, LIS3DH_CHIP_ID); dev->error_code = LIS3DH_WRONG_CHIP_ID; return false; } return true; } static bool lis3dh_reset (lis3dh_sensor_t* dev) { if (!dev) return false; dev->error_code = LIS3DH_OK; uint8_t reg[8] = { 0 }; // initialize sensor completely including setting in power down mode lis3dh_reg_write (dev, LIS3DH_REG_TEMP_CFG , reg, 8); lis3dh_reg_write (dev, LIS3DH_REG_FIFO_CTRL, reg, 1); lis3dh_reg_write (dev, LIS3DH_REG_INT1_CFG , reg, 1); lis3dh_reg_write (dev, LIS3DH_REG_INT1_THS , reg, 2); lis3dh_reg_write (dev, LIS3DH_REG_INT2_CFG , reg, 1); lis3dh_reg_write (dev, LIS3DH_REG_INT2_THS , reg, 2); lis3dh_reg_write (dev, LIS3DH_REG_CLICK_CFG, reg, 1); lis3dh_reg_write (dev, LIS3DH_REG_CLICK_THS, reg, 4); return true; } bool lis3dh_reg_read(lis3dh_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) { if (!dev || !data) return false; return (dev->addr) ? lis3dh_i2c_read (dev, reg, data, len) : lis3dh_spi_read (dev, reg, data, len); } bool lis3dh_reg_write(lis3dh_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) { if (!dev || !data) return false; return (dev->addr) ? lis3dh_i2c_write (dev, reg, data, len) : lis3dh_spi_write (dev, reg, data, len); } #define LIS3DH_SPI_BUF_SIZE 64 // SPI register data buffer size of ESP866 #define LIS3DH_SPI_READ_FLAG 0x80 #define LIS3DH_SPI_WRITE_FLAG 0x00 #define LIS3DH_SPI_AUTO_INC_FLAG 0x40 static bool lis3dh_spi_read(lis3dh_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) { if (!dev || !data) return false; if (len >= LIS3DH_SPI_BUF_SIZE) { dev->error_code |= LIS3DH_SPI_BUFFER_OVERFLOW; error_dev ("Error on read from SPI slave on bus 1. Tried to transfer " "more than %d byte in one read operation.", __FUNCTION__, dev, LIS3DH_SPI_BUF_SIZE); return false; } uint8_t addr = (reg & 0x3f) | LIS3DH_SPI_READ_FLAG | LIS3DH_SPI_AUTO_INC_FLAG; static uint8_t mosi[LIS3DH_SPI_BUF_SIZE]; static uint8_t miso[LIS3DH_SPI_BUF_SIZE]; memset (mosi, 0xff, LIS3DH_SPI_BUF_SIZE); memset (miso, 0xff, LIS3DH_SPI_BUF_SIZE); mosi[0] = addr; if (!spi_transfer_pf (dev->bus, dev->cs, mosi, miso, len+1)) { error_dev ("Could not read data from SPI", __FUNCTION__, dev); dev->error_code |= LIS3DH_SPI_READ_FAILED; return false; } // shift data one by left, first byte received while sending register address is invalid for (int i=0; i < len; i++) data[i] = miso[i+1]; #ifdef LIS3DH_DEBUG_LEVEL_2 printf("LIS3DH %s: read the following bytes from reg %02x: ", __FUNCTION__, reg); for (int i=0; i < len; i++) printf("%02x ", data[i]); printf("\n"); #endif return true; } static bool lis3dh_spi_write(lis3dh_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) { if (!dev || !data) return false; uint8_t addr = (reg & 0x3f) | LIS3DH_SPI_WRITE_FLAG | LIS3DH_SPI_AUTO_INC_FLAG; static uint8_t mosi[LIS3DH_SPI_BUF_SIZE]; if (len >= LIS3DH_SPI_BUF_SIZE) { dev->error_code |= LIS3DH_SPI_BUFFER_OVERFLOW; error_dev ("Error on write to SPI slave on bus 1. Tried to transfer more" "than %d byte in one write operation.", __FUNCTION__, dev, LIS3DH_SPI_BUF_SIZE); return false; } reg &= 0x7f; // first byte in output is the register address mosi[0] = addr; // shift data one byte right, first byte in output is the register address for (int i = 0; i < len; i++) mosi[i+1] = data[i]; #ifdef LIS3DH_DEBUG_LEVEL_2 printf("LIS3DH %s: Write the following bytes to reg %02x: ", __FUNCTION__, reg); for (int i = 1; i < len+1; i++) printf("%02x ", mosi[i]); printf("\n"); #endif if (!spi_transfer_pf (dev->bus, dev->cs, mosi, NULL, len+1)) { error_dev ("Could not write data to SPI.", __FUNCTION__, dev); dev->error_code |= LIS3DH_SPI_WRITE_FAILED; return false; } return true; } #define I2C_AUTO_INCREMENT (0x80) static bool lis3dh_i2c_read(lis3dh_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) { if (!dev || !data) return false; debug_dev ("Read %d byte from i2c slave register %02x.", __FUNCTION__, dev, len, reg); if (len > 1) reg |= I2C_AUTO_INCREMENT; int result = i2c_slave_read(dev->bus, dev->addr, ®, data, len); if (result) { dev->error_code |= (result == -EBUSY) ? LIS3DH_I2C_BUSY : LIS3DH_I2C_READ_FAILED; error_dev ("Error %d on read %d byte from I2C slave register %02x.", __FUNCTION__, dev, result, len, reg); return false; } # ifdef LIS3DH_DEBUG_LEVEL_2 printf("LIS3DH %s: Read following bytes: ", __FUNCTION__); printf("%02x: ", reg & 0x7f); for (int i=0; i < len; i++) printf("%02x ", data[i]); printf("\n"); # endif return true; } static bool lis3dh_i2c_write(lis3dh_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) { if (!dev || !data) return false; debug_dev ("Write %d byte to i2c slave register %02x.", __FUNCTION__, dev, len, reg); if (len > 1) reg |= I2C_AUTO_INCREMENT; int result = i2c_slave_write(dev->bus, dev->addr, ®, data, len); if (result) { dev->error_code |= (result == -EBUSY) ? LIS3DH_I2C_BUSY : LIS3DH_I2C_WRITE_FAILED; error_dev ("Error %d on write %d byte to i2c slave register %02x.", __FUNCTION__, dev, result, len, reg); return false; } # ifdef LIS3DH_DEBUG_LEVEL_2 printf("LIS3DH %s: Wrote the following bytes: ", __FUNCTION__); printf("%02x: ", reg & 0x7f); for (int i=0; i < len; i++) printf("%02x ", data[i]); printf("\n"); # endif return true; }