From 1569299c92474e34714b0ce867d8a0d1d0ce46e9 Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Sun, 7 Nov 2021 23:36:25 +0100 Subject: [PATCH] drivers: support for ST L3Gxxxx 3-axis gyroscope family --- drivers/Makefile.dep | 4 + drivers/include/l3gxxxx.h | 1999 ++++++++++++++++++++++ drivers/l3gxxxx/Makefile | 1 + drivers/l3gxxxx/Makefile.dep | 25 + drivers/l3gxxxx/Makefile.include | 19 + drivers/l3gxxxx/include/l3gxxxx_params.h | 385 +++++ drivers/l3gxxxx/include/l3gxxxx_regs.h | 196 +++ drivers/l3gxxxx/l3gxxxx.c | 1143 +++++++++++++ 8 files changed, 3772 insertions(+) create mode 100644 drivers/include/l3gxxxx.h create mode 100644 drivers/l3gxxxx/Makefile create mode 100644 drivers/l3gxxxx/Makefile.dep create mode 100644 drivers/l3gxxxx/Makefile.include create mode 100644 drivers/l3gxxxx/include/l3gxxxx_params.h create mode 100644 drivers/l3gxxxx/include/l3gxxxx_regs.h create mode 100644 drivers/l3gxxxx/l3gxxxx.c diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 422f974a76..a8bf37de77 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -76,6 +76,10 @@ ifneq (,$(filter itg320x_%,$(USEMODULE))) USEMODULE += itg320x endif +ifneq (,$(filter l3gxxxx_%,$(USEMODULE))) + USEMODULE += l3gxxxx +endif + ifneq (,$(filter lis2dh12%,$(USEMODULE))) USEMODULE += lis2dh12 endif diff --git a/drivers/include/l3gxxxx.h b/drivers/include/l3gxxxx.h new file mode 100644 index 0000000000..7dc8bbc8ce --- /dev/null +++ b/drivers/include/l3gxxxx.h @@ -0,0 +1,1999 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_l3gxxxx L3Gxxxx 3-axis gyroscope sensor family + * @ingroup drivers_sensors + * @ingroup drivers_saul + * @brief Device Driver for ST L3Gxxxx 3-axis gyroscope sensor family + * + * \section l3gxxxx Driver for ST L3Gxxxx 3-axis gyroscope sensor family + * + * ## Table of contents {#l3gxxxx_toc} + * + * 1. [Overview](#l3gxxxx_overview) + * 1. [About the sensor](#l3gxxxx_about) + * 2. [Supported features](#l3gxxxx_supported) + * 2. [Measurement Process](#l3gxxxx_measurement_process) + * 1. [Sensor modes](#l3gxxxx_sensor_modes) + * 2. [Output Data Rates and Filters](#l3gxxxx_odr_filters) + * 3. [Using the driver (basic functionality)](#l3gxxxx_using_driver) + * 1. [Initializaton](#l3gxxxx_initialization) + * 2. [Output data format](#l3gxxxx_output_data) + * 3. [Fetching data](#l3gxxxx_fetching_data) + * 4. [Using the FIFO](#l3gxxxx_fifo) + * 1. [Configuration of the FIFO](#l3gxxxx_fifo_config) + * 2. [Reading data from the FIFO](#l3gxxxx_fifo_read_data) + * 5. [Using Interrupts](#l3gxxxx_interrupts) + * 1. [Data interrupts (data ready and FIFO status) on signal `INT2/DRDY`] + * (#l3gxxxx_data_interrupt) + * 2. [Event interrupts (Axes movement and wake-up) on signal `INT1`] + * (#l3gxxxx_event_interrupt) + * 3. [Interrupt context problem](#l3gxxxx_interrupt_context) + * 4. [Interrupt signal properties](#l3gxxxx_interrupt_signal) + * 6. [Power Saving](#l3gxxxx_power_saving) + * 7. [Low level functions](#l3gxxxx_low_level) + * 8. [Default configuration](#l3gxxxx_default_configuration) + * + * # Overview {#l3gxxxx_overview} + * + * ## About the sensor {#l3gxxxx_about} + * + * ST L3Gxxxx sensors are low-power **3-axis angular rate sensors** connected + * to **I2C** or **SPI** with a full scale of up to **2000 dps**. It supports + * different measuring rates with a user selectable bandwidth. + * + * **Main features** of the sensor are: + * - 3 selectable full scales of ±245, ±500, and ±2000 dps + * - 7 measuring rates from 12.5 Hz to 800 Hz with 4 bandwidths + * - 16 bit angular rate value data output + * - 2 dedicated interrupt signals for data and event interrupts + * - integrated high-pass filters with 3 modes and 10 different cutoff + * frequencies + * - embedded temperature sensor with 8 bit data output + * - embedded 32 levels of 16 bit data output FIFO + * - I2C and SPI digital interface + * - embedded power-down and sleep mode with fast power-on and wake-up + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * ## Supported Features {#l3gxxxx_supported} + * + * The driver supports the following sensors of the L3Gxxxx 3-axis gyro sensor + * family. Used sensor variant has to be specified by using the respective + * pseudomodule. + * + *
+ * | Sensor Variant | Pseudomodule | Vendor Status | + * |:-----------------|:--------------|:--------------------------------| + * | L3GD20H | `l3gd20h` | Not recommended for new designs | + * | L3GD20 | `l3gd20` | Obsolete | + * | L3G4200D | `l3g4200_ng` | Obsolete | + * | A3G4250D | `a3g4250d` | Active | + * | I3G4250D | `i3g4250d` | Active | + *

+ * + * The driver is modular and supports different levels of functionality, which + * can be enabled using pseudomodules according to the requirements of the + * application. This ensures that the driver only uses as much ROM + * and RAM as really needed. + * + * As basic functionality the driver supports + * - a static configuration of the sensor by a default configuration parameter + * set of type #l3gxxxx_params_t as defined in the file l3gxxxx_params.h + * - the polling of raw output data or angular rates in millidegrees per + * second (mdps) + * - the power-down and power-up of the sensor + * - the use of the I2C or SPI interface + * + * The following pseudomodules are used to enable additional functionalities: + *
+ * | Pseudomodule | Functionality | + * |:--------------------|:--------------------------------------------------------| + * | `l3gxxxx_i2c` | I2C interface enabled | + * | `l3gxxxx_spi` | SPI interface enabled | + * | `l3gxxxx_low_odr` | Low output data rates enabled (L3GD20H only) | + * | `l3gxxxx_fifo` | 32 level FIFO enabled | + * | `l3gxxxx_irq_data` | Data interrupt (`INT2/DRDY`) handling enabled | + * | `l3gxxxx_irq_event` | Event interrupt (`INT1`) handling enabled | + * | `l3gxxxx_sleep` | Sleep and wake-up functions enabled | + * | `l3gxxxx_config` | Functions for changing configurations at runtime nabled | + *

+ * + * The following table shows the mapping of which modules have to be used + * to enable which functions of the L3Gxxxx. + * + *
+ * | Feature | Module | + * |:------------------------------------------------------------- |:--------------------| + * | 16 bit angular rate data output (raw and angular rate) | `l3gxxxx` | + * | Full scales of ±245, ±500, and ±2000 dps | `l3gxxxx` | + * | Using high-pass filter (HPF) and low-pass filter (LPF1/LPF2) | `l3gxxxx` | + * | Output data rates (ODR) from 100 Hz to 800 Hz | `l3gxxxx` | + * | Output data rates (ODR) from 12.5 Hz to 50 Hz (L3GD20H only) | `l3gxxxx_low_odr` | + * | Polling data | `l3gxxxx` | + * | SAUL sensor interface | `l3gxxxx` | + * | Power-down and power-up functionality | `l3gxxxx` | + * | Sleep and wake-up functionality | `l3gxxxx_sleep` | + * | 32 level FIFO handling | `l3gxxxx_fifo` | + * | Data interrupt (`INT2/DRDY`) handling (data ready and FIFO) | `l3gxxxx_irq_data` | + * | Event interrupt (`INT1`) handling (Axes movement and wake-up) | `l3gxxxx_irq_event` | + * | Configuration of all sensor functions at runtime | `l3gxxxx_config` | + * | I2C interface | `l3gxxxx_i2c` | + * | SPI interface (SPI mode 3) | `l3gxxxx_spi` | + *

+ * + * @note + * - Multiple L3Gxxxx sensors of same type with both SPI and I2C interfaces + * can be used simultaneously. If neither the I2C nor the SPI interface + * are enabled by using the modules `l3gxxxx_i2c` or `l3gxxxx_spi`, + * the I2C interface is used by default.
+ *
+ * - In default configuration, the sensor is configured with + * an output data rate (ODR) of 100 Hz with a LPF2 cutoff frequency + * of 25 Hz (#L3GXXXX_ODR_100_25) and a full scale of 245 dps + * (#L3GXXXX_SCALE_245_DPS). The data are filtered with HPF and LPF2, + * where the HPF is used in normal mode with reset (#L3GXXXX_HPF_NORMAL) + * and a cutoff frequency of 8 Hz (see #l3gxxxx_config_hpf).
+ *
+ * This configuration can either be changed by overriding default + * parameters #CONFIG_L3GXXXX_ODR, #CONFIG_L3GXXXX_SCALE, + * #CONFIG_L3GXXXX_FILTER_SEL, #CONFIG_L3GXXXX_HPF_MODE and + * #CONFIG_L3GXXXX_HPF_CUTOFF or by using functions #l3gxxxx_set_mode, + * #l3gxxxx_set_scale, #l3gxxxx_select_output_filter, #l3gxxxx_config_hpf + * if module `l3gxxxx_config` is enabled.
+ *
+ * - If module 'l3gxxxx_fifo' is used, the FIFO is enabled in mode + * #L3GXXXX_FIFO with a watermark level (threshold) of 23, i.e. the + * interrupt #L3GXXXX_INT_FIFO_WATERMARK is triggered (if enabled) when + * the 24th sample is stored in the FIFO.
+ *
+ * This configuration can be changed either by overriding the default + * configuration parameters #CONFIG_L3GXXXX_FIFO_MODE and + * #CONFIG_L3GXXXX_FIFO_WATERMARK or by function #l3gxxxx_set_fifo_mode + * if module `l3gxxxx_config` is used.
+ *
+ * - If the handling of data interrupts on signal `INT2/DRDY` is + * enabled by module `l3gxxxx_irq_data`, it depends on whether module + * `l3gxxxx_fifo` is used, which data interrupts are enabled + * by default. If `l3gxxxx_fifo` is used, #L3GXXXX_INT_FIFO_WATERMARK and + * #L3GXXXX_INT_FIFO_OVERRUN interrupts are enabled by default. Otherwise + * only #L3GXXXX_INT_DATA_READY is enabled by default.
+ *
+ * This configuration can be changed using function #l3gxxxx_enable_int.
+ *
+ * - If the handling of event interrupts on signal `INT1` is enabled + * by module `l3gxxxx_irq_event`, the high event interrupt is enabled by + * default for all axes. This means that for each individual axis an + * interrupt is generated when the absolute value of its angular rate + * exceeds a threshold value of ~30 dps (high event).
+ *
+ * The axis data are filtered with HPF and LPF2. The interrupt signal + * `INT1` is triggered when the data for one axis becomes greater than + * the specified threshold (OR condition). The interrupt is latched + * by default.
+ *
+ * This configuration can be changed either by overriding the default + * configuration parameters #CONFIG_L3GXXXX_INT1_X_THRESH, + * #CONFIG_L3GXXXX_INT1_Y_THRESH, #CONFIG_L3GXXXX_INT1_Z_THRESH, + * #CONFIG_L3GXXXX_INT1_X_LT_THRESH, #CONFIG_L3GXXXX_INT1_X_GT_THRESH, + * #CONFIG_L3GXXXX_INT1_Y_LT_THRESH, #CONFIG_L3GXXXX_INT1_Y_GT_THRESH, + * #CONFIG_L3GXXXX_INT1_Z_LT_THRESH, #CONFIG_L3GXXXX_INT1_Z_GT_THRESH, + * #CONFIG_L3GXXXX_INT1_FILTER, #CONFIG_L3GXXXX_INT1_AND and + * #CONFIG_L3GXXXX_INT1_LATCH or at runtime using function + * #l3gxxxx_set_int_event_cfg function.
+ *
+ * The `INT1` signal is a HIGH active push/pull output. + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * # Measurement Process {#l3gxxxx_measurement_process} + * + * ## Sensor modes {#l3gxxxx_sensor_modes} + * + * L3Gxxxx sensors provide different operating modes. + * + * - **Power-down mode** is configured automatically after power up boot + * sequence. In this mode, all gyros are switched off. Therefore, it takes + * up to 100 ms to switch to another mode. The power consumption in this mode + * is about 1 uA. + * + * - **Normal mode** is the normal measurement mode. All gyros are switched on + * and at least one axis is enabled for measurements. Measurements are + * performed at a defined output data rate (**ODR**). The power consumption + * in this mode is about 5 mA. + * + * - **Sleep mode** is the normal mode when no axes era enabled for + * measurement. In this modes, all gyros are kept switched on. Therefore, + * it only takes 1/ODR to switch to normal mode if low pass filtering is + * disabled or 6/ODR if low pass filtering is enabled. The power consumption + * in this mode is about 2.5 mA. + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * ## Output Data Rates and Filters {#l3gxxxx_odr_filters} + * + * In normal mode, measurements are performed at a defined output rate (ODR) + * with a user selectable bandwidth. + * + * ### Used filter selection + * + * L3Gxxxx sensors integrate a combination + * of two low pass filters (LPF) and one high pass filter (HPF). + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * +---------------> L3GXXXX_NO_FILTER + * | +----- + + * +------------+--->| |---> L3GXXXX_LPF2_ONLY + * | | LPF2 | + * +-----+ +------+ | +-----+ +--->| |---> L3GXXXX_HPF_AND_LPF2 + * | | | | | | | | +------+ + * | ADC |-->| LPF1 |--+-->| HPF |--+---------------> L3GXXXX_HPF_ONLY + * | | | | | | + * +-----+ +------+ +-----+ + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * First, raw sensor data are always filtered by LPF1 with a cutoff frequency + * that is fixed for the selected output data rate (ODR), see #l3gxxxx_odr_t. + * Resulting data can then optionally be filtered by HPF and/or LPF2. Both + * filters can be used or bypassed. + * + * The figure above shows possible **filter selections** and the driver symbols + * defined by #l3gxxxx_filter_sel_t. These can be used to set the filter + * combination separately for the output data and the data for event interrupt + * generation. + * + *
+ * | Driver symbol | High pass filter (HPF) used | Low pass filter 2 (LPF2) used | + * |:----------------------|:---------------------------:|:------------------------------: + * | #L3GXXXX_NO_FILTER | - | - | + * | #L3GXXXX_HPF_ONLY | x | - | + * | #L3GXXXX_LPF2_ONLY | - | x | + * | #L3GXXXX_HPF_AND_LPF2 | x | x | + *

+ * + * The default filter selection for the output data is #L3GXXXX_HPF_AND_LPF2 + * and is defined by the default configuration parameter + * #CONFIG_L3GXXXX_FILTER_SEL. If the module `l3gxxxx_config` is used, it can + * be changed at runtime using function #l3gxxxx_select_output_filter. + * + * The default filter selection for event interrupt generation is + * #L3GXXXX_HPF_AND_LPF2 and is defined by default configuration parameter + * #CONFIG_L3GXXXX_INT1_FILTER. It can be changed at runtime with function + * #l3gxxxx_set_int_event_cfg. + * + * @note Since same filters are used for the output data as well as the + * data used for event interrupt generation (selective axes movement / wake-up), + * the configuration of the filters always affects both data. If the HPF is + * enabled for filtering the output data, it is also active for filtering the + * sensor data used for interrupt generation if the LPF2 is enabled for + * interrupt generation. The other way around, the HPF is also active for + * filtering the output data when it is enabled for interrupt generation and + * when the LPF2 is enabled for the output data. + * + * ### High pass filter mode + * + * The high pass filter (HPF) can be used in different modes. + * + *
+ * | Driver symbol | HPF mode | + * |:-----------------------|:------------------------| + * | #L3GXXXX_HPF_NORMAL | Normal mode | + * | #L3GXXXX_HPF_REFERENCE | Reference mode | + * | #L3GXXXX_HPF_AUTORESET | Auto-reset on interrupt | + *

+ * + * In normal mode, the HPF can be reset by reading the REFERENCE + * register, which instantly deletes the DC component + * of the angular rate. In reference mode, output data are the difference + * of raw sensor data and the contents of the REFERENCE register. In autoreset + * mode, HPF is automatically reset when a configured event interrupt occurs. + * + * The default HPF mode is #L3GXXXX_HPF_NORMAL and is defined by the + * default configuration parameter #CONFIG_L3GXXXX_HPF_MODE. If module + * `l3gxxxx_config` is used, it can be changed at runtime using + * function #l3gxxxx_config_hpf. + * + * ### Output data rates and filter cutoff frequencies + * + * The cutoff frequencies of LPF1 and LPF2 are determined by used output + * data rate #l3gxxxx_odr_t. The following **output data rates (ODR)** + * and the LPF1/LPF2 cutoff frequencies are defined + * (Reference: Application Note AN4506): + * + *
+ * | Mode | ODR [Hz] | LPF1 cutoff [Hz] | LPF2 cutoff [Hz] | Driver symbol | + * |:------- |:--------:|:----------------:|:----------------:|:---------------------| + * | Normal | 100 | 32 | 12.5 | #L3GXXXX_ODR_100_12 | + * | Normal | 100 | 32 | 25 | #L3GXXXX_ODR_100_25 | + * | Normal | 200 | 63.3 | 12.5 | #L3GXXXX_ODR_200_12 | + * | Normal | 200 | 63.3 | 25 | #L3GXXXX_ODR_200_25 | + * | Normal | 200 | 63.3 | 50 | #L3GXXXX_ODR_200_50 | + * | Normal | 200 | 63.3 | 70 | #L3GXXXX_ODR_200_70 | + * | Normal | 400 | 128 | 20 | #L3GXXXX_ODR_400_20 | + * | Normal | 400 | 128 | 25 | #L3GXXXX_ODR_400_25 | + * | Normal | 400 | 128 | 50 | #L3GXXXX_ODR_400_50 | + * | Normal | 400 | 128 | 110 | #L3GXXXX_ODR_400_110 | + * | Normal | 800 | 211 | 30 | #L3GXXXX_ODR_800_30 | + * | Normal | 800 | 211 | 35 | #L3GXXXX_ODR_800_35 | + * | Normal | 800 | 211 | 30 | #L3GXXXX_ODR_800_30 | + * | Normal | 800 | 211 | 100 | #L3GXXXX_ODR_800_100 | + * | Low ODR | 12.5 | 3.9 | - | #L3GXXXX_ODR_12 | + * | Low ODR | 25 | 7.8 | - | #L3GXXXX_ODR_25 | + * | Low ODR | 50 | 16 | 16.6 | #L3GXXXX_ODR_50 | + *

+ * + * @note Low ODRs are only available on L3GD20H and if module `l3gxxxx_low_odr` + * is used. + * + * The default output data rate (ODR) is #L3GXXXX_ODR_100_12 and defined by + * the default configuration parameter #CONFIG_L3GXXXX_ODR. If module + * `l3gxxxx_config` is used, it can be changed at runtime using + * function #l3gxxxx_set_mode, for example: + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_set_mode(&dev, L3GXXXX_ODR_400_20, true, true, false); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * The **cutoff frequencies** of the HPF depend on the selected output data + * rate (ODR) and are specified by an index from 0 to 9, as shown in the + * following table. All frequencies are given in Hz. + * + *
+ * | ODR [Hz] | 12.5 | 25 | 50 | 100 | 200 | 400 | 800 | + * |---------:|:-----:|:-----:|:-----:|:----:|:----:|:----:|:---:| + * | 0 | 1 | 2 | 4 | 8 | 15 | 30 | 56 | + * | 1 | 0.5 | 1 | 2 | 4 | 8 | 15 | 30 | + * | 2 | 0.2 | 0.5 | 1 | 2 | 4 | 8 | 15 | + * | 3 | 0.1 | 0.2 | 0.5 | 1 | 2 | 4 | 8 | + * | 4 | 0.05 | 0.1 | 0.2 | 0.5 | 1 | 2 | 4 | + * | 5 | 0.02 | 0.05 | 0.1 | 0.2 | 0.5 | 1 | 2 | + * | 6 | 0.01 | 0.02 | 0.05 | 0.1 | 0.2 | 0.5 | 1 | + * | 7 | 0.005 | 0.01 | 0.02 | 0.05 | 0.1 | 0.2 | 0.5 | + * | 8 | 0.002 | 0.005 | 0.01 | 0.02 | 0.05 | 0.1 | 0.2 | + * | 9 | 0.001 | 0.002 | 0.005 | 0.01 | 0.02 | 0.05 | 0.1 | + *

+ * + * The default cutoff frequency of HPF is 8 Hz (index 0) and set by the + * default configuration parameter #CONFIG_L3GXXXX_HPF_CUTOFF. If module + * `l3gxxxx_config` is used, it can be changed at runtime using function + * #l3gxxxx_config_hpf, + * for example: + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_config_hpf(&dev, L3GXXXX_HPF_NORMAL, 0); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * # Using the driver {#l3gxxxx_using_driver} + * + * ## Initializaton {#l3gxxxx_initialization} + * + * The **easiest way to use the driver** is simply to initialize the sensor + * with function #l3gxxxx_init using the default configuration parameter set + * #l3gxxxx_params as defined in file l3gxxxx_params.h. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * static l3gxxxx_t dev; + * + * if (l3gxxxx_init(&dev, &l3gxxxx_params[0]) != L3DG20H_OK) { + * ... // error handling + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * After this initialization, the sensor is fully operational and data can + * be fetched either by polling or interrupt driven. + * + * @note Function #l3gxxxx_init resets the sensor completely. All registers + * are reset to default values and the embedded FIFO is cleared. + * + * The default configuration parameter set defines + * - the communication interface, + * - the output data rate (ODR) including LPF1/LPF2 cutoff frequencies, + * - the filter combination and HPF cutoff frequency, + * - the sensitivity level selected by full scale, + * - the FIFO parameters if module `l3gxxxx_fifo` is used, + * - the data interrupt (`INT2/DRDY`) pin if module `l3gxxxx_irq_data` is used, and + * - the event interrupt (`INT1`) configuration if module `l3gxxxx_irq_event` is used. + * + * Most of these configuration parameters can also be changed at runtime + * by respective functions if the module `l3gxxxx_config` is used or by + * overriding default configuration parameters. Detailed information about + * the default configuration can be found in section + * [Configuration](#l3gxxxx_default_configuration). + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * ## Output data format {#l3gxxxx_output_data} + * + * In normal mode, the sensor determines periodically the angular rate for all + * axes that are enabled for measurement and produces raw output data + * with the selected output data rate (ODR). + * + * These **raw output data** are 16-bit signed integer values in two’s + * complement representation. Their range and their resolution depend on the + * sensitivity of the sensor which is selected by the **full scale** value. + * L3Gxxxx sensors allow to select the following full scales: + * + *
+ * | Full Scale | Sensitivity | Driver symbol | Remark | + * | -----------:|------------:|:------------------------|:--------------------------| + * | ±245 dps | 8.75 mdps | #L3GXXXX_SCALE_245_DPS | | + * | ±500 dps | 17.50 mdps | #L3GXXXX_SCALE_500_DPS | not available on A3G4250D | + * | ±2000 dps | 70.00 mdps | #L3GXXXX_SCALE_2000_DPS | not available on A3G4250D | + *

+ * + * @note On the A34250D, only 245 dps (#L3GXXXX_SCALE_245_DPS) is available + * as full scale value. + * + * The default full scale value is ±245 dps which is defined by the default + * configuration parameter #CONFIG_L3GXXXX_SCALE. If module `l3gxxxx_config` + * is used, it can be changed at runtime using function #l3gxxxx_set_scale, + * for example: + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_set_scale(&dev, L3GXXXX_SCALE_500_DPS); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * ## Fetching data {#l3gxxxx_fetching_data} + * + * To get the information whether new data are available, the user task can + * either use + * + * - the function #l3gxxxx_data_ready to check periodically whether new + * output data are available, or + * - the data ready interrupt (`INT2/DRDY`) which is triggered as soon as new + * output data are available (see below). + * + * Last measurement results can then be fetched either + * + * - as 16 bit raw output data of type #l3gxxxx_raw_data_t using function + * #l3gxxxx_read_raw or + * - as 32 bit integer angular rates type #l3gxxxx_data_t in + * millidegrees per second (mdps) using function #l3gxxxx_read. + * + * It is recommended to use function #l3gxxxx_read since the driver already + * converts raw output data to angular rates according to the selected + * full scale value. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * while (1) + * { + * l3gxxxx_data_t data; + * + * // execute task every 20 ms + * xtimer_usleep(20 * US_PER_MS); + * ... + * // test for new data and fetch them if available + * if ((l3gxxxx_data_ready(&dev) > 0) && + * (l3gxxxx_read(&dev, &data) == L3GXXXX_OK)) { + * // do something with data + * ... + * } + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @note + * The functions #l3gxxxx_read and #l3gxxxx_read_raw always return the last + * available results. If these functions are called more often than + * measurements are performed, some measurement results are retrieved + * multiple times. If these functions are called too rarely, some measurement + * results will be lost. + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * # Using the FIFO {#l3gxxxx_fifo} + * + * In order to limit the rate at which the host processor has to fetch the + * data, L3Gxxxx sensors embed a FIFO buffer. This is in particular helpful + * at high output data rates. The FIFO buffer can work in seven different + * modes and is able to store up to 32 data samples. Please refer the + * [datasheet](http://www.st.com/resource/en/datasheet/l3gd20.pdf) or + * [application note](http://www.st.com/resource/en/application_note/dm00119036.pdf) + * for more details. + * + *
+ * | Driver symbol | FIFO mode | Remark | + * |---------------------------|-----------------------|:------------------------| + * | #L3GXXXX_BYPASS | Bypass mode | FIFO is not used | + * | #L3GXXXX_FIFO | FIFO mode | | + * | #L3GXXXX_STREAM | Stream mode | | + * | #L3GXXXX_STREAM_TO_FIFO | Stream-to-FIFO mode | L3GD20H and L3GD20 only | + * | #L3GXXXX_BYPASS_TO_STREAM | Bypass-to-Stream mode | L3GD20H and L3GD20 only | + * | #L3GXXXX_DYNAMIC_STREAM | Dynamic Stream mode | L3GD20H only | + * | #L3GXXXX_BYPASS_TO_FIFO | Bypass to FIFO mode | L3GD20H only | + *

+ * + * A watermark level (threshold) can be set for the FIFO. If the number of + * data samples in the FIFO exceeds this value, the watermark flag is set + * and the interrupt #L3GXXXX_INT_FIFO_WATERMARK is triggered, if enabled. + * This interrupt can be used to gather a minimum number of samples of raw + * output data before the data are fetched as a single read operation from + * the sensor. + * + * ## Configuration of the FIFO {#l3gxxxx_fifo_config} + * + * The default FIFO mode is defined by the default configuration parameter + * #CONFIG_L3GXXXX_FIFO_MODE. The default watermark level (threshold) of + * the FIFO is defined by the default configuration parameter + * #CONFIG_L3GXXXX_FIFO_WATERMARK. + * + * If module `l3gxxxx_config` is used, both configuration parameters can be + * changed at runtime with function #l3gxxxx_set_fifo_mode. This function + * takes two parameters, the FIFO mode and the watermark level. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * ... + * // set the FIFO mode with a watermark level (threshold) of 10, i.e. the + * // watermark flag is set or the interrupt is triggered for the 11th sample + * l3gxxxx_set_fifo_mode(&dev, L3GXXXX_STREAM, 10); + * ... + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @note To clear the FIFO at any time, set the FIFO mode to #L3GXXXX_BYPASS + * and back to the desired FIFO mode. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_set_fifo_mode(&dev, L3GXXXX_BYPASS, 0); + * l3gxxxx_set_fifo_mode(&dev, L3GXXXX_STREAM, 10); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ## Reading data from FIFO {#l3gxxxx_fifo_read_data} + * + * To read data from the FIFO, just use either + * + * - function #l3gxxxx_read_raw_fifo to get all raw output data stored + * in the FIFO or + * - function #l3gxxxx_read_fifo to get all data stored in the FIFO + * and converted to angular rates in mdps (millidegrees per second). + * + * Both functions clear the FIFO and return the number of samples read + * from the FIFO. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_data_fifo_t data; + * + * while (1) + * { + * // execute task every 500 ms + * xtimer_usleep(500 * US_PER_MS); + * ... + * // test for new data + * if (l3gxxxx_data_ready(&dev) > 0) { + * + * // fetch data from fifo + * int num = l3gxxxx_read_fifo(dev, data); + * + * for (int i = 0; i < num; i++) { + * // do something with data[i] ... + * } + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * # Using Interrupts {#l3gxxxx_interrupts} + * + * L3Gxxxx sensors allow to activate interrupts on two different signals: + * + * - for data (data ready and FIFO status) on signal `INT2/DRDY`, and + * - for events (axis movement and wake-up) on signal `INT1`. + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * ## Data interrupts (data ready and FIFO status) on signal INT2/DRDY {#l3gxxxx_data_interrupt} + * + * Interrupts on signal `INT2/DRDY` can be generated by the following sources: + * + *
+ * | Interrupt source | Driver symbol | + * |:-----------------------------------------|:----------------------------| + * | Output data are ready to be read | #L3GXXXX_INT_DATA_READY | + * | FIFO content exceeds the watermark level | #L3GXXXX_INT_FIFO_WATERMARK | + * | FIFO is completely filled | #L3GXXXX_INT_FIFO_OVERRUN | + * | FIFO becomes empty | #L3GXXXX_INT_FIFO_EMPTY | + *

+ * + * #L3GXXXX_INT_DATA is the bitwise OR combination of these symbols. + * + * @note Using data interrupts requires to enable module `l3gxxxx_irq_data`. + * + * Each interrupt source can be enabled or disabled separately with + * function #l3gxxxx_enable_int. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_enable_int(&dev, L3GXXXX_INT_DATA_READY, true); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * If `l3gxxxx_fifo` is used, #L3GXXXX_INT_FIFO_WATERMARK and + * #L3GXXXX_INT_FIFO_OVERRUN interrupts are enabled by default. Otherwise + * only #L3GXXXX_INT_DATA_READY is enabled by default. + * + * The MCU GPIO pin used for the `INT2/DRDY` interrupt signal has to be defined + * by the hardware configuration parameter #L3GXXXX_INT2_PIN. + * + * Once a data interrupt is enabled, function #l3gxxxx_wait_int can be + * used to wait for an interrupt on signal `INT2/DRDY`. This function + * returns a structure with the interrupt sources of type #l3gxxxx_int_src_t + * which contains a flag for each possible data interrupt source in + * member #l3gxxxx_int_src_t::data that can be tested for true. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_int_src_t int_src = l3gxxxx_wait_int(&dev); + * + * if (int_src.data.data_ready) { + * l3gxxxx_read(&dev, &data) + * ... + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * If module `l3gxxxx_fifo` is used, the corresponding interrupt sources can + * be testsed. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_int_src_t int_src = l3gxxxx_wait_int(&dev); + * + * ... + * if (int_src.data.fifo_threshold) { + * l3gxxxx_read(&dev, &data); + * ... + * } + * if (int_src.data.fifo_overrun) { + * printf("FIFO overrun"); + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * ## Event interrupts (axes movement and wake-up) on signal INT1 {#l3gxxxx_event_interrupt} + * + * This interrupt signal allows to recognize independent rotations of + * the X, Y and Z axes. For this purpose, a separate threshold can be + * defined for each axis. If activated, the angular rate of each axis + * is compared with its threshold to check whether it is below or above + * the threshold. The results of all activated comparisons are combined + * OR or AND to generate the interrupt signal. + * + * The configuration of the thresholds, the activated comparisons and + * selected AND/OR combination allows to recognize special situations + * like selective axis movement (SA) or axes movement wake-up (WU). + * + * - **Selective axis movement recognition (SA)** means that only one + * axis is rotating. This is the case if the angular rate of selected + * axis is above its threshold AND angular rates of all other axes are + * below their thresholds. + * + * - **Axis movement wake-up (WU)** means that the angular rate of any + * axis is above its threshold (OR). + * + * @note Using event interrupts requires to enable module `l3gxxxx_irq_event`. + * + * The MCU GPIO pin used for the `INT1` interrupt signal is defined by the + * hardware configuration parameter #L3GXXXX_INT1_PIN. + * + * The default configuration for event interrupts is defined by + * #L3GXXXX_INT1_PARAMS. This configuration can be changed either by + * overriding default configuration parameters + * #CONFIG_L3GXXXX_INT1_X_THRESH, #CONFIG_L3GXXXX_INT1_Y_THRESH, + * #CONFIG_L3GXXXX_INT1_Z_THRESH, + * #CONFIG_L3GXXXX_INT1_X_LT_THRESH, #CONFIG_L3GXXXX_INT1_X_GT_THRESH, + * #CONFIG_L3GXXXX_INT1_Y_LT_THRESH, #CONFIG_L3GXXXX_INT1_Y_GT_THRESH, + * #CONFIG_L3GXXXX_INT1_Z_LT_THRESH, #CONFIG_L3GXXXX_INT1_Z_GT_THRESH, + * #CONFIG_L3GXXXX_INT1_FILTER, #CONFIG_L3GXXXX_INT1_AND and + * #CONFIG_L3GXXXX_INT1_LATCH or at runtime using function + * #l3gxxxx_set_int_event_cfg with a set of parameters + * of type #l3gxxxx_int_event_cfg_t that contains the configuration. + * For example, selective axis movement recognition (SA) for the z-axis + * could be configured as following. With this configuration, the event + * interrupt is only triggered if all conditions are met. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_int_event_cfg_t int_cfg; + * + * // thresholds + * int_cfg.x_threshold = 100; + * int_cfg.y_threshold = 100; + * int_cfg.z_threshold = 1000; + * + * // X axis below threshold enabled + * int_cfg.x_low_enabled = true; + * int_cfg.x_high_enabled = false; + * + * // Y axis below threshold enabled + * int_cfg.y_low_enabled = true; + * int_cfg.y_high_enabled = false; + * + * // Z axis below threshold enabled + * int_cfg.z_low_enabled = false; + * int_cfg.z_high_enabled = true; + * + * // AND combination of all conditions + * int_cfg.and_or = true; + * + * // further parameters + * int_cfg.filter = L3GXXXX_HPF_ONLY; + * int_cfg.latch = true; + * int_cfg.duration = 0; + * int_cfg.wait = false; + * + * // set the configuration and enable the interrupt + * l3gxxxx_set_int_event_cfg(&dev, &int_cfg); + * l3gxxxx_enable_int(&dev, L3GXXXX_INT_EVENT, true); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * The data structure #l3gxxxx_int_event_cfg_t defines + * - whether the interrupt signal should be latched until the interrupt + * source is read by function #l3gxxxx_wait_int, + * - which filters are applied to the data used for interrupt generation, + * - which time measured in 1/ODR an interrupt condition has to be given before + * the interrupt is generated, and + * - whether this time is also used when interrupt condition is no + * longer given before interrupt signal is reset. + * + * @note + * - If the interrupt is configured to be latched, the interrupt signal is + * active until the interrupt source is read by function #l3gxxxx_wait_int + * AND next raw output data are available. Otherwise the interrupt signal is + * active as long as the interrupt condition is satisfied. + * - The driver function #l3gxxxx_wait_int uses the leading flank of the + * interrupt signal to detect an interrupt and read the interrupt source. + * + * Function #l3gxxxx_enable_int is used to enable or disable the + * event interrupt generation (#L3GXXXX_INT_EVENT). + * + * As with data interrupts function #l3gxxxx_wait_int can be used to + * wait for an interrupt on signal `INT1` if event interrupts are enabled. + * This function returns a structure with the interrupt sources of type + * #l3gxxxx_int_src_t which contains a flag for each possible event interrupt + * source in member #l3gxxxx_int_src_t::event that can be tested for true. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_int_src_t int_src = l3gxxxx_wait_int(&dev); + * + * if (int_src.event.x_low) { + * printf("x below "); + * } + * if (int_src.event.x_high) { + * printf("x above "); + * } + * ... + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Activating all threshold comparisons and using the OR combination is the + * most flexible way to realize functions like selective axis movement by + * combining the different interrupt sources as required by the application. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_int_event_cfg_t int_cfg; + * + * // thresholds + * int_cfg.x_threshold = 100; + * int_cfg.y_threshold = 100; + * int_cfg.z_threshold = 100; + * + * // X axis below and above threshold enabled + * int_cfg.x_low_enabled = true; + * int_cfg.x_high_enabled = true; + * + * // Y axis below and above threshold enabled + * int_cfg.y_low_enabled = true; + * int_cfg.y_high_enabled = true; + * + * // Z axis below and above threshold enabled + * int_cfg.z_low_enabled = true; + * int_cfg.z_high_enabled = true; + * + * // OR combination of all conditions + * int_cfg.and_or = false; + * ... + * // set the configuration and enable the interrupt + * l3gxxxx_set_int_event_cfg(&dev, &int_cfg); + * l3gxxxx_enable_int(&dev, l3gxxxx_int_event, true); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Following example shows the selective axis movement recognition (SA) + * for the Z-axis. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * l3gxxxx_int_src_t int_src = l3gxxxx_wait_int(&dev); + * + * if (int_src.event.y_low && int_src.event.y_low && int_src.event.z_high) { + * // selective axis movement of Z-axis + * ... + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ## Interrupt context problem {#l3gxxxx_interrupt_context} + * + * All functions of the driver require direct access to the sensor via + * I2C or SPI which does not work in interrupt context. + * + * Therefore, the driver prevents the direct use of the interrupts and + * application specific ISRs. The only way to use interrupts is to call + * the function #l3gxxxx_wait_int which enables the interrupt signals + * for the configured MCU GPIO pins and then blocks the calling thread + * until an interrupt is triggered. + * + * Once an interrupt is triggered, the driver handles the interrupt with + * an internal ISR and then returns from the #l3gxxxx_wait_int function. + * The return value is a structure with the interrupt sources of type + * #l3gxxxx_int_src_t, which contains a flag for each possible interrupt + * source that can be tested for true. + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * ## Interrupt signal properties {#l3gxxxx_interrupt_signal} + * + * By default, interrupt signals are high active push/pull outputs. + * + * # Power Saving {#l3gxxxx_power_saving} + * + * L3Gxxxx sensors offer two modes for power saving: + * + * - **Power-down** mode + * - **Sleep** mode + * + * While in power-down mode almost all internal blocks of the device including + * the gyros are switched off, in sleep mode only the measuring functions for + * all three axes are deactivated. Therefore, the time to continue measurements + * after sleep mode is drastically shorter than after power down. + * + *
+ * | Starting mode | Target mode | Turn-on time typical | + * | ------------- | ----------- | ---------------------| + * | Power-down | Normal | 100 ms | + * | Power-down | Sleep | 100 ms | + * | Sleep | Normal | 1/ODR when LPF2 disabled
6/ODR when LPF2 enabled | + *

+ * + * L3Gxxxx sensors can be powered down when no measurements are required + * using the function #l3gxxxx_power_down. The power consumption is reduced + * to some uA in power-down mode. To restart the L3Gxxxx in previous + * measurement mode, the #l3gxxxx_power_up function can be used. + * + * If module `l3gxxxx_sleep` is enabled, the sleep mode can be activated with + * function #l3gxxxx_sleep. The power consumption is then reduced from 4.8 mA + * to 2.4 mA and thus to half. The #l3gxxxx_wake_up function continues the + * measurement in previous operation mode. + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * # Low level functions {#l3gxxxx_low_level} + * + * L3Gxxxx sensors are complex and flexible sensors with a lot of + * features. It can be used for a big number of different use cases. + * Since it is impossible to implement a high level interface + * which is generic enough to cover all the functionality of the sensor + * for all different use cases, there are two low level interface + * functions that allow direct read and write access to the registers + * of the sensor. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * bool l3gxxxx_reg_read(l3gxxxx_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); + * bool l3gxxxx_reg_write(l3gxxxx_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @warning + * These functions should only be used to do something special that + * is not covered by the high level interface AND if you exactly + * know what you do and what it might affect. Please be aware that + * it might affect the high level interface. + * + * [Back to Table of Contents](#l3gxxxx_toc) + * + * # Default configuration {#l3gxxxx_default_configuration} + * + * Default sensor hardware configurations are set in file `l3gxxxx_params.h` + * using the following defines: + * + *
+ * | Hardware configuration | Driver name | Default Value | + * |:-----------------------|:------------------|:--------------------| + * | I2C device | #L3GXXXX_I2C_DEV | I2C_DEV(0) | + * | I2C address | #L3GXXXX_I2C_ADDR | #L3GXXXX_I2C_ADDR_2 | + * | SPI device | #L3GXXXX_SPI_DEV | SPI_DEV(0) | + * | SPI clock frequency | #L3GXXXX_SPI_CLK | SPI_CLK_1MHZ | + * | SPI CS signal | #L3GXXXX_SPI_CS | GPIO_PIN(0,0) | + * | `INT1` MCU pin | #L3GXXXX_INT1_PIN | GPIO_PIN(0,1) | + * | `INT2/DRDY` MCU pin | #L3GXXXX_INT2_PIN | GPIO_PIN(0,2) | + *

+ * + * These hardware configurations can be overridden either by the board + * definition or by defining them in the `CFLAGS` variable in the make + * command, for example: + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * DRIVER=l3gd20h USEMODULE='l3gxxxx_irq_data` \ + * CLFAGS='-DL3GXXXX_INT2_PIN=GPIO_PIN\(0,5\)' \ + * BOARD=... make -C tests/driver_l3gxxxx + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * The default configuration of the sensor is defined in file + * `l3gxxxx_params.h` using the following defines: + * + *
+ * | Configuration parameter | Driver name | Default Value | + * |:-------------------------------------|:------------------------------|:----------------------| + * | ODR and LPF2 cutoff frequency | #CONFIG_L3GXXXX_ODR | #L3GXXXX_ODR_100_25 | + * | Full scale | #CONFIG_L3GXXXX_SCALE | #L3GXXXX_SCALE_245_DPS| + * | Filter selection used for output data| #CONFIG_L3GXXXX_FILTER_SEL | #L3GXXXX_HPF_AND_LPF2 | + * | HPF mode used for output data | #CONFIG_L3GXXXX_HPF_MODE | #L3GXXXX_HPF_NORMAL | + * | HPF cutoff frequency 8 Hz | #CONFIG_L3GXXXX_HPF_CUTOFF | 0 | + * | FIFO mode if FIFO is used | #CONFIG_L3GXXXX_FIFO_MODE | #L3GXXXX_FIFO | + * | FIFO threshold value if FIFO is used | #CONFIG_L3GXXXX_FIFO_WATERMARK| 23 | + * | INT1 threshold for X axis (~30 dps) | #CONFIG_L3GXXXX_INT1_X_THRESH | 4012 | + * | INT1 threshold for Y axis (~30 dps) | #CONFIG_L3GXXXX_INT1_Y_THRESH | 4012 | + * | INT1 threshold for Z axis (~30 dps) | #CONFIG_L3GXXXX_INT1_Z_THRESH | 4012 | + * | INT1 interrupt enable for X > X threshold | #CONFIG_L3GXXXX_INT1_X_GT_THRESH | true | + * | INT1 interrupt enable for Y > Y threshold | #CONFIG_L3GXXXX_INT1_Y_GT_THRESH | true | + * | INT1 interrupt enable for Z > Z threshold | #CONFIG_L3GXXXX_INT1_Z_GT_THRESH | true | + * | INT1 interrupt enable for X < X threshold | #CONFIG_L3GXXXX_INT1_X_LT_THRESH | false | + * | INT1 interrupt enable for Y < Y threshold | #CONFIG_L3GXXXX_INT1_Y_LT_THRESH | false | + * | INT1 interrupt enable for Z < Z threshold | #CONFIG_L3GXXXX_INT1_Z_LT_THRESH | false | + * | INT1 filter selection | #CONFIG_L3GXXXX_INT1_FILTER | #L3GXXXX_HPF_AND_LPF2 | + * | INT1 interrupt combination | #CONFIG_L3GXXXX_INT1_AND | false | + * | INT1 interrupt latch enabled | #CONFIG_L3GXXXX_INT1_LATCH | true | + *

+ * + * Single or all parameters of the default configuration can be overridden + * either by defining them in the variable `CFLAGS` in the make command + * line or by placing a modified file `l3gxxxx_params.h` in the + * application directory, for example: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * DRIVER=l3gd20h USEMODULE='l3gxxxx_low_odr l3gxxxx_irq_data` \ + * CLFAGS='-DCONFIG_L3GXXXX_ODR=L3GXXXX_ODR_12 -DL3GXXXX_INT2_PIN=GPIO_PIN\(0,5\)' \ + * BOARD=... make -C tests/driver_l3gxxxx + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @author Gunar Schorcht + * + * @{ + * @file + * @brief Device Driver for ST L3Gxxxx 3-axis gyroscope sensor family + */ + +#ifndef L3GXXXX_H +#define L3GXXXX_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +#include "mutex.h" +#include "periph/gpio.h" +#include "periph/i2c.h" +#include "periph/spi.h" + +#include "l3gxxxx_regs.h" + +#if !IS_USED(MODULE_L3GD20H) && !IS_USED(MODULE_L3GD20) \ + && !IS_USED(MODULE_L3G4200_NG) \ + && !IS_USED(MODULE_A3G4250D) \ + && !IS_USED(MODULE_I3G4250D) +#error Please select your sensor variant by using the respective pseudomodule. +#endif + +/** + * @name L3Gxxxx addresses + * @{ + */ +#if IS_USED(MODULE_L3GD20H) || IS_USED(MODULE_L3GD20) +#define L3GXXXX_I2C_ADDR_1 (0x6a) /**< SDO pin is low */ +#define L3GXXXX_I2C_ADDR_2 (0x6b) /**< SDO pin is high */ +#else +#define L3GXXXX_I2C_ADDR_1 (0x68) /**< SDO pin is low */ +#define L3GXXXX_I2C_ADDR_2 (0x69) /**< SDO pin is high */ +#endif +/** @} */ + +/** + * @name L3Gxxxx chip ids + * @{ + */ +#define L3GXXXX_CHIP_ID_L3GD20H (0xd7) /**< Chip ID for L3GD20H */ +#define L3GXXXX_CHIP_ID_L3GD20 (0xd4) /**< Chip ID for L3GD20 */ +#define L3GXXXX_CHIP_ID_X3G42XXD (0xd3) /**< Chip ID for L3G4200D, I3G4250D, A3G4250D */ +/** @} */ + +/** Definition of error codes */ +typedef enum { + L3GXXXX_OK, /**< success */ + L3GXXXX_ERROR_I2C, /**< I2C communication error */ + L3GXXXX_ERROR_SPI, /**< SPI communication error */ + L3GXXXX_ERROR_WRONG_CHIP_ID, /**< wrong chip id read from WHO_AM_I reg */ + L3GXXXX_ERROR_INV_DEV, /**< invalid device type used */ + L3GXXXX_ERROR_INV_MODE, /**< sensor mode is invalid or not available */ + L3GXXXX_ERROR_INV_FIFO_MODE, /**< FIFO mode is invalid or not available */ + L3GXXXX_ERROR_INV_INT_TYPE, /**< invalid interrupt type used */ + L3GXXXX_ERROR_NO_NEW_DATA, /**< no new data are available */ + L3GXXXX_ERROR_RAW_DATA, /**< reading raw output data failed */ + L3GXXXX_ERROR_RAW_DATA_FIFO, /**< reading raw output data from FIFO failed */ + L3GXXXX_ERROR_NO_INT1_PIN, /**< `INT1` signal pin not configured */ + L3GXXXX_ERROR_NO_INT2_PIN, /**< `INT2/DRDY` signal pin not configured */ + L3GXXXX_ERROR_BYPASS_MODE, /**< sensor is in bypass mode */ + L3GXXXX_ERROR_FIFO_MODE, /**< sensor is in FIFO mode */ +} l3gxxxx_error_codes_t; + +/** + * @brief Sensor output data rates (ODR) and LPF2 cutoff frequencies + * + * The following output data rates (ODR) and the LPF1/LPF2 cutoff frequencies + * are defined (Reference: Application Note AN4506): + * + *
+ * | Mode | ODR [Hz] | LPF1 cutoff [Hz] | LPF2 cutoff [Hz] | + * |:-----------------------|:--------:|:----------------:|:----------------:| + * | High ODR | | | | + * | L3GXXXX_ODR_100_12 | 100 | 32 | 12.5 | + * | L3GXXXX_ODR_100_25 | 100 | 32 | 25 | + * | L3GXXXX_ODR_200_12 | 200 | 63.3 | 12.5 | + * | L3GXXXX_ODR_200_25 | 200 | 63.3 | 25 | + * | L3GXXXX_ODR_200_50 | 200 | 63.3 | 50 | + * | L3GXXXX_ODR_200_70 | 200 | 63.3 | 70 | + * | L3GXXXX_ODR_400_20 | 400 | 128 | 20 | + * | L3GXXXX_ODR_400_25 | 400 | 128 | 25 | + * | L3GXXXX_ODR_400_50 | 400 | 128 | 50 | + * | L3GXXXX_ODR_400_110 | 400 | 128 | 110 | + * | L3GXXXX_ODR_800_30 | 800 | 211 | 30 | + * | L3GXXXX_ODR_800_35 | 800 | 211 | 35 | + * | L3GXXXX_ODR_800_50 | 800 | 211 | 50 | + * | L3GXXXX_ODR_800_100 | 800 | 211 | 100 | + * | | | | | + * | Low ODR (L3GD20H only) | | | | + * | L3GXXXX_ODR_12 | 12.5 | 3.9 | - | + * | L3GXXXX_ODR_25 | 25 | 7.8 | - | + * | L3GXXXX_ODR_50 | 50 | 16 | 16.6 | + *

+ * + * Detailed information about the filter chain and possible + * filter combinations can be found in the section + * [Output data rates and filters](#l3gxxxx_odr_filters). + * + * While LPF1 is always used, LPF2 and HPF have to be explicitly enabled + * by the configuration parameter l3gxxxx_params_t::filter_sel or the + * #l3gxxxx_select_output_filter function if module `l3gxxxx_config` is + * used. #L3GXXXX_ODR_100_25 is used by the default configuration. + * + * @note Low data rates 12.5 Hz, 25 Hz and 50 Hz are only supported by L3GXXXX. + */ +typedef enum { + L3GXXXX_ODR_100_12 = 0x00, /**< High ODR 100 Hz, LPF1 cutoff 32 Hz, LPF2 cutoff 12.5 Hz */ + L3GXXXX_ODR_100_25 = 0x01, /**< High ODR 100 Hz, LPF1 cutoff 32 Hz, LPF2 cutoff 25 Hz */ + L3GXXXX_ODR_200_12 = 0x04, /**< High ODR 200 Hz, LPF1 cutoff 63.3 Hz, LPF2 cutoff 12.5 Hz */ + L3GXXXX_ODR_200_25 = 0x05, /**< High ODR 200 Hz, LPF1 cutoff 63.3 Hz, LPF2 cutoff 25 Hz */ + L3GXXXX_ODR_200_50 = 0x06, /**< High ODR 200 Hz, LPF1 cutoff 63.3 Hz, LPF2 cutoff 50 Hz */ + L3GXXXX_ODR_200_70 = 0x07, /**< High ODR 200 Hz, LPF1 cutoff 63.3 Hz, LPF2 cutoff 70 Hz */ + L3GXXXX_ODR_400_20 = 0x08, /**< High ODR 400 Hz, LPF1 cutoff 128 Hz, LPF2 cutoff 20 Hz */ + L3GXXXX_ODR_400_25 = 0x09, /**< High ODR 400 Hz, LPF1 cutoff 128 Hz, LPF2 cutoff 25 Hz */ + L3GXXXX_ODR_400_50 = 0x0a, /**< High ODR 400 Hz, LPF1 cutoff 128 Hz, LPF2 cutoff 50 Hz */ + L3GXXXX_ODR_400_110 = 0x0b, /**< High ODR 400 Hz, LPF1 cutoff 128 Hz, LPF2 cutoff 110 Hz */ + L3GXXXX_ODR_800_30 = 0x0c, /**< High ODR 400 Hz, LPF1 cutoff 211 Hz, LPF2 cutoff 30 Hz */ + L3GXXXX_ODR_800_35 = 0x0d, /**< High ODR 400 Hz, LPF1 cutoff 211 Hz, LPF2 cutoff 35 Hz */ + L3GXXXX_ODR_800_50 = 0x0e, /**< High ODR 400 Hz, LPF1 cutoff 211 Hz, LPF2 cutoff 50 Hz */ + L3GXXXX_ODR_800_100 = 0x0f, /**< High ODR 400 Hz, LPF1 cutoff 211 Hz, LPF2 cutoff 100 Hz */ +#if IS_USED(MODULE_L3GD20H) || IS_USED(MODULE_L3GXXXX_LOW_ODR) || DOXYGEN + L3GXXXX_ODR_12 = 0x10, /**< Low ODR 12.5 Hz, LPF1 cutoff 3.9 Hz, LPF2 not used */ + L3GXXXX_ODR_25 = 0x14, /**< Low ODR 25 Hz, LPF1 cutoff 7.8 Hz, LPF2 not used */ + L3GXXXX_ODR_50 = 0x18, /**< Low ODR 50 Hz, LPF1 cutoff 16 Hz, LPF2 cutoff 16.6 Hz */ +#endif +} l3gxxxx_odr_t; + +/** + * @brief Full scale in degrees per second (dps) + * + * The full scale value determines the sensitivity of the sensor and thus + * the range and resolution of the sensor's output data. The resolution + * of the output data is about Full Scale/INT16_MAX. + * + * @note On the A34250D, only 245 dps (#L3GXXXX_SCALE_245_DPS) is + * available as full scale value. + */ +typedef enum { + L3GXXXX_SCALE_245_DPS = 0, /**< 245 dps (default) */ + L3GXXXX_SCALE_500_DPS = 1, /**< 500 dps */ + L3GXXXX_SCALE_2000_DPS = 2, /**< 2000 dps */ +} l3gxxxx_scale_t; + +/** + * @brief FIFO mode + * + * The integrated FIFO with up to 32 data samples can be used in different + * modes. The mode defines the behavior of FIFO when it becomes full. + */ +typedef enum { + L3GXXXX_BYPASS = 0, /**< FIFO is not used (default) */ + + L3GXXXX_FIFO = 1, /**< Data samples are stored in the FIFO until + it is full */ + L3GXXXX_STREAM = 2, /**< FIFO is used as ring buffer and newest + data samples are stored continuously */ +#if IS_USED(MODULE_L3GD20H) || IS_USED(MODULE_L3GD20) || DOXYGEN + L3GXXXX_STREAM_TO_FIFO = 3, /**< FIFO is used in Stream mode until an + interrupt, switches then to FIFO mode + (L3GD20H and L3GD20 only) */ + L3GXXXX_BYPASS_TO_STREAM = 4, /**< FIFO is not used until an interrupt, + switches then to Stream mode + (L3GD20H and L3GD20 only) */ +#endif +#if IS_USED(MODULE_L3GD20H) || DOXYGEN + L3GXXXX_DYNAMIC_STREAM = 6, /**< like Stream mode, but differs in reading + the first data sample after emptying + (L3GD20H only) */ + L3GXXXX_BYPASS_TO_FIFO = 7 /**< FIFO is not used until an interrupt, + switches then to FIFO mode + (L3GD20H only) */ +#endif +} l3gxxxx_fifo_mode_t; + +/** + * @brief High pass filter (HPF) and low pass filter 2 (LPF2) selection + * + * L3Gxxxx sensors integrate a combination of two low pass filters (LPF) and + * one high pass filter (HPF). + * + * First, raw sensor data are always filtered by LPF1 with a cutoff frequency + * that is fixed for the selected output data rate (ODR), see #l3gxxxx_odr_t. + * Resulting data can then optionally be filtered by HPF and/or LPF2. Both + * filters can be used or bypassed. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * +---------------> L3GXXXX_NO_FILTER + * | +----- + + * +------------+--->| |---> L3GXXXX_LPF2_ONLY + * | | LPF2 | + * +-----+ +------+ | +-----+ +--->| |---> L3GXXXX_HPF_AND_LPF2 + * | | | | | | | | +------+ + * | ADC |-->| LPF1 |--+-->| HPF |--+---------------> L3GXXXX_HPF_ONLY + * | | | | | | + * +-----+ +------+ +-----+ + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * #l3gxxxx_filter_sel_t defines the possible filter combinations that can + * be used to select the filters for the output data and for the + * interrupt generation separately. + * + * The default filter selection for the output data is #L3GXXXX_HPF_AND_LPF2 + * and is defined by the default configuration parameter + * #CONFIG_L3GXXXX_FILTER_SEL. If the module `l3gxxxx_config` is used, it can + * be changed at runtime using function #l3gxxxx_select_output_filter. + * + * The default filter selection for event interrupt generation is + * #L3GXXXX_HPF_AND_LPF2 and is defined by default configuration parameter + * #CONFIG_L3GXXXX_INT1_FILTER. It can be changed at runtime with function + * #l3gxxxx_set_int_event_cfg. + * + * @note Since same filters are used for the output data as well as the + * data used for event interrupt generation (selective axes movement / wake-up), + * the configuration of the filters always affects both data. If the HPF is + * enabled for filtering the output data, it is also active for filtering the + * sensor data used for interrupt generation if the LPF2 is enabled for + * interrupt generation. The other way around, the HPF is also active for + * filtering the output data when it is enabled for interrupt generation and + * when the LPF2 is enabled for the output data. + * + * The cutoff frequencies of LPF1 and LPF2 are determined by the used output + * data rate #l3gxxxx_odr_t, see section [Output Data Rates and Filters] + * (#l3gxxxx_odr_filters). The default cutoff frequency of HPF is 8 Hz and + * set by the default configuration parameter #CONFIG_L3GXXXX_HPF_CUTOFF. + * If module `l3gxxxx_config` is used, it can be changed at runtime using + * function #l3gxxxx_config_hpf. + */ +typedef enum { + L3GXXXX_NO_FILTER = 0, /**< HPF not used, LPF2 not used (default) */ + L3GXXXX_HPF_ONLY = 1, /**< HPF used, LPF2 not used */ + L3GXXXX_LPF2_ONLY = 2, /**< HPF not used, LPF2 used */ + L3GXXXX_HPF_AND_LPF2 = 3 /**< HPF used, LPF2 used */ +} l3gxxxx_filter_sel_t; + +/** + * @brief HPF (high pass filter) modes + * + * The high pass filter can be used in different modes. + */ +typedef enum { + L3GXXXX_HPF_NORMAL = 0, /**< Normal mode, HPF is reset by reading + the REFERENCE register */ + L3GXXXX_HPF_REFERENCE = 1, /**< Reference mode, output data are the + difference to the REFERENCE register */ + L3GXXXX_HPF_AUTORESET = 3 /**< Autoreset mode, HPF is automatically reset + when a configured event interrupt occurs */ +} l3gxxxx_hpf_mode_t; + +/** + * @brief Interrupt types + * + * L3Gxxxx sensors support different types of interrupts. These are on the + * one hand the various data interrupts on signal `INT2/DRDY` and on the + * other hand event interrupts on signal `INT1`. + * + * The enumeration values correspond to the according bits in register + * CTRL3 (#L3GXXXX_REG_CTRL3). + * + * #L3GXXXX_INT_DATA combines the various data interrupts to an ORed value. + */ +typedef enum { + /** + * Data interrupt on signal `INT2/DRDY`: Output data are ready to be read. + */ + L3GXXXX_INT_DATA_READY = L3GXXXX_INT2_DRDY, + /** + * Data interrupt on signal `INT2/DRDY`: FIFO filling exceeds the + * watermark level (threshold) + */ + L3GXXXX_INT_FIFO_WATERMARK = L3GXXXX_INT2_WTM, + /** + * Data interrupt on signal `INT2/DRDY`: FIFO is completely filled + */ + L3GXXXX_INT_FIFO_OVERRUN = L3GXXXX_INT2_ORUN, + /** + * Data interrupt on signal `INT2/DRDY`: FIFO becomes empty + */ + L3GXXXX_INT_FIFO_EMPTY = L3GXXXX_INT2_EMPTY, + /** + * Event interrupt on signal `INT1`: Angular rate of one or more axes + * is lower or higher than the configured threshold. + */ + L3GXXXX_INT_EVENT = L3GXXXX_INT1_IG, +} l3gxxxx_int_types_t; + +/** + * @brief Data interrupts (Data ready and FIFO status) + * + * This define combines the data interrupt types of #l3gxxxx_int_types_t + * that use the `INT2/DRDY` signal to an ORed value. + */ +#define L3GXXXX_INT_DATA (L3GXXXX_INT_DATA_READY | \ + L3GXXXX_INT_FIFO_WATERMARK | \ + L3GXXXX_INT_FIFO_OVERRUN | \ + L3GXXXX_INT_FIFO_EMPTY) + +/** + * @brief Event interrupt generator configuration (axis movement and wake-up) + * + * memset to 0 to disable all interrupt conditions (default) + */ +typedef struct { + uint16_t x_threshold; /**< X threshold value in full scale / INT16_MAX */ + uint16_t y_threshold; /**< Y threshold value in full scale / INT16_MAX */ + uint16_t z_threshold; /**< Z threshold value in full scale / INT16_MAX */ + + bool x_low_enabled; /**< Interrupt enabled for |X| < X threshold (X low event) */ + bool x_high_enabled; /**< Interrupt enabled for |X| > X threshold (X high event) */ + + bool y_low_enabled; /**< Interrupt enabled for |Y| < Y threshold (Y low event) */ + bool y_high_enabled; /**< Interrupt enabled for |Y| > Y threshold (Y high event) */ + + bool z_low_enabled; /**< Interrupt enabled for |Z| < Z threshold (Z low event) */ + bool z_high_enabled; /**< Interrupt enabled for |Z| > Z threshold (Y high event) */ + + l3gxxxx_filter_sel_t filter; /**< HPF and LPF2 filter selection used + for threshold comparison */ + + bool and_or; /**< Combination of interrupt events (true=AND, false=OR):
+ AND - all enabled axes passed their tresholds
+ OR - at least one axis passed its threshold */ + bool latch; /**< Latch the interrupt when true until the interrupt + source has been read by function l3gxxxx_wait_int. */ + uint8_t duration; /**< Duration in 1/ODR an interrupt condition has to be + given before the interrupt is generated. */ + bool wait; /**< When true, duration is also used when interrupt + condition in no longer given before interrupt + signal is reset. */ + bool counter_mode; /**< DCRM is not documented and not used therefore. */ +} l3gxxxx_int_event_cfg_t; + +/** + * @brief Event interrupt sources (axis movement and wake-up) + */ +typedef union { + struct { + uint8_t x_low :1; /**< true on |X| < X threshold (X low event) */ + uint8_t x_high:1; /**< true on |X| > X threshold (X high event) */ + uint8_t y_low :1; /**< true on |Y| < Y threshold (Y low event) */ + uint8_t y_high:1; /**< true on |Y| > Y threshold (Y high event) */ + uint8_t z_low :1; /**< true on |Z| < Z threshold (Z low event) */ + uint8_t z_high:1; /**< true on |Z| > Z threshold (Z high event) */ + uint8_t active:1; /**< true when one ore more events have been generated */ + uint8_t unused:1; /**< not used */ + }; + uint8_t val; /**< event interrupt sources as value that + can be used for bitwise operations */ +} l3gxxxx_int_event_src_t; + +/** + * @brief Data interrupt sources (data ready and FIFO status) + */ +typedef union { + struct { + uint8_t fifo_empty :1; /**< true when FIFO is empty */ + uint8_t fifo_overrun :1; /**< true when FIFO is completely filled */ + uint8_t fifo_watermark:1; /**< true when FIFO filling > watermark */ + uint8_t data_ready :1; /**< true when data are ready to read */ + uint8_t unused :4; /**< not used */ + }; + uint8_t val; /**< data interrupt sources as value that + can be used for bitwise operations */ +} l3gxxxx_int_data_src_t; + +/** + * @brief Composite type for all possible interrupt sources + * + * This type combines the possible interrupt sources for event interrupts on + * signal `INT1` (l3gxxxx_int_event_src_t) with those for data interrupts on + * signal `INT2/DRDY` (l3gxxxx_int_data_src_t). + */ +typedef struct { + l3gxxxx_int_event_src_t event; /**< event interrupt sources */ + l3gxxxx_int_data_src_t data; /**< data interrupt sources */ +} l3gxxxx_int_src_t; + +/** + * @brief `INT1`, `INT2/DRDY` sensor signal activity level + */ +typedef enum { + L3GXXXX_HIGH = 0, /**< INT signals are High active (default) */ + L3GXXXX_LOW /**< INT signals are Low active */ +} l3gxxxx_int_pin_level_t; + +/** + * @brief `INT1`, `INT2/DRDY` sensor signal type + */ +typedef enum { + L3GXXXX_PUSH_PULL = 0, /**< INT pins are push/pull outputs (default) */ + L3GXXXX_OPEN_DRAIN /**< INT pins are open-drain */ +} l3gxxxx_int_pin_type_t; + +/** + * @brief Raw output data set as two's complements + */ +typedef struct { + int16_t x; /**< X angular rate (roll) as 16 bit two's complements */ + int16_t y; /**< Y angular rate (pitch) as 16 bit two's complements */ + int16_t z; /**< Z angular rate (yaw) as 16 bit two's complements */ +} l3gxxxx_raw_data_t; + +/** + * @brief Angular rates in millidegrees per seconds (mdps) + */ +typedef struct { + int32_t x; /**< X angular rate (roll) */ + int32_t y; /**< Y angular rate (pitch) */ + int32_t z; /**< Z angular rate (yaw) */ +} l3gxxxx_data_t; + +/** + * @brief Raw output data FIFO type + */ +typedef l3gxxxx_raw_data_t l3gxxxx_raw_data_fifo_t[32]; + +/** + * @brief Angular rates FIFO type + */ +typedef l3gxxxx_data_t l3gxxxx_data_fifo_t[32]; + +/** + * @brief L3Gxxxx interface types + */ +typedef enum { +#if IS_USED(MODULE_L3GXXXX_I2C) || DOXYGEN + L3GXXXX_I2C, /**< I2C interface used */ +#endif +#if IS_USED(MODULE_L3GXXXX_SPI) || DOXYGEN + L3GXXXX_SPI, /**< SPI interface used */ +#endif +} l3gxxxx_if_t; + +#if IS_USED(MODULE_L3GXXXX_I2C) || DOXYGEN +/** + * @brief L3Gxxxx I2C interface parameters + */ +typedef struct { + i2c_t dev; /**< I2C device used */ + uint8_t addr; /**< I2C slave address */ +} l3gxxxx_i2c_params_t; +#endif + +#if IS_USED(MODULE_L3GXXXX_SPI) || DOXYGEN +/** + * @brief L3Gxxxx SPI interface parameters + */ +typedef struct { + spi_t dev; /**< SPI device used */ + spi_clk_t clk; /**< SPI clock speed */ + gpio_t cs; /**< SPI chip Select pin */ +} l3gxxxx_spi_params_t; +#endif + +/** + * @brief L3Gxxxx Hardware interface parameters union + */ +typedef struct { + l3gxxxx_if_t type; /**< I2C/SPI interface type selector */ + union { +#if IS_USED(MODULE_L3GXXXX_I2C) || DOXYGEN + l3gxxxx_i2c_params_t i2c; /**< I2C interface parameters */ +#endif +#if IS_USED(MODULE_L3GXXXX_SPI) || DOXYGEN + l3gxxxx_spi_params_t spi; /**< SPI interface parameters */ +#endif + }; +} l3gxxxx_if_params_t; + +/** + * @brief L3Gxxxx device initialization parameters + */ +typedef struct { + l3gxxxx_if_params_t if_params; /**< Interface parameters (I2C/SPI) */ + + l3gxxxx_odr_t odr; /**< ODR and LPF2 cutoff frequency */ + l3gxxxx_scale_t scale; /**< Full scale */ + + l3gxxxx_filter_sel_t filter_sel; /**< HPF/LPF2 filter selection */ + l3gxxxx_hpf_mode_t hpf_mode; /**< HPF mode */ + uint8_t hpf_cutoff; /**< HPF cutoff frequency 0..9, see + l3gxxxx_config_hpf for details */ +#if IS_USED(MODULE_L3GXXXX_FIFO) || DOXYGEN + l3gxxxx_fifo_mode_t fifo_mode; /**< FIFO operation mode */ + uint8_t fifo_watermark; /**< FIFO watermark setting 0..31 */ +#endif + +#if IS_USED(MODULE_L3GXXXX_IRQ_DATA) || DOXYGEN + gpio_t int2_pin; /**< MCU GPIO pin for data interrupts + on signal `INT2/DRDY` pin */ +#endif + +#if IS_USED(MODULE_L3GXXXX_IRQ_EVENT) || DOXYGEN + l3gxxxx_int_event_cfg_t int1_cfg; /**< event interrupt parameters */ + gpio_t int1_pin; /**< MCU GPIO pin for event interrupts + on signal `INT1` */ +#endif + +} l3gxxxx_params_t; + +/** + * @brief L3Gxxxx sensor device data structure type + */ +typedef struct { + l3gxxxx_params_t params; /**< Device initialization parameters */ + l3gxxxx_int_types_t int_type; /**< Type of the last interrupt triggered */ + mutex_t int_lock; /**< Used to lock the calling thread while + waiting for an interrupt */ + enum { /**< Sensor detected at runtime */ + L3GD20H, /**< L3GD20H detected */ + L3GD20, /**< L3GD20 detected */ + X3G42XXD /**< L3G400D, I3G4250D or A3G4250D detected */ + } sensor; /**< recognized sensor type */ +} l3gxxxx_t; + +/** + * @name Sensor initialization and configuration + * @{ + */ + +/** + * @brief Initialize the L3Gxxxx sensor device + * + * This function resets the sensor and initializes it according to the + * given configuration parameter set. All registers are reset to their + * default values. The FIFO is cleared. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor to be initialized + * @param[in] params L3Gxxxx configuration parameters + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_init(l3gxxxx_t *dev, const l3gxxxx_params_t *params); + +#if IS_USED(MODULE_L3GXXXX_CONFIG) || DOXYGEN +/** + * @brief Set sensor mode + * + * @note This function is available only if module `l3gxxxx_config` is used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[in] odr output data rate (ODR) and LPF2 cutoff frequency + * @param[in] x enable X axis measurements if true + * @param[in] y enable Y axis measurements if true + * @param[in] z enable Z axis measurements if true + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_set_mode(l3gxxxx_t *dev, + l3gxxxx_odr_t odr, bool x, bool y, bool z); + +/** + * @brief Set full scale + * + * @note This function is available only if module `l3gxxxx_config` is used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[in] scale fulle scale + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_set_scale(l3gxxxx_t *dev, l3gxxxx_scale_t scale); + +#endif /* IS_USED(MODULE_L3GXXXX_CONFIG) || DOXYGEN */ +/** @} */ + +/** + * @name Power saving functions + * @{ + */ +/** + * @brief Power down the sensor + * + * Changes the sensor operation mode to power-down mode. In this mode almost all + * internal blocks including the gyros are switched off. I2C and SPI interfaces + * are still active. The content of the configuration registers is preserved. + * + * @param[in] dev Device descriptor of L3Gxxxx device to read from + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_power_down (l3gxxxx_t *dev); + +/** + * @brief Power up the sensor + * + * Swichtes the sensor back into the last active operation mode. It takes + * up to 100 ms since the gyros have to be switched on. + * + * @param[in] dev Device descriptor of L3Gxxxx device to read from + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_power_up (l3gxxxx_t *dev); + +#if IS_USED(MODULE_L3GXXXX_SLEEP) || DOXYGEN +/** + * @brief Sleep mode + * + * Activates the sleep mode of the sensor. In this mode, measurements for all + * axes are disabled, but the gyroscopes remain on. To return from sleep mode, + * function #l3gxxxx_wake_up is used. + * + * @note This function is available only if module `l3gxxxx_sleep` is used. + * + * @param[in] dev Device descriptor of L3Gxxxx device to read from + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_sleep (l3gxxxx_t *dev); + +/** + * @brief Wake-up the sensor + * + * Swichtes the sensor back into the last active operation mode. It takes only + * 1/ODR when LPF2 is disabled and 6/ODR when LPF2 is enabled to + * continue measurements. + * + * @note This function is available only if module `l3gxxxx_sleep` is used. + * + * @param[in] dev Device descriptor of L3Gxxxx device to read from + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_wake_up (l3gxxxx_t *dev); + +#endif /* IS_USED(MODULE_L3GXXXX_SLEEP) || DOXYGEN */ +/** @} */ + +/** + * @name Basic sensor data handling + * @{ + */ + +/** + * @brief Data ready status function + * + * This function returns the number of new data samples that are ready to be + * read or 0 if no new data samples are available. + * + * If the FIFO is not used or used in bypass mode (#L3GXXXX_BYPASS), the + * maximum number of available data samples is 1. If another FIFO mode is + * used, the number of available data samples is equal to the number of + * new data samples in the FIFO. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * + * @return number of data samples available for read or negative error code, + * see #l3gxxxx_error_codes_t + */ +int l3gxxxx_data_ready(const l3gxxxx_t *dev); + +/** + * @brief Read last sample of angular rates in millidegree per second (mpds) + * + * Raw output data are read from the sensor and converted to angular rates + * in millidegrees per second (mdps). The resolution of the angular rates + * depends on the configured full scale value as follows: + * + *
+ * | Full Scale | Resolution | Driver symbol | Remark | + * | -----------:|-----------------:|:------------------------|:--------------------------| + * | ±245 dps | 8.75 mdps / LSB | #L3GXXXX_SCALE_245_DPS | | + * | ±500 dps | 17.50 mdps / LSB | #L3GXXXX_SCALE_500_DPS | not available on A3G4250D | + * | ±2000 dps | 70.00 mdps / LSB | #L3GXXXX_SCALE_2000_DPS | not available on A3G4250D | + *

+ * + * @note If the FIFO is enabled by module `l3gxxxxx_fifo`, the function + * returns only the last sample. To read all samples from the FIFO, function + * l3gxxxx_read_fifo has to be used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[out] data last sample of angular rates in mdps + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_read(const l3gxxxx_t *dev, l3gxxxx_data_t *data); + +/** + * @brief Read last sample of raw output data as 16 bit two's complements + * + * @note If the FIFO is enabled by module `l3gxxxxx_fifo`, the function + * returns only the last sample. To read all samples from the FIFO, function + * l3gxxxx_read_raw_fifo has to be used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param raw last sample of raw output data as 16 bit two's + * complements + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_read_raw(const l3gxxxx_t *dev, l3gxxxx_raw_data_t *raw); + +/** @} */ + +#if IS_USED(MODULE_L3GXXXX_FIFO) || DOXYGEN + +/** + * @name FIFO handling + * @{ + */ + +#if IS_USED(MODULE_L3GXXXX_CONFIG) || DOXYGEN +/** + * @brief Set FIFO mode and watermark level (threshold) + * + * The FIFO buffer can work in seven different modes and is able to store + * up to 32 data samples, see #l3gxxxx_fifo_mode_t. The use of the FIFO allows + * to reduce the interaction events of the MCU with the sensor and thus to + * save power. + * + * The watermark level can be used to define the number of raw output data + * samples that have to be stored in the FIFO before the watermark flag is + * set and the #L3GXXXX_INT_FIFO_WATERMARK is triggered, if enabled. The + * watermark flag is set and the interrupt #L3GXXXX_INT_FIFO_WATERMARK is + * triggered when the number of samples stored in the FIFO becomes greater + * than this watermark level (threshold). + * + * @note This function is available only if modules `l3gxxxx_fifo` + * and `l3gxxxx_config` are used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[in] mode FIFO mode + * @param[in] watermark FIFO watermark (ignored in bypass mode) + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_set_fifo_mode(l3gxxxx_t *dev, + l3gxxxx_fifo_mode_t mode, uint8_t watermark); + +#endif /* IS_USED(MODULE_L3GXXXX_CONFIG) || DOXYGEN */ + +/** + * @brief Get all samples of angular rates stored in the FIFO (unit mdps) + * + * This function reads all samples of raw output data from the FIFO and + * converts them to angular rates in millidegrees per second (mdps) according + * to the configured full scale. For details about the resolution of these + * angular rates see l3gxxxx_read. + * + * In bypass mode (#L3GXXXX_BYPASS), it returns only the last sample. + * + * @note This function is available only if module `l3gxxxx_fifo` is used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[out] data array for up to 32 samples of angular rates in mdps + * + * @return number of data samples read on success, or negative error code, + * see #l3gxxxx_error_codes_t + */ +int l3gxxxx_read_fifo(const l3gxxxx_t *dev, + l3gxxxx_data_fifo_t data); + +/** + * @brief Get all samples of raw output data stored in the FIFO + * + * This function reads all samples of raw output data from the FIFO. + * In bypass mode (#L3GXXXX_BYPASS), it returns only the last raw + * output data sample. + * + * @note This function is available only if module `l3gxxxx_fifo` is used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[out] raw array for up to 32 raw output data as two's complement + * + * @return number of data samples read on success, or negative error code, + * see #l3gxxxx_error_codes_t + */ +int l3gxxxx_read_raw_fifo(const l3gxxxx_t *dev, + l3gxxxx_raw_data_fifo_t raw); + +#endif /* IS_USED(MODULE_L3GXXXX_FIFO) || DOXYGEN */ +/** @} */ + +#if IS_USED(MODULE_L3GXXXX_CONFIG) || DOXYGEN +/** + * @name Filter configuration and handling + * @{ + */ + +/** + * @brief Filter selection for raw output data + * + * L3Gxxxx supports a combination of a high pass filter (HPF) and a second + * low pass filter (LPF2). This function selects the combination of the HPF + * and the LPF2 applied to raw output data. + * + * @note + * - This function is available only if module `l3gxxxx_config` is used. + * - The filter selection for the output data also affects the filter + * selection for event interrupt generation. If the HPF is enabled for + * filtering the output data, it is also active for filtering the sensor + * data used for interrupt generation if the LPF2 is enabled for interrupt + * generation. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[in] filter selected filters for output values + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_select_output_filter(l3gxxxx_t *dev, + l3gxxxx_filter_sel_t filter); + +/** + * @brief Config HPF (high pass filter) for output data + * + * The cutoff frequency of the HPF depends on the selected output data rate + * (ODR). The following table shows the possible values of parameter \p cutoff + * and the cutoff frequencies for the according ODRs. All frequencies are + * given in Hz. + * + *
+ * | cutoff / ODR | 12.5 | 25 | 50 | 100 | 200 | 400 | 800 | + * |-------------:|:-----:|:-----:|:-----:|:----:|:----:|:----:|:---:| + * | 0 | 1 | 2 | 4 | 8 | 15 | 30 | 56 | + * | 1 | 0.5 | 1 | 2 | 4 | 8 | 15 | 30 | + * | 2 | 0.2 | 0.5 | 1 | 2 | 4 | 8 | 15 | + * | 3 | 0.1 | 0.2 | 0.5 | 1 | 2 | 4 | 8 | + * | 4 | 0.05 | 0.1 | 0.2 | 0.5 | 1 | 2 | 4 | + * | 5 | 0.02 | 0.05 | 0.1 | 0.2 | 0.5 | 1 | 2 | + * | 6 | 0.01 | 0.02 | 0.05 | 0.1 | 0.2 | 0.5 | 1 | + * | 7 | 0.005 | 0.01 | 0.02 | 0.05 | 0.1 | 0.2 | 0.5 | + * | 8 | 0.002 | 0.005 | 0.01 | 0.02 | 0.05 | 0.1 | 0.2 | + * | 9 | 0.001 | 0.002 | 0.005 | 0.01 | 0.02 | 0.05 | 0.1 | + *

+ * + * @note This function is available only if module `l3gxxxx_config` is used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[in] mode high pass filter mode, see #l3gxxxx_hpf_mode_t + * @param[in] cutoff cutoff frequency (depends on ODR) [0 ... 9] + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_config_hpf(const l3gxxxx_t *dev, + l3gxxxx_hpf_mode_t mode, uint8_t cutoff); + +/** + * @brief Set HPF (high pass filter) reference + * + * Used to set the reference for HPF in reference mode #L3GXXXX_HPF_REFERENCE + * and to reset the HPF in autoreset mode #L3GXXXX_HPF_AUTORESET. + * Reference is given as two's complement. + * + * @note This function is available only if module `l3gxxxx_config` is used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[in] ref reference in #L3GXXXX_HPF_REFERENCE mode, otherwise ignored + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_set_hpf_ref(const l3gxxxx_t *dev, int8_t ref); + +/** + * @brief Get HPF (high pass filter) reference + * + * Returns the content of the REFERENCE register. In normal mode + * #L3GXXXX_HPF_NORMAL, it is also used to reset the HPF. + * + * @note This function is available only if module `l3gxxxx_config` is used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[out] ref reference + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_get_hpf_ref(const l3gxxxx_t *dev, int8_t *ref); + +#endif /* IS_USED(MODULE_L3GXXXX_CONFIG) || DOXYGEN */ +/** @} */ + +#if IS_USED(MODULE_L3GXXXX_IRQ) || DOXYGEN +/** + * @name Interrupt configuration and handling + * @{ + */ + +/** + * @brief Enable or disable event and/or data interrupts on signal `INT1` and `INT2/DRDY` + * + * This function is used to enable or disable interrupts. The parameter \p mask + * is the ORed value of the interrupts that are enabled or disabled by the + * function call. + * + * @pre MCU GPIO pins for the `INT1` signal respectively the `INT2/DRDY` + * signal have to be defined for enabled interrupts + * (l3gxxxx_params_t::int1_pin and l3gxxxx_params_t::int2_pin). + * + * @note This function is available only if module `l3gxxxx_irq_data` and/or + * module `l3gxxxx_irq_event` are used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[in] mask interrupts to be enabled or disabled + * @param[in] enable enable the interrupts if true, otherwise disable them + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_enable_int(const l3gxxxx_t *dev, + l3gxxxx_int_types_t mask, bool enable); + +/** + * @brief Wait for event and/or data interrupts on signals `INT1` and `INT2/DRDY` + * + * The function waits for a configured interrupt and returns the sources of + * triggered interrupts. Since data interrupts (data ready and FIFO status) + * and event interrupts (axis movement and wake-up) use different signals, + * both data and event interrupts can occur simultaneously. The return + * value of type l3gxxxx_int_src_t contains all sources for which the interrupt + * conditions are fulfilled at the same time. + * + * @pre MCU GPIO pins for the `INT1` signal respectively the `INT2/DRDY` + * signal have to be defined for enabled interrupts + * (l3gxxxx_params_t::int1_pin and l3gxxxx_params_t::int2_pin). + * + * @note This function is available only if module `l3gxxxx_irq_data` and/or + * module `l3gxxxx_irq_event` are used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +l3gxxxx_int_src_t l3gxxxx_wait_int(l3gxxxx_t *dev); + +#if IS_USED(MODULE_L3GXXXX_IRQ_EVENT) || DOXYGEN +/** + * @brief Set new configuration for event interrupt generation + * + * The event interrupt generator produces interrupts (axis movement and wake-up) + * on signal `INT1` whenever the angular rate of one or more axes becomes higher + * or lower than defined thresholds. + * + * This function can be used at runtime to change the configuration of + * the interrupt conditions for event interrupt generation. This includes + * thresholds for all axes, enabled threshold interrupts, filter selection + * used for interrupt generation and other parameters, see + * l3gxxxx_int_event_cfg_t for details. + * + * @note This function is available only if module `l3gxxxx_irq_event` is used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[in] cfg event interrupt generator configuration + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_set_int_event_cfg(const l3gxxxx_t *dev, + const l3gxxxx_int_event_cfg_t *cfg); + +#if IS_USED(MODULE_L3GXXXX_CONFIG) || DOXYGEN +/** + * @brief Get current configuration of event interrupt generation + * + * This function can be used to retrieve the configuration of interrupt + * conditions currently used to generate event interrupts. See + * l3gxxxx_int_event_cfg_t for details. + * + * @note This function is available only if module `l3gxxxx_irq_event` and + * module `l3gxxxx_irq_config` are used. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[out] cfg event interrupt generator configuration + * + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_get_int_event_cfg(const l3gxxxx_t *dev, + l3gxxxx_int_event_cfg_t *cfg); + +#endif /* IS_USED(MODULE_L3GXXXX_CONFIG) || DOXYGEN */ +#endif /* IS_USED(MODULE_L3GXXXX_IRQ_EVENT) || DOXYGEN */ + +/** @} */ + +#endif /* IS_USED(MODULE_L3GXXXX_IRQ) || DOXYGEN */ + +/** + * @name Low level interface functions + * @{ + */ + +/** + * @brief Direct write to register + * + * @note This function should only be used to do something special that + * is not covered by the high level interface AND if you exactly know what you + * do and what effects it might have. Please be aware that it might affect the + * high level interface. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[in] reg address of the first register to be changed + * @param[in] data pointer to the data to be written to the register + * @param[in] len number of bytes to be written to the register + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_reg_write(const l3gxxxx_t *dev, + uint8_t reg, const uint8_t *data, uint8_t len); + +/** + * @brief Direct read from register + * + * @note This function should only be used to do something special that + * is not covered by the high level interface AND if you exactly know what you + * do and what effects it might have. Please be aware that it might affect the + * high level interface. + * + * @param[in] dev device descriptor of the L3Gxxxx sensor + * @param[in] reg address of the first register to be read + * @param[out] data pointer to the data to be read from the register + * @param[in] len number of bytes to be read from the register + * @retval L3GXXXX_OK on success + * @retval L3GXXXX_ERROR_* negative error code, see #l3gxxxx_error_codes_t + */ +int l3gxxxx_reg_read(const l3gxxxx_t *dev, + uint8_t reg, uint8_t *data, uint8_t len); +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* L3GXXXX_H */ +/** @} */ diff --git a/drivers/l3gxxxx/Makefile b/drivers/l3gxxxx/Makefile new file mode 100644 index 0000000000..7131c04432 --- /dev/null +++ b/drivers/l3gxxxx/Makefile @@ -0,0 +1 @@ +include $(RIOTMAKE)/driver_with_saul.mk diff --git a/drivers/l3gxxxx/Makefile.dep b/drivers/l3gxxxx/Makefile.dep new file mode 100644 index 0000000000..88e267106d --- /dev/null +++ b/drivers/l3gxxxx/Makefile.dep @@ -0,0 +1,25 @@ +ifeq (,$(filter l3gd20% l3g4200d_ng %3g4250d,$(USEMODULE))) + # pull in L3GD20H variant by default if no interface is defined + USEMODULE += l3gd20h +endif + +ifeq (,$(filter l3gxxxx_spi,$(USEMODULE))) + # pull in I2C variant by default if no interface is defined + USEMODULE += l3gxxxx_i2c +endif + +ifneq (,$(filter l3gxxxx_spi,$(USEMODULE))) + FEATURES_REQUIRED += periph_spi +endif + +ifneq (,$(filter l3gxxxx_i2c,$(USEMODULE))) + FEATURES_REQUIRED += periph_i2c +endif + +ifneq (,$(filter l3gxxxx_irq_%,$(USEMODULE))) + USEMODULE += l3gxxxx_irq +endif + +ifneq (,$(filter l3gxxxx_irq,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio_irq +endif diff --git a/drivers/l3gxxxx/Makefile.include b/drivers/l3gxxxx/Makefile.include new file mode 100644 index 0000000000..29cce4db96 --- /dev/null +++ b/drivers/l3gxxxx/Makefile.include @@ -0,0 +1,19 @@ +# include variants of L3Gxxxx driver as pseudo modules +PSEUDOMODULES += a3g4250d +PSEUDOMODULES += i3g4250d +PSEUDOMODULES += l3g4200d_ng +PSEUDOMODULES += l3gd20 +PSEUDOMODULES += l3gd20h + +PSEUDOMODULES += l3gxxxx_i2c +PSEUDOMODULES += l3gxxxx_spi +PSEUDOMODULES += l3gxxxx_low_odr +PSEUDOMODULES += l3gxxxx_fifo +PSEUDOMODULES += l3gxxxx_irq +PSEUDOMODULES += l3gxxxx_irq_data +PSEUDOMODULES += l3gxxxx_irq_event +PSEUDOMODULES += l3gxxxx_sleep +PSEUDOMODULES += l3gxxxx_config + +USEMODULE_INCLUDES_l3gxxxx := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_l3gxxxx) diff --git a/drivers/l3gxxxx/include/l3gxxxx_params.h b/drivers/l3gxxxx/include/l3gxxxx_params.h new file mode 100644 index 0000000000..b0ae1bb382 --- /dev/null +++ b/drivers/l3gxxxx/include/l3gxxxx_params.h @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_l3gxxxx + * @brief Default configuration for ST L3Gxxxx 3-axis gyroscope sensor family + * @author Gunar Schorcht + * @file + * @{ + */ + +#ifndef L3GXXXX_PARAMS_H +#define L3GXXXX_PARAMS_H + +#include "board.h" +#include "l3gxxxx.h" +#include "saul_reg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Default hardware configuration + * @{ + */ +#if IS_USED(MODULE_L3GXXXX_I2C) || DOXYGEN + +#ifndef L3GXXXX_I2C_DEV +/** Default I2C device, if the I2C interface is used */ +#define L3GXXXX_I2C_DEV (I2C_DEV(0)) +#endif + +#ifndef L3GXXXX_I2C_ADDR +/** Default I2C address, if the I2C interface is used */ +#define L3GXXXX_I2C_ADDR (L3GXXXX_I2C_ADDR_2) +#endif + +#ifndef L3GXXXX_I2C_IF_PARAMS +/** Default I2C interface parameter set */ +#define L3GXXXX_I2C_IF_PARAMS .if_params.type = L3GXXXX_I2C, \ + .if_params.i2c.dev = L3GXXXX_I2C_DEV, \ + .if_params.i2c.addr = L3GXXXX_I2C_ADDR, +#endif + +#endif /* MODULE_L3GXXXX_I2C || DOXYGEN */ + +#if IS_USED(MODULE_L3GXXXX_SPI) || DOXYGEN + +#ifndef L3GXXXX_SPI_DEV +/** Default SPI device, if the SPI interface is used */ +#define L3GXXXX_SPI_DEV SPI_DEV(0) +#endif + +#ifndef L3GXXXX_SPI_CLK +/** Default SPI clock frequency, if the SPI interface is used */ +#define L3GXXXX_SPI_CLK (SPI_CLK_1MHZ) +#endif + +#ifndef L3GXXXX_SPI_CS +/** Default SPI CS signal, if the SPI interface is used */ +#define L3GXXXX_SPI_CS (GPIO_PIN(0, 0)) +#endif + +#ifndef L3GXXXX_SPI_IF_PARAMS +/** Default SPI interface parameter set */ +#define L3GXXXX_SPI_IF_PARAMS .if_params.type = L3GXXXX_SPI, \ + .if_params.spi.dev = L3GXXXX_SPI_DEV, \ + .if_params.spi.clk = L3GXXXX_SPI_CLK, \ + .if_params.spi.cs = L3GXXXX_SPI_CS, +#endif + +#endif /* MODULE_L3GXXXX_SPI || DOXYGEN */ + +#ifndef L3GXXXX_INT1_PIN +/** Default MCU pin for INT1 signal */ +#define L3GXXXX_INT1_PIN (GPIO_PIN(0, 1)) +#endif + +#ifndef L3GXXXX_INT2_PIN +/** Default MCU pin for INT2/DRDY signal */ +#define L3GXXXX_INT2_PIN (GPIO_PIN(0, 2)) +#endif + +/** @} */ + +/** + * @name Default sensor configuration + * @{ + */ +#if !DOXYGEN +/* Mapping of Kconfig defines to the respective driver enumeration values */ + +#ifdef CONFIG_L3GXXXX_ODR_100_12 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_100_12) +#elif CONFIG_L3GXXXX_ODR_100_25 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_100_25) +#elif CONFIG_L3GXXXX_ODR_200_12 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_200_12) +#elif CONFIG_L3GXXXX_ODR_200_25 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_200_25) +#elif CONFIG_L3GXXXX_ODR_200_50 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_200_50) +#elif CONFIG_L3GXXXX_ODR_200_70 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_200_70) +#elif CONFIG_L3GXXXX_ODR_400_20 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_400_20) +#elif CONFIG_L3GXXXX_ODR_400_25 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_400_25) +#elif CONFIG_L3GXXXX_ODR_400_50 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_400_50) +#elif CONFIG_L3GXXXX_ODR_400_110 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_400_110) +#elif CONFIG_L3GXXXX_ODR_800_30 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_800_30) +#elif CONFIG_L3GXXXX_ODR_800_35 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_800_35) +#elif CONFIG_L3GXXXX_ODR_800_50 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_800_50) +#elif CONFIG_L3GXXXX_ODR_800_100 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_800_100) +#elif CONFIG_L3GXXXX_ODR_12 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_12) +#elif CONFIG_L3GXXXX_ODR_25 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_25) +#elif CONFIG_L3GXXXX_ODR_50 +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_50) +#endif + +#ifdef CONFIG_L3GXXXX_SCALE_245_DPS +#define CONFIG_L3GXXXX_SCALE (L3GXXXX_SCALE_245_DPS) +#elif CONFIG_L3GXXXX_SCALE_500_DPS +#define CONFIG_L3GXXXX_SCALE (L3GXXXX_SCALE_500_DPS) +#elif CONFIG_L3GXXXX_SCALE_2000_DPS +#define CONFIG_L3GXXXX_SCALE (L3GXXXX_SCALE_2000_DPS) +#endif + +#ifdef CONFIG_L3GXXXX_NO_FILTER +#define CONFIG_L3GXXXX_FILTER_SEL (L3GXXXX_NO_FILTER) +#elif CONFIG_L3GXXXX_HPF_ONLY +#define CONFIG_L3GXXXX_FILTER_SEL (L3GXXXX_HPF_ONLY) +#elif CONFIG_L3GXXXX_LPF2_ONLY +#define CONFIG_L3GXXXX_FILTER_SEL (L3GXXXX_LPF2_ONLY) +#elif CONFIG_L3GXXXX_HPF_AND_LPF2 +#define CONFIG_L3GXXXX_FILTER_SEL (L3GXXXX_HPF_AND_LPF2) +#endif + +#ifdef CONFIG_L3GXXXX_HPF_NORMAL +#define CONFIG_L3GXXXX_HPF_MODE (L3GXXXX_HPF_NORMAL) +#elif CONFIG_L3GXXXX_HPF_REFERENCE +#define CONFIG_L3GXXXX_HPF_MODE (L3GXXXX_HPF_REFERENCE) +#elif CONFIG_L3GXXXX_HPF_AUTORESET +#define CONFIG_L3GXXXX_HPF_MODE (L3GXXXX_HPF_AUTORESET) +#endif + +#ifdef CONFIG_L3GXXXX_FIFO_MODE_BYPASS +#define CONFIG_L3GXXXX_FIFO_MODE (L3GXXXX_BYPASS) +#elif CONFIG_L3GXXXX_FIFO_MODE_FIFO +#define CONFIG_L3GXXXX_FIFO_MODE (L3GXXXX_FIFO) +#elif CONFIG_L3GXXXX_FIFO_MODE_STREAM +#define CONFIG_L3GXXXX_FIFO_MODE (L3GXXXX_STREAM) +#elif CONFIG_L3GXXXX_FIFO_MODE_STREAM_TO_FIFO +#define CONFIG_L3GXXXX_FIFO_MODE (L3GXXXX_STREAM_TO_FIFO) +#elif CONFIG_L3GXXXX_FIFO_MODE_BYPASS_TO_STREAM +#define CONFIG_L3GXXXX_FIFO_MODE (L3GXXXX_BYPASS_TO_STREAM) +#elif CONFIG_L3GXXXX_FIFO_MODE_DYNAMIC_STREAM +#define CONFIG_L3GXXXX_FIFO_MODE (L3GXXXX_DYNAMIC_STREAM +#elif CONFIG_L3GXXXX_FIFO_MODE_BYPASS_TO_FIFO +#define CONFIG_L3GXXXX_FIFO_MODE (L3GXXXX_BYPASS_TO_FIFO) +#endif + +#ifdef CONFIG_L3GXXXX_INT1_NO_FILTER +#define CONFIG_L3GXXXX_INT1_FILTER (L3GXXXX_NO_FILTER) +#elif CONFIG_L3GXXXX_INT1_HPF_ONLY +#define CONFIG_L3GXXXX_INT1_FILTER (L3GXXXX_HPF_ONLY) +#elif CONFIG_L3GXXXX_INT1_LPF2_ONLY +#define CONFIG_L3GXXXX_INT1_FILTER (L3GXXXX_LPF2_ONLY) +#elif CONFIG_L3GXXXX_INT1_HPF_AND_LPF2 +#define CONFIG_L3GXXXX_INT1_FILTER (L3GXXXX_HPF_AND_LPF2) +#endif + +#endif /* !DOXYGEN */ + +#ifndef CONFIG_L3GXXXX_ODR +/** Default ODR and cut-off frequency */ +#define CONFIG_L3GXXXX_ODR (L3GXXXX_ODR_100_25) +#endif + +#ifndef CONFIG_L3GXXXX_SCALE +/** Default full scale */ +#define CONFIG_L3GXXXX_SCALE (L3GXXXX_SCALE_245_DPS) +#endif + +#ifndef CONFIG_L3GXXXX_FILTER_SEL +/** Default filter selection used for output data */ +#define CONFIG_L3GXXXX_FILTER_SEL (L3GXXXX_HPF_AND_LPF2) +#endif + +#ifndef CONFIG_L3GXXXX_HPF_MODE +/** Default HPF mode used for output data */ +#define CONFIG_L3GXXXX_HPF_MODE (L3GXXXX_HPF_NORMAL) +#endif + +#ifndef CONFIG_L3GXXXX_HPF_CUTOFF +/** Default HPF cutoff frequency 8 Hz */ +#define CONFIG_L3GXXXX_HPF_CUTOFF (0) +#endif + +#ifndef CONFIG_L3GXXXX_FIFO_MODE +/** Default FIFO mode if FIO is used */ +#define CONFIG_L3GXXXX_FIFO_MODE (L3GXXXX_FIFO) +#endif + +#ifndef CONFIG_L3GXXXX_FIFO_WATERMARK +/** Default FIFO watermark level (threshold) value if FIO is used */ +#define CONFIG_L3GXXXX_FIFO_WATERMARK (23) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_X_THRESH +/** Default INT1 threshold for X axis events (~30 dps at fulls scale of ±245 dps) */ +#define CONFIG_L3GXXXX_INT1_X_THRESH (4012) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_X_GT_THRESH +/** Default INT1 interrupt enable for |X| > X threshold (X high event) */ +#define CONFIG_L3GXXXX_INT1_X_GT_THRESH (true) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_X_LT_THRESH +/** Default INT1 interrupt enable for |X| < X threshold (X low event) */ +#define CONFIG_L3GXXXX_INT1_X_LT_THRESH (false) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_Y_THRESH +/** Default INT1 threshold for Y axis events (~30 dps at fulls scale of ±245 dps) */ +#define CONFIG_L3GXXXX_INT1_Y_THRESH (4012) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_Y_GT_THRESH +/** Default INT1 interrupt enable for |Y| < Y threshold (Y low event) */ +#define CONFIG_L3GXXXX_INT1_Y_GT_THRESH (true) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_Y_LT_THRESH +/** Default INT1 interrupt enable for |Y| > Y threshold (Y high event) */ +#define CONFIG_L3GXXXX_INT1_Y_LT_THRESH (false) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_Z_THRESH +/** Default INT1 threshold for Z axis events (~30 dps at fulls scale of ±245 dps) */ +#define CONFIG_L3GXXXX_INT1_Z_THRESH (4012) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_Z_GT_THRESH +/** Default INT1 interrupt enable for |Z| < Z threshold (Z low event) */ +#define CONFIG_L3GXXXX_INT1_Z_GT_THRESH (true) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_Z_LT_THRESH +/** Default INT1 interrupt enable for |Z| > Z threshold (Z high event) */ +#define CONFIG_L3GXXXX_INT1_Z_LT_THRESH (false) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_FILTER +/** Default filter selection used for INT1 interrupt */ +#define CONFIG_L3GXXXX_INT1_FILTER (L3GXXXX_HPF_AND_LPF2) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_AND +/** Default event interrupt combination is OR */ +#define CONFIG_L3GXXXX_INT1_AND (false) +#endif + +#ifndef CONFIG_L3GXXXX_INT1_LATCH +/** Default INT1 event interrupt latch enabled */ +#define CONFIG_L3GXXXX_INT1_LATCH (true) +#endif + +#if IS_USED(MODULE_L3GXXXX_IRQ_EVENT) || DOXYGEN +/** Default INT1 parameter set */ +#define L3GXXXX_INT1_PARAMS .int1_pin = L3GXXXX_INT1_PIN, \ + .int1_cfg.x_high_enabled = CONFIG_L3GXXXX_INT1_X_GT_THRESH, \ + .int1_cfg.y_high_enabled = CONFIG_L3GXXXX_INT1_Y_GT_THRESH, \ + .int1_cfg.z_high_enabled = CONFIG_L3GXXXX_INT1_Z_GT_THRESH, \ + .int1_cfg.x_low_enabled = CONFIG_L3GXXXX_INT1_X_LT_THRESH, \ + .int1_cfg.y_low_enabled = CONFIG_L3GXXXX_INT1_Y_LT_THRESH, \ + .int1_cfg.z_low_enabled = CONFIG_L3GXXXX_INT1_Z_LT_THRESH, \ + .int1_cfg.x_threshold = CONFIG_L3GXXXX_INT1_X_THRESH, \ + .int1_cfg.y_threshold = CONFIG_L3GXXXX_INT1_Y_THRESH, \ + .int1_cfg.z_threshold = CONFIG_L3GXXXX_INT1_Z_THRESH, \ + .int1_cfg.filter = CONFIG_L3GXXXX_INT1_FILTER, \ + .int1_cfg.and_or = CONFIG_L3GXXXX_INT1_AND, \ + .int1_cfg.latch = CONFIG_L3GXXXX_INT1_LATCH, +#else +#define L3GXXXX_INT1_PARAMS +#endif + +#if IS_USED(MODULE_L3GXXXX_IRQ_DATA) || DOXYGEN +/** Default INT2 parameter set */ +#define L3GXXXX_INT2_PARAMS .int2_pin = L3GXXXX_INT2_PIN, +#else +#define L3GXXXX_INT2_PARAMS +#endif + +#if IS_USED(MODULE_L3GXXXX_FIFO) || DOXYGEN +/** Default FIFO parameter set */ +#define L3GXXXX_FIFO_PARAMS .fifo_mode = CONFIG_L3GXXXX_FIFO_MODE, \ + .fifo_watermark = CONFIG_L3GXXXX_FIFO_WATERMARK, +#else +#define L3GXXXX_FIFO_PARAMS +#endif + +#if IS_USED(MODULE_L3GXXXX_I2C) || DOXYGEN + +#ifndef L3GXXXX_I2C_PARAMS +/** Default I2C device parameter set */ +#define L3GXXXX_I2C_PARAMS { \ + L3GXXXX_I2C_IF_PARAMS \ + .odr = CONFIG_L3GXXXX_ODR, \ + .scale = CONFIG_L3GXXXX_SCALE, \ + .filter_sel = CONFIG_L3GXXXX_FILTER_SEL, \ + .hpf_mode = CONFIG_L3GXXXX_HPF_MODE, \ + .hpf_cutoff = CONFIG_L3GXXXX_HPF_CUTOFF, \ + L3GXXXX_FIFO_PARAMS \ + L3GXXXX_INT1_PARAMS \ + L3GXXXX_INT2_PARAMS \ + } +#endif +#endif /* MODULE_L3GXXXX_I2C || DOXYGEN */ + +#if IS_USED(MODULE_L3GXXXX_SPI) || DOXYGEN +#ifndef L3GXXXX_SPI_PARAMS +/** Default SPI device parameter set */ +#define L3GXXXX_SPI_PARAMS { \ + L3GXXXX_SPI_IF_PARAMS \ + .odr = CONFIG_L3GXXXX_ODR, \ + .scale = CONFIG_L3GXXXX_SCALE, \ + .filter_sel = CONFIG_L3GXXXX_FILTER_SEL, \ + .hpf_mode = CONFIG_L3GXXXX_HPF_MODE, \ + .hpf_cutoff = CONFIG_L3GXXXX_HPF_CUTOFF, \ + L3GXXXX_FIFO_PARAMS \ + L3GXXXX_INT1_PARAMS \ + L3GXXXX_INT2_PARAMS \ + } +#endif +#endif /* MODULE_L3GXXXX_SPI || DOXYGEN */ + +#ifndef L3GXXXX_SAUL_INFO +/** Default SAUL device info */ +#define L3GXXXX_SAUL_INFO { .name = "l3gxxxx" } +#endif +/**@}*/ + +/** + * @brief Allocate some memory to store the actual configuration + */ +static const l3gxxxx_params_t l3gxxxx_params[] = +{ +#if IS_USED(MODULE_L3GXXXX_I2C) || DOXYGEN + L3GXXXX_I2C_PARAMS, +#endif +#if IS_USED(MODULE_L3GXXXX_SPI) || DOXYGEN + L3GXXXX_SPI_PARAMS, +#endif +}; + +/** + * @brief Additional meta information to keep in the SAUL registry + */ +static const saul_reg_info_t l3gxxxx_saul_info[] = +{ + L3GXXXX_SAUL_INFO +}; + +#ifdef __cplusplus +} +#endif + +#endif /* L3GXXXX_PARAMS_H */ +/** @} */ diff --git a/drivers/l3gxxxx/include/l3gxxxx_regs.h b/drivers/l3gxxxx/include/l3gxxxx_regs.h new file mode 100644 index 0000000000..206b06bb03 --- /dev/null +++ b/drivers/l3gxxxx/include/l3gxxxx_regs.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_l3gxxxx + * @brief Register definitions for ST L3Gxxxx 3-axis gyroscope sensor family + * @author Gunar Schorcht + * @file + * @{ + */ + +#ifndef L3GXXXX_REGS_H +#define L3GXXXX_REGS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @name Register addresses + * @{ + */ +#define L3GXXXX_REG_WHO_AM_I (0x0f) /**< Register address WHO_AM_I */ +#define L3GXXXX_REG_CTRL1 (0x20) /**< Register address CTRL1 */ +#define L3GXXXX_REG_CTRL2 (0x21) /**< Register address CTRL2 */ +#define L3GXXXX_REG_CTRL3 (0x22) /**< Register address CTRL3 */ +#define L3GXXXX_REG_CTRL4 (0x23) /**< Register address CTRL4 */ +#define L3GXXXX_REG_CTRL5 (0x24) /**< Register address CTRL5 */ +#define L3GXXXX_REG_REFERENCE (0x25) /**< Register address REFERENCE */ +#define L3GXXXX_REG_OUT_TEMP (0x26) /**< Register address OUT_TEMP */ +#define L3GXXXX_REG_STATUS (0x27) /**< Register address STATUS */ +#define L3GXXXX_REG_OUT_X_L (0x28) /**< Register address OUT_X_L */ +#define L3GXXXX_REG_OUT_X_H (0x29) /**< Register address OUT_X_H */ +#define L3GXXXX_REG_OUT_Y_L (0x2a) /**< Register address OUT_Y_L */ +#define L3GXXXX_REG_OUT_Y_H (0x2b) /**< Register address OUT_Y_H */ +#define L3GXXXX_REG_OUT_Z_L (0x2c) /**< Register address OUT_Z_L */ +#define L3GXXXX_REG_OUT_Z_H (0x2d) /**< Register address OUT_Z_H */ +#define L3GXXXX_REG_FIFO_CTRL (0x2e) /**< Register address FIFO_CTRL */ +#define L3GXXXX_REG_FIFO_SRC (0x2f) /**< Register address FIFO_SRC */ +#define L3GXXXX_REG_IG_CFG (0x30) /**< Register address IG_CFG */ +#define L3GXXXX_REG_IG_SRC (0x31) /**< Register address IG_SRC */ +#define L3GXXXX_REG_IG_THS_XH (0x32) /**< Register address IG_THS_XH */ +#define L3GXXXX_REG_IG_THS_XL (0x33) /**< Register address IG_THS_XL */ +#define L3GXXXX_REG_IG_THS_YH (0x34) /**< Register address IG_THS_YH */ +#define L3GXXXX_REG_IG_THS_YL (0x35) /**< Register address IG_THS_YL */ +#define L3GXXXX_REG_IG_THS_ZH (0x36) /**< Register address IG_THS_ZH */ +#define L3GXXXX_REG_IG_THS_ZL (0x37) /**< Register address IG_THS_ZL */ +#define L3GXXXX_REG_IG_DURATION (0x38) /**< Register address IG_DURATION */ +#define L3GXXXX_REG_LOW_ODR (0x39) /**< Register address LOW_ODR */ +/** @} */ + +/** + * @name Register structure definitions + * @{ + */ +#define L3GXXXX_ZYXOR (0x80) /**< L3GXXXX_REG_STATUS<7> */ +#define L3GXXXX_ZOR (0x40) /**< L3GXXXX_REG_STATUS<6> */ +#define L3GXXXX_YOR (0x20) /**< L3GXXXX_REG_STATUS<5> */ +#define L3GXXXX_XOR (0x10) /**< L3GXXXX_REG_STATUS<4> */ +#define L3GXXXX_ZYXDA (0x08) /**< L3GXXXX_REG_STATUS<3> */ +#define L3GXXXX_ZDA (0x04) /**< L3GXXXX_REG_STATUS<2> */ +#define L3GXXXX_YDA (0x02) /**< L3GXXXX_REG_STATUS<1> */ +#define L3GXXXX_XDA (0x01) /**< L3GXXXX_REG_STATUS<0> */ + +#define L3GXXXX_ANY_DATA_READY (0x07) /**< L3GXXXX_REG_STATUS<2:0> */ +#define L3GXXXX_ANY_DATA_READY_S (0) /**< L3GXXXX_REG_STATUS<2:0> */ + +#define L3GXXXX_ODR (0xc0) /**< L3GXXXX_REG_CTRL1<7:6> */ +#define L3GXXXX_BW (0x30) /**< L3GXXXX_REG_CTRL1<5:4> */ +#define L3GXXXX_ODR_BW (0xf0) /**< L3GXXXX_REG_CTRL1<7:4> */ +#define L3GXXXX_POWER_MODE (0x08) /**< L3GXXXX_REG_CTRL1<3> */ +#define L3GXXXX_Z_ENABLED (0x04) /**< L3GXXXX_REG_CTRL1<2> */ +#define L3GXXXX_Y_ENABLED (0x02) /**< L3GXXXX_REG_CTRL1<1> */ +#define L3GXXXX_X_ENABLED (0x01) /**< L3GXXXX_REG_CTRL1<0> */ +#define L3GXXXX_XYZ_ENABLED (0x07) /**< L3GXXXX_REG_CTRL1<2:0> */ + +#define L3GXXXX_ODR_S (6) /**< L3GXXXX_REG_CTRL1<7:6> */ +#define L3GXXXX_BW_S (4) /**< L3GXXXX_REG_CTRL1<5:4> */ +#define L3GXXXX_ODR_BW_S (4) /**< L3GXXXX_REG_CTRL1<7:4> */ +#define L3GXXXX_POWER_MODE_S (3) /**< L3GXXXX_REG_CTRL1<3> */ +#define L3GXXXX_Z_ENABLED_S (2) /**< L3GXXXX_REG_CTRL1<2> */ +#define L3GXXXX_Y_ENABLED_S (1) /**< L3GXXXX_REG_CTRL1<1> */ +#define L3GXXXX_X_ENABLED_S (1) /**< L3GXXXX_REG_CTRL1<0> */ +#define L3GXXXX_XYZ_ENABLED_S (0) /**< L3GXXXX_REG_CTRL1<2:0> */ + +#define L3GXXXX_EXTR_EN (0x80) /**< L3GXXXX_REG_CTRL2<7> */ +#define L3GXXXX_LVL_EN (0x40) /**< L3GXXXX_REG_CTRL2<6> */ +#define L3GXXXX_HPF_MODE (0x30) /**< L3GXXXX_REG_CTRL2<5:4> */ +#define L3GXXXX_HPF_CUTOFF (0x0f) /**< L3GXXXX_REG_CTRL2<3:0> */ + +#define L3GXXXX_EXTR_EN_S (7) /**< L3GXXXX_REG_CTRL2<7> */ +#define L3GXXXX_LVL_EN_S (6) /**< L3GXXXX_REG_CTRL2<6> */ +#define L3GXXXX_HPF_MODE_S (4) /**< L3GXXXX_REG_CTRL2<5:4> */ + +#define L3GXXXX_INT1_IG (0x80) /**< L3GXXXX_REG_CTRL3<7> */ +#define L3GXXXX_INT1_BOOT (0x40) /**< L3GXXXX_REG_CTRL3<6> */ +#define L3GXXXX_HL_ACTIVE (0x20) /**< L3GXXXX_REG_CTRL3<5> */ +#define L3GXXXX_PP_OD (0x10) /**< L3GXXXX_REG_CTRL3<4> */ +#define L3GXXXX_INT2_DRDY (0x08) /**< L3GXXXX_REG_CTRL3<3> */ +#define L3GXXXX_INT2_WTM (0x04) /**< L3GXXXX_REG_CTRL3<2> */ +#define L3GXXXX_INT2_ORUN (0x02) /**< L3GXXXX_REG_CTRL3<1> */ +#define L3GXXXX_INT2_EMPTY (0x01) /**< L3GXXXX_REG_CTRL3<0> */ + +#define L3GXXXX_INT1_IG_S (7) /**< L3GXXXX_REG_CTRL3<7> */ +#define L3GXXXX_INT1_BOOT_S (6) /**< L3GXXXX_REG_CTRL3<6> */ +#define L3GXXXX_HL_ACTIVE_S (5) /**< L3GXXXX_REG_CTRL3<5> */ +#define L3GXXXX_PP_OD_S (4) /**< L3GXXXX_REG_CTRL3<4> */ +#define L3GXXXX_INT2_DRDY_S (3) /**< L3GXXXX_REG_CTRL3<3> */ +#define L3GXXXX_INT2_WTM_S (2) /**< L3GXXXX_REG_CTRL3<2> */ +#define L3GXXXX_INT2_ORUN_S (1) /**< L3GXXXX_REG_CTRL3<1> */ +#define L3GXXXX_INT2_EMPTY_S (0) /**< L3GXXXX_REG_CTRL3<0> */ + +#define L3GXXXX_BLOCK_DATA_UPDATE (0x80) /**< L3GXXXX_REG_CTRL4<7> */ +#define L3GXXXX_BIG_LITTLE_ENDIAN (0x40) /**< L3GXXXX_REG_CTRL4<6> */ +#define L3GXXXX_FULL_SCALE (0x30) /**< L3GXXXX_REG_CTRL4<5:4> */ + +#define L3GXXXX_FULL_SCALE_S (4) /**< L3GXXXX_REG_CTRL4<5:4> */ + +#define L3GXXXX_BOOT (0x80) /**< L3GXXXX_REG_CTRL5<7> */ +#define L3GXXXX_FIFO_EN (0x40) /**< L3GXXXX_REG_CTRL5<6> */ +#define L3GXXXX_STOP_ON_WTM (0x20) /**< L3GXXXX_REG_CTRL5<5> */ +#define L3GXXXX_HP_ENABLED (0x10) /**< L3GXXXX_REG_CTRL5<4> */ +#define L3GXXXX_IG_SEL (0x0c) /**< L3GXXXX_REG_CTRL5<3:2> */ +#define L3GXXXX_OUT_SEL (0x03) /**< L3GXXXX_REG_CTRL5<1:0> */ + +#define L3GXXXX_BOOT_S (7) /**< L3GXXXX_REG_CTRL5<7> */ +#define L3GXXXX_FIFO_EN_S (6) /**< L3GXXXX_REG_CTRL5<6> */ +#define L3GXXXX_STOP_ON_WTM_S (5) /**< L3GXXXX_REG_CTRL5<5> */ +#define L3GXXXX_HP_ENABLED_S (4) /**< L3GXXXX_REG_CTRL5<4> */ +#define L3GXXXX_IG_SEL_S (2) /**< L3GXXXX_REG_CTRL5<3:2> */ +#define L3GXXXX_OUT_SEL_S (0) /**< L3GXXXX_REG_CTRL5<1:0> */ + +#define L3GXXXX_FIFO_MODE (0xe0) /**< L3GXXXX_REG_FIFO_CTRL<7:5> */ +#define L3GXXXX_FIFO_WATERMARK (0x1f) /**< L3GXXXX_REG_FIFO_CTRL<4:0> */ + +#define L3GXXXX_FIFO_MODE_S (5) /**< L3GXXXX_REG_FIFO_CTRL<7:5> */ +#define L3GXXXX_FIFO_WATERMARK_S (0) /**< L3GXXXX_REG_FIFO_CTRL<4:0> */ + +#define L3GXXXX_FIFO_WTM (0x80) /**< L3GXXXX_REG_FIFO_SRC<7> */ +#define L3GXXXX_FIFO_OVR (0x40) /**< L3GXXXX_REG_FIFO_SRC<6> */ +#define L3GXXXX_FIFO_EMPTY (0x20) /**< L3GXXXX_REG_FIFO_SRC<5> */ +#define L3GXXXX_FIFO_FFS (0x1f) /**< L3GXXXX_REG_FIFO_SRC<4:0> */ + +#define L3GXXXX_FIFO_WTM_S (7) /**< L3GXXXX_REG_FIFO_SRC<7> */ +#define L3GXXXX_FIFO_OVR_S (6) /**< L3GXXXX_REG_FIFO_SRC<6> */ +#define L3GXXXX_FIFO_EMPTY_S (5) /**< L3GXXXX_REG_FIFO_SRC<5> */ +#define L3GXXXX_FIFO_FFS_S (0) /**< L3GXXXX_REG_FIFO_SRC<4:0> */ + +#define L3GXXXX_INT1_AND_OR (0x80) /**< L3GXXXX_REG_IG_CFG<7> */ +#define L3GXXXX_INT1_LATCH (0x40) /**< L3GXXXX_REG_IG_CFG<6> */ +#define L3GXXXX_INT1_Z_HIGH (0x20) /**< L3GXXXX_REG_IG_CFG<5>, L3GXXXX_REG_IG_SRC<5> */ +#define L3GXXXX_INT1_Z_LOW (0x10) /**< L3GXXXX_REG_IG_CFG<4>, L3GXXXX_REG_IG_SRC<4> */ +#define L3GXXXX_INT1_Y_HIGH (0x08) /**< L3GXXXX_REG_IG_CFG<3>, L3GXXXX_REG_IG_SRC<3> */ +#define L3GXXXX_INT1_Y_LOW (0x04) /**< L3GXXXX_REG_IG_CFG<2>, L3GXXXX_REG_IG_SRC<2> */ +#define L3GXXXX_INT1_X_HIGH (0x02) /**< L3GXXXX_REG_IG_CFG<1>, L3GXXXX_REG_IG_SRC<1> */ +#define L3GXXXX_INT1_X_LOW (0x01) /**< L3GXXXX_REG_IG_CFG<0>, L3GXXXX_REG_IG_SRC<0> */ + +#define L3GXXXX_INT1_AND_OR_S (7) /**< L3GXXXX_REG_IG_CFG<7> */ +#define L3GXXXX_INT1_LATCH_S (6) /**< L3GXXXX_REG_IG_CFG<6> */ +#define L3GXXXX_INT1_Z_HIGH_S (5) /**< L3GXXXX_REG_IG_CFG<5>, L3GXXXX_REG_IG_SRC<5> */ +#define L3GXXXX_INT1_Z_LOW_S (4) /**< L3GXXXX_REG_IG_CFG<4>, L3GXXXX_REG_IG_SRC<4> */ +#define L3GXXXX_INT1_Y_HIGH_S (3) /**< L3GXXXX_REG_IG_CFG<3>, L3GXXXX_REG_IG_SRC<3> */ +#define L3GXXXX_INT1_Y_LOW_S (2) /**< L3GXXXX_REG_IG_CFG<2>, L3GXXXX_REG_IG_SRC<2> */ +#define L3GXXXX_INT1_X_HIGH_S (1) /**< L3GXXXX_REG_IG_CFG<1>, L3GXXXX_REG_IG_SRC<1> */ +#define L3GXXXX_INT1_X_LOW_S (0) /**< L3GXXXX_REG_IG_CFG<0>, L3GXXXX_REG_IG_SRC<0> */ + +#define L3GXXXX_INT1_ACTIVE (0x40) /**< L3GXXXX_REG_IG_SRC<6> */ + +#define L3GXXXX_INT1_WAIT (0x80) /**< L3GXXXX_REG_IG_DURATION<7> */ +#define L3GXXXX_INT1_DURATION (0x3f) /**< L3GXXXX_REG_IG_DURATION<6:0> */ + +#define L3GXXXX_INT1_WAIT_S (7) /**< L3GXXXX_REG_IG_DURATION<7> */ +#define L3GXXXX_INT1_DURATION_S (0) /**< L3GXXXX_REG_IG_DURATION<6:0> */ + +#define L3GXXXX_DRDY_HL (0x20) /**< L3GXXXX_REG_LOW_ODR<5> */ +#define L3GXXXX_SW_RESET (0x04) /**< L3GXXXX_REG_LOW_ODR<2> */ +#define L3GXXXX_LOW_ODR (0x01) /**< L3GXXXX_REG_LOW_ODR<0> */ + +#define L3GXXXX_DRDY_HL_S (5) /**< L3GXXXX_REG_LOW_ODR<5> */ +#define L3GXXXX_SW_RESET_S (2) /**< L3GXXXX_REG_LOW_ODR<2> */ +#define L3GXXXX_LOW_ODR_S (0) /**< L3GXXXX_REG_LOW_ODR<0> */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* L3GXXXX_REGS_H */ +/** @} */ diff --git a/drivers/l3gxxxx/l3gxxxx.c b/drivers/l3gxxxx/l3gxxxx.c new file mode 100644 index 0000000000..9b7b5621ab --- /dev/null +++ b/drivers/l3gxxxx/l3gxxxx.c @@ -0,0 +1,1143 @@ +/* + * Copyright (C) 2018 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_l3gxxxx + * @brief Device Driver for ST L3Gxxxx 3-axis gyroscope family + * @author Gunar Schorcht + * @file + * @{ + */ + +#include +#include + +#include "l3gxxxx_regs.h" +#include "l3gxxxx.h" + +#include "byteorder.h" +#include "log.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#if ENABLE_DEBUG + +#define ASSERT_PARAM(cond) \ + if (!(cond)) { \ + DEBUG("[l3gxxxx] %s: %s\n", \ + __func__, "parameter condition (" # cond ") not fulfilled"); \ + assert(cond); \ + } + +#define DEBUG_DEV(m, d, ...) \ + DEBUG("[l3gxxxx] %s dev=%" PRIxPTR ": " m "\n", \ + __func__, (unsigned int)d, ## __VA_ARGS__) + +#else /* ENABLE_DEBUG */ + +#define ASSERT_PARAM(cond) assert(cond); +#define DEBUG_DEV(m, d, ...) + +#endif /* ENABLE_DEBUG */ + +/* set a single bit defined by mask `m` in 8-bit register value `r` to value `v` */ +#define _SET_REG_BIT(r, m, v) r = ((v) & ~m) ? r | m : r & ~m +/* get the value of a single bit defined by mask `m` from 8-bit register value `r` */ +#define _GET_REG_BIT(r, m) (r & m) ? 1 : 0 + +/* set the bits defined by mask `m` in 8-bit register value `r` to value `v` */ +#define _SET_REG_VALUE(r, m, v) r = (r & ~m) | (((v) << m ## _S) & m) +/* get the value the bits defined by mask `m` from 8-bit register value `r` */ +#define _GET_REG_VALUE(r, m) ((r & ~m) >> m ## _S) + +#if IS_USED(MODULE_L3GXXXX_SPI) +#define _SPI_DEV (dev->params.if_params.spi.dev) +#define _SPI_CS (dev->params.if_params.spi.cs) +#define _SPI_CLK (dev->params.if_params.spi.clk) +#define _IS_DEV_SPI (dev->params.if_params.type == L3GXXXX_SPI) +#endif + +#if IS_USED(MODULE_L3GXXXX_I2C) +#define _I2C_DEV (dev->params.if_params.i2c.dev) +#define _I2C_ADDR (dev->params.if_params.i2c.addr) +#define _IS_DEV_I2C (dev->params.if_params.type == L3GXXXX_I2C) +#endif + +#define L3GXXXX_INT_FIFO (L3GXXXX_INT_FIFO_WATERMARK | \ + L3GXXXX_INT_FIFO_OVERRUN) + +int l3gxxxx_reg_update(const l3gxxxx_t *dev, + uint8_t reg, uint8_t mask, uint8_t val); + +/** Forward declaration of functions for internal use */ + +static int _is_available(l3gxxxx_t *dev); +static void _acquire(const l3gxxxx_t *dev); +static void _release(const l3gxxxx_t *dev); +static int _read(const l3gxxxx_t *dev, uint8_t reg, uint8_t *data, uint8_t len); +static int _write(const l3gxxxx_t *dev, uint8_t reg, const uint8_t *data, uint8_t len); +static int _update(const l3gxxxx_t *dev, uint8_t reg, uint8_t mask, uint8_t val); + +#if IS_USED(MODULE_L3GXXXX_IRQ_EVENT) +static int _get_int_event_src(const l3gxxxx_t *dev, l3gxxxx_int_event_src_t *src); +#endif +#if IS_USED(MODULE_L3GXXXX_IRQ_DATA) +static int _get_int_data_src(const l3gxxxx_t *dev, l3gxxxx_int_data_src_t *src); +#endif + +#define msb_lsb_to_type(t, b, o) (t)(((t)b[o] << 8) | b[o + 1]) + +int l3gxxxx_init(l3gxxxx_t *dev, const l3gxxxx_params_t *params) +{ + int res = L3GXXXX_OK; + + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(params != NULL); + DEBUG_DEV("params=%p", dev, params); + + /* init sensor data structure */ + dev->params = *params; + mutex_init(&dev->int_lock); + +#if IS_USED(MODULE_L3GXXXX_SPI) + ASSERT_PARAM(gpio_is_valid(_SPI_CS)); + /* for SPI, we only need to initialize the chip select pin */ + if (spi_init_cs(_SPI_DEV, _SPI_CS) != SPI_OK) { + return -L3GXXXX_ERROR_SPI; + } +#endif /* IS_USED(MODULE_L3GXXXX_SPI) */ + + _acquire(dev); + + /* check availability of the sensor */ + if ((res = _is_available(dev)) != L3GXXXX_OK) { + _release(dev); + return res; + } + +#if IS_USED(MODULE_A3G4250D) + ASSERT_PARAM((dev->params.scale == L3GXXXX_SCALE_245_DPS) || + (dev->sensor != X3G42XXD)); +#endif + + uint8_t reg[8] = { }; + + res |= _write(dev, L3GXXXX_REG_CTRL1, reg, 6); + res |= _write(dev, L3GXXXX_REG_FIFO_CTRL, reg, 1); + res |= _write(dev, L3GXXXX_REG_IG_CFG, reg, 1); + res |= _write(dev, L3GXXXX_REG_IG_THS_XH, reg, IS_USED(MODULE_L3GD20H) ? 8 : 7); + + reg[0] = (dev->params.odr << L3GXXXX_ODR_BW_S) | L3GXXXX_POWER_MODE + | L3GXXXX_XYZ_ENABLED; + reg[1] = (dev->params.hpf_mode << L3GXXXX_HPF_MODE_S) | + (dev->params.hpf_cutoff & L3GXXXX_HPF_CUTOFF); + reg[3] = (dev->params.scale << L3GXXXX_FULL_SCALE_S) | L3GXXXX_BLOCK_DATA_UPDATE; + reg[4] = (dev->params.filter_sel & L3GXXXX_OUT_SEL); + + /* HPF has to be enabled by a separate bit if filter mode is 1 or 3 */ + reg[4] |= (dev->params.filter_sel & 1) ? L3GXXXX_HP_ENABLED : 0; + + /* initialize sensor completely including setting in power down mode */ + res |= _write(dev, L3GXXXX_REG_CTRL1, reg, 6); + +#if IS_USED(MODULE_L3GD20H) && IS_USED(MODULE_L3GXXXX_LOW_ODR) + /* in case of low ODR, low data rate flag has to be set */ + if (dev->params.odr >= L3GXXXX_ODR_12) { + ASSERT_PARAM(dev->sensor == L3GD20H); + res |= _update(dev, L3GXXXX_REG_LOW_ODR, L3GXXXX_LOW_ODR, 1); + } +#endif + +#if IS_USED(MODULE_L3GXXXX_FIFO) + uint8_t fifo_ctrl = (dev->params.fifo_mode << L3GXXXX_FIFO_MODE_S) + | (dev->params.fifo_watermark & L3GXXXX_FIFO_WATERMARK); + res |= _write(dev, L3GXXXX_REG_FIFO_CTRL, &fifo_ctrl, 1); + + reg[4] |= L3GXXXX_FIFO_EN; +#endif /* IS_USED(MODULE_L3GXXXX_FIFO) */ + + res |= _write(dev, L3GXXXX_REG_CTRL1, reg, 5); + _release(dev); + + if (res != L3GXXXX_OK) { + return res; + } + +#if IS_USED(MODULE_L3GXXXX_IRQ_DATA) + /* enable FIFO interrupts if FIFO is used, DRDY interrupt otherwise */ + if ((res = l3gxxxx_enable_int(dev, IS_USED(MODULE_L3GXXXX_FIFO) + ? L3GXXXX_INT_FIFO + : L3GXXXX_INT_DATA_READY, true))) { + return res; + } +#endif /* IS_USED(MODULE_L3GXXXX_IRQ_DATA) */ + +#if IS_USED(MODULE_L3GXXXX_IRQ_EVENT) + if ((res = l3gxxxx_set_int_event_cfg(dev, &dev->params.int1_cfg)) || + (res = l3gxxxx_enable_int(dev, L3GXXXX_INT_EVENT, true), res)) { + return res; + } +#endif /* IS_USED(MODULE_L3GXXXX_IRQ_EVENT) */ + +#if IS_USED(MODULE_L3GXXXX_I2C) + if (_IS_DEV_I2C) { + DEBUG_DEV("I2C device initialized: bus %d, addr=%02x", + dev, _I2C_DEV, _I2C_ADDR); + } +#endif /* IS_USED(MODULE_L3GXXXX_I2C) */ + +#if IS_USED(MODULE_L3GXXXX_SPI) + if (_IS_DEV_SPI) { + DEBUG_DEV("SPI device initialized: bus %d, cs=%u", + dev, _SPI_DEV, (unsigned)_SPI_CS); + } +#endif /* IS_USED(MODULE_L3GXXXX_SPI) */ + + return res; +} + +int l3gxxxx_data_ready(const l3gxxxx_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + int res = L3GXXXX_OK; + + uint8_t reg; + +#if IS_USED(MODULE_L3GXXXX_FIFO) + if (dev->params.fifo_mode == L3GXXXX_BYPASS) { + res = l3gxxxx_reg_read(dev, L3GXXXX_REG_STATUS, ®, 1); + return res ? res : _GET_REG_BIT(reg, L3GXXXX_ANY_DATA_READY); + } + else { + res = l3gxxxx_reg_read(dev, L3GXXXX_REG_FIFO_SRC, ®, 1); + return res ? res : _GET_REG_VALUE(reg, L3GXXXX_FIFO_FFS); + } +#else + res = l3gxxxx_reg_read(dev, L3GXXXX_REG_STATUS, ®, 1); + return res ? res : _GET_REG_BIT(reg, L3GXXXX_ANY_DATA_READY); +#endif +} + +/* + * scale factors for conversion of raw sensor data to degree for possible + * sensitivities according to mechanical characteristics in datasheet + * multiplied by 4,000 + */ +static const uint16_t L3GXXXX_SCALES[3] = { 35, 70, 280 }; + +int l3gxxxx_read(const l3gxxxx_t *dev, l3gxxxx_data_t *data) + +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(data != NULL); + DEBUG_DEV("data=%p", dev, data); + + int res = L3GXXXX_OK; + + l3gxxxx_raw_data_t raw; + + if ((res = l3gxxxx_read_raw(dev, &raw)) != L3GXXXX_OK) { + return res; + } + + data->x = (raw.x * L3GXXXX_SCALES[dev->params.scale]) >> 2; + data->y = (raw.y * L3GXXXX_SCALES[dev->params.scale]) >> 2; + data->z = (raw.z * L3GXXXX_SCALES[dev->params.scale]) >> 2; + + return res; +} + +int l3gxxxx_read_raw(const l3gxxxx_t *dev, l3gxxxx_raw_data_t *raw) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(raw != NULL); + DEBUG_DEV("raw=%p", dev, raw); + + int res = L3GXXXX_OK; + +#if IS_USED(MODULE_L3GXXXX_FIFO) + /* if not in bypass mode we read out the FIFO and return newest data sample */ + if (dev->params.fifo_mode != L3GXXXX_BYPASS) { + l3gxxxx_raw_data_fifo_t raw_fifo = {}; + int num; + + /* read raw data samples from FIFO */ + if ((num = l3gxxxx_read_raw_fifo(dev, raw_fifo)) < 0) { + DEBUG_DEV("reading raw data samples from FIFO failed", dev); + return num; + } + + if (num == 0) { + DEBUG_DEV("no raw data in FIFO", dev); + return -L3GXXXX_ERROR_NO_NEW_DATA; + } + + *raw = raw_fifo[num - 1]; + + return res; + } +#endif /* IS_USED(MODULE_L3GXXXX_FIFO) */ + + uint8_t data[6]; + + /* read raw data sample and clear interrupt signal INT2/DRDY if used */ + if (l3gxxxx_reg_read(dev, L3GXXXX_REG_OUT_X_L, data, 6)) { + DEBUG_DEV("could not get raw data", dev); + return -L3GXXXX_ERROR_RAW_DATA; + } + + /* L3GXXXX_REG_CTRL4.BLE = 0, Data LSB @ lower address */ + raw->x = byteorder_lebuftohs(&data[0]); + raw->y = byteorder_lebuftohs(&data[2]); + raw->z = byteorder_lebuftohs(&data[4]); + + return res; +} + +#if IS_USED(MODULE_L3GXXXX_FIFO) + +int l3gxxxx_read_fifo(const l3gxxxx_t *dev, l3gxxxx_data_fifo_t data) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(data != NULL); + DEBUG_DEV("", dev); + + l3gxxxx_raw_data_fifo_t raw = {}; + int num; + + if ((num = l3gxxxx_read_raw_fifo(dev, raw)) < 0) { + DEBUG_DEV("reading raw data samples from FIFO failed", dev); + return num; + } + + for (int i = 0; i < num; i++) { + data[i].x = (raw[i].x * L3GXXXX_SCALES[dev->params.scale]) >> 2; + data[i].y = (raw[i].y * L3GXXXX_SCALES[dev->params.scale]) >> 2; + data[i].z = (raw[i].z * L3GXXXX_SCALES[dev->params.scale]) >> 2; + } + + return num; +} + +int l3gxxxx_read_raw_fifo(const l3gxxxx_t *dev, l3gxxxx_raw_data_fifo_t raw) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(raw != NULL); + DEBUG_DEV("", dev); + + int res = L3GXXXX_OK; + + /* in bypass mode, we use l3gxxxx_read_raw to return one sample */ + if (dev->params.fifo_mode == L3GXXXX_BYPASS) { + res = l3gxxxx_read_raw(dev, raw); + return (res == L3GXXXX_OK) ? 1 : res; + } + + uint8_t reg; + + _acquire(dev); + + /* read FIFO state */ + if (_read(dev, L3GXXXX_REG_FIFO_SRC, ®, 1) != L3GXXXX_OK) { + _release(dev); + return res; + } + + /* if nothing is in the FIFO, just return with 0 */ + if (reg & L3GXXXX_FIFO_EMPTY) { + _release(dev); + return 0; + } + + /* read samples from FIFO */ + int samples = (reg & L3GXXXX_FIFO_FFS) + ((reg & L3GXXXX_FIFO_OVR) ? 1 : 0); + + /* read samples from FIFO */ + for (int i = 0; i < samples; i++) { + + uint8_t data[6]; + + /* read raw data sample */ + res |= _read(dev, L3GXXXX_REG_OUT_X_L, data, 6); + + /* L3GXXXX_REG_CTRL4.BLE = 0, Data LSB @ lower address */ + raw[i].x = byteorder_lebuftohs(&data[0]); + raw[i].y = byteorder_lebuftohs(&data[2]); + raw[i].z = byteorder_lebuftohs(&data[4]); + } + + res |= _read(dev, L3GXXXX_REG_FIFO_SRC, ®, 1); + + if (reg & L3GXXXX_FIFO_FFS) { + DEBUG_DEV("New samples stored in FIFO while reading out the FIFO, " + "output data rate (ODR) is too high", dev); + } + + if (dev->params.fifo_mode == L3GXXXX_FIFO && samples == 32) { + /* clean FIFO (see app note) */ + res |= _update(dev, L3GXXXX_REG_FIFO_CTRL, L3GXXXX_FIFO_MODE, L3GXXXX_BYPASS); + res |= _update(dev, L3GXXXX_REG_FIFO_CTRL, L3GXXXX_FIFO_MODE, L3GXXXX_FIFO); + } + + _release(dev); + + return (res == L3GXXXX_OK) ? samples : res; +} +#endif /* IS_USED(MODULE_L3GXXXX_FIFO) */ + +int l3gxxxx_power_down(l3gxxxx_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + return l3gxxxx_reg_update(dev, L3GXXXX_REG_CTRL1, L3GXXXX_POWER_MODE, 0); +} + +int l3gxxxx_power_up(l3gxxxx_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + int res = l3gxxxx_reg_update(dev, L3GXXXX_REG_CTRL1, L3GXXXX_POWER_MODE, 1); + + return res; +} + +#if IS_USED(MODULE_L3GXXXX_SLEEP) + +int l3gxxxx_sleep(l3gxxxx_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + return l3gxxxx_reg_update(dev, L3GXXXX_REG_CTRL1, L3GXXXX_XYZ_ENABLED, 0); +} + +int l3gxxxx_wake_up(l3gxxxx_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + int res = l3gxxxx_reg_update(dev, L3GXXXX_REG_CTRL1, L3GXXXX_XYZ_ENABLED, + L3GXXXX_XYZ_ENABLED); + return res; +} +#endif /* IS_USED(MODULE_L3GXXXX_SLEEP) */ + +#if IS_USED(MODULE_L3GXXXX_IRQ) + +int l3gxxxx_enable_int(const l3gxxxx_t *dev, + l3gxxxx_int_types_t mask, bool enable) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("mask=%02x enable=%d", dev, mask, enable); + +#if IS_USED(MODULE_L3GXXXX_IRQ_EVENT) + if (enable && (mask & L3GXXXX_INT_EVENT)) { + ASSERT_PARAM(gpio_is_valid(dev->params.int1_pin)); + } +#endif + +#if IS_USED(MODULE_L3GXXXX_IRQ_DATA) + if (enable && (mask & L3GXXXX_INT_DATA)) { + ASSERT_PARAM(gpio_is_valid(dev->params.int2_pin)); + } +#endif + + return l3gxxxx_reg_update(dev, L3GXXXX_REG_CTRL3, mask, enable ? 0xff : 0x00); +} + +#if IS_USED(MODULE_L3GXXXX_IRQ_EVENT) +static void _cb_int1(void *arg) +{ + l3gxxxx_t *dev = (l3gxxxx_t *)arg; + dev->int_type |= L3GXXXX_INT_EVENT; + mutex_unlock(&dev->int_lock); +} +#endif /* IS_USED(MODULE_L3GXXXX_IRQ_EVENT) */ + +#if IS_USED(MODULE_L3GXXXX_IRQ_DATA) +static void _cb_int2(void *arg) +{ + l3gxxxx_t *dev = (l3gxxxx_t *)arg; + dev->int_type |= L3GXXXX_INT_DATA; + mutex_unlock(&dev->int_lock); +} +#endif /* IS_USED(L3GXXXX_IRQ_DRDY) */ + +l3gxxxx_int_src_t l3gxxxx_wait_int(l3gxxxx_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + dev->int_type = 0; + +#if IS_USED(MODULE_L3GXXXX_IRQ_EVENT) + ASSERT_PARAM(gpio_is_valid(dev->params.int1_pin)); + /* init INT1 signal pin and enable the interrupt */ + gpio_init_int(dev->params.int1_pin, GPIO_IN, GPIO_RISING, _cb_int1, dev); +#endif /* IS_USED(MODULE_L3GXXXX_IRQ_EVENT) */ + +#if IS_USED(MODULE_L3GXXXX_IRQ_DATA) + ASSERT_PARAM(gpio_is_valid(dev->params.int2_pin)); + /* init INT2/DRDY signal pin and enable the interrupt */ + gpio_init_int(dev->params.int2_pin, GPIO_IN, GPIO_RISING, _cb_int2, dev); +#endif /* IS_USED(L3GXXXX_IRQ_DRDY) */ + + /* wait for an interrupt */ + mutex_lock(&dev->int_lock); + +#if IS_USED(MODULE_L3GXXXX_IRQ_EVENT) + gpio_irq_disable(dev->params.int1_pin); +#endif /* IS_USED(MODULE_L3GXXXX_IRQ_EVENT) */ + +#if IS_USED(MODULE_L3GXXXX_IRQ_DATA) + gpio_irq_disable(dev->params.int2_pin); +#endif /* IS_USED(L3GXXXX_IRQ_DRDY) */ + + l3gxxxx_int_src_t int_src = { }; + +#if IS_USED(MODULE_L3GXXXX_IRQ_EVENT) + if (dev->int_type & L3GXXXX_INT_EVENT) { + _get_int_event_src(dev, &int_src.event); + } +#endif /* IS_USED(MODULE_L3GXXXX_IRQ_EVENT) */ + +#if IS_USED(MODULE_L3GXXXX_IRQ_DATA) + if (dev->int_type & L3GXXXX_INT_DATA) { + _get_int_data_src(dev, &int_src.data); + } +#endif /* IS_USED(L3GXXXX_IRQ_DRDY) */ + + return int_src; +} + +#endif /* IS_USED(MODULE_L3GXXXX_IRQ) */ + +#if IS_USED(MODULE_L3GXXXX_IRQ_EVENT) + +int l3gxxxx_set_int_event_cfg(const l3gxxxx_t *dev, + const l3gxxxx_int_event_cfg_t *cfg) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(cfg != NULL); + DEBUG_DEV("config=%p", dev, cfg); + + int res = L3GXXXX_OK; + + uint8_t ig_cfg = 0; + uint8_t ig_dur = 0; + uint8_t ig_ths[6] = { 0 }; + + _SET_REG_BIT(ig_cfg, L3GXXXX_INT1_X_LOW, cfg->x_low_enabled); + _SET_REG_BIT(ig_cfg, L3GXXXX_INT1_X_HIGH, cfg->x_high_enabled); + + _SET_REG_BIT(ig_cfg, L3GXXXX_INT1_Y_LOW, cfg->y_low_enabled); + _SET_REG_BIT(ig_cfg, L3GXXXX_INT1_Y_HIGH, cfg->y_high_enabled); + + _SET_REG_BIT(ig_cfg, L3GXXXX_INT1_Z_LOW, cfg->z_low_enabled); + _SET_REG_BIT(ig_cfg, L3GXXXX_INT1_Z_HIGH, cfg->z_high_enabled); + + _SET_REG_BIT(ig_cfg, L3GXXXX_INT1_LATCH, cfg->latch); + _SET_REG_BIT(ig_cfg, L3GXXXX_INT1_AND_OR, cfg->and_or); + + _SET_REG_BIT(ig_dur, L3GXXXX_INT1_WAIT, cfg->wait); + _SET_REG_VALUE(ig_dur, L3GXXXX_INT1_DURATION, cfg->duration); + + ig_ths[0] = (cfg->x_threshold >> 8) & 0x7f; + ig_ths[1] = (cfg->x_threshold & 0xff); + ig_ths[2] = (cfg->y_threshold >> 8) & 0x7f; + ig_ths[3] = (cfg->y_threshold & 0xff); + ig_ths[4] = (cfg->z_threshold >> 8) & 0x7f; + ig_ths[5] = (cfg->z_threshold & 0xff); + + _acquire(dev); + + /* write the thresholds to registers IG_THS_* */ + res |= _write(dev, L3GXXXX_REG_IG_THS_XH, ig_ths, 6); + + /* write duration configuration to IG_DURATION */ + res |= _write(dev, L3GXXXX_REG_IG_DURATION, &ig_dur, 1); + + /* write INT1 configuration to IG_CFG */ + res |= _write(dev, L3GXXXX_REG_IG_CFG, &ig_cfg, 1); + + /* filter selection used for threshold comparison for INT1 generation */ + res |= _update(dev, L3GXXXX_REG_CTRL5, L3GXXXX_IG_SEL, cfg->filter); + + /* try to set HPen in case LPF2 and HPF is used */ + if (cfg->filter == L3GXXXX_HPF_AND_LPF2) { + res |= _update(dev, L3GXXXX_REG_CTRL5, L3GXXXX_HP_ENABLED, 1); + } + + _release(dev); + + return res; +} + +static int _get_int_event_src(const l3gxxxx_t *dev, + l3gxxxx_int_event_src_t *src) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(src != NULL); + DEBUG_DEV("src=%p", dev, src); + + int res = L3GXXXX_OK; + + uint8_t ig_cfg; + uint8_t ig_src; + + _acquire(dev); + res |= _read(dev, L3GXXXX_REG_IG_CFG, (uint8_t *)&ig_cfg, 1); + res |= _read(dev, L3GXXXX_REG_IG_SRC, (uint8_t *)&ig_src, 1); + _release(dev); + + src->val = ig_src & ig_cfg & (L3GXXXX_INT1_X_LOW | L3GXXXX_INT1_X_HIGH | + L3GXXXX_INT1_Y_LOW | L3GXXXX_INT1_Y_HIGH | + L3GXXXX_INT1_Z_LOW | L3GXXXX_INT1_Z_HIGH); + src->active = (ig_src & L3GXXXX_INT1_ACTIVE) ? 1 : 0; + + return res; +} +#endif /* IS_USED(MODULE_L3GXXXX_IRQ_EVENT) */ + +#if IS_USED(MODULE_L3GXXXX_IRQ_DATA) + +static int _get_int_data_src(const l3gxxxx_t *dev, + l3gxxxx_int_data_src_t *src) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(src != NULL); + DEBUG_DEV("src=%p", dev, src); + + int res = L3GXXXX_OK; + + uint8_t fifo_src; + uint8_t status; + + _acquire(dev); + res |= _read(dev, L3GXXXX_REG_STATUS, &status, 1); + res |= _read(dev, L3GXXXX_REG_FIFO_SRC, &fifo_src, 1); + _release(dev); + + src->data_ready = _GET_REG_BIT(status, L3GXXXX_ANY_DATA_READY); + src->fifo_watermark = _GET_REG_BIT(fifo_src, L3GXXXX_FIFO_WTM); + src->fifo_overrun = _GET_REG_BIT(fifo_src, L3GXXXX_FIFO_OVR); + src->fifo_empty = _GET_REG_BIT(fifo_src, L3GXXXX_FIFO_EMPTY); + + return res; +} +#endif /* IS_USED(MODULE_L3GXXXX_IRQ_DATA) */ + +#if IS_USED(MODULE_L3GXXXX_CONFIG) + +int l3gxxxx_set_mode(l3gxxxx_t *dev, l3gxxxx_odr_t odr, + bool x, bool y, bool z) +{ + ASSERT_PARAM(dev != NULL); + + DEBUG_DEV("odr=%02x x=%d y=%d z=%d", dev, odr, x, y, z); + + int res = L3GXXXX_OK; + + dev->params.odr = odr; + + uint8_t ctrl1; + + ctrl1 = (odr << L3GXXXX_ODR_BW_S) & L3GXXXX_ODR_BW; + ctrl1 |= L3GXXXX_POWER_MODE; + ctrl1 |= x ? L3GXXXX_X_ENABLED : 0; + ctrl1 |= y ? L3GXXXX_Y_ENABLED : 0; + ctrl1 |= z ? L3GXXXX_Z_ENABLED : 0; + + _acquire(dev); + res |= _write(dev, L3GXXXX_REG_CTRL1, &ctrl1, 1); + +#if IS_USED(MODULE_L3GD20H) && IS_USED(MODULE_L3GXXXX_LOW_ODR) + /* in case of low ODR, low data rate flag has to be set */ + if (dev->params.odr >= L3GXXXX_ODR_12) { + ASSERT_PARAM(dev->sensor == L3GD20H); + res |= _update(dev, L3GXXXX_REG_LOW_ODR, L3GXXXX_LOW_ODR, 1); + } +#endif + _release(dev); + + return res; +} + +int l3gxxxx_set_scale(l3gxxxx_t *dev, l3gxxxx_scale_t scale) +{ + ASSERT_PARAM(dev != NULL); +#if IS_USED(MODULE_A3G4250) + ASSERT_PARAM((scale == L3GXXXX_SCALE_245_DPS) || (dev->sensor != X3G42XXD)); +#endif + DEBUG_DEV("scale=%02x", dev, scale); + + dev->params.scale = scale; + + /* read CTRL4 register and write scale */ + return l3gxxxx_reg_update(dev, L3GXXXX_REG_CTRL4, L3GXXXX_FULL_SCALE, scale); +} + +int l3gxxxx_select_output_filter(l3gxxxx_t *dev, + l3gxxxx_filter_sel_t filter) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("filter=%02x", dev, filter); + + dev->params.filter_sel = filter; + + int res = L3GXXXX_OK; + + _acquire(dev); + + /* set the register OUT_SEL in any case */ + res |= _update(dev, L3GXXXX_REG_CTRL5, L3GXXXX_OUT_SEL, filter); + /* HPF has to be enabled by a separate bit if filter mode is 1 or 3 */ + res |= _update(dev, L3GXXXX_REG_CTRL5, L3GXXXX_HP_ENABLED, filter & 1); + + _release(dev); + + return res; +} + +int l3gxxxx_config_hpf(const l3gxxxx_t *dev, + l3gxxxx_hpf_mode_t mode, uint8_t cutoff) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(cutoff <= 9); + DEBUG_DEV("mode=%02x cutoff=%d", dev, mode, cutoff); + + int res = L3GXXXX_OK; + + _acquire(dev); + res |= _update(dev, L3GXXXX_REG_CTRL2, L3GXXXX_HPF_MODE, mode); + res |= _update(dev, L3GXXXX_REG_CTRL2, L3GXXXX_HPF_CUTOFF, cutoff); + _release(dev); + + return res; +} + +int l3gxxxx_set_hpf_ref(const l3gxxxx_t *dev, int8_t ref) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("ref=%d", dev, ref); + + return l3gxxxx_reg_write(dev, L3GXXXX_REG_REFERENCE, (uint8_t *)&ref, 1); +} + +int l3gxxxx_get_hpf_ref(const l3gxxxx_t *dev, int8_t *ref) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(ref != NULL); + DEBUG_DEV("ref=%p", dev, ref); + + return l3gxxxx_reg_read(dev, L3GXXXX_REG_REFERENCE, (uint8_t *)&ref, 1); +} + +#if IS_USED(MODULE_L3GXXXX_FIFO) + +int l3gxxxx_set_fifo_mode(l3gxxxx_t *dev, l3gxxxx_fifo_mode_t mode, + uint8_t watermark) +{ + ASSERT_PARAM(dev != NULL); +#if !IS_USED(L3GD20H) && !IS_USED(L3GD20) + ASSERT_PARAM(mode <= L3GXXXX_STREAM); +#endif +#if !IS_USED(L3GD20H) + ASSERT_PARAM(mode <= L3GXXXX_DYNAMIC_STREAM); +#endif + DEBUG_DEV("mode=%d watermark=%d", dev, mode, watermark); + + int res = L3GXXXX_OK; + + dev->params.fifo_mode = mode; + + _acquire(dev); + + /* read CTRL5 register and write FIFO_EN flag */ + res |= _update(dev, L3GXXXX_REG_CTRL5, L3GXXXX_FIFO_EN, (mode != L3GXXXX_BYPASS)); + + /* read FIFO_CTRL register and write FIFO mode */ + res |= _update(dev, L3GXXXX_REG_FIFO_CTRL, L3GXXXX_FIFO_WATERMARK, watermark); + res |= _update(dev, L3GXXXX_REG_FIFO_CTRL, L3GXXXX_FIFO_MODE, mode); + + _release(dev); + + return res; +} +#endif /* IS_USED(MODULE_L3GXXXX_FIFO) */ + +int l3gxxxx_get_int_event_cfg(const l3gxxxx_t *dev, + l3gxxxx_int_event_cfg_t *cfg) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(cfg != NULL); + DEBUG_DEV("config=%p", dev, cfg); + + int res = L3GXXXX_OK; + + uint8_t ig_cfg; + uint8_t ig_dur; + uint8_t ig_ths[6]; + uint8_t ctrl3; + uint8_t ctrl5; + + _acquire(dev); + res |= _read(dev, L3GXXXX_REG_IG_THS_XH, ig_ths, 6); + res |= _read(dev, L3GXXXX_REG_IG_CFG, &ig_cfg, 1); + res |= _read(dev, L3GXXXX_REG_IG_DURATION, &ig_dur, 1); + res |= _read(dev, L3GXXXX_REG_CTRL3, &ctrl3, 1); + res |= _read(dev, L3GXXXX_REG_CTRL5, &ctrl5, 1); + _release(dev); + + cfg->x_low_enabled = _GET_REG_BIT(ig_cfg, L3GXXXX_INT1_X_LOW); + cfg->x_high_enabled = _GET_REG_BIT(ig_cfg, L3GXXXX_INT1_X_HIGH); + + cfg->y_low_enabled = _GET_REG_BIT(ig_cfg, L3GXXXX_INT1_Y_LOW); + cfg->y_high_enabled = _GET_REG_BIT(ig_cfg, L3GXXXX_INT1_Y_HIGH); + + cfg->z_low_enabled = _GET_REG_BIT(ig_cfg, L3GXXXX_INT1_Z_LOW); + cfg->z_high_enabled = _GET_REG_BIT(ig_cfg, L3GXXXX_INT1_Z_HIGH); + + cfg->x_threshold = msb_lsb_to_type(uint16_t, ig_ths, 0); + cfg->y_threshold = msb_lsb_to_type(uint16_t, ig_ths, 2); + cfg->z_threshold = msb_lsb_to_type(uint16_t, ig_ths, 4); + + cfg->filter = _GET_REG_VALUE(ctrl5, L3GXXXX_IG_SEL); + + cfg->and_or = _GET_REG_BIT(ig_cfg, L3GXXXX_INT1_AND_OR); + cfg->latch = _GET_REG_BIT(ig_cfg, L3GXXXX_INT1_LATCH); + + cfg->wait = _GET_REG_BIT(ig_dur, L3GXXXX_INT1_WAIT); + cfg->duration = _GET_REG_VALUE(ig_dur, L3GXXXX_INT1_DURATION); + + cfg->counter_mode = 0; + + return res; +} + +#endif /* IS_USED(MODULE_L3GXXXX_CONFIG) */ + +int l3gxxxx_reg_read(const l3gxxxx_t *dev, + uint8_t reg, uint8_t *data, uint8_t len) +{ + int res; + + _acquire(dev); + res = _read(dev, reg, data, len); + _release(dev); + + return res; +} + +int l3gxxxx_reg_write(const l3gxxxx_t *dev, + uint8_t reg, const uint8_t *data, uint8_t len) +{ + int res; + + _acquire(dev); + res = _write(dev, reg, data, len); + _release(dev); + + return res; +} + +int l3gxxxx_reg_update(const l3gxxxx_t *dev, + uint8_t reg, uint8_t mask, uint8_t val) +{ + int res; + + _acquire(dev); + res = _update(dev, reg, mask, val); + _release(dev); + + return res; +} + +/** Functions for internal use only */ + +#define L3GXXXX_I2C_L3GD20X_ADDR_1 (0x6a) /* L3GD20x I2C address 1 */ +#define L3GXXXX_I2C_L3GD20X_ADDR_2 (0x6b) /* L3GD20x I2C address 2 */ +#define L3GXXXX_I2C_X3G42XXD_ADDR_1 (0x68) /* x3G42xxD I2C address 1 */ +#define L3GXXXX_I2C_X3G42XXD_ADDR_2 (0x69) /* x3G42xxD I2C address 2 */ + +/** + * @brief Check the chip ID to test whether sensor is available + * @note Communication bus has to be acquired when enter. + */ +static int _is_available(l3gxxxx_t *dev) +{ + DEBUG_DEV("", dev); + + int res; + + uint8_t chip_id, tries = 16; + + /* try to read the chip id from L3GXXXX_REG_WHO_AM_I */ + do { + res = _read(dev, L3GXXXX_REG_WHO_AM_I, &chip_id, 1); + } while ((chip_id == 0xFF || chip_id == 0x0) && --tries); + +#if IS_USED(MODULE_L3GXXXX_I2C) + if ((dev->params.if_params.type == L3GXXXX_I2C) && (res != L3GXXXX_OK)) { + /* if the interface type is I2C but the chip ID couldn't be read + from configured address, we probe possible I2C addresses */ + +#if IS_USED(MODULE_L3GD20H) || IS_USED(MODULE_L3GD20) + if (res != L3GXXXX_OK) { + dev->params.if_params.i2c.addr = L3GXXXX_I2C_L3GD20X_ADDR_1; + res = _read(dev, L3GXXXX_REG_WHO_AM_I, &chip_id, 1); + } + if (res != L3GXXXX_OK) { + dev->params.if_params.i2c.addr = L3GXXXX_I2C_L3GD20X_ADDR_2; + res = _read(dev, L3GXXXX_REG_WHO_AM_I, &chip_id, 1); + } +#endif /* IS_USED(MODULE_L3GD20H) || IS_USED(MODULE_L3GD20) */ + +#if IS_USED(MODULE_L3G4200D_NG) || IS_USED(MODULE_I3G4250D) || IS_USED(MODULE_A3G4250D) + if (res != L3GXXXX_OK) { + dev->params.if_params.i2c.addr = L3GXXXX_I2C_X3G42XXD_ADDR_1; + res = _read(dev, L3GXXXX_REG_WHO_AM_I, &chip_id, 1); + } + if (res != L3GXXXX_OK) { + dev->params.if_params.i2c.addr = L3GXXXX_I2C_X3G42XXD_ADDR_2; + res = _read(dev, L3GXXXX_REG_WHO_AM_I, &chip_id, 1); + } +#endif /* IS_USED(MODULE_L3G4200D_NG) || IS_USED(MODULE_I3G4250D) || IS_USED(MODULE_A3G4250D) */ + + } +#endif /* #if IS_USED(MODULE_L3GXXXX_I2C) */ + + if (res != L3GXXXX_OK) { + return res; + } + + /* determine the sensor type */ + switch (chip_id) { +#if IS_USED(MODULE_L3GD20H) + case L3GXXXX_CHIP_ID_L3GD20H: dev->sensor = L3GD20H; + break; +#endif +#if IS_USED(MODULE_L3GD20) + case L3GXXXX_CHIP_ID_L3GD20: dev->sensor = L3GD20; + break; +#endif +#if IS_USED(MODULE_L3G4200D) || IS_USED(MODULE_I3G4250D) || IS_USED(MODULE_A3G4250D) + case L3GXXXX_CHIP_ID_X3G42XXD: dev->sensor = X3G42XXD; + break; +#endif + default: DEBUG_DEV("sensor is not available, wrong chip id %02x", + dev, chip_id); + return -L3GXXXX_ERROR_WRONG_CHIP_ID; + } + + return res; +} + +static int _update(const l3gxxxx_t *dev, + uint8_t reg, uint8_t mask, uint8_t val) +{ + DEBUG_DEV("reg=%02x mask=%02x val=%02x", dev, reg, mask, val); + + int res = L3GXXXX_OK; + + uint8_t reg_val; + uint8_t shift = 0; + + while (!((mask >> shift) & 0x01)) { + shift++; + } + + /* read current register value */ + res |= _read(dev, reg, ®_val, 1); + + /* set masked bits to the given value */ + reg_val = (reg_val & ~mask) | ((val << shift) & mask); + + /* write back new register value */ + res |= _write(dev, reg, ®_val, 1); + + return res; +} + +#define L3GXXXX_SPI_READ_FLAG 0x80 +#define L3GXXXX_SPI_WRITE_FLAG 0x00 +#define L3GXXXX_SPI_AUTO_INC_FLAG 0x40 + +#define L3GXXXX_I2C_AUTO_INC_FLAG 0x80 + +static void _acquire(const l3gxxxx_t *dev) +{ + ASSERT_PARAM(dev != NULL); +#if IS_USED(MODULE_L3GXXXX_SPI) + if (dev->params.if_params.type == L3GXXXX_SPI) { + spi_acquire(_SPI_DEV, _SPI_CS, SPI_MODE_3, _SPI_CLK); + return; + } +#endif +#if IS_USED(MODULE_L3GXXXX_I2C) + if (dev->params.if_params.type == L3GXXXX_I2C) { + i2c_acquire(_I2C_DEV); + return; + } +#endif + return; +} + +static void _release(const l3gxxxx_t *dev) +{ + ASSERT_PARAM(dev != NULL); +#if IS_USED(MODULE_L3GXXXX_SPI) + if (dev->params.if_params.type == L3GXXXX_SPI) { + spi_release(_SPI_DEV); + return; + } +#endif +#if IS_USED(MODULE_L3GXXXX_I2C) + if (dev->params.if_params.type == L3GXXXX_I2C) { + i2c_release(_I2C_DEV); + return; + } +#endif + return; +} + +static int _read(const l3gxxxx_t *dev, uint8_t reg, uint8_t *data, uint8_t len) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(data != NULL); + ASSERT_PARAM(len != 0); + + DEBUG_DEV("read %d byte from sensor registers starting at addr 0x%02x", + dev, len, reg); + +#if IS_USED(MODULE_L3GXXXX_SPI) + if (dev->params.if_params.type == L3GXXXX_SPI) { + reg &= 0x3f; + reg |= L3GXXXX_SPI_READ_FLAG; + reg |= L3GXXXX_SPI_AUTO_INC_FLAG; + + /* the first byte sent is the register address */ + spi_transfer_byte(_SPI_DEV, _SPI_CS, true, reg); + spi_transfer_bytes(_SPI_DEV, _SPI_CS, false, 0, data, len); + + if (ENABLE_DEBUG) { + printf("[l3gxxxx] %s dev=%" PRIxPTR ": read following bytes: ", + __func__, (unsigned int)dev); + for (uint8_t i = 0; i < len; i++) { + printf("%02x ", data[i]); + } + printf("\n"); + } + return L3GXXXX_OK; + } +#endif /* IS_USED(MODULE_L3GXXXX_SPI) */ + +#if IS_USED(MODULE_L3GXXXX_I2C) + if (dev->params.if_params.type == L3GXXXX_I2C) { + if (len > 1) { + reg |= L3GXXXX_I2C_AUTO_INC_FLAG; + } + + int res = i2c_read_regs(_I2C_DEV, _I2C_ADDR, reg, data, len, 0); + + if (res != 0) { + DEBUG_DEV("I2C addr=%02x could not read %d bytes from sensor " + "registers starting at addr %02x, reason %d (%s)", + dev, _I2C_ADDR, len, reg, res, strerror(res * -1)); + return -L3GXXXX_ERROR_I2C; + } + + if (ENABLE_DEBUG) { + printf("[l3gxxxx] %s dev=%" PRIxPTR ": read following bytes: ", + __func__, (unsigned int)dev); + for (uint8_t i = 0; i < len; i++) { + printf("%02x ", data[i]); + } + printf("\n"); + } + return L3GXXXX_OK; + } +#endif /* IS_USED(MODULE_L3GXXXX_I2C) */ + + return -L3GXXXX_ERROR_INV_DEV; +} + +static int _write(const l3gxxxx_t *dev, + uint8_t reg, const uint8_t *data, uint8_t len) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(data != NULL); + ASSERT_PARAM(len != 0); + + DEBUG_DEV("write %d bytes to sensor registers starting at addr 0x%02x", + dev, len, reg); + +#if IS_USED(MODULE_L3GXXXX_SPI) + if (dev->params.if_params.type == L3GXXXX_SPI) { + reg &= 0x3f; + reg |= L3GXXXX_SPI_WRITE_FLAG; + reg |= L3GXXXX_SPI_AUTO_INC_FLAG; + + if (ENABLE_DEBUG) { + printf("[l3gxxxx] %s dev=%" PRIxPTR ": write following bytes: ", + __func__, (unsigned int)dev); + for (uint8_t i = 0; i < len; i++) { + printf("%02x ", data[i]); + } + printf("\n"); + } + + spi_transfer_byte(_SPI_DEV, _SPI_CS, true, reg); + spi_transfer_bytes(_SPI_DEV, _SPI_CS, false, data, 0, len); + + return L3GXXXX_OK; + } +#endif /* IS_USED(MODULE_L3GXXXX_SPI) */ + +#if IS_USED(MODULE_L3GXXXX_I2C) + if (dev->params.if_params.type == L3GXXXX_I2C) { + if (len > 1) { + reg |= L3GXXXX_I2C_AUTO_INC_FLAG; + } + + if (ENABLE_DEBUG) { + printf("[l3gxxxx] %s dev=%" PRIxPTR ": write following bytes: ", + __func__, (unsigned int)dev); + for (uint8_t i = 0; i < len; i++) { + printf("%02x ", data[i]); + } + printf("\n"); + } + + int res; + + if (!data || !len) { + res = i2c_write_byte(_I2C_DEV, _I2C_ADDR, reg, 0); + } + else { + res = i2c_write_regs(_I2C_DEV, _I2C_ADDR, reg, data, len, 0); + } + + if (res != L3GXXXX_OK) { + DEBUG_DEV("I2C addr=%02x could not write %d bytes to sensor " + "registers starting at addr 0x%02x, reason %d (%s)", + dev, _I2C_ADDR, len, reg, res, strerror(res * -1)); + return -L3GXXXX_ERROR_I2C; + } + + return res; + } +#endif /* IS_USED(MODULE_L3GXXXX_I2C) */ + + return -L3GXXXX_ERROR_INV_DEV; +}