esp-open-rtos/extras/l3gd20h/README.md
2018-01-20 17:01:59 +05:00

36 KiB
Raw Permalink Blame History

Driver for the L3GD20H 3-axes digital output gyroscope

The driver is for the usage with the ESP8266 and esp-open-rtos. If you can't find it in folder extras/l3gd20h of original repository, it is not yet merged. Please take a look to branch l3gd20h of my fork in that case.

It is also working with ESP32 and ESP-IDF using a wrapper component for ESP8266 functions, see folder components/esp8266_wrapper, as well as Linux based systems using a wrapper library.

The driver can also be used with L3GD20 and L3G4200D.

About the sensor

L3GD20H is a low-power 3-axis angular rate sensor 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
  • 8 bit temperature data output
  • 2 dedicated interrupt signals for data and event interrupts
  • integrated high-pass filters with 3 modes and 10 different cut off frequencies
  • embedded temperature sensor
  • embedded 32 levels of 16 bit data output FIFO
  • I2C and SPI digital output interface
  • embedded power-down and sleep mode with fast turn-on and wake-up

Sensor operation

Sensor modes

L3GD20H provides 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.

  • 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).

  • Sleep mode is the normal mode when no axis is 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.

Output Data Rates

In normal mode, measurements are performed at a defined output rate with a user selectable bandwidth. Following output data rates (ODR) are supported.

Mode Output Data Rate (ODR) Driver symbol
Power down - l3gd20h_power_down
Normal mode 12.5 Hz l3gd20h_normal_12_5
Normal mode 25 Hz l3gd20h_normal_25
Normal mode 50 Hz l3gd20h_normal_50
Normal mode 100 Hz l3gd20h_normal_100
Normal mode 200 Hz l3gd20h_normal_200
Normal mode 400 Hz l3gd20h_normal_400
Normal mode 800 Hz l3gd20h_normal_800

Output data rates (ODR) of less than 100 Hz are called Low Data Rates.

For each ODR, one of the four bandwidths 0...3 can be selected that defines the cutoff frequency (please refer datasheet) of an embedded low pass filter for the measurement results.

The easiest way to use the sensor is simply to initialize it with function l3gd20h_init_sensor and then set it to the normal mode with function l3gd20h_set_mode to start measurements with the given output data rate (ODR). The bandwidth of the embedded low-pass filter and the axes x, y and z that are activated for measurements are also given as parameters.

...
static l3gd20h_sensor_t* sensor = 0;
...
if ((sensor = l3gd20h_init_sensor (I2C_BUS, L3GD20H_I2C_ADDRESS_2, 0)))
{
    ...
    l3gd20h_set_mode (sensor, l3gd20h_normal_odr_200, 3, true, true, true);
    ...
}
...

In this example, a L3GD20H sensor connected to I2C is initialized and set to normal mode to start measurements for all three axes with an output data rate (ODR) of 200 Hz and bandwidth 3 (please refer datasheet).

Please note:

  • Function l3gd20h_init_sensor resets the sensor completely, switches it to the power down mode, and returns a pointer to a sensor device data structure on success. All registers are reset to default values and the embedded FIFO is cleared.
  • All sensor configurations should be done before calling function l3gd20h_set_mode. In particular, the interrupt configuration should be performed before to avoid loosing the first interrupt and locking the system.

Measurement results

Output data format

In normal mode, sensor determines periodically the angular rate for all axes that are enabled for measurement and produces output data with the selected output data rate (ODR).

Function l3gd20h_new_data or the data ready interrupt (see below) can be used to determine when new data are available.

Raw output data (raw data) are given as 16-bit signed integer values in 2s complement representation. The range and the resolution of these data depend on the sensitivity of the sensor which is selected by the full scale value. The L3GD20H allows to select the following full scales:

Full Scale Resolution Driver symbol
±245 dps 2 mdps l3gd20h_scale_245_dps
±500 dps 4 mdps l3gd20h_scale_500_dps
±2000 dps 16 mdps l3gd20h_scale_2000_dps

By default, a full scale of ±245 dps is used. Function l3gd20h_set_scale can be used to change it.

l3gd20h_set_scale(sensor, l3gd20h_scale_500_dps);

Fetching output data

To get the information whether new data are available, the user task can either use

  • the function l3gd20h_new_data to check periodically whether new output data are available, or
  • the data ready interrupt (DRDY) which is thrown as soon as new output data are available (see below).

Last measurement results can then be fetched either

  • as raw data using function l3gd20h_get_raw_data or
  • as floating point values in dps (degrees per second) using function l3gd20h_get_float_data.

It is recommended to use function l3gd20h_get_float_data since it already converts measurement results to real values according to the selected full scale.

void user_task_periodic(void *pvParameters)
{
    l3gd20h_float_data_t data;

    while (1)
    {
        // execute task every 10 ms
        vTaskDelay (10/portTICK_PERIOD_MS);
        ...
        // test for new data
        if (!l3gd20h_new_data (sensor))
            continue;
    
        // fetch new data
        if (l3gd20h_get_float_data (sensor, &data))
        {
            // do something with data
            ...
        }
    }
}

Please note: The functions l3gd20h_get_float_data and l3gd20h_get_raw_data 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.

Filters

L3GD20H provides embedded low-pass as well as high-pass filtering capabilities to improve measurement results. It is possible to independently apply the filters on the output data and/or on the data used for event interrupt generation (selective axis movement and wake up, see below) separately. Please refer the datasheet or application note for more details.

The filters applied to the output data are selected with function l3gd20h_select_output_filter. Following selections are possible:

Driver symbol Low pass filter (LPF2) used High pass filter (HPF) used
l3gd20h_no_filter - -
l3gd20h_hpf_only x -
l3gd20h_lpf2_only - x
l3gd20h_hpf_and_lpf2 x x

These filters can also be applied to data used for event interrupt generation (selective axis movement and wake up). The filter mode is defined by member filter in the settings of interrupt generator configuration, see function l3gd20h_set_int_event_config.

While the cutoff frequency of the low pass filter (LPF2) is fixed and depends only on the output data rate (ODR), the mode and the cutoff frequency of the high pass filter can be configured using function l3gd20h_config_hpf. Following HPF modes are available:

Driver symbol HPF mode
l3gd20h_hpf_normal Normal mode
l3gd20h_hpf_reference Reference mode
l3gd20h_hpf_autoreset Auto-reset on interrupt

For each output data rate (ODR), 10 different HPF cutoff frequencies can be used.

...
// select LPF/HPF
l3gd20h_select_output_filter (sensor, l3gd20h_hpf_only);

// configure HPF in normal mode with cutoff frequency 0
l3gd20h_config_hpf (sensor, l3gd20h_hpf_normal, 2);

// reset the reference by a dummy read
l3gd20h_get_hpf_ref (sensor);
...

Please 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.

FIFO

In order to limit the rate at which the host processor has to fetch the data, the L3GD20H embeds a first-in first-out buffer (FIFO). 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 angular rate samples. Please refer the datasheet or application note for more details.

Driver symbol FIFO mode
l3gd20h_bypass Bypass mode (FIFO is not used)
l3gd20h_fifo FIFO mode
l3gd20h_stream Stream mode
l3gd20h_stream_to_fifo Stream-to-FIFO mode
l3gd20h_bypass_to_stream Bypass-to-Stream mode
l3gd20h_dynamic_stream Dynamic Stream mode
l3gd20h_bypass_to_fifo Bypass to FIFO mode

The FIFO mode can be set using function l3gd20h_set_fifo_mode. This function takes two parameters, the FIFO mode and a threshold value which defines a watermark level. When the FIFO content exceeds this level, a watermark flag is set and an interrupt can be generated. They can be used to gather a minimum number of axes angular rate samples with the sensor before the data are fetched as a single read operation from the sensor.

...
// clear FIFO
l3gd20h_set_fifo_mode (sensor, l3gd20h_bypass, 0);

//  activate FIFO mode
l3gd20h_set_fifo_mode (sensor, l3gd20h_stream, 10);
...

Please note: To clear the FIFO at any time, set the FIFO mode to l3gd20h_bypass and back to the desired FIFO mode.

To read data from the FIFO, simply use either

  • the function l3gd20h_get_raw_data_fifo to all get raw output data stored in FIFO or
  • the function l3gd20h_get_float_data_fifo to get all data stored in FIFO and converted to real values in dps (degrees per second).

Both functions clear the FIFO and return the number of samples read from the FIFO.

void user_task_periodic (void *pvParameters)
{
    l3gd20h_float_data_fifo_t  data;

    while (1)
    {
        // execute task every 500 ms
        vTaskDelay (500/portTICK_PERIOD_MS);
        ...
        // test for new data
        if (!l3gd20h_new_data (sensor))
            continue;
    
        // fetch data from fifo
        uint8_t num = l3gd20h_get_float_data_fifo (sensor, data);
        
        for (int i = 0; i < num; i++)
        {
           // do something with data[i] ...
        }
}

Interrupts

The L3GD20H allows to activate interrupts on two dedicated interrupt signals

  • for data interrupts (data ready and FIFO status) on signal DRDY/INT2, and
  • for event interrupts (axis movement and wake up) on signal INT1.

Data interrupts (data ready and FIFO status) on signal DRDY/INT2

Interrupts on signal DRDY/INT2 can be generated by following sources:

Interrupt source Driver symbol
Output data become ready to read l3gd20h_int_data_ready
FIFO content exceeds the watermark level l3gd20h_int_fifo_threshold
FIFO is completely filled l3gd20h_int_fifo_overrun
FIFO becomes empty l3gd20h_int_fifo_empty

Each of these interrupt sources can be enabled or disabled separately with function l3gd20h_enable_int. By default all interrupt sources are disabled.

l3gd20h_enable_int (sensor, l3gd20h_int_data_ready, true);

Whenever the interrupt signal DRDY/INT2 is generated, function l3gd20h_get_int_data_source can be used to determine the source of the interrupt signal. This function returns a data structure of type l3gd20h_int_data_source_t that contains a boolean member for each source that can be tested for true.

void int2_handler ()
{
   l3gd20h_int_data_source_t source;

   // get the interrupt source of INT2
   l3gd20h_get_int_data_source (sensor, &source);

   // in case of data ready interrupt, get the results and do something with them
   if (source.data_ready)
   {
      l3gd20h_get_float_data (sensor, &data)

      // do something with data
      ...
   }
}

Event interrupts (Axes movement and wake up) on signal INT1

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 wakeup (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).

To configure event interrupts, the function l3gd20h_set_int_event_config has to be used with a parameter of structured data type l3gd20h_int_event_config_t which contains the configuration. For example, selective axis movement recognition (SA) for the z-axis could be configured as following:

l3gd20h_int_event_config_t int_cfg;

// thresholds
int_cfg.x_threshold = 100;
int_cfg.y_threshold = 100;
int_cfg.z_threshold = 1000;

// x axis below threshold
int_cfg.x_low_enabled  = false;
int_cfg.x_high_enabled = true;

// y axis below threshold
int_cfg.y_low_enabled  = true;
int_cfg.y_high_enabled = false;

// z axis below threshold
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 = l3gd20h_hpf_only;
int_cfg.latch = true;
int_cfg.duration = 0;
int_cfg.wait = false;

// set the configuration and enable the interrupt
l3gd20h_set_int_cfg (sensor, &int_cfg);
l3gd20h_enable_int (sensor, l3gd20h_int_event, true);

Furthermore, with this data structure it is also configured

  • whether the interrupt signal should latched until the interrupt source is read,
  • which filters are applied to data used for interrupt generation,
  • which time 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 in no longer given before interrupt signal is reset.

Function l3gd20h_enable_int is used to enable or disable the event interrupt generation.

As with data ready and FIFO interrupts, function l3gd20h_get_int1_source can be used to determine the source of the interrupt signal whenever it is generated. This function returns a data structure of type l3gd20h_int1_source_t that contain a boolean member for each source that can be tested for true.

void int1_handler ()
{
   l3gd20h_int_event_source_t source;

   // get the source of INT1 reset INT1 signal
   l3gd20h_get_int_event_source (sensor, &source);

   // if all conditions where true interrupt
   if (source.active)
   {
      l3gd20h_get_float_data (sensor, &data)

      // do something with data
      ...
   }
}

Please note: If the interrupt is configured to be latched, the interrupt signal is active until the interrupt source is read. Otherwise the interrupt signal is only active as long as the interrupt condition is satisfied.

Please note Activating all threshold comparisons and the OR combination is the most flexible way, functions like selective axis movement can then be realized combining the different interrupt sources. Following example realizes also the selective axis movement recognition (SA) for the z-axis.

l3gd20h_int_event_config_t int_cfg;

// thresholds
int_cfg.x_threshold = 100;
int_cfg.y_threshold = 100;
int_cfg.z_threshold = 100;

// x axis
int_cfg.x_low_enabled  = true;
int_cfg.x_high_enabled = true;

// y axis
int_cfg.y_low_enabled  = true;
int_cfg.y_high_enabled = true;

// z axis
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
l3gd20h_set_int_cfg (sensor, &int_cfg);
l3gd20h_enable_int (sensor, l3gd20h_int_event, true);
void int1_handler ()
{
   l3gd20h_int1_source_t source;

   // get the interrupt source of INT1
   l3gd20h_get_int1_source (sensor, &source);

   // if all conditions where true interrupt
   if (source.y_low && source.y_low && source.z_high)
   {
      // selective axis movement of z-axis
      ...
   }
}

Interrupt signal properties

By default, interrupt signals are high active. Using function l3gd20h_config_int_signals, the level of the interrupt signal and the type of the interrupt outputs can be changed.

Driver symbol Meaning
l3gd20h_high_active Interrupt signal is high active (default)
l3gd20h_low_active Interrupt signal is low active
Driver symbol Meaning
l3gd20h_push_pull Interrupt output is pushed/pulled
l3gd20h_open_drain Interrupt output is open-drain

Temperature sensor

The L3GD20H contains a temperature sensor. Function l3gd20h_get_temperature can be used to get the temperature. The temperature is given as 8-bit signed integer values in 2s complement.

Low level functions

The L3GD20H is a very complex and flexible sensor with a lot of features. It can be used for a big number of different use cases. Since it is quite 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.

bool l3gd20h_reg_read  (l3gd20h_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len);
bool l3gd20h_reg_write (l3gd20h_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len);

Please note 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.

Usage

First, the hardware configuration has to be established.

Hardware configurations

Following figure shows a possible hardware configuration for ESP8266 and ESP32 if I2C interface is used to connect the sensor.

  +-----------------+     +----------+
  | ESP8266 / ESP32 |     | L3GD20H  |
  |                 |     |          |
  |   GPIO 14 (SCL) >-----> SCL      |
  |   GPIO 13 (SDA) <-----> SDA      |
  |   GPIO 5        <------ INT1     |
  |   GPIO 4        <------ DRDY/INT2|
  +-----------------+     +----------+

If SPI interface is used, configuration for ESP8266 and ESP32 could look like following.

  +-----------------+     +----------+              +-----------------+     +----------+
  | ESP8266         |     | L3GD20H  |              | ESP32           |     | L3GD20H  |
  |                 |     |          |              |                 |     |          |
  |   GPIO 14 (SCK) ------> SCK      |              |   GPIO 16 (SCK) ------> SCK      |
  |   GPIO 13 (MOSI)------> SDI      |              |   GPIO 17 (MOSI)------> SDI      |
  |   GPIO 12 (MISO)<------ SDO      |              |   GPIO 18 (MISO)<------ SDO      |
  |   GPIO 2  (CS)  ------> CS       |              |   GPIO 19 (CS)  ------> CS       |
  |   GPIO 5        <------ INT1     |              |   GPIO 5        <------ INT1     |
  |   GPIO 4        <------ DRDY/INT2|              |   GPIO 4        <------ DRDY/INT2|
  +-----------------+     +----------+              +-----------------+     +----------+

Communication interface settings

Dependent on the hardware configuration, the communication interface and interrupt settings have to be defined. In case ESP32 is used, the configuration could look like

#ifdef ESP_PLATFORM  // ESP32 (ESP-IDF)

// user task stack depth for ESP32
#define TASK_STACK_DEPTH 2048

// SPI interface definitions for ESP32
#define SPI_BUS       HSPI_HOST
#define SPI_SCK_GPIO  16
#define SPI_MOSI_GPIO 17
#define SPI_MISO_GPIO 18
#define SPI_CS_GPIO   19

#else  // ESP8266 (esp-open-rtos)

// user task stack depth for ESP8266
#define TASK_STACK_DEPTH 256

// SPI interface definitions for ESP8266
#define SPI_BUS       1
#define SPI_SCK_GPIO  14
#define SPI_MOSI_GPIO 13
#define SPI_MISO_GPIO 12
#define SPI_CS_GPIO   2   // GPIO 15, the default CS of SPI bus 1, can't be used

#endif  // ESP_PLATFORM

// I2C interface defintions for ESP32 and ESP8266
#define I2C_BUS       0
#define I2C_SCL_PIN   14
#define I2C_SDA_PIN   13
#define I2C_FREQ      I2C_FREQ_100K

// interrupt GPIOs defintions for ESP8266 and ESP32
#define INT1_PIN      5
#define INT2_PIN      4

Main program

Initialization

If I2C interfaces are used, they have to be initialized first.

i2c_init (I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ);

SPI interface has only to be initialized explicitly on ESP32 platform to declare the GPIOs that are used for SPI interface.

#ifdef ESP_PLATFORM
spi_bus_init (SPI_BUS, SPI_SCK_GPIO, SPI_MISO_GPIO, SPI_MOSI_GPIO);
#endif

Once the interfaces are initialized, function l3gd20h_init_sensor has to be called for each L3GD20H sensor in order to initialize the sensor and to check its availability as well as its error state. This function returns a pointer to a sensor device data structure or NULL in case of error.

The parameter bus specifies the ID of the I2C or SPI bus to which the sensor is connected.

static l3gd20h_sensor_t* sensor;

For sensors connected to an I2C interface, a valid I2C slave address has to be defined as parameter addr. In that case parameter cs is ignored.

sensor = l3gd20h_init_sensor (I2C_BUS, L3GD20H_I2C_ADDRESS_2, 0);

If parameter addr is 0, the sensor is connected to a SPI bus. In that case, parameter cs defines the GPIO used as CS signal.

sensor = l3gd20h_init_sensor (SPI_BUS, 0, SPI_CS_GPIO);

The remaining of the program is independent on the communication interface.

Configuring the sensor

Optionally, you could wish to set some measurement parameters. For details see the sections above, the header file of the driver l3gd20h.h, and of course the data sheet of the sensor.

Starting measurements

As last step, the sensor mode has be set to start periodic measurement. The sensor mode can be changed anytime later.

...
// start periodic measurement with output data rate of 12.5 Hz
l3gd20h_set_mode (sensor, l3gd20h_normal_odr_12_5, 3, true, true, true);
...

Periodic user task

Finally, a user task that uses the sensor has to be created.

Please note: To avoid concurrency situations when driver functions are used to access the sensor, for example to read data, the user task must not be created until the sensor configuration is completed.

The user task can use different approaches to fetch new data. Either new data are fetched periodically or interrupt signals are used when new data are available or a configured event happens.

If new data are fetched periodically the implementation of the user task is quite simple and could look like following.

void user_task_periodic(void *pvParameters)
{
    l3gd20h_float_data_t data;

    while (1)
    {
        // execute task every 10 ms
        vTaskDelay (10/portTICK_PERIOD_MS);
        ...
        // test for new data
        if (!l3gd20h_new_data (sensor))
            continue;
    
        // fetch new data
        if (l3gd20h_get_float_data (sensor, &data))
        {
            // do something with data
            ...
        }
    }
}
...
// create a user task that fetches data from sensor periodically
xTaskCreate(user_task_periodic, "user_task_periodic", TASK_STACK_DEPTH, NULL, 2, NULL);

The user task simply tests periodically with a higher rate than the output data rate (ODR) of the sensor whether new data are available. If new data are available, it fetches the data.

Interrupt user task

A different approach is to use one of the interrupts INT1 or INT2.

  • DRDY/INT2 is triggered when new data become available or the FIFO queue status changes.
  • INT1 is triggered when configured axis movements are recognized.

In both cases, the user has to implement an interrupt handler that either fetches the data directly or triggers a task, that is waiting to fetch the data.

static QueueHandle_t gpio_evt_queue = NULL;

// Interrupt handler which resumes sends an event to the waiting user_task_interrupt

void IRAM int_signal_handler (uint8_t gpio)
{
    // send an event with GPIO to the interrupt user task
    xQueueSendFromISR(gpio_evt_queue, &gpio, NULL);
}

// User task that fetches the sensor values

void user_task_interrupt (void *pvParameters)
{
    uint32_t gpio_num;

    while (1)
    {
        if (xQueueReceive(gpio_evt_queue, &gpio_num, portMAX_DELAY))
        {
            // test for new data
            if (!l3gd20h_new_data (sensor))
                continue;
    
            // fetch new data
            if (l3gd20h_get_float_data (sensor, &data))
            {
                // do something with data
                ...
            }
        }
    }
}
...

// create a task that is triggered only in case of interrupts to fetch the data

xTaskCreate(user_task_interrupt, "user_task_interrupt", TASK_STACK_DEPTH, NULL, 2, NULL);
...

In this example, there is

  • a task that is fetching data when it receives an event, and
  • an interrupt handler that generates the event on interrupt.

Finally, interrupt handlers have to be activated for the GPIOs which are connected to the interrupt signals.

// configure interrupt pins for *INT1* and *INT2* signals and set the interrupt handler
gpio_set_interrupt(INT1_PIN, GPIO_INTTYPE_EDGE_POS, int_signal_handler);
gpio_set_interrupt(INT2_PIN, GPIO_INTTYPE_EDGE_POS, int_signal_handler);

Furthermore, the interrupts have to be enabled and configured in the L3GD20H sensor, see section Interrupts above.

Full Example

/* -- use following constants to define the example mode ----------- */

// #define SPI_USED    // if defined SPI is used, otherwise I2C
// #define FIFO_MODE   // multiple sample read mode
// #define INT_DATA    // data interrupts used (data ready and FIFO status)
// #define INT_EVENT   // event interrupts used (axis movement and wake up)

#if defined(INT_EVENT) || defined(INT_DATA)
#define INT_USED
#endif

/* -- includes -------------------------------------------------- */

#include "l3gd20h.h"

/* -- platform dependent definitions ---------------------------- */

#ifdef ESP_PLATFORM  // ESP32 (ESP-IDF)

// user task stack depth
#define TASK_STACK_DEPTH 2048

// define SPI interface for L3GD20H sensors
#define SPI_BUS       HSPI_HOST
#define SPI_SCK_GPIO  16
#define SPI_MOSI_GPIO 17
#define SPI_MISO_GPIO 18
#define SPI_CS_GPIO   19

#else  // ESP8266 (esp-open-rtos)

// user task stack depth
#define TASK_STACK_DEPTH 256

// define SPI interface for L3GD20H sensors
#define SPI_BUS       1
#define SPI_SCK_GPIO  14
#define SPI_MOSI_GPIO 13
#define SPI_MISO_GPIO 12
#define SPI_CS_GPIO   2   // GPIO 15, the default CS of SPI bus 1, can't be used

#endif  // ESP_PLATFORM

// define I2C interfaces for L3GD20H sensors
#define I2C_BUS       0
#define I2C_SCL_PIN   14
#define I2C_SDA_PIN   13
#define I2C_FREQ      I2C_FREQ_100K

// define GPIOs for interrupt
#define INT1_PIN      5
#define INT2_PIN      4

/* -- user tasks ---------------------------------------------- */

static l3gd20h_sensor_t* sensor;

/**
 * Common function used to get sensor data.
 */
void read_data (void)
{
    #ifdef FIFO_MODE
    
    l3gd20h_float_data_fifo_t  data;

    if (l3gd20h_new_data (sensor))
    {
        uint8_t num = l3gd20h_get_float_data_fifo (sensor, data);
        printf("%.3f L3GD20H num=%d\n", (double)sdk_system_get_time()*1e-3, num);
        for (int i = 0; i < num; i++)
            // max. full scale is +-2000 dps and best sensitivity is 1 mdps, i.e. 7 digits
            printf("%.3f L3GD20H (xyz)[dps]: %+9.3f %+9.3f  %+9.3f\n",
                   (double)sdk_system_get_time()*1e-3, data[i].x, data[i].y, data[i].z);
    }
    
    #else // !FIFO_MODE
    
    l3gd20h_float_data_t  data;

    if (l3gd20h_new_data (sensor) &&
        l3gd20h_get_float_data (sensor, &data))
        // max. full scale is +-2000 dps and best sensitivity is 1 mdps, i.e. 7 digits
        printf("%.3f L3GD20H (xyz)[dps]: %+9.3f %+9.3f  %+9.3f\n",
               (double)sdk_system_get_time()*1e-3, data.x, data.y, data.z);
               
    #endif // FIFO_MODE
}


#ifdef INT_USED
/**
 * In this case, axes movement wake up interrupt *INT1* and/or data ready
 * interrupt *INT2* are used. While data ready interrupt *INT2* is generated
 * every time new data are available or the FIFO status changes, the axes
 * movement wake up interrupt *INT1* is triggered when output data across
 * defined thresholds.
 *
 * When interrupts are used, the user has to define interrupt handlers that
 * either fetches the data directly or triggers a task which is waiting to
 * fetch the data. In this example, the interrupt handler sends an event to
 * a waiting task to trigger the data gathering.
 */

static QueueHandle_t gpio_evt_queue = NULL;

// User task that fetches the sensor values.

void user_task_interrupt (void *pvParameters)
{
    uint8_t gpio_num;

    while (1)
    {
        if (xQueueReceive(gpio_evt_queue, &gpio_num, portMAX_DELAY))
        {
            if (gpio_num == INT1_PIN)
            {
                l3gd20h_int_event_source_t source;

                // get the source of INT1 reset INT1 signal
                l3gd20h_get_int_event_source (sensor, &source);

                // in case of data ready interrupt, get the results and do something with them
                if (source.active)
                    read_data ();
            }
            else if (gpio_num == INT2_PIN)
            {
                l3gd20h_int_data_source_t source;

                // get the source of INT2
                l3gd20h_get_int_data_source (sensor, &source);

                // if data ready interrupt, get the results and do something with them
                read_data();
            }
        }
    }
}

// Interrupt handler which resumes sends an event to the waiting user_task_interrupt

void IRAM int_signal_handler (uint8_t gpio)
{
    // send an event with GPIO to the interrupt user task
    xQueueSendFromISR(gpio_evt_queue, &gpio, NULL);
}

#else // !INT_USED

/*
 * In this case, no interrupts are used and the user task fetches the sensor
 * values periodically every seconds.
 */

void user_task_periodic(void *pvParameters)
{
    vTaskDelay (100/portTICK_PERIOD_MS);
    
    while (1)
    {
        // read sensor data
        read_data ();

        // passive waiting until 1 second is over
        vTaskDelay (100/portTICK_PERIOD_MS);
    }
}

#endif // INT_USED

/* -- main program ---------------------------------------------- */

void user_init(void)
{
    // Set UART Parameter.
    uart_set_baud(0, 115200);
    // Give the UART some time to settle
    vTaskDelay(1);

    /** -- MANDATORY PART -- */

    #ifdef SPI_USED

    // init the sensor connnected to SPI
    spi_bus_init (SPI_BUS, SPI_SCK_GPIO, SPI_MISO_GPIO, SPI_MOSI_GPIO);

    // init the sensor connected to SPI_BUS with SPI_CS_GPIO as chip select.
    sensor = l3gd20h_init_sensor (SPI_BUS, 0, SPI_CS_GPIO);

    #else  // I2C

    // init all I2C bus interfaces at which L3GD20H sensors are connected
    i2c_init (I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ);
    
    // init the sensor with slave address L3GD20H_I2C_ADDRESS_2 connected to I2C_BUS.
    sensor = l3gd20h_init_sensor (I2C_BUS, L3GD20H_I2C_ADDRESS_2, 0);

    #endif  // SPI_USED
    
    if (sensor)
    {
        #ifdef INT_USED

        /** --- INTERRUPT CONFIGURATION PART ---- */
        
        // Interrupt configuration has to be done before the sensor is set
        // into measurement mode to avoid losing interrupts

        // create an event queue to send interrupt events from interrupt
        // handler to the interrupt task
        gpio_evt_queue = xQueueCreate(10, sizeof(uint8_t));

        // configure interupt pins for *INT1* and *INT2* signals and set the
        // interrupt handler
        gpio_enable(INT1_PIN, GPIO_INPUT);
        gpio_enable(INT2_PIN, GPIO_INPUT);
        gpio_set_interrupt(INT1_PIN, GPIO_INTTYPE_EDGE_POS, int_signal_handler);
        gpio_set_interrupt(INT2_PIN, GPIO_INTTYPE_EDGE_POS, int_signal_handler);

        #endif  // INT_USED
        
        /** -- SENSOR CONFIGURATION PART --- */

        // set type and polarity of INT signals if necessary
        // l3gd20h_config_int_signals (dev, l3gd20h_push_pull, l3gd20h_high_active);

        #ifdef INT_EVENT
        // enable event interrupts (axis movement and wake up)
        l3gd20h_int_event_config_t int_cfg;
    
        l3gd20h_get_int_event_config (sensor, &int_cfg);
    
        int_cfg.x_high_enabled = true;
        int_cfg.y_high_enabled = true;
        int_cfg.z_high_enabled = true;
        int_cfg.x_low_enabled  = false;
        int_cfg.y_low_enabled  = false;
        int_cfg.z_low_enabled  = false;
        int_cfg.x_threshold = 1000;
        int_cfg.y_threshold = 1000;
        int_cfg.z_threshold = 1000;
    
        int_cfg.filter = l3gd20h_hpf_only;
        int_cfg.and_or = false;
        int_cfg.duration = 0;
        int_cfg.latch = true;
    
        l3gd20h_set_int_event_config (sensor, &int_cfg);
        l3gd20h_enable_int (sensor, l3gd20h_int_event, true);
        
        #endif // INT_EVENT
        
        #ifdef INT_DATA
        // enable data ready (DRDY) and FIFO interrupt signal *INT2*
        // NOTE: DRDY and FIFO interrupts must not be enabled at the same time
        #ifdef FIFO_MODE
        l3gd20h_enable_int (sensor, l3gd20h_int_fifo_overrun, true);
        l3gd20h_enable_int (sensor, l3gd20h_int_fifo_threshold, true);
        #else
        l3gd20h_enable_int (sensor, l3gd20h_int_data_ready, true);
        #endif
        #endif // INT_DATA

        #ifdef FIFO_MODE
        // clear FIFO and activate FIFO mode if needed
        l3gd20h_set_fifo_mode (sensor, l3gd20h_bypass, 0);
        l3gd20h_set_fifo_mode (sensor, l3gd20h_stream, 10);
        #endif
        
        // select LPF/HPF, configure HPF and reset the reference by dummy read
        l3gd20h_select_output_filter (sensor, l3gd20h_hpf_only);
        l3gd20h_config_hpf (sensor, l3gd20h_hpf_normal, 0);
        l3gd20h_get_hpf_ref (sensor);

        // LAST STEP: Finally set scale and sensor mode to start measurements
        l3gd20h_set_scale(sensor, l3gd20h_scale_245_dps);
        l3gd20h_set_mode (sensor, l3gd20h_normal_odr_12_5, 3, true, true, true);

        /** -- TASK CREATION PART --- */

        // must be done last to avoid concurrency situations with the sensor
        // configuration part

        #ifdef INT_USED

        // create a task that is triggered only in case of interrupts to fetch the data
        xTaskCreate(user_task_interrupt, "user_task_interrupt", TASK_STACK_DEPTH, NULL, 2, NULL);
        
        #else // INT_USED

        // create a user task that fetches data from sensor periodically
        xTaskCreate(user_task_periodic, "user_task_periodic", TASK_STACK_DEPTH, NULL, 2, NULL);

        #endif
    }
    else
        printf("Could not initialize L3GD20H sensor\n");
}