diff --git a/examples/lsm303d/Makefile b/examples/lsm303d/Makefile new file mode 100644 index 0000000..2073588 --- /dev/null +++ b/examples/lsm303d/Makefile @@ -0,0 +1,3 @@ +PROGRAM=LSM303D +EXTRA_COMPONENTS = extras/i2c extras/lsm303d +include ../../common.mk diff --git a/examples/lsm303d/lsm303d_example.c b/examples/lsm303d/lsm303d_example.c new file mode 100644 index 0000000..957c027 --- /dev/null +++ b/examples/lsm303d/lsm303d_example.c @@ -0,0 +1,432 @@ +/** + * Simple example with one sensor connected to I2C or SPI. It demonstrates the + * different approaches to fetch the data. Either one of the interrupt signals + * is used or new data are fetched periodically. + * + * Harware configuration: + * + * I2C + * + * +-----------------+ +----------+ + * | ESP8266 / ESP32 | | LSM303D | + * | | | | + * | GPIO 14 (SCL) ----> SCL | + * | GPIO 13 (SDA) <---> SDA | + * | GPIO 5 <---- INT1 | + * | GPIO 4 <---- INT2 | + * +-----------------+ +----------+ + * + * SPI + * + * +-----------------+ +----------+ +-----------------+ +----------+ + * | ESP8266 | | LSM303D | | ESP32 | | LSM303D | + * | | | | | | | | + * | 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 <---- INT2 | | GPIO 4 <---- INT2 | + * +-----------------+ +---------+ +-----------------+ +----------+ + */ + +/* -- use following constants to define the example mode ----------- */ + +// #define SPI_USED // SPI interface is used, otherwise I2C +// #define FIFO_MODE // multiple sample read mode +// #define TEMP_USED // temperature sensor used +// #define INT_DATA // data interrupts used (data ready and FIFO status) +// #define INT_EVENT // inertial event interrupts used (axis movement or 6D/4D orientation) +// #define INT_CLICK // click detection interrupts used +// #define INT_THRESH // magnetic value exceeds threshold interrupt used + +#if defined(INT_DATA) || defined(INT_EVENT) || defined(INT_CLICK) || defined(INT_THRESH) +#define INT_USED +#endif + +/* -- includes ----------------------------------------------------- */ + +#include "lsm303d.h" + +/** -- platform dependent definitions ------------------------------ */ + +#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 + +/* -- user tasks --------------------------------------------------- */ + +static lsm303d_sensor_t* sensor; + +/** + * Common function used to get sensor data. + */ +void read_data () +{ + #ifdef FIFO_MODE + + lsm303d_float_a_data_fifo_t fifo; + + // test for new accelerator data data + if (lsm303d_new_a_data (sensor)) + { + // fetch the accelerator data stored in FIFO + uint8_t num = lsm303d_get_float_a_data_fifo (sensor, fifo); + + printf("%.3f LSM303D num=%d\n", (double)sdk_system_get_time()*1e-3, num); + + for (int i=0; i < num; i++) + // max. full scale is +-16 g and best resolution is 1 mg, i.e. 5 digits + printf("%.3f LSM303D (xyz)[g] ax=%+7.3f ay=%+7.3f az=%+7.3f\n", + (double)sdk_system_get_time()*1e-3, + fifo[i].ax, fifo[i].ay, fifo[i].az); + } + + #else + + lsm303d_float_a_data_t a_data; + + // test for new accelerator data and fetch them + if (lsm303d_new_a_data (sensor) && + lsm303d_get_float_a_data (sensor, &a_data)) + // max. full scale is +-16 g and best resolution is 1 mg, i.e. 5 digits + printf("%.3f LSM303D (xyz)[g] ax=%+7.3f ay=%+7.3f az=%+7.3f\n", + (double)sdk_system_get_time()*1e-3, + a_data.ax, a_data.ay, a_data.az); + + #endif // FIFO_MODE + + lsm303d_float_m_data_t m_data; + + // test for new magnetometer data and fetch them + if (lsm303d_new_m_data (sensor) && + lsm303d_get_float_m_data (sensor, &m_data)) + // max. full scale is +-12 Gs and best resolution is 1 mGs, i.e. 5 digits + printf("%.3f LSM303D (xyz)[Gs] mx=%+7.3f my=%+7.3f mz=%+7.3f\n", + (double)sdk_system_get_time()*1e-3, + m_data.mx, m_data.my, m_data.mz); + + #ifdef TEMP_USED + float temp = lsm303d_get_temperature (sensor); + + printf("%.3f LSM303D (tmp)[°C] %+7.3f\n", (double)sdk_system_get_time()*1e-3, temp); + #endif +} + + +#ifdef INT_USED +/** + * In this case, any of the possible interrupts on interrupt signal *INT1* is + * used to fetch the data. + * + * 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; + + while (1) + { + if (xQueueReceive(gpio_evt_queue, &gpio, portMAX_DELAY)) + { + lsm303d_int_data_source_t data_src = {}; + lsm303d_int_event_source_t event_src = {}; + lsm303d_int_click_source_t click_src = {}; + lsm303d_int_m_thresh_source_t thresh_src = {}; + + // get the source of the interrupt that reset *INTx* signals + #ifdef INT_DATA + lsm303d_get_int_data_source (sensor, &data_src); + #endif + #ifdef INT_THRESH + lsm303d_get_int_m_thresh_source(sensor, &thresh_src); + #endif + #ifdef INT_EVENT + lsm303d_get_int_event_source (sensor, &event_src, lsm303d_int_event1_gen); + #endif + #ifdef INT_CLICK + lsm303d_get_int_click_source (sensor, &click_src); + #endif + + // in case of DRDY interrupt + if (data_src.a_data_ready || data_src.m_data_ready) + read_data (); + + // in case of FIFO interrupts read the whole FIFO + else if (data_src.fifo_thresh || data_src.fifo_overrun) + read_data (); + + // in case of magnetic threshold interrupt + else if (thresh_src.active) + { + printf("%.3f LSM303D ", (double)sdk_system_get_time()*1e-3); + if (thresh_src.x_pos) printf("x exceeds threshold on positive side\n"); + if (thresh_src.y_pos) printf("y exceeds threshold on positive side\n"); + if (thresh_src.z_pos) printf("z exceeds threshold on positive side\n"); + if (thresh_src.x_neg) printf("x exceeds threshold on negative side\n"); + if (thresh_src.y_neg) printf("y exceeds threshold on negative side\n"); + if (thresh_src.z_neg) printf("z exceeds threshold on negative side\n"); + } + + // in case of event interrupt + else if (event_src.active) + { + printf("%.3f LSM303D ", (double)sdk_system_get_time()*1e-3); + if (event_src.x_low) printf("x is lower than threshold\n"); + if (event_src.y_low) printf("y is lower than threshold\n"); + if (event_src.z_low) printf("z is lower than threshold\n"); + if (event_src.x_high) printf("x is higher than threshold\n"); + if (event_src.y_high) printf("y is higher than threshold\n"); + if (event_src.z_high) printf("z is higher than threshold\n"); + } + + // in case of click detection interrupt + else if (click_src.active) + printf("%.3f LSM303D %s\n", (double)sdk_system_get_time()*1e-3, + click_src.s_click ? "single click" : "double click"); + } + } +} + +// Interrupt handler which resumes user_task_interrupt on 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 example, user task fetches the sensor values 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(200/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 SPI interface at which LMS303D sensors are connected + 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 = lsm303d_init_sensor (SPI_BUS, 0, SPI_CS_GPIO); + + #else + + // init all I2C busses at which LSM303D sensors are connected + i2c_init (I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ); + + // init the sensor with slave address LSM303D_I2C_ADDRESS_2 connected to I2C_BUS. + sensor = lsm303d_init_sensor (I2C_BUS, LSM303D_I2C_ADDRESS_2, 0); + + #endif + + 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 the type of INTx signals if necessary + // lsm303d_config_int_signals (sensor, lsm303d_push_pull); + + #ifdef INT_DATA + // enable data interrupts on *INT2* (data ready or FIFO overrun and FIFO threshold) + // data ready and FIFO status interrupts must not be enabled at the same time + #ifdef FIFO_MODE + lsm303d_enable_int (sensor, lsm303d_int_fifo_overrun, lsm303d_int2_signal, true); + lsm303d_enable_int (sensor, lsm303d_int_fifo_thresh , lsm303d_int2_signal, true); + #else + lsm303d_enable_int (sensor, lsm303d_int_a_data_ready, lsm303d_int2_signal, true); + lsm303d_enable_int (sensor, lsm303d_int_m_data_ready, lsm303d_int2_signal, true); + #endif // FIFO_MODE + #endif // INT_DATA + + #ifdef INT_THRESH + // enable magnetic threshold interrupts on signal *INT1* + lsm303d_int_m_thresh_config_t m_thresh_config; + + m_thresh_config.threshold = 2000; + m_thresh_config.x_enabled = true; + m_thresh_config.y_enabled = true; + m_thresh_config.z_enabled = true; + m_thresh_config.latch = true; + m_thresh_config.signal_level = lsm303d_high_active; + + lsm303d_set_int_m_thresh_config (sensor, &m_thresh_config); + lsm303d_enable_int (sensor, lsm303d_int_m_thresh, lsm303d_int1_signal, true); + #endif // INT_THRESH + + #ifdef INT_EVENT + // enable inertial event interrupts on *INT1* + lsm303d_int_event_config_t event_config; + + event_config.mode = lsm303d_or; // axes movement wake-up + // event_config.mode = lsm303d_and; // free fall + // event_config.mode = lsm303d_6d_movement; + // event_config.mode = lsm303d_6d_position; + // event_config.mode = lsm303d_4d_movement; + // event_config.mode = lsm303d_4d_position; + event_config.threshold = 50; + event_config.x_low_enabled = false; + event_config.x_high_enabled = true; + event_config.y_low_enabled = false; + event_config.y_high_enabled = true; + event_config.z_low_enabled = false; + event_config.z_high_enabled = true; + event_config.duration = 0; + event_config.latch = true; + + lsm303d_set_int_event_config (sensor, &event_config, lsm303d_int_event1_gen); + lsm303d_enable_int (sensor, lsm303d_int_event1, lsm303d_int1_signal, true); + #endif // INT_EVENT + + #ifdef INT_CLICK + // enable single click interrupt for z-axis on signal *INT1* + lsm303d_int_click_config_t click_config; + + click_config.threshold = 10; + click_config.x_single = false; + click_config.x_double = false; + click_config.y_single = false; + click_config.y_double = false; + click_config.z_single = true; + click_config.z_double = false; + click_config.latch = true; + click_config.time_limit = 1; + click_config.time_latency = 1; + click_config.time_window = 3; + + lsm303d_set_int_click_config (sensor, &click_config); + lsm303d_enable_int (sensor, lsm303d_int_click, lsm303d_int1_signal, true); + #endif // INT_CLICK + + #ifdef FIFO_MODE + // clear the FIFO + lsm303d_set_fifo_mode (sensor, lsm303d_bypass, 0); + + // activate the FIFO with a threshold of 10 samples (max. 31); if + // interrupt *lsm303d_fifo_thresh* is enabled, an interrupt is + // generated when the FIFO content exceeds this threshold, i.e., + // when 11 samples are stored in FIFO + lsm303d_set_fifo_mode (sensor, lsm303d_stream, 10); + #endif + + // configure HPF and implicitly reset the reference by a dummy read + lsm303d_config_a_hpf (sensor, lsm303d_hpf_normal, true, true, true, true); + + #ifdef TEMP_USED + // enable the temperature sensor + lsm303d_enable_temperature (sensor, true); + #endif + + // LAST STEP: Finally set scale and mode to start measurements + lsm303d_set_a_scale(sensor, lsm303d_a_scale_2_g); + lsm303d_set_m_scale(sensor, lsm303d_m_scale_4_Gs); + lsm303d_set_a_mode (sensor, lsm303d_a_odr_12_5, lsm303d_a_aaf_bw_773, true, true, true); + lsm303d_set_m_mode (sensor, lsm303d_m_odr_12_5, lsm303d_m_low_res, lsm303d_m_continuous); + + /** -- 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 LSM303D sensor\n"); +} + diff --git a/extras/lsm303d/README.md b/extras/lsm303d/README.md new file mode 100644 index 0000000..c5ea142 --- /dev/null +++ b/extras/lsm303d/README.md @@ -0,0 +1,1216 @@ +# Driver for the LSM303D e-Compass 3D accelerometer and 3D magnetometer module + +The driver is for the usage with the ESP8266 and [esp-open-rtos](https://github.com/SuperHouse/esp-open-rtos). If you can't find it in folder [extras/lsm303d](https://github.com/SuperHouse/esp-open-rtos/tree/master/extras) of original repository, it is not yet merged. Please take a look to branch [lsm303d](https://github.com/gschorcht/esp-open-rtos/tree/lsm303d) of my fork in that case. + +It is also working with ESP32 and [ESP-IDF](https://github.com/espressif/esp-idf.git) using a wrapper component for ESP8266 functions, see folder ```components/esp8266_wrapper```, as well as Linux based systems using a wrapper library. + +## About the sensor + +LSM303D is a high performance **3D digital linear acceleration and magnetic sensor** connected to **I2C** or **SPI** with a full scale of up to **±16 g** and **±12 Gauss**. + +**Main features** of the sensor are: + +- 3 magnetic field channels +- 3 acceleration channels +- ±2/±4/±8/±12 Gauss dynamically selectable magnetic full-scale +- ±2/±4/±6/±8/±16 g dynamically selectable linear acceleration full-scale +- different measuring rates for accelerator and magnetometer +- 16-bit data output +- embedded temperature sensor +- embedded 32 levels of 16 bit data output FIFO +- integrated high-pass filters +- programmable interrupt generators +- 6D/4D orientation detection +- free-fall detection +- motion detection +- single click and double click detection +- magnetic field detection + +## Sensor operation + +### Sensor modes + +LSM303D provides different operating modes. + +- **Power-down mode** is configured automatically after power up boot sequence. In this mode, almost all internal blocks of the device are switched off. Register content is preserved, but there are no measurements performed. + +- **Normal mode**: is the operation mode in which measurements are performed at a defined output data rate (**ODR**). + +The operation mode can be defined separately for the accelerator and the magnetometer. + +### Output Data Rates + +In normal mode, the accelerator and/or the magnetometer perform measurements with a defined output data rates (ODR). This output data rate can be defined separately for the accelerometer and the magnetometer. + +**Acceleration sensor** supports the following output data rates + +Mode | Output data rate (ODR) | Driver symbol +:------------- |---------:|:--------------- +Power-down | - | ```lsm303d_a_power_down``` +Normal mode | 3.125 Hz | ```lsm303d_a_odr_3_125``` +Normal mode | 6.25 Hz | ```lsm303d_a_odr_6_25``` +Normal mode | 12.5 Hz | ```lsm303d_a_odr_12_5``` +Normal mode | 25 Hz | ```lsm303d_a_odr_25``` +Normal mode | 50 Hz | ```lsm303d_a_odr_50``` +Normal mode | 100 Hz | ```lsm303d_a_odr_100``` +Normal mode | 200 Hz | ```lsm303d_a_odr_200``` +Normal mode | 400 Hz | ```lsm303d_a_odr_400``` +Normal mode | 800 Hz | ```lsm303d_a_odr_800``` +Normal mode | 1600 Hz | ```lsm303d_a_odr_1600``` + +**Magnetic sensor** supports the following output data rates + +Mode | Output data rate (ODR) | Driver symbol +:------------- |---------:|:--------------- +Power-down | - | ```lsm303d_m_do_not_use``` +Normal mode | 3.125 Hz | ```lsm303d_m_odr_3_125``` +Normal mode | 6.25 Hz | ```lsm303d_m_odr_6_25``` +Normal mode | 12.5 Hz | ```lsm303d_m_odr_12_5``` +Normal mode | 25 Hz | ```lsm303d_m_odr_25``` +Normal mode | 50 Hz | ```lsm303d_m_odr_50``` +Normal mode | 100 Hz | ```lsm303d_m_odr_100``` + +**Please note**: An output data rate of 100 Hz is only available for the magnetometer if the output data rate of the accelerometer is greater than 50 Hz or the accelerometer is in power-down mode. + +### Setting operation mode and output data rate + +The **easiest way to use the sensor** is to initialize it with the ```lsm303d_init_sensor``` function and then switch it to any measurement mode to start measurement with a given output data rate (ODR). The acceleration sensor mode is set with the ```lsm303d_set_a_mode``` function and the magnetometer mode is set with lsm303d_set_m_mode function. +``` +... +static lsm303d_sensor_t* sensor = 0; +... +if ((sensor = lsm303d_init_sensor (I2C_BUS, LSM303D_I2C_ADDRESS_2, 0))) +{ + ... + lsm303d_set_a_mode (sensor, lsm303d_a_odr_12_5, lsm303d_a_aaf_bw_773, true, true, true); + lsm303d_set_m_mode (sensor, lsm303d_m_odr_12_5, lsm303d_m_low_res, lsm303d_m_continuous); + ... +} +... + +``` +In this example, the LSM303D sensor is connected to I2C. After its initialization, both the accelerometer and the magnetometer are switched to the normal measuring mode with an output data rate of 12.5 Hz each. + +```lsm303d_set_a_mode``` function requires the bandwidth of the anti-alias filter (AAF) and the activated axes x, y and z as additional parameters. Possible bandwidths of the accelerator anti-alias filter are: + +AAF Bandwidth | Driver symbol +-------------:|:------------- +773 Hz | ```lsm303d_a_aaf_bw_773``` +362 Hz | ```lsm303d_a_aaf_bw_362``` +194 Hz | ```lsm303d_a_aaf_bw_194``` +50 Hz | ```lsm303d_a_aaf_bw_50``` + +```lsm303d_set_m_mode``` function requires the resolution and the measurement mode as additional parameters. It is possible to define + +- low resolution (```lsm303d_m_low_res```) and +- high resolution (```lsm303d_m_low_res```). + +Unfortunately, it is not documented what it exactly means. As measurement mode, the user can select + +- continuous conversion mode (```lsm303d_m_continuous```), +- single conversion mode (```lsm303d_m_single```), or +- power-down mode (```lsm303d_m_power_down```). + +During single-conversion mode, the device performs a single measurement and switches then back to the power-down mode. During continuous-conversion mode, the device continuously performs measurements with defined output data rate. In power-down mode the magnetometer is switched off. + +**Please note:** +- ```lsm303d_init_sensor``` function resets the sensor completely. That is, all sensor registers are reset to their default values and the sensor is switched to the power-down mode. The function returns a pointer to an sensor device data structure on success which is allocated from memory. +- All sensor configurations should be done before calling ```lsm303d_a_set_mode``` or ```lsm303d_a_set_mode``` function. In particular, the interrupt configuration should be performed before to avoid loosing the first interrupt and locking the system. + +## Measurement results + +### Output data format + +The sensor determines periodically the accelerations and/or magnetic values for all axes that are enabled for measurement and produces output data with the selected output data rate (ODR). + +Raw **output data** (**raw data**) are given as 16-bit signed integer values in 2’s complement representation and are always left-aligned. The range and the resolution of raw data depend on the sensitivity of the sensor which is selected by the **full scale** parameter. LSM303D allows to select the following full scales: + +Accelerometer full scale | Resolution | Driver symbol +---------------------:|-----------:|:----------- + ±2 g | 0.061 mg | ```lsm303d_a_scale_2_g``` + ±4 g | 0.122 mg | ```lsm303d_a_scale_4_g``` + ±6 g | 0.183 mg | ```lsm303d_a_scale_6_g``` + ±8 g | 0.244 mg | ```lsm303d_a_scale_8_g``` +±16 g | 0.732 mg | ```lsm303d_a_scale_16_g``` + +Magnetormeter full scale | Resolution | Driver symbol +---------------------:|:-----------|:------ + ±2 Gauss | 0.080 mGauss | ```lsm303d_m_scale_2_Gs``` + ±4 Gauss | 0.160 mGauss | ```lsm303d_m_scale_4_Gs``` + ±8 Gauss | 0.320 mGauss | ```lsm303d_m_scale_8_Gs``` +±12 Gauss | 0.479 mGauss | ```lsm303d_m_scale_12_Gs``` + +By default, a full scale of ±2 g is used for the acceleration and ±2 Gauss for the magnetic measurement. ```lsm303d_set_a_scale``` and ```lsm303d_set_m_scale``` functions can be used to change these values. + +``` +lsm303d_set_a_scale(sensor, lsm303d_a_scale_4_g); +lsm303d_set_m_scale(sensor, lsm303d_m_scale_8_Gs); +``` + +### Fetching output data + +To get the information whether new data are available, the user task can either use + +- the ```lsm303d_new_a_data``` and ```lsm303d_new_m_data``` functions to check periodically whether new output data are available, or +- the data ready interrupt (DRDY) which are thrown as soon as new output data are available (see below). + +Once new new data are available, they can be retrieved either + +- as raw data with the ```lsm303d_get_raw_a_data``` and ```lsm303d_get_raw_m_data``` functions, or +- as floating-point values with the ```lsm303d_get_float_a_data``` and ```lsm303d_get_float_m_data``` functions in g and Gauss, respectively. + +It is recommended to use ```lsm303d_get_a_float_data``` and ```lsm303d_get_float_m_data``` functions since they already converts measurement results to real values according to the selected full scales. + +``` +void user_task_periodic(void *pvParameters) +{ + lsm303d_float_a_data_t a_data; + + while (1) + { + // execute task every 10 ms + vTaskDelay (10/portTICK_PERIOD_MS); + ... + // test for new accelerator data and fetch them + if (lsm303d_new_a_data (sensor) && + lsm303d_get_float_a_data (sensor, &a_data)) + { + // do something with data + ... + } + + lsm303d_float_m_data_t m_data; + + // test for new magnetometer data and fetch them + if (lsm303d_new_m_data (sensor) && + lsm303d_get_float_m_data (sensor, &m_data)) + + { + // do something with data + ... + } + ... + } +} +``` + +**Please note:** +```lsm303d_get_float_a_data```, ```lsm303d_get_raw_a_data```, ```lsm303d_get_float_m_data```, and ```lsm303d_get_raw_m_data``` functions always return the last available results. If these functions are called more often than measurements are taken, some measurement results are retrieved multiple times. If these functions are called too rarely, some measurement results will be lost. + +### High pass filtering + +LSM303D provides embedded high-pass filtering capabilities for acceleration data to improve measurement results. Please refer the [datasheet](http://www.st.com/resource/en/datasheet/lsm303d.pdf). + +The high pass filter (HPF) can independently apply to + +- the raw accelerator output data, +- the accelerator data used for click detection, and +- the accelerator data used for inertial interrupt generation like wake-up, free fall or 6D/4D orientation detection. + +The mode of the high pass filter can be configured using ```lsm303d_config_a_hpf``` function. Following HPF modes are available: + +HPF mode | Driver symbol +:--------------|:--------- +Normal mode | ```lsm303d_hpf_normal``` +Reference mode | ```lsm303d_hpf_reference``` +Auto-reset on interrupt | ```lsm303d_hpf_autoreset``` + +The cutoff frequencies of the HPF are fixed and not documented. + +``` +... +// configure HPF and implicitly reset the reference by a dummy read +lsm303d_config_a_hpf (sensor, lsm303d_hpf_normal, true, true, true, true); +... +``` + +If HPF normal mode (```lsm303d_hpf_normal```) is used, ```lsm303d_config_a_hpf``` function implicitly reads the reference registers to reset the reference. + +### FIFO + +In order to limit the rate at which the host processor has to fetch the data, the LSM303D embeds a first-in first-out buffer (FIFO) for accelerator output data. This is in particular helpful at high output data rates. The FIFO buffer can work in different modes and is able to store up to 32 accelerometer samples. Please refer the [datasheet](http://www.st.com/resource/en/datasheet/lsm303d.pdf). + +FIFO mode | Driver symbol +--------------|------------------------- +Bypass mode (FIFO is not used) | ```lsm303d_bypass``` +FIFO mode | ```lsm303d_fifo``` +Stream mode | ```lsm303d_stream``` +Stream-to-FIFO mode | ```lsm303d_stream_to_fifo``` +Bypass-to-Stream mode | ```lsm303d_bypass_to_stream``` + +The FIFO mode can be set using function ```lsm303d_set_fifo_mode```. This function takes as parameters + +- the FIFO mode, and +- a threshold value. + +The threshold value is used by the sensor to set a flag and to generate optionally an interrupt when the FIFO content exceeds this value. It can be used to gather a minimum number of axes acceleration samples by the sensor before the data are fetched from the sensor as a single read operation. + +``` +... +// clear FIFO +lsm303d_set_fifo_mode (sensor, lsm303d_bypass, 0); + +// activate FIFO mode +lsm303d_set_fifo_mode (sensor, lsm303d_stream, 10); +... +``` + +In this example an the threshold flag would be set, when 11 acceleration samples are stored in the FIFO. + +**Please note**: +- To clear the FIFO at any time, set the FIFO mode to ```lsm303d_bypass``` and back to the desired FIFO mode. +- When FIFO is used, ```lsm303d_new_a_data``` function can also be used to check whether there are new data stored in the FIFO. It returns true if at least one acceleration data sample is stored in the FIFO. + +To read data from the FIFO, simply use either ```lsm303d_get_raw_a_data_fifo``` or ```lsm303d_get_float_a_data_fifo``` function to all get accelerator output data stored in the FIFO. Both functions clear the FIFO implicitly and return the number of samples read from the FIFO. + +``` +void user_task_periodic (void *pvParameters) +{ + lsm303d_float_a_data_fifo_t fifo; + + while (1) + { + // execute task every 500 ms + vTaskDelay (500/portTICK_PERIOD_MS); + ... + + // test for new accelerator data data + if (lsm303d_new_a_data (sensor)) + { + // fetch the accelerator data stored in FIFO + uint8_t num = lsm303d_get_float_a_data_fifo (sensor, fifo); + + for (int i = 0; i < num; i++) + { + // do something with data[i] ... + } + } + ... +} +``` + +## Interrupts + +The LSM303D supports two dedicated interrupt signals **```INT1```** and **```INT2```** and four different types of interrupts: + +- **data** interrupts (data ready, FIFO status), +- **inertial event** interrupts (axis movement, free fall, 6D/4D orientation detection), +- **click detection** interrupts (single click, double click) +- **magnetic threshold** detection interrupts. + +### Data interrupts (data ready and FIFO status) + +Following sources can generate data interrupts: + +Interrupt source | Interrupt Signals | Driver symbol +:-----------------|:-------------|:---------------- +Accelerator data become ready to read | ```INT1```, ```INT2``` | ```lsm303d_int_a_data_ready``` +Magnetometer data become ready to read | ```INT1```, ```INT2``` | ```lsm303d_int_m_data_ready``` +FIFO becomes empty | ```INT1``` | ```lsm303d_int_fifo_thresh``` +FIFO content exceeds the threshold value | ```INT2``` | ```lsm303d_int_fifo_thresh``` +FIFO is completely filled | ```INT2``` | ```lsm303d_int_fifo_overrun``` + +Each of these interrupt sources can be enabled or disabled separately using the ```lsm303d_enable_int``` function. By default, all interrupt sources are disabled. + +``` +lsm303d_enable_int (sensor, lsm303d_int_a_data_ready, lsm303d_int2_signal, true); +``` + +Whenever a data interrupt is generated at the specified interrupt signal, the ```lsm303d_get_int_data_source``` function can be used to determine the source of the data interrupt. This function returns a data structure of type ```lsm303d_int_data_source_t``` that contain a boolean member for each source that can be tested for true. + +``` +void int2_handler () +{ + lsm303d_int_data_source_t data_src; + + // get the source of the interrupt on *INT2* signal + lsm303d_get_int_data_source (sensor, &data_src); + + // in case of data ready interrupt, get the results and do something with them + if (data_src.a_data_ready) + ... // read accelerator data + + // in case of FIFO interrupts read the whole FIFO + else if (data_src.fifo_thresh || data_src.fifo_overrun) + ... // read FIFO data + ... +} +``` + +**Please note:** While FIFO interrupts are reset as soon as the interrupt source is read, the data-ready interrupts are not reset until the data has been read. + +### Inertial event interrupts + +Inertial interrupt generators allow to generate interrupts when certain inertial events occur (event interrupts), that is, the acceleration of defined axes is higher or lower than a defined threshold. If activated, the acceleration of each axis is compared with a defined threshold to check whether it is below or above the threshold. The results of all activated comparisons are then combined OR or AND to generate the interrupt signal. + +The configuration of the threshold, the activated comparisons and the selected AND/OR combination allows to recognize special situations: + +- **Axis movement** refers the special condition that the acceleration measured along any axis is above the defined threshold (```lsm303d_or```). +- **Free fall detection** refers the special condition that the acceleration measured along all the axes goes to zero (```lsm303d_and```). +- **6D/4D orientation detection** refers to the special condition that the measured acceleration along certain axes is above and along the other axes is below the threshold which indicates a particular orientation (```lsm303d_6d_movement```, ```lsm303d_6d_position```, ```lsm303d_4d_movement```, ```lsm303d_4d_position```). + +Inertial event interrupts can be configured with the ```lsm303d_set_int_event_config``` function. This function requires as parameters the configuration of type ```lsm303d_int_event_config_t``` and the interrupt generator to be used for inertial event interrupts. + +Inertial event interrupts have to be enabled or disabled using ```lsm303d_enable_int``` function. The interrupt signal on which the interrupts are generated is given as parameter. + +For example, axis movement detection interrupt generated by inertial interrupt generator 2 on signal ```INT1``` could be configured as following: + +``` +lsm303d_int_event_config_t event_config; + +event_config.mode = lsm303d_or; +event_config.threshold = 50; +event_config.x_low_enabled = false; +event_config.x_high_enabled = true; +event_config.y_low_enabled = false; +event_config.y_high_enabled = true; +event_config.z_low_enabled = false; +event_config.z_high_enabled = true; + +event_config.duration = 0; +event_config.latch = true; + +lsm303d_set_int_event_config (sensor, &event_config, lsm303d_int_event2_gen); +lsm303d_enable_int (sensor, lsm303d_int_event1, lsm303d_int1_signal, true); +``` + +The parameter of type ```lsm303d_int_event_config_t``` also configures + +- whether the interrupt should be latched until the interrupt source is read, and +- which time given in 1/ODR an interrupt condition has to be given before the interrupt is generated. + +As with data ready and FIFO status interrupts, ```lsm303d_get_int_event_source``` function can be used to determine the source of an inertial event interrupt whenever it is generated. This function returns a data structure of type ```lsm303d_int_event_source_t``` which contains a boolean member for each source that can be tested for true. + +``` +void int1_handler () +{ + lsm303d_int_event_source_t event_src; + + // get the source of the interrupt from interrupt generator 2 on *INT1* signal + lsm303d_get_int_event_source (sensor, &event_src, lsm303d_int_event2_gen); + + // in case of inertial event interrupt from interrupt generator 2 + if (event_src.active) + ... // do something + ... +} +``` + +**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 (```lsm303d_or```) is the most flexible way to deal with inertial event interrupts. Functions such as free fall detection and so on can then be realized by suitably combining the various interrupt sources by the user task. Following example realizes the free fall detection in user task. + +``` +lsm303d_int_event_config_t event_config; + +event_config.mode = lsm303d_or; +event_config.threshold = 10; +event_config.x_low_enabled = true; +event_config.x_high_enabled = true; +event_config.y_low_enabled = true; +event_config.y_high_enabled = true; +event_config.z_low_enabled = true; +event_config.z_high_enabled = true; + +event_config.duration = 0; +event_config.latch = true; + +lsm303d_set_int_event_config (sensor, &event_config, lsm303d_int_event2_gen); +lsm303d_enable_int (sensor, lsm303d_int_event1, lsm303d_int1_signal, true); +``` + +``` +void int1_handler () +{ + lsm303d_int_event_source_t event_src; + + // get the source of the interrupt from interrupt generator 2 on *INT1* signal + lsm303d_get_int_event_source (sensor, &event_src, lsm303d_int_event2_gen); + + // test for free fall condition (all accelerations are below the threshold) + if (event_src.x_low && event_src.y_low && event_src.z_low) + ... // do something + ... +} + +``` + +### Click detection interrupts + +A sequence of acceleration values over time measured along certain axes can be used to detect single and double clicks. Please refer the [datasheet](http://www.st.com/resource/en/datasheet/lsm303d.pdf). + +Click detection interrupts are configured using the ``` lsm303d_set_int_click_config``` function. This function requires the configuration of type ```lsm303d_int_click_config_t``` as parameter. The interrupt has to be activated or deactivated using the ```lsm303d_enable_int``` function with the interrupt signal on which the interrupts are generated as parameter. + +In following example, the single click detection for z-axis is enabled with a time limit of 1/ODR, a time latency of 1/ODR and a time window of 3/ODR. + +``` +lsm303d_int_click_config_t click_config; + +click_config.threshold = 10; +click_config.x_single = false; +click_config.x_double = false; +click_config.y_single = false; +click_config.y_double = false; +click_config.z_single = true; +click_config.z_double = false; +click_config.latch = true; +click_config.time_limit = 1; +click_config.time_latency = 1; +click_config.time_window = 3; + +lsm303d_set_int_click_config (sensor, &click_config); +lsm303d_enable_int (sensor, lsm303d_int_click, lsm303d_int1_signal, true); +``` + +**Please note:** Because there is no application note for the LSM303D sensor, please refer to the [application note](http://www.st.com/resource/en/application_note/cd00290365.pdf) for the LIS3DH sensor for more information about the configuration parameters that are used in the same way. + +As with other interrupts, the function ```lsm303d_get_int_click_source``` can be used to determine the source of the interrupt signal whenever it is generated. This function returns a data structure of type ```lsm303d_int_click_source_t``` that contains a boolean member for each source that can be tested for true. + +``` +void int1_handler () +{ + lsm303d_int_click_source_t click_src; + + // get the source of the interrupt on *INT1* signal + lsm303d_get_int_click_source (sensor, &click_src); + + // detect single click along z-axis + if (click_src.z_click && click_src.s_click) + ... // do something + ... +} + +``` +**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. + +### Magnetic threshold interrupts + +Magnetic threshold detection of LSM303D allows to generate interrupts whenever measured magnetic data exceed a defined threshold value at positive or negative side. It can be enabled for each axis separately. The defined threshold is valid for all enabled axes. + +Magnetic threshold interrupts can be configured with ```lsm303d_set_int_m_thresh_config``` function . This function requires configuration of type ```lsm303d_int_m_thresh_config_t``` as parameter. The interrupt has to be activated or deactivated using the ```lsm303d_enable_int``` function with the interrupt signal on which the interrupts are generated as parameter. + + +``` +lsm303d_int_m_thresh_config_t m_thresh_config; + +m_thresh_config.threshold = 2000; +m_thresh_config.x_enabled = true; +m_thresh_config.y_enabled = true; +m_thresh_config.z_enabled = true; +m_thresh_config.latch = true; +m_thresh_config.signal_level = lsm303d_high_active; + +lsm303d_set_int_m_thresh_config (sensor, &m_thresh_config); +lsm303d_enable_int (sensor, lsm303d_int_m_thresh, lsm303d_int1_signal, true); + +``` + +In this example, magnetic threshold detection is enabled for all axes and a threshold of 2000 is defined. + +The parameter of type ```lsm303d_int_m_thresh_config_t``` also configures + +- whether the interrupt signal should latched until the interrupt source is read, and +- whether the interrupt signal is high (default) or low active. + +```lsm303d_get_int_m_thresh_source``` function can be used to determine the source of an magnetic threshold interrupt whenever it is generated. This function returns a data structure of type ```lsm303d_int_m_thresh_config_t``` which contains a boolean member for each source that can be tested for true. + +``` +void int1_handler () +{ + lsm303d_int_m_thresh_source_t thresh_src; + + // get the source of the interrupt on signal *INT1* + lsm303d_get_int_m_thresh_source(sensor, &thresh_src); + + // test the source of the interrupt + if (thresh_src.active) + { + if (thresh_srcx_pos || thresh_srcx_neg) ... ; // do something + if (thresh_srcy_pos || thresh_srcy_neg) ... ; // do something + if (thresh_srcz_pos || thresh_srcz_neg) ... ; // do something + } + ... +} +``` + +**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. + +### Interrupt signal properties + +By default, interrupt signals are high active. For magnetic threshold interrupts this can be changed using the configuration. The type of the interrupt outputs can be changed using the ```lsm303d_config_int_signals``` function. + +Type | Driver symbol +:-------------|:------- +Interrupt output is pushed/pulled | ```lsm303d_push_pull``` +Interrupt output is open-drain | ```lsm303d_open_drain``` + + +``` +lsm303d_config_int_signals (sensor, lsm303d_push_pull); +``` + +## Temperature sensor + +The LIS3MDL sensor contains an internal temperature sensor. It can be activated and deactivated with the ```lsm303d_enable_temperature``` function. Using ```lsm303d_get_temperature``` function, the temperature can be determined as a floating point value in degrees. The temperature is measured by the sensor at the same rate as the magnetic data. + +## Low level functions + +The LSM303D 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 lsm303d_reg_read (lsm303d_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); +bool lsm303d_reg_write (lsm303d_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 driver's high level interface AND if you exactly know what you do and what it might affect. Please be aware that it might always 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 | | LSM303D | + | | | | + | GPIO 14 (SCL) >-----> SCL | + | GPIO 13 (SDA) <-----> SDA | + | GPIO 5 <------ INT1 | + | GPIO 4 <------ INT2 | + +-----------------+ +----------+ +``` + +If SPI interface is used, configuration for ESP8266 and ESP32 could look like following. + +``` + +-----------------+ +----------+ +-----------------+ +----------+ + | ESP8266 | | LSM303D | | ESP32 | | LSM303D | + | | | | | | | | + | 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 <------ INT2 | | GPIO 4 <------ 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. + +``` +spi_bus_init (SPI_BUS, SPI_SCK_GPIO, SPI_MISO_GPIO, SPI_MOSI_GPIO); +``` + +Once the interfaces are initialized, function ```lsm303d_init_sensor``` has to be called for each LSM303D 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 lsm303d_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 = lsm303d_init_sensor (I2C_BUS, LSM303D_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 = lsm303d_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 ```lsm303d.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 +lsm303d_set_a_mode (sensor, lsm303d_a_odr_12_5, lsm303d_a_aaf_bw_773, true, true, true); +lsm303d_set_m_mode (sensor, lsm303d_m_odr_12_5, lsm303d_m_low_res, lsm303d_m_continuous); +... +``` + +#### 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) +{ + lsm303d_float_a_data_t a_data; + lsm303d_float_m_data_t m_data; + + while (1) + { + // execute task every 10 ms + vTaskDelay (10/portTICK_PERIOD_MS); + ... + // test for new accelerator data and fetch them + if (lsm303d_new_a_data (sensor) && + lsm303d_get_float_a_data (sensor, &a_data)) + { + // do something with accelerator data + ... + } + + // test for new magnetometer data and fetch them + if (lsm303d_new_m_data (sensor) && + lsm303d_get_float_m_data (sensor, &m_data)) + { + // do something with magnetometer 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 rate higher 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 **interrupt** signals INT1 or INT2. In this case, 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 accelerator data and fetch them + if (lsm303d_new_a_data (sensor) && + lsm303d_get_float_a_data (sensor, &a_data)) + { + // do something with accelerator data + ... + } + + // test for new magnetometer data and fetch them + if (lsm303d_new_m_data (sensor) && + lsm303d_get_float_m_data (sensor, &m_data)) + { + // do something with magnetometer 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 LSM303D sensor, see section **Interrupts** above. + +## Full Example + +``` +/* -- use following constants to define the example mode ----------- */ + +// #define SPI_USED // SPI interface is used, otherwise I2C +// #define FIFO_MODE // multiple sample read mode +// #define TEMP_USED // temperature sensor used +// #define INT_DATA // data interrupts used (data ready and FIFO status) +// #define INT_EVENT // inertial event interrupts used (axis movement or 6D/4D orientation) +// #define INT_CLICK // click detection interrupts used +// #define INT_THRESH // magnetic value exceeds threshold interrupt used + +#if defined(INT_DATA) || defined(INT_EVENT) || defined(INT_CLICK) || defined(INT_THRESH) +#define INT_USED +#endif + +/* -- includes ----------------------------------------------------- */ + +#include "lsm303d.h" + +/** -- platform dependent definitions ------------------------------ */ + +#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 + +/* -- user tasks --------------------------------------------------- */ + +static lsm303d_sensor_t* sensor; + +/** + * Common function used to get sensor data. + */ +void read_data () +{ + #ifdef FIFO_MODE + + lsm303d_float_a_data_fifo_t fifo; + + // test for new accelerator data data + if (lsm303d_new_a_data (sensor)) + { + // fetch the accelerator data stored in FIFO + uint8_t num = lsm303d_get_float_a_data_fifo (sensor, fifo); + + printf("%.3f LSM303D num=%d\n", (double)sdk_system_get_time()*1e-3, num); + + for (int i=0; i < num; i++) + // max. full scale is +-16 g and best resolution is 1 mg, i.e. 5 digits + printf("%.3f LSM303D (xyz)[g] ax=%+7.3f ay=%+7.3f az=%+7.3f\n", + (double)sdk_system_get_time()*1e-3, + fifo[i].ax, fifo[i].ay, fifo[i].az); + } + + #else + + lsm303d_float_a_data_t a_data; + + // test for new accelerator data and fetch them + if (lsm303d_new_a_data (sensor) && + lsm303d_get_float_a_data (sensor, &a_data)) + // max. full scale is +-16 g and best resolution is 1 mg, i.e. 5 digits + printf("%.3f LSM303D (xyz)[g] ax=%+7.3f ay=%+7.3f az=%+7.3f\n", + (double)sdk_system_get_time()*1e-3, + a_data.ax, a_data.ay, a_data.az); + + #endif // FIFO_MODE + + lsm303d_float_m_data_t m_data; + + // test for new magnetometer data and fetch them + if (lsm303d_new_m_data (sensor) && + lsm303d_get_float_m_data (sensor, &m_data)) + // max. full scale is +-12 Gs and best resolution is 1 mGs, i.e. 5 digits + printf("%.3f LSM303D (xyz)[Gs] mx=%+7.3f my=%+7.3f mz=%+7.3f\n", + (double)sdk_system_get_time()*1e-3, + m_data.mx, m_data.my, m_data.mz); + + #ifdef TEMP_USED + float temp = lsm303d_get_temperature (sensor); + + printf("%.3f LSM303D (tmp)[°C] %+7.3f\n", (double)sdk_system_get_time()*1e-3, temp); + #endif +} + + +#ifdef INT_USED +/** + * In this case, any of the possible interrupts on interrupt signal *INT1* is + * used to fetch the data. + * + * 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; + + while (1) + { + if (xQueueReceive(gpio_evt_queue, &gpio, portMAX_DELAY)) + { + lsm303d_int_data_source_t data_src = {}; + lsm303d_int_event_source_t event_src = {}; + lsm303d_int_click_source_t click_src = {}; + lsm303d_int_m_thresh_source_t thresh_src = {}; + + // get the source of the interrupt that reset *INTx* signals + #ifdef INT_DATA + lsm303d_get_int_data_source (sensor, &data_src); + #endif + #ifdef INT_THRESH + lsm303d_get_int_m_thresh_source(sensor, &thresh_src); + #endif + #ifdef INT_EVENT + lsm303d_get_int_event_source (sensor, &event_src, lsm303d_int_event1_gen); + #endif + #ifdef INT_CLICK + lsm303d_get_int_click_source (sensor, &click_src); + #endif + + // in case of DRDY interrupt + if (data_src.a_data_ready || data_src.m_data_ready) + read_data (); + + // in case of FIFO interrupts read the whole FIFO + else if (data_src.fifo_thresh || data_src.fifo_overrun) + read_data (); + + // in case of magnetic threshold interrupt + else if (thresh_src.active) + { + printf("%.3f LSM303D ", (double)sdk_system_get_time()*1e-3); + if (thresh_src.x_pos) printf("x exceeds threshold on positive side\n"); + if (thresh_src.y_pos) printf("y exceeds threshold on positive side\n"); + if (thresh_src.z_pos) printf("z exceeds threshold on positive side\n"); + if (thresh_src.x_neg) printf("x exceeds threshold on negative side\n"); + if (thresh_src.y_neg) printf("y exceeds threshold on negative side\n"); + if (thresh_src.z_neg) printf("z exceeds threshold on negative side\n"); + } + + // in case of event interrupt + else if (event_src.active) + { + printf("%.3f LSM303D ", (double)sdk_system_get_time()*1e-3); + if (event_src.x_low) printf("x is lower than threshold\n"); + if (event_src.y_low) printf("y is lower than threshold\n"); + if (event_src.z_low) printf("z is lower than threshold\n"); + if (event_src.x_high) printf("x is higher than threshold\n"); + if (event_src.y_high) printf("y is higher than threshold\n"); + if (event_src.z_high) printf("z is higher than threshold\n"); + } + + // in case of click detection interrupt + else if (click_src.active) + printf("%.3f LSM303D %s\n", (double)sdk_system_get_time()*1e-3, + click_src.s_click ? "single click" : "double click"); + } + } +} + +// Interrupt handler which resumes user_task_interrupt on 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 example, user task fetches the sensor values 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(200/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 SPI interface at which LMS303D sensors are connected + 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 = lsm303d_init_sensor (SPI_BUS, 0, SPI_CS_GPIO); + + #else + + // init all I2C busses at which LSM303D sensors are connected + i2c_init (I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ); + + // init the sensor with slave address LSM303D_I2C_ADDRESS_2 connected to I2C_BUS. + sensor = lsm303d_init_sensor (I2C_BUS, LSM303D_I2C_ADDRESS_2, 0); + + #endif + + 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 the type of INTx signals if necessary + // lsm303d_config_int_signals (sensor, lsm303d_push_pull); + + #ifdef INT_DATA + // enable data interrupts on *INT2* (data ready or FIFO overrun and FIFO threshold) + // data ready and FIFO status interrupts must not be enabled at the same time + #ifdef FIFO_MODE + lsm303d_enable_int (sensor, lsm303d_int_fifo_overrun, lsm303d_int2_signal, true); + lsm303d_enable_int (sensor, lsm303d_int_fifo_thresh , lsm303d_int2_signal, true); + #else + lsm303d_enable_int (sensor, lsm303d_int_a_data_ready, lsm303d_int2_signal, true); + lsm303d_enable_int (sensor, lsm303d_int_m_data_ready, lsm303d_int2_signal, true); + #endif // FIFO_MODE + #endif // INT_DATA + + #ifdef INT_THRESH + // enable magnetic threshold interrupts on signal *INT1* + lsm303d_int_m_thresh_config_t m_thresh_config; + + m_thresh_config.threshold = 2000; + m_thresh_config.x_enabled = true; + m_thresh_config.y_enabled = true; + m_thresh_config.z_enabled = true; + m_thresh_config.latch = true; + m_thresh_config.signal_level = lsm303d_high_active; + + lsm303d_set_int_m_thresh_config (sensor, &m_thresh_config); + lsm303d_enable_int (sensor, lsm303d_int_m_thresh, lsm303d_int1_signal, true); + #endif // INT_THRESH + + #ifdef INT_EVENT + // enable inertial event interrupts on *INT1* + lsm303d_int_event_config_t event_config; + + event_config.mode = lsm303d_or; // axes movement wake-up + // event_config.mode = lsm303d_and; // free fall + // event_config.mode = lsm303d_6d_movement; + // event_config.mode = lsm303d_6d_position; + // event_config.mode = lsm303d_4d_movement; + // event_config.mode = lsm303d_4d_position; + event_config.threshold = 50; + event_config.x_low_enabled = false; + event_config.x_high_enabled = true; + event_config.y_low_enabled = false; + event_config.y_high_enabled = true; + event_config.z_low_enabled = false; + event_config.z_high_enabled = true; + event_config.duration = 0; + event_config.latch = true; + + lsm303d_set_int_event_config (sensor, &event_config, lsm303d_int_event1_gen); + lsm303d_enable_int (sensor, lsm303d_int_event1, lsm303d_int1_signal, true); + #endif // INT_EVENT + + #ifdef INT_CLICK + // enable single click interrupt for z-axis on signal *INT1* + lsm303d_int_click_config_t click_config; + + click_config.threshold = 10; + click_config.x_single = false; + click_config.x_double = false; + click_config.y_single = false; + click_config.y_double = false; + click_config.z_single = true; + click_config.z_double = false; + click_config.latch = true; + click_config.time_limit = 1; + click_config.time_latency = 1; + click_config.time_window = 3; + + lsm303d_set_int_click_config (sensor, &click_config); + lsm303d_enable_int (sensor, lsm303d_int_click, lsm303d_int1_signal, true); + #endif // INT_CLICK + + #ifdef FIFO_MODE + // clear the FIFO + lsm303d_set_fifo_mode (sensor, lsm303d_bypass, 0); + + // activate the FIFO with a threshold of 10 samples (max. 31); if + // interrupt *lsm303d_fifo_thresh* is enabled, an interrupt is + // generated when the FIFO content exceeds this threshold, i.e., + // when 11 samples are stored in FIFO + lsm303d_set_fifo_mode (sensor, lsm303d_stream, 10); + #endif + + // configure HPF and implicitly reset the reference by a dummy read + lsm303d_config_a_hpf (sensor, lsm303d_hpf_normal, true, true, true, true); + + #ifdef TEMP_USED + // enable the temperature sensor + lsm303d_enable_temperature (sensor, true); + #endif + + // LAST STEP: Finally set scale and mode to start measurements + lsm303d_set_a_scale(sensor, lsm303d_a_scale_2_g); + lsm303d_set_m_scale(sensor, lsm303d_m_scale_4_Gs); + lsm303d_set_a_mode (sensor, lsm303d_a_odr_12_5, lsm303d_a_aaf_bw_773, true, true, true); + lsm303d_set_m_mode (sensor, lsm303d_m_odr_12_5, lsm303d_m_low_res, lsm303d_m_continuous); + + /** -- 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 LSM303D sensor\n"); +} + +``` diff --git a/extras/lsm303d/component.mk b/extras/lsm303d/component.mk new file mode 100644 index 0000000..40a5202 --- /dev/null +++ b/extras/lsm303d/component.mk @@ -0,0 +1,10 @@ +# Component makefile for extras/lsm303d + +# expected anyone using LIS3MDL driver includes it as 'lis3mld/lis3mld.h' +INC_DIRS += $(lsm303d_ROOT).. +INC_DIRS += $(lsm303d_ROOT) + +# args for passing into compile rule generation +lsm303d_SRC_DIR = $(lsm303d_ROOT) + +$(eval $(call component_compile_rules,lsm303d)) diff --git a/extras/lsm303d/lsm303d.c b/extras/lsm303d/lsm303d.c new file mode 100644 index 0000000..1e0171f --- /dev/null +++ b/extras/lsm303d/lsm303d.c @@ -0,0 +1,1693 @@ +/* + * Driver for LSM303D 3-axes digital accelerometer and magnetometer connected + * either to I2C or SPI. + * + * This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos) + * [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32 + * and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux + * based systems using a wrapper library for ESP8266 functions. + * + * --------------------------------------------------------------------------- + * + * The BSD License (3-clause license) + * + * Copyright (c) 2018 Gunar Schorcht (https://github.com/gschorcht) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * The information provided is believed to be accurate and reliable. The + * copyright holder assumes no responsibility for the consequences of use + * of such information nor for any infringement of patents or other rights + * of third parties which may result from its use. No license is granted by + * implication or otherwise under any patent or patent rights of the copyright + * holder. + */ + +#include +#include + +#include "lsm303d.h" + +#ifdef debug +#undef debug +#undef debug_dev +#endif + +#ifdef error +#undef error +#undef error_dev +#endif + +#if defined(LSM303D_DEBUG_LEVEL_2) +#define debug(s, f, ...) printf("%s %s: " s "\n", "LSM303D", f, ## __VA_ARGS__) +#define debug_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "LSM303D", f, d->bus, d->addr, ## __VA_ARGS__) +#else +#define debug(s, f, ...) +#define debug_dev(s, f, d, ...) +#endif + +#if defined(LSM303D_DEBUG_LEVEL_1) || defined(LSM303D_DEBUG_LEVEL_2) +#define error(s, f, ...) printf("%s %s: " s "\n", "LSM303D", f, ## __VA_ARGS__) +#define error_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "LSM303D", f, d->bus, d->addr, ## __VA_ARGS__) +#else +#define error(s, f, ...) +#define error_dev(s, f, d, ...) +#endif + +// -- register addresses --------------------------- + +#define LSM303D_REG_TEMP_OUT_L 0x05 +#define LSM303D_REG_TEMP_OUT_H 0x06 + +#define LSM303D_REG_STATUS_M 0x07 +#define LSM303D_REG_OUT_X_L_M 0x08 +#define LSM303D_REG_OUT_X_H_M 0x09 +#define LSM303D_REG_OUT_Y_L_M 0x0a +#define LSM303D_REG_OUT_Y_H_M 0x0b +#define LSM303D_REG_OUT_Z_L_M 0x0c +#define LSM303D_REG_OUT_Z_H_M 0x0d + +#define LSM303D_REG_WHO_AM_I 0x0f + +#define LSM303D_REG_INT_CTRL_M 0x12 +#define LSM303D_REG_INT_SRC_M 0x13 +#define LSM303D_REG_INT_THS_L_M 0x14 +#define LSM303D_REG_INT_THS_H_M 0x15 +#define LSM303D_REG_OFFSET_X_L_M 0x16 +#define LSM303D_REG_OFFSET_X_M_M 0x17 +#define LSM303D_REG_OFFSET_Y_L_M 0x18 +#define LSM303D_REG_OFFSET_Y_M_M 0x19 +#define LSM303D_REG_OFFSET_Z_L_M 0x1a +#define LSM303D_REG_OFFSET_Z_M_M 0x1b + +#define LSM303D_REG_REFERENCE_X 0x1c +#define LSM303D_REG_REFERENCE_Y 0x1d +#define LSM303D_REG_REFERENCE_Z 0x1e + +#define LSM303D_REG_CTRL0 0x1f +#define LSM303D_REG_CTRL1 0x20 +#define LSM303D_REG_CTRL2 0x21 +#define LSM303D_REG_CTRL3 0x22 +#define LSM303D_REG_CTRL4 0x23 +#define LSM303D_REG_CTRL5 0x24 +#define LSM303D_REG_CTRL6 0x25 +#define LSM303D_REG_CTRL7 0x26 + +#define LSM303D_REG_STATUS_A 0x27 +#define LSM303D_REG_OUT_X_L_A 0x28 +#define LSM303D_REG_OUT_X_H_A 0x29 +#define LSM303D_REG_OUT_Y_L_A 0x2a +#define LSM303D_REG_OUT_Y_H_A 0x2b +#define LSM303D_REG_OUT_Z_L_A 0x2c +#define LSM303D_REG_OUT_Z_H_A 0x2d + +#define LSM303D_REG_FIFO_CTRL 0x2e +#define LSM303D_REG_FIFO_SRC 0x2f + +#define LSM303D_REG_IG_CFG1 0x30 +#define LSM303D_REG_IG_SRC1 0x31 +#define LSM303D_REG_IG_THS1 0x32 +#define LSM303D_REG_IG_DUR1 0x33 +#define LSM303D_REG_IG_CFG2 0x34 +#define LSM303D_REG_IG_SRC2 0x35 +#define LSM303D_REG_IG_THS2 0x36 +#define LSM303D_REG_IG_DUR2 0x37 +#define LSM303D_REG_CLICK_CFG 0x38 +#define LSM303D_REG_CLICK_SRC 0x39 +#define LSM303D_REG_CLICK_THS 0x3a +#define LSM303D_REG_TIME_LIMIT 0x3b +#define LSM303D_REG_TIME_LATENCY 0x3c +#define LSM303D_REG_TIME_WINDOW 0x3d + +// -- register structure definitions --------------- +// +// ACC = accelerator +// MAG = magnetometer + +// magnetometer data status (LSM303D_REG_STATUS_M = 0x07) +struct lsm303d_reg_status_m +{ + uint8_t XMDA :1; // STATUS_M<0> MAG X axis new data available + uint8_t YMDA :1; // STATUS_M<1> MAG Y axis new data available + uint8_t ZMDA :1; // STATUS_M<2> MAG Z axis new data available + uint8_t ZYXMDA :1; // STATUS_M<3> MAG X, Y and Z axis new data available + uint8_t XMOR :1; // STATUS_M<4> MAG X axis data overrun + uint8_t YMOR :1; // STATUS_M<5> MAG Y axis data overrun + uint8_t ZMOR :1; // STATUS_M<6> MAG Z axis data overrun + uint8_t ZYXMOR :1; // STATUS_M<7> MAG X, Y and Z axis data overrun +}; + +#define LSM303D_ANY_M_DATA_READY 0x07 // LSM303D_REG_STATUS_M<3:0> + +// accelerometer data status (LSM303D_REG_STATUS_A = 0x27) +struct lsm303d_reg_status_a +{ + uint8_t XADA :1; // STATUS_A<0> ACC X axis new data available + uint8_t YADA :1; // STATUS_A<1> ACC Y axis new data available + uint8_t ZADA :1; // STATUS_A<2> ACC Z axis new data available + uint8_t ZYXADA:1; // STATUS_A<3> ACC X, Y and Z axis new data available + uint8_t XAOR :1; // STATUS_A<4> ACC X axis data overrun + uint8_t YAOR :1; // STATUS_A<5> ACC Y axis data overrun + uint8_t ZAOR :1; // STATUS_A<6> ACC Z axis data overrun + uint8_t ZYXAOR:1; // STATUS_A<7> ACC X, Y and Z axis data overrun +}; + +#define LSM303D_ANY_A_DATA_READY 0x07 // LSM303D_REG_STATUS_A<3:0> + +// MAG interrupt control register (LSM303D_REG_INT_CTRL_M = 0x12) +struct lsm303d_reg_int_ctrl_m +{ + uint8_t MIEN :1; // INT_CTRL_M<0> Enable interrupt generation for magnetic data + uint8_t D4D :1; // INT_CTRL_M<1> 4D enable + uint8_t MIEL :1; // INT_CTRL_M<2> Latch interrupt request + uint8_t MIEA :1; // INT_CTRL_M<3> Interrupt polarity + uint8_t PP_OD :1; // INT_CTRL_M<4> Interrupt pin configuration + uint8_t ZMIEN :1; // INT_CTRL_M<5> Enable interrupt recognition for Z axis + uint8_t YMIEN :1; // INT_CTRL_M<6> Enable interrupt recognition for Y axis + uint8_t XMIEN :1; // INT_CTRL_M<7> Enable interrupt recognition for X axis +}; + +// MAG interrupt source register (LSM303D_REG_INT_SRC_M = 0x13) +struct lsm303d_reg_int_src_m +{ + uint8_t MINT :1; // INT_SRC_M<0> MAG interrupt event occurs + uint8_t MROI :1; // INT_SRC_M<1> Internal measurement range overflow + uint8_t M_NTH_Z :1; // INT_SRC_M<2> MAG z value exceeds threshold on negative side + uint8_t M_NTH_Y :1; // INT_SRC_M<3> MAG y value exceeds threshold on negative side + uint8_t M_NTH_X :1; // INT_SRC_M<4> MAG x value exceeds threshold on negative side + uint8_t M_PTH_Z :1; // INT_SRC_M<5> MAG z value exceeds threshold on positive side + uint8_t M_PTH_Y :1; // INT_SRC_M<6> MAG y value exceeds threshold on positive side + uint8_t M_PTH_X :1; // INT_SRC_M<7> MAG x value exceeds threshold on positive side +}; + +// control register 0 (LSM303D_REG_CTRL1 = 0x1f) +struct lsm303d_reg_ctrl0 +{ + uint8_t HPIS2 :1; // CTRL0<0> HPF enabled for interrupt generator 2 + uint8_t HPIS1 :1; // CTRL0<1> HPF enabled for interrupt generator 1 + uint8_t HP_Click:1; // CTRL0<2> HPF enabled for click detection + uint8_t unused :2; // CTRL0<4:3> unused + uint8_t FTH_EN :1; // CTRL0<5> FIFO programmable threshold enable + uint8_t FIFO_EN :1; // CTRL0<6> FIFO enable + uint8_t BOOT :1; // CTRL0<7> Reboot memory content +}; + +// control register 1 (LSM303D_REG_CTRL1 = 0x20) +struct lsm303d_reg_ctrl1 +{ + uint8_t AXEN :1; // CTRL1<0> ACC X axis enable + uint8_t AYEN :1; // CTRL1<1> ACC Y axis enable + uint8_t AZEN :1; // CTRL1<2> ACC Z axis enable + uint8_t BDU :1; // CTRL1<3> ACC and MAG block data update + uint8_t AODR :4; // CTRL1<7:4> ACC data rate selection +}; + +// control register 2 (LSM303D_REG_CTRL2 = 0x21) +struct lsm303d_reg_ctrl2 +{ + uint8_t SIM :1; // CTRL2<0> SPI serial interface mode + uint8_t AST :1; // CTRL2<1> ACC self test enable + uint8_t unused:1; // CTRL2<2> unused + uint8_t AFS :3; // CTRL2<5:3> ACC full scale selection + uint8_t ABW :2; // CTRL2<7:6> ACC anti-alias filter bandwidth selection +}; + +// control register 3 (LSM303D_REG_CTRL3 = 0x22) +struct lsm303d_reg_ctrl3 +{ + uint8_t INT1_EMPTY :1; // CTRL3<0> FIFO empty indication on INT1 enable + uint8_t INT1_DRDY_M :1; // CTRL3<1> MAG data ready signal on INT1 enable + uint8_t INT1_DRDY_A :1; // CTRL3<2> ACC data ready signal on INT1 enable + uint8_t INT1_IGM :1; // CTRL3<3> MAG interrupt generator on INT1 enable + uint8_t INT1_IG2 :1; // CTRL3<4> ACC inertial interrupt generator 2 on INT1 enable + uint8_t INT1_IG1 :1; // CTRL3<5> ACC inertial interrupt generator 1 on INT1 enable + uint8_t INT1_Click :1; // CTRL3<6> CLICK generator interrupt on INT1 enable + uint8_t INT1_BOOT :1; // CTRL3<7> BOOT on INT1 enable +}; + +// control register 4 (LSM303D_REG_CTRL4 = 0x23) +struct lsm303d_reg_ctrl4 +{ + uint8_t INT2_FTH :1; // CTRL4<0> FIFO threshold interrupt on INT2 enable + uint8_t INT2_Overrun:1; // CTRL4<1> FIFO Overrun interrupt on INT2 + uint8_t INT2_DRDY_M :1; // CTRL4<2> MAG data ready signal on INT2 enable + uint8_t INT2_DRDY_A :1; // CTRL4<3> ACC data ready signal on INT2 enable + uint8_t INT2_IGM :1; // CTRL4<4> MAG interrupt generator on INT2 enable + uint8_t INT2_IG2 :1; // CTRL4<5> ACC inertial interrupt generator 2 on INT2 enable + uint8_t INT2_IG1 :1; // CTRL4<6> ACC inertial interrupt generator 1 on INT2 enable + uint8_t INT2_Click :1; // CTRL4<7> CLICK generator interrupt on INT2 enable +}; + +// control register 5 (LSM303D_REG_CTRL5 = 0x24) +struct lsm303d_reg_ctrl5 +{ + uint8_t LIR1 :1; // CTRL5<0> Latch interrupt request on INT1 + uint8_t LIR2 :1; // CTRL5<1> Latch interrupt request on INT2 + uint8_t M_ODR :3; // CTRL5<4:2> MAG data rate selection + uint8_t M_RES :2; // CTRL5<6:5> MAG resolution + uint8_t TEMP_EN :1; // CTRL5<7> Temperature sensor enable +}; + +// control register 6 (LSM303D_REG_CTRL6 = 0x25) +struct lsm303d_reg_ctrl6 +{ + uint8_t unused1 :5; // CTRL6<4:0> unused + uint8_t MFS :2; // CTRL6<6:5> MAG full scale selection + uint8_t unused2 :1; // CTRL6<7> unused +}; + +// control register 7 (LSM303D_REG_CTRL7 = 0x26) +struct lsm303d_reg_ctrl7 +{ + uint8_t MD :2; // CTRL7<1:0> MAG sensor mode + uint8_t MLP :1; // CTRL7<2> MAG data low-power mode + uint8_t unused :1; // CTRL7<3> unused + uint8_t T_ONLY :1; // CTRL7<4> Temperature sensor only mode + uint8_t AFDS :1; // CTRL7<5> ACC data filtered + uint8_t AHPM :2; // CTRL7<7:6> ACC HPF mode +}; + +// FIFO control (LSM303D_REG_FIFO_CTRL = 0x2e) +struct lsm303d_reg_fifo_ctrl +{ + uint8_t FTH :5; // FIFO_CTRL<4:0> FIFO threshold level + uint8_t FM :3; // FIFO_CTRL<7:5> FIFO mode selection +}; + +// FIFO source (LSM303D_REG_FIFO_SRC = 0x2f) +struct lsm303d_reg_fifo_src +{ + uint8_t FFS :5; // FIFO_SRC<4:0> FIFO samples stored + uint8_t EMPTY:1; // FIFO_SRC<5> FIFO is empty + uint8_t OVRN :1; // FIFO_SRC<6> FIFO buffer full + uint8_t FTH :1; // FIFO_SRC<7> FIFO content exceeds watermark +}; + +// ACC interrupt generator IG_CFGx (LSM303D_REG_IG_CFGx = 0x30, 0x34) +struct lsm303d_reg_ig_cfgx +{ + uint8_t XLIE :1; // IG_CFGx<0> ACC x value below threshold enabled + uint8_t XHIE :1; // IG_CFGx<1> ACC x value above threshold enabled + uint8_t YLIE :1; // IG_CFGx<2> ACC y value below threshold enabled + uint8_t YHIE :1; // IG_CFGx<3> ACC y value above threshold enabled + uint8_t ZLIE :1; // IG_CFGx<4> ACC z value below threshold enabled + uint8_t ZHIE :1; // IG_CFGx<5> ACC z value above threshold enabled + uint8_t D6D :1; // IG_CFGx<6> 6D/4D detection detecetion enabled + uint8_t AOI :1; // IG_CFGx<7> AND/OR combination of interrupt events +}; + +// ACC interrupt source IG_SRCx (LSM303D_REG_IG_SRCx = 0x31, 0x35) +struct lsm303d_reg_ig_srcx +{ + uint8_t XL :1; // IG_SRCx<0> ACC x value is below threshold + uint8_t XH :1; // IG_SRCx<1> ACC x value is above threshold + uint8_t YL :1; // IG_SRCx<2> ACC y value is below threshold + uint8_t YH :1; // IG_SRCx<3> ACC y value is above threshold + uint8_t ZL :1; // IG_SRCx<4> ACC z value is below threshold + uint8_t ZH :1; // IG_SRCx<5> ACC z value is above threshold + uint8_t IA :1; // IG_SRCx<6> ACC interrupt active + uint8_t unused:1; // IG_SRCx<7> unused +}; + +// CLICK_CFGx (LSM303D_REG_CLICL_CFG = 0x38) +struct lsm303d_reg_click_cfg +{ + uint8_t XS :1; // CLICK_CFG<0> X axis single click enabled + uint8_t XD :1; // CLICK_CFG<1> X axis double click enabled + uint8_t YS :1; // CLICK_CFG<2> Y axis single click enabled + uint8_t YD :1; // CLICK_CFG<3> Y axis double click enabled + uint8_t ZS :1; // CLICK_CFG<4> Z axis single click enabled + uint8_t ZD :1; // CLICK_CFG<5> Z axis double click enabled + uint8_t unused:2; // CLICK_CFG<7:6> unused +}; + + +/** Forward declaration of functions for internal use */ + +static bool lsm303d_reset (lsm303d_sensor_t* dev); +static bool lsm303d_is_available(lsm303d_sensor_t* dev); + +static bool lsm303d_i2c_read (lsm303d_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); +static bool lsm303d_i2c_write (lsm303d_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); +static bool lsm303d_spi_read (lsm303d_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); +static bool lsm303d_spi_write (lsm303d_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len); + +#define msb_lsb_to_type(t,b,o) (t)(((t)b[o] << 8) | b[o+1]) +#define lsb_msb_to_type(t,b,o) (t)(((t)b[o+1] << 8) | b[o]) +#define lsb_to_type(t,b,o) (t)(b[o]) + +#define lsm303d_update_reg(dev,addr,type,elem,value) \ + { \ + struct type __reg; \ + if (!lsm303d_reg_read (dev, (addr), (uint8_t*)&__reg, 1)) \ + return false; \ + __reg.elem = (value); \ + if (!lsm303d_reg_write (dev, (addr), (uint8_t*)&__reg, 1)) \ + return false; \ + } + +lsm303d_sensor_t* lsm303d_init_sensor (uint8_t bus, uint8_t addr, uint8_t cs) +{ + lsm303d_sensor_t* dev; + + if ((dev = malloc (sizeof(lsm303d_sensor_t))) == NULL) + return NULL; + + // init sensor data structure + dev->bus = bus; + dev->addr = addr; + dev->cs = cs; + + dev->error_code = LSM303D_OK; + dev->a_scale = lsm303d_a_scale_2_g; + dev->m_scale = lsm303d_m_scale_4_Gs; + dev->m_res = lsm303d_m_low_res; + dev->fifo_mode = lsm303d_bypass; + dev->fifo_first = true; + + // if addr==0 then SPI is used and has to be initialized + if (!addr && !spi_device_init (bus, cs)) + { + error_dev ("Could not initialize SPI interface.", __FUNCTION__, dev); + free (dev); + return NULL; + } + + // check availability of the sensor + if (!lsm303d_is_available (dev)) + { + error_dev ("Sensor is not available.", __FUNCTION__, dev); + free (dev); + return NULL; + } + + // reset the sensor + if (!lsm303d_reset(dev)) + { + error_dev ("Could not reset the sensor device.", __FUNCTION__, dev); + free (dev); + return NULL; + } + + // set block data update as default + lsm303d_update_reg (dev, LSM303D_REG_CTRL1, lsm303d_reg_ctrl1, BDU, 1); + + // not necessary, following values are the defaults + // lsm303d_update_reg (dev, LSM303D_REG_CTRL2, lsm303d_reg_ctrl2, AFS, lsm303d_a_scale_2_g); + // lsm303d_update_reg (dev, LSM303D_REG_CTRL6, lsm303d_reg_ctrl6, MFS, lsm303d_m_scale_4_Gs); + + // clear FIFO + // lsm303d_set_fifo_mode (sensor, lsm303d_bypass, 0); + + return dev; +} + +bool lsm303d_set_a_mode (lsm303d_sensor_t* dev, + lsm303d_a_odr_t odr, lsm303d_a_aaf_bw_t bw, + bool x, bool y, bool z) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + struct lsm303d_reg_ctrl1 ctrl1; + struct lsm303d_reg_ctrl2 ctrl2; + + // read current register values + if (!lsm303d_reg_read (dev, LSM303D_REG_CTRL1, (uint8_t*)&ctrl1, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_CTRL2, (uint8_t*)&ctrl2, 1)) + return false; + + // set mode + ctrl1.AXEN = x; + ctrl1.AYEN = y; + ctrl1.AZEN = z; + ctrl1.AODR = odr; + + ctrl2.ABW = bw; + + if (!lsm303d_reg_write (dev, LSM303D_REG_CTRL1, (uint8_t*)&ctrl1, 1) || + !lsm303d_reg_write (dev, LSM303D_REG_CTRL2, (uint8_t*)&ctrl2, 1)) + return false; + + // switching times + vTaskDelay (50/portTICK_PERIOD_MS); + + return false; +} + + +bool lsm303d_set_m_mode (lsm303d_sensor_t* dev, + lsm303d_m_odr_t odr, + lsm303d_m_resolution_t res, + lsm303d_m_mode_t mode) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + struct lsm303d_reg_ctrl5 ctrl5; + struct lsm303d_reg_ctrl7 ctrl7; + + // read current register values + if (!lsm303d_reg_read (dev, LSM303D_REG_CTRL5, (uint8_t*)&ctrl5, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_CTRL7, (uint8_t*)&ctrl7, 1)) + return false; + + // set mode + if (odr == lsm303d_m_low_power) + { + ctrl7.MLP = true; + ctrl5.M_ODR = lsm303d_m_odr_3_125; + } + else + { + ctrl7.MLP = false; + ctrl5.M_ODR = odr; + } + + ctrl7.MD = mode; + ctrl5.M_RES = res; + + // write register values + if (!lsm303d_reg_write (dev, LSM303D_REG_CTRL5, (uint8_t*)&ctrl5, 1) || + !lsm303d_reg_write (dev, LSM303D_REG_CTRL7, (uint8_t*)&ctrl7, 1)) + return false; + + // switching times + vTaskDelay (50/portTICK_PERIOD_MS); + + return false; +} + + +bool lsm303d_set_a_scale (lsm303d_sensor_t* dev, lsm303d_a_scale_t scale) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + dev->a_scale = scale; + + // read CTRL2 register and write scale + lsm303d_update_reg (dev, LSM303D_REG_CTRL2, lsm303d_reg_ctrl2, AFS, scale); + + return true; +} + + +bool lsm303d_set_m_scale (lsm303d_sensor_t* dev, lsm303d_m_scale_t scale) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + dev->m_scale = scale; + + // read CTRL5 register and write scale + lsm303d_update_reg (dev, LSM303D_REG_CTRL6, lsm303d_reg_ctrl6, MFS, scale); + + return true; +} + + +bool lsm303d_set_fifo_mode (lsm303d_sensor_t* dev, lsm303d_fifo_mode_t mode, + uint8_t thresh) +{ + if (!dev) return false; + + if (thresh > 31) + { + error_dev ("FIFO threshold is greate than the maximum of 31.", __FUNCTION__, dev); + dev->error_code = LSM303D_FIFO_THRESHOLD_INVALID; + return false; + } + + dev->error_code = LSM303D_OK; + dev->fifo_mode = mode; + + // read CTRL1 register and write FIFO_EN flag + lsm303d_update_reg (dev, LSM303D_REG_CTRL0, lsm303d_reg_ctrl0, FIFO_EN, mode != lsm303d_bypass); + lsm303d_update_reg (dev, LSM303D_REG_CTRL0, lsm303d_reg_ctrl0, FTH_EN , mode != lsm303d_bypass); + + struct lsm303d_reg_fifo_ctrl fifo_ctrl = { + .FTH = thresh, + .FM = mode, + }; + + // write FIFO_CTRL register + if (!lsm303d_reg_write (dev, LSM303D_REG_FIFO_CTRL, (uint8_t*)&fifo_ctrl, 1)) + return false; + + return true; +} + + +bool lsm303d_new_a_data (lsm303d_sensor_t* dev) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + if (dev->fifo_mode == lsm303d_bypass) + { + struct lsm303d_reg_status_a status; + + if (!lsm303d_reg_read (dev, LSM303D_REG_STATUS_A, (uint8_t*)&status, 1)) + { + error_dev ("Could not get sensor status", __FUNCTION__, dev); + return false; + } + return status.XADA || status.YADA || status.ZADA; + } + else + { + struct lsm303d_reg_fifo_src fifo_src; + + if (!lsm303d_reg_read (dev, LSM303D_REG_FIFO_SRC, (uint8_t*)&fifo_src, 1)) + { + error_dev ("Could not get fifo source register data", __FUNCTION__, dev); + return false; + } + return !fifo_src.EMPTY; + } +} + +bool lsm303d_new_m_data (lsm303d_sensor_t* dev) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + struct lsm303d_reg_status_m status; + + if (!lsm303d_reg_read (dev, LSM303D_REG_STATUS_M, (uint8_t*)&status, 1)) + { + error_dev ("Could not get sensor status", __FUNCTION__, dev); + return false; + } + return status.XMDA || status.YMDA || status.ZMDA; +} + + +/** + * Scaling factors for the conversion of raw sensor data to floating point g + * values. Scaling factors are from mechanical characteristics in datasheet. + * + * scale/sensitivity resolution + * +-2 g 0.061 mg/LSB + * +-4 g 0.122 mg/LSB + * +-6 g 0.183 mg/LSB + * +-8 g 0.244 mg/LSB + * +-16 g 0.732 mg/LSB + */ +const static double LSM303D_A_SCALES[5] = +{ 0.061/1000, 0.122/1000, 0.183/1000, 0.244/1000, 0.732/1000 }; + +bool lsm303d_get_float_a_data (lsm303d_sensor_t* dev, lsm303d_float_a_data_t* data) +{ + if (!dev || !data) return false; + + lsm303d_raw_a_data_t raw; + + if (!lsm303d_get_raw_a_data (dev, &raw)) + return false; + + data->ax = LSM303D_A_SCALES[dev->a_scale] * raw.ax; + data->ay = LSM303D_A_SCALES[dev->a_scale] * raw.ay; + data->az = LSM303D_A_SCALES[dev->a_scale] * raw.az; + + return true; +} + + +uint8_t lsm303d_get_float_a_data_fifo (lsm303d_sensor_t* dev, lsm303d_float_a_data_fifo_t data) +{ + if (!dev || !data) return false; + + lsm303d_raw_a_data_fifo_t raw; + + uint8_t num = lsm303d_get_raw_a_data_fifo (dev, raw); + + for (int i = 0; i < num; i++) + { + data[i].ax = LSM303D_A_SCALES[dev->a_scale] * raw[i].ax; + data[i].ay = LSM303D_A_SCALES[dev->a_scale] * raw[i].ay; + data[i].az = LSM303D_A_SCALES[dev->a_scale] * raw[i].az; + } + return num; +} + + +/** + * Scaling factors for the conversion of raw sensor data to floating point Gauss + * values. Scaling factors are from sensor characteristics in datasheet. + * + * scale/sensitivity resolution + * +-2 Gauss 0.080 mGauss/LSB + * +-4 Gauss 0.160 mGauss/LSB + * +-8 Gauss 0.320 mGauss/LSB + * +-12 Gauss 0.479 mGauss/LSB + */ +const static double LSM303D_M_SCALES[5] = { 0.080/1000, 0.160/1000, 0.320/1000, 0.479/1000 }; + +bool lsm303d_get_float_m_data (lsm303d_sensor_t* dev, lsm303d_float_m_data_t* data) +{ + if (!dev || !data) return false; + + lsm303d_raw_m_data_t raw; + + if (!lsm303d_get_raw_m_data (dev, &raw)) + return false; + + data->mx = LSM303D_M_SCALES[dev->m_scale] * raw.mx; + data->my = LSM303D_M_SCALES[dev->m_scale] * raw.my; + data->mz = LSM303D_M_SCALES[dev->m_scale] * raw.mz; + + return true; +} + + +bool lsm303d_get_raw_a_data (lsm303d_sensor_t* dev, lsm303d_raw_a_data_t* raw) +{ + if (!dev || !raw) return false; + + dev->error_code = LSM303D_OK; + + // abort if not in bypass mode + if (dev->fifo_mode != lsm303d_bypass) + { + dev->error_code = LSM303D_SENSOR_IN_BYPASS_MODE; + error_dev ("Sensor is in FIFO mode, use lsm303d_get_*_data_fifo to get data", + __FUNCTION__, dev); + return false; + } + + uint8_t buf[6]; + + // read raw data sample + if (!lsm303d_reg_read (dev, LSM303D_REG_OUT_X_L_A, buf, 6)) + { + error_dev ("Could not get raw data sample", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_RAW_A_DATA_FAILED; + return false; + } + + raw->ax = buf[1] << 8 | buf[0]; + raw->ay = buf[3] << 8 | buf[2]; + raw->az = buf[5] << 8 | buf[4]; + + return true; +} + + +uint8_t lsm303d_get_raw_a_data_fifo (lsm303d_sensor_t* dev, lsm303d_raw_a_data_fifo_t raw) +{ + if (!dev) return 0; + + dev->error_code = LSM303D_OK; + + // in bypass mode, use lsm303d_get_raw_data to return one sample + if (dev->fifo_mode == lsm303d_bypass) + return lsm303d_get_raw_a_data (dev, raw) ? 1 : 0; + + struct lsm303d_reg_fifo_src fifo_src; + + // read FIFO state + if (!lsm303d_reg_read (dev, LSM303D_REG_FIFO_SRC, (uint8_t*)&fifo_src, 1)) + { + dev->error_code |= LSM303D_FIFO_GET_SRC_FAILED; + error_dev ("Could not get fifo source register data", __FUNCTION__, dev); + return 0; + } + + // if nothing is in the FIFO, just return with 0 + if (fifo_src.EMPTY) + return 0; + + uint8_t samples = fifo_src.FFS + (fifo_src.OVRN ? 1 : 0); + uint8_t buf[6]; + + // read samples from FIFO + for (int i = 0; i < samples; i++) + { + if (!lsm303d_reg_read (dev, LSM303D_REG_OUT_X_L_A, buf, 6)) + { + error_dev ("Could not get raw data samples", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_RAW_A_DATA_FIFO_FAILED; + return i; + } + + raw[i].ax = buf[1] << 8 | buf[0]; + raw[i].ay = buf[3] << 8 | buf[2]; + raw[i].az = buf[5] << 8 | buf[4]; + } + lsm303d_reg_read (dev, LSM303D_REG_FIFO_SRC, (uint8_t*)&fifo_src, 1); + + // if FFS is not 0 after all samples read, ODR is higher than fetching rate + if (fifo_src.FFS) + { + dev->error_code = LSM303D_ODR_TOO_HIGH; + error_dev ("New samples were stored in FIFO while reading, " + "output data rate (ODR) might be too high", __FUNCTION__, dev); + return 0; + } + + if (dev->fifo_mode == lsm303d_fifo && samples == 32) + { + // clean FIFO + lsm303d_update_reg (dev, LSM303D_REG_FIFO_CTRL, lsm303d_reg_fifo_ctrl, FM, lsm303d_bypass); + lsm303d_update_reg (dev, LSM303D_REG_FIFO_CTRL, lsm303d_reg_fifo_ctrl, FM, lsm303d_fifo); + } + + return samples; +} + + +bool lsm303d_get_raw_m_data (lsm303d_sensor_t* dev, lsm303d_raw_m_data_t* raw) +{ + if (!dev || !raw) return false; + + dev->error_code = LSM303D_OK; + + uint8_t buf[6]; + + // read raw data sample + if (!lsm303d_reg_read (dev, LSM303D_REG_OUT_X_L_M, buf, 6)) + { + error_dev ("Could not get raw data sample", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_RAW_M_DATA_FAILED; + return false; + } + + raw->mx = buf[1] << 8 | buf[0]; + raw->my = buf[3] << 8 | buf[2]; + raw->mz = buf[5] << 8 | buf[4]; + + return true; +} + + +bool lsm303d_enable_int (lsm303d_sensor_t* dev, + lsm303d_int_type_t type, + lsm303d_int_signal_t signal, bool value) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + struct lsm303d_reg_int_ctrl_m int_ctrl_m; + struct lsm303d_reg_ctrl3 ctrl3; + struct lsm303d_reg_ctrl4 ctrl4; + + uint8_t* reg1 = NULL; + uint8_t* reg2 = NULL; + uint8_t addr1 = 0; + uint8_t addr2 = 0; + + // determine the addr of the register to change + if (type == lsm303d_int_m_thresh) + { + reg1 = (uint8_t*)&int_ctrl_m; + addr1 = LSM303D_REG_INT_CTRL_M; + + reg2 = (signal == lsm303d_int1_signal) ? (uint8_t*)&ctrl3 : (uint8_t*)&ctrl4; + addr2 = (signal == lsm303d_int1_signal) ? LSM303D_REG_CTRL3 : LSM303D_REG_CTRL4; + } + else if (type == lsm303d_int_fifo_empty || signal == lsm303d_int1_signal) + { + reg1 = (uint8_t*)&ctrl3; + addr1 = LSM303D_REG_CTRL3; + } + else + { + reg1 = (uint8_t*)&ctrl4; + addr1 = LSM303D_REG_CTRL4; + } + + // read the register + if ((reg1 && !lsm303d_reg_read (dev, addr1, reg1, 1)) || + (reg2 && !lsm303d_reg_read (dev, addr2, reg2, 1))) + { + error_dev ("Could not read interrupt control registers", __FUNCTION__, dev); + dev->error_code |= LSM303D_INT_ENABLE_FAILED; + return false; + } + + // change the register + switch (type) + { + case lsm303d_int_a_data_ready: if (signal == lsm303d_int1_signal) + ctrl3.INT1_DRDY_A = value; + else + ctrl4.INT2_DRDY_A = value; + break; + + case lsm303d_int_m_data_ready: if (signal == lsm303d_int1_signal) + ctrl3.INT1_DRDY_M = value; + else + ctrl4.INT2_DRDY_M = value; + break; + + case lsm303d_int_m_thresh: int_ctrl_m.MIEN = value; + if (signal == lsm303d_int1_signal) + ctrl3.INT1_IGM = value; + else + ctrl4.INT2_IGM = value; + break; + + case lsm303d_int_fifo_empty: ctrl3.INT1_EMPTY = value; + break; + + case lsm303d_int_fifo_thresh: ctrl4.INT2_FTH = value; + break; + + case lsm303d_int_fifo_overrun: ctrl4.INT2_FTH = value; + break; + + case lsm303d_int_event1: if (signal == lsm303d_int1_signal) + ctrl3.INT1_IG1 = value; + else + ctrl4.INT2_IG1 = value; + break; + + case lsm303d_int_event2: if (signal == lsm303d_int1_signal) + ctrl3.INT1_IG2 = value; + else + ctrl4.INT2_IG2 = value; + break; + + case lsm303d_int_click: if (signal == lsm303d_int1_signal) + ctrl3.INT1_Click = value; + else + ctrl4.INT2_Click = value; + break; + + default: error_dev ("Wrong interrupt type in enable function", __FUNCTION__, dev); + dev->error_code |= LSM303D_INT_TYPE_WRONG; + return false; + } + + if ((reg1 && !lsm303d_reg_write (dev, addr1, reg1, 1)) || + (reg2 && !lsm303d_reg_write (dev, addr2, reg2, 1))) + { + error_dev ("Could not enable/disable interrupt", __FUNCTION__, dev); + dev->error_code |= LSM303D_INT_ENABLE_FAILED; + return false; + } + + return true; +} + + +bool lsm303d_get_int_data_source (lsm303d_sensor_t* dev, + lsm303d_int_data_source_t* source) +{ + if (!dev || !source) return false; + + dev->error_code = LSM303D_OK; + + uint8_t status_a; + uint8_t status_m; + struct lsm303d_reg_fifo_src fifo_src; + + if (!lsm303d_reg_read (dev, LSM303D_REG_STATUS_A, &status_a, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_STATUS_M, &status_m, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_FIFO_SRC, (uint8_t*)&fifo_src, 1)) + { + error_dev ("Could not read source of interrupt INT1/INT2 from sensor", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_INT_DATA_SOURCE_FAILED; + return false; + } + + source->a_data_ready = status_a & LSM303D_ANY_A_DATA_READY; + source->m_data_ready = status_m & LSM303D_ANY_M_DATA_READY; + + source->fifo_empty = fifo_src.EMPTY; + source->fifo_thresh = fifo_src.FTH; + source->fifo_overrun = fifo_src.OVRN; + + return true; +} + + +bool lsm303d_set_int_m_thresh_config (lsm303d_sensor_t* dev, + lsm303d_int_m_thresh_config_t* config) +{ + if (!dev || !config) return false; + + dev->error_code = LSM303D_OK; + + struct lsm303d_reg_int_ctrl_m int_ctrl_m; + + if (!lsm303d_reg_read (dev, LSM303D_REG_INT_CTRL_M, (uint8_t*)&int_ctrl_m, 1)) + { + error_dev ("Could not read configuration of magnetic threshold interrupt", __FUNCTION__, dev); + dev->error_code |= LSM303D_SET_M_THRESH_CONFIG_FAILED; + return false; + } + + int_ctrl_m.XMIEN = config->x_enabled; + int_ctrl_m.YMIEN = config->y_enabled; + int_ctrl_m.ZMIEN = config->z_enabled; + + int_ctrl_m.MIEL = config->latch; + int_ctrl_m.MIEA = config->signal_level; + + uint8_t int_ths_m [2] = { config->threshold & 0xff, config->threshold >> 8 }; + + if (// write the threshold to registers INT_THS_*_M + !lsm303d_reg_write (dev, LSM303D_REG_INT_THS_L_M, int_ths_m, 2) || + + // write configuration to INT_CTRL_M + !lsm303d_reg_write (dev, LSM303D_REG_INT_CTRL_M, (uint8_t*)&int_ctrl_m, 1)) + { + error_dev ("Could not configure magnetic threshold interrupt", __FUNCTION__, dev); + dev->error_code |= LSM303D_SET_M_THRESH_CONFIG_FAILED; + return false; + } + + return true; +} + + +bool lsm303d_get_int_m_thresh_config (lsm303d_sensor_t* dev, + lsm303d_int_m_thresh_config_t* config) +{ + if (!dev || !config) return false; + + dev->error_code = LSM303D_OK; + + struct lsm303d_reg_int_ctrl_m int_ctrl_m; + uint8_t int_ths_m [2]; + + if (!lsm303d_reg_read (dev, LSM303D_REG_INT_THS_L_M, int_ths_m, 2) || + !lsm303d_reg_read (dev, LSM303D_REG_INT_CTRL_M , (uint8_t*)&int_ctrl_m, 1)) + { + error_dev ("Could not read configuration of magnetic threshold interrupt", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_M_THRESH_CONFIG_FAILED; + return false; + } + + config->x_enabled = int_ctrl_m.XMIEN; + config->y_enabled = int_ctrl_m.YMIEN; + config->z_enabled = int_ctrl_m.ZMIEN; + + config->latch = int_ctrl_m.MIEL; + config->signal_level = int_ctrl_m.MIEA; + + config->threshold = int_ths_m[1] << 8 | int_ths_m[0]; + + return true; +} + + +bool lsm303d_get_int_m_thresh_source (lsm303d_sensor_t* dev, + lsm303d_int_m_thresh_source_t* source) +{ + if (!dev || !source) return false; + + dev->error_code = LSM303D_OK; + + struct lsm303d_reg_int_src_m int_src_m; + struct lsm303d_reg_int_ctrl_m int_ctrl_m; + + if (!lsm303d_reg_read (dev, LSM303D_REG_INT_SRC_M , (uint8_t*)&int_src_m , 1) || + !lsm303d_reg_read (dev, LSM303D_REG_INT_CTRL_M, (uint8_t*)&int_ctrl_m, 1)) + { + error_dev ("Could not read source of interrupt INT from sensor", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_M_THRESH_SOURCE_FAILED; + return false; + } + + source->active = int_src_m.MINT; + + source->x_pos = int_src_m.M_PTH_X & int_ctrl_m.XMIEN; + source->x_neg = int_src_m.M_NTH_X & int_ctrl_m.XMIEN; + + source->y_pos = int_src_m.M_PTH_Y & int_ctrl_m.YMIEN; + source->y_neg = int_src_m.M_NTH_Y & int_ctrl_m.YMIEN; + + source->z_pos = int_src_m.M_PTH_Z & int_ctrl_m.ZMIEN; + source->z_neg = int_src_m.M_NTH_Z & int_ctrl_m.ZMIEN; + + return true; +} + + +bool lsm303d_set_int_event_config (lsm303d_sensor_t* dev, + lsm303d_int_event_config_t* config, + lsm303d_int_event_gen_t gen) +{ + if (!dev || !config) return false; + + dev->error_code = LSM303D_OK; + + struct lsm303d_reg_ig_cfgx ig_cfgx; + + ig_cfgx.XLIE = config->x_low_enabled; + ig_cfgx.XHIE = config->x_high_enabled; + + ig_cfgx.YLIE = config->y_low_enabled; + ig_cfgx.YHIE = config->y_high_enabled; + + ig_cfgx.ZLIE = config->z_low_enabled; + ig_cfgx.ZHIE = config->z_high_enabled; + + bool d4d_int = false; + + switch (config->mode) + { + case lsm303d_or : ig_cfgx.AOI = 0; ig_cfgx.D6D = 0; break; + case lsm303d_and : ig_cfgx.AOI = 1; ig_cfgx.D6D = 0; break; + + case lsm303d_4d_movement : d4d_int = true; + case lsm303d_6d_movement : ig_cfgx.AOI = 0; ig_cfgx.D6D = 1; break; + + case lsm303d_4d_position : d4d_int = true; + case lsm303d_6d_position : ig_cfgx.AOI = 1; ig_cfgx.D6D = 1; break; + } + + uint8_t ig_cfgx_addr = (gen == lsm303d_int_event1_gen) ? LSM303D_REG_IG_CFG1 : LSM303D_REG_IG_CFG2; + uint8_t ig_thsx_addr = (gen == lsm303d_int_event1_gen) ? LSM303D_REG_IG_THS1 : LSM303D_REG_IG_THS2; + uint8_t ig_durx_addr = (gen == lsm303d_int_event1_gen) ? LSM303D_REG_IG_DUR1 : LSM303D_REG_IG_DUR2; + + if (// write the thresholds to registers IG_THSx + !lsm303d_reg_write (dev, ig_thsx_addr, &config->threshold, 1) || + + // write duration configuration to IG_DURx + !lsm303d_reg_write (dev, ig_durx_addr, &config->duration, 1) || + + // write configuration to IG_CFGx + !lsm303d_reg_write (dev, ig_cfgx_addr, (uint8_t*)&ig_cfgx, 1)) + { + error_dev ("Could not configure interrupt INT1", __FUNCTION__, dev); + dev->error_code |= LSM303D_SET_EVENT_CONFIG_FAILED; + return false; + } + + if (gen == lsm303d_int_event1_gen) + { + lsm303d_update_reg (dev, LSM303D_REG_CTRL5, lsm303d_reg_ctrl5, LIR1, config->latch); + } + else + { + lsm303d_update_reg (dev, LSM303D_REG_CTRL5, lsm303d_reg_ctrl5, LIR2, config->latch); + } + lsm303d_update_reg (dev, LSM303D_REG_INT_CTRL_M, lsm303d_reg_int_ctrl_m, D4D, d4d_int); + + return true; +} + + +bool lsm303d_get_int_event_config (lsm303d_sensor_t* dev, + lsm303d_int_event_config_t* config, + lsm303d_int_event_gen_t gen) +{ + if (!dev || !config) return false; + + dev->error_code = LSM303D_OK; + + uint8_t ig_cfgx_addr = (gen == lsm303d_int_event1_gen) ? LSM303D_REG_IG_CFG1 : LSM303D_REG_IG_CFG2; + uint8_t ig_thsx_addr = (gen == lsm303d_int_event1_gen) ? LSM303D_REG_IG_THS1 : LSM303D_REG_IG_THS2; + uint8_t ig_durx_addr = (gen == lsm303d_int_event1_gen) ? LSM303D_REG_IG_DUR1 : LSM303D_REG_IG_DUR2; + + struct lsm303d_reg_int_ctrl_m int_ctrl_m; + struct lsm303d_reg_ig_cfgx ig_cfgx; + struct lsm303d_reg_ctrl3 ctrl3; + struct lsm303d_reg_ctrl5 ctrl5; + + if (!lsm303d_reg_read (dev, ig_cfgx_addr, (uint8_t*)&ig_cfgx, 1) || + !lsm303d_reg_read (dev, ig_thsx_addr, (uint8_t*)&config->threshold, 1) || + !lsm303d_reg_read (dev, ig_durx_addr, (uint8_t*)&config->duration, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_INT_CTRL_M, (uint8_t*)&int_ctrl_m, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_CTRL3, (uint8_t*)&ctrl3, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_CTRL5, (uint8_t*)&ctrl5, 1)) + { + error_dev ("Could not read interrupt configuration from sensor", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_EVENT_CONFIG_FAILED; + return false; + } + + config->x_low_enabled = ig_cfgx.XLIE; + config->x_high_enabled = ig_cfgx.XHIE; + + config->y_low_enabled = ig_cfgx.YLIE; + config->y_high_enabled = ig_cfgx.YHIE; + + config->z_low_enabled = ig_cfgx.ZLIE; + config->z_high_enabled = ig_cfgx.ZHIE; + + config->latch = (gen == lsm303d_int_event1_gen) ? ctrl5.LIR1 : ctrl5.LIR2; + + bool d4d_int = int_ctrl_m.D4D; + + if (ig_cfgx.AOI) + { + if (ig_cfgx.D6D && d4d_int) + config->mode = lsm303d_4d_position; + else if (ig_cfgx.D6D && !d4d_int) + config->mode = lsm303d_6d_position; + else + config->mode = lsm303d_and; + } + else + { + if (ig_cfgx.D6D && d4d_int) + config->mode = lsm303d_4d_movement; + else if (ig_cfgx.D6D && !d4d_int) + config->mode = lsm303d_6d_movement; + else + config->mode = lsm303d_or; + } + + return true; +} + + +bool lsm303d_get_int_event_source (lsm303d_sensor_t* dev, + lsm303d_int_event_source_t* source, + lsm303d_int_event_gen_t gen) +{ + if (!dev || !source) return false; + + dev->error_code = LSM303D_OK; + + uint8_t ig_cfgx_addr = (gen == lsm303d_int_event1_gen) ? LSM303D_REG_IG_CFG1 : LSM303D_REG_IG_CFG2; + uint8_t ig_srcx_addr = (gen == lsm303d_int_event1_gen) ? LSM303D_REG_IG_SRC1 : LSM303D_REG_IG_SRC2; + + struct lsm303d_reg_ig_cfgx ig_cfgx; + struct lsm303d_reg_ig_srcx ig_srcx; + + if (!lsm303d_reg_read (dev, ig_srcx_addr, (uint8_t*)&ig_srcx, 1) || + !lsm303d_reg_read (dev, ig_cfgx_addr, (uint8_t*)&ig_cfgx, 1)) + { + error_dev ("Could not read source of interrupt INT1/INT2 from sensor", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_EVENT_SOURCE_FAILED; + return false; + } + + + source->active = ig_srcx.IA; + source->x_low = ig_srcx.XL & ig_cfgx.XLIE; + source->x_high = ig_srcx.XH & ig_cfgx.XHIE; + source->y_low = ig_srcx.YL & ig_cfgx.YLIE; + source->y_high = ig_srcx.YH & ig_cfgx.YHIE; + source->z_low = ig_srcx.ZL & ig_cfgx.ZLIE; + source->z_high = ig_srcx.ZH & ig_cfgx.ZHIE; + + return true; +} + + +bool lsm303d_set_int_click_config (lsm303d_sensor_t* dev, + lsm303d_int_click_config_t* config) +{ + if (!dev || !config) return false; + + dev->error_code = LSM303D_OK; + + struct lsm303d_reg_click_cfg click_cfg; + + click_cfg.XS = config->x_single; + click_cfg.XD = config->x_double; + + click_cfg.YS = config->y_single; + click_cfg.YD = config->y_double; + + click_cfg.ZS = config->z_single; + click_cfg.ZD = config->z_double; + + uint8_t click_ths = config->threshold | ((config->latch) ? 0x80 : 0x00); + + if (!lsm303d_reg_write (dev, LSM303D_REG_CLICK_CFG , (uint8_t*)&click_cfg, 1) || + !lsm303d_reg_write (dev, LSM303D_REG_CLICK_THS , (uint8_t*)&click_ths, 1) || + !lsm303d_reg_write (dev, LSM303D_REG_TIME_LIMIT , (uint8_t*)&config->time_limit, 1) || + !lsm303d_reg_write (dev, LSM303D_REG_TIME_LATENCY, (uint8_t*)&config->time_latency, 1) || + !lsm303d_reg_write (dev, LSM303D_REG_TIME_WINDOW , (uint8_t*)&config->time_window, 1)) + { + error_dev ("Could not configure click detection interrupt", __FUNCTION__, dev); + dev->error_code |= LSM303D_SET_CLICK_CONFIG_FAILED; + return false; + } + + return true; +} + +bool lsm303d_get_int_click_config (lsm303d_sensor_t* dev, + lsm303d_int_click_config_t* config) +{ + if (!dev || !config) return false; + + dev->error_code = LSM303D_OK; + + struct lsm303d_reg_click_cfg click_cfg; + uint8_t click_ths; + + if (!lsm303d_reg_read (dev, LSM303D_REG_CLICK_CFG , (uint8_t*)&click_cfg, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_CLICK_THS , (uint8_t*)&click_ths, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_TIME_LIMIT , (uint8_t*)&config->time_limit, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_TIME_LATENCY, (uint8_t*)&config->time_latency, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_TIME_WINDOW , (uint8_t*)&config->time_window, 1)) + { + error_dev ("Could not configure click detection interrupt", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_CLICK_CONFIG_FAILED; + return false; + } + + config->x_single = click_cfg.XS; + config->x_double = click_cfg.XD; + + config->y_single = click_cfg.YS; + config->y_double = click_cfg.YD; + + config->z_single = click_cfg.ZS; + config->z_double = click_cfg.ZD; + + config->threshold= click_ths & 0x7f; + config->latch = click_ths & 0x80; + + return true; +} + +bool lsm303d_get_int_click_source (lsm303d_sensor_t* dev, + lsm303d_int_click_source_t* source) +{ + if (!dev || !source) return false; + + dev->error_code = LSM303D_OK; + + if (!lsm303d_reg_read (dev, LSM303D_REG_CLICK_SRC, (uint8_t*)source, 1)) + { + error_dev ("Could not read source of click interrupt from sensor", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_CLICK_SOURCE_FAILED; + return false; + } + + return true; +} + + + +bool lsm303d_config_int_signals (lsm303d_sensor_t* dev, + lsm303d_int_signal_type_t type) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + lsm303d_update_reg (dev, LSM303D_REG_INT_CTRL_M, lsm303d_reg_int_ctrl_m, PP_OD, type); + + return true; +} + + +bool lsm303d_config_a_hpf (lsm303d_sensor_t* dev, + lsm303d_hpf_mode_t mode, + bool data, bool click, bool int1, bool int2) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + lsm303d_update_reg (dev, LSM303D_REG_CTRL7, lsm303d_reg_ctrl7, AHPM , mode); + lsm303d_update_reg (dev, LSM303D_REG_CTRL7, lsm303d_reg_ctrl7, AFDS , data); + lsm303d_update_reg (dev, LSM303D_REG_CTRL0, lsm303d_reg_ctrl0, HP_Click, click); + lsm303d_update_reg (dev, LSM303D_REG_CTRL0, lsm303d_reg_ctrl0, HPIS1 , int1); + lsm303d_update_reg (dev, LSM303D_REG_CTRL0, lsm303d_reg_ctrl0, HPIS2 , int2); + + int8_t x_ref; + int8_t y_ref; + int8_t z_ref; + + if (mode == lsm303d_hpf_normal) + lsm303d_get_a_hpf_ref (dev, &x_ref, &y_ref, &z_ref); + + return true; +} + + +bool lsm303d_set_a_hpf_ref (lsm303d_sensor_t* dev, + int8_t x_ref, int8_t y_ref, int8_t z_ref) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + if (!lsm303d_reg_write (dev, LSM303D_REG_REFERENCE_X, (uint8_t*)&x_ref, 1) || + !lsm303d_reg_write (dev, LSM303D_REG_REFERENCE_Y, (uint8_t*)&y_ref, 1) || + !lsm303d_reg_write (dev, LSM303D_REG_REFERENCE_Z, (uint8_t*)&z_ref, 1)) + { + error_dev ("Could not set high pass filter reference", __FUNCTION__, dev); + dev->error_code |= LSM303D_SET_HPF_REF_FAILED; + return false; + } + + return true; +} + + +bool lsm303d_get_a_hpf_ref (lsm303d_sensor_t* dev, + int8_t* x_ref, int8_t* y_ref, int8_t* z_ref) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + if (!lsm303d_reg_read (dev, LSM303D_REG_REFERENCE_X, (uint8_t*)x_ref, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_REFERENCE_Y, (uint8_t*)y_ref, 1) || + !lsm303d_reg_read (dev, LSM303D_REG_REFERENCE_Z, (uint8_t*)z_ref, 1)) + { + error_dev ("Could not get high pass filter reference", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_HPF_REF_FAILED; + return false; + } + + return true; +} + + +bool lsm303d_set_m_offset (lsm303d_sensor_t* dev, + int16_t x_off, int16_t y_off, int16_t z_off) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + uint8_t buf [6] = { + x_off & 0xff, x_off >> 8, + y_off & 0xff, y_off >> 8, + z_off & 0xff, z_off >> 8 + }; + + if (!lsm303d_reg_write (dev, LSM303D_REG_OFFSET_X_L_M, buf, 6)) + { + error_dev ("Could not set magnetic offset", __FUNCTION__, dev); + dev->error_code |= LSM303D_SET_M_OFFSET_FAILED; + return false; + } + + return true; +} + + +bool lsm303d_get_m_offset (lsm303d_sensor_t* dev, + int16_t* x_off, int16_t* y_off, int16_t* z_off) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + uint8_t buf [6]; + + if (!lsm303d_reg_read (dev, LSM303D_REG_OFFSET_X_L_M, buf, 6)) + { + error_dev ("Could not get magnetic offset", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_M_OFFSET_FAILED; + return false; + } + + *x_off = buf[1] << 8 | buf[0]; + *y_off = buf[3] << 8 | buf[2]; + *z_off = buf[5] << 8 | buf[4]; + + return true; +} + + +bool lsm303d_enable_temperature (lsm303d_sensor_t* dev, bool enable) +{ + lsm303d_update_reg (dev, LSM303D_REG_CTRL5, lsm303d_reg_ctrl5, TEMP_EN, enable); + + return true; +} + + +float lsm303d_get_temperature (lsm303d_sensor_t* dev) +{ + uint8_t regs[2]; + + // read raw data sample + if (!lsm303d_reg_read (dev, LSM303D_REG_TEMP_OUT_L, regs, 2)) + { + error_dev ("Could not get temperature data sample", __FUNCTION__, dev); + dev->error_code |= LSM303D_GET_RAW_T_DATA_FAILED; + return false; + } + + return (int16_t)(regs[1] << 8 | regs[0]) / 8.0 + 25.0; +} + + +/** Functions for internal use only */ + +/** + * @brief Check the chip ID to test whether sensor is available + */ +static bool lsm303d_is_available (lsm303d_sensor_t* dev) +{ + uint8_t chip_id; + + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + if (!lsm303d_reg_read (dev, LSM303D_REG_WHO_AM_I, &chip_id, 1)) + return false; + + if (chip_id != LSM303D_CHIP_ID) + { + error_dev ("Chip id %02x is wrong, should be %02x.", + __FUNCTION__, dev, chip_id, LSM303D_CHIP_ID); + dev->error_code = LSM303D_WRONG_CHIP_ID; + return false; + } + + return true; +} + +static bool lsm303d_reset (lsm303d_sensor_t* dev) +{ + if (!dev) return false; + + dev->error_code = LSM303D_OK; + + uint8_t int_ctrl_m = 0x08; // 0xe8 + uint8_t ctrl_regs[] = { 0x00, 0x00 /*0x07*/, 0x00, 0x00, 0x00, 0x18, 0x20, 0x01 }; + uint8_t null_regs[11] = { 0 }; + + // initialize sensor completely including setting in power down mode + lsm303d_reg_write (dev, LSM303D_REG_INT_CTRL_M , &int_ctrl_m, 1 ); + lsm303d_reg_write (dev, LSM303D_REG_INT_THS_L_M, null_regs , 11); + lsm303d_reg_write (dev, LSM303D_REG_CTRL0 , ctrl_regs , 8 ); + lsm303d_reg_write (dev, LSM303D_REG_FIFO_CTRL , null_regs , 1 ); + lsm303d_reg_write (dev, LSM303D_REG_IG_CFG1 , null_regs , 1 ); + lsm303d_reg_write (dev, LSM303D_REG_IG_THS1 , null_regs , 2 ); + lsm303d_reg_write (dev, LSM303D_REG_IG_CFG2 , null_regs , 1 ); + lsm303d_reg_write (dev, LSM303D_REG_IG_THS2 , null_regs , 2 ); + lsm303d_reg_write (dev, LSM303D_REG_CLICK_CFG , null_regs , 1 ); + lsm303d_reg_write (dev, LSM303D_REG_CLICK_THS , null_regs , 4 ); + + return true; +} + + +bool lsm303d_reg_read(lsm303d_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + return (dev->addr) ? lsm303d_i2c_read (dev, reg, data, len) + : lsm303d_spi_read (dev, reg, data, len); +} + + +bool lsm303d_reg_write(lsm303d_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + return (dev->addr) ? lsm303d_i2c_write (dev, reg, data, len) + : lsm303d_spi_write (dev, reg, data, len); +} + + +#define LSM303D_SPI_BUF_SIZE 64 // SPI register data buffer size of ESP866 + +#define LSM303D_SPI_READ_FLAG 0x80 +#define LSM303D_SPI_WRITE_FLAG 0x00 +#define LSM303D_SPI_AUTO_INC_FLAG 0x40 + +static bool lsm303d_spi_read(lsm303d_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + if (len >= LSM303D_SPI_BUF_SIZE) + { + dev->error_code |= LSM303D_SPI_BUFFER_OVERFLOW; + error_dev ("Error on read from SPI slave on bus 1. Tried to transfer " + "more than %d byte in one read operation.", + __FUNCTION__, dev, LSM303D_SPI_BUF_SIZE); + return false; + } + + uint8_t addr = (reg & 0x3f) | LSM303D_SPI_READ_FLAG | LSM303D_SPI_AUTO_INC_FLAG; + + static uint8_t mosi[LSM303D_SPI_BUF_SIZE]; + static uint8_t miso[LSM303D_SPI_BUF_SIZE]; + + memset (mosi, 0xff, LSM303D_SPI_BUF_SIZE); + memset (miso, 0xff, LSM303D_SPI_BUF_SIZE); + + mosi[0] = addr; + + if (!spi_transfer_pf (dev->bus, dev->cs, mosi, miso, len+1)) + { + error_dev ("Could not read data from SPI", __FUNCTION__, dev); + dev->error_code |= LSM303D_SPI_READ_FAILED; + return false; + } + + // shift data one by left, first byte received while sending register address is invalid + for (int i=0; i < len; i++) + data[i] = miso[i+1]; + + #ifdef LSM303D_DEBUG_LEVEL_2 + printf("LSM303D %s: read the following bytes from reg %02x: ", __FUNCTION__, reg); + for (int i=0; i < len; i++) + printf("%02x ", data[i]); + printf("\n"); + #endif + + return true; +} + + +static bool lsm303d_spi_write(lsm303d_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + uint8_t addr = (reg & 0x3f) | LSM303D_SPI_WRITE_FLAG | LSM303D_SPI_AUTO_INC_FLAG; + + static uint8_t mosi[LSM303D_SPI_BUF_SIZE]; + + if (len >= LSM303D_SPI_BUF_SIZE) + { + dev->error_code |= LSM303D_SPI_BUFFER_OVERFLOW; + error_dev ("Error on write to SPI slave on bus 1. Tried to transfer more" + "than %d byte in one write operation.", + __FUNCTION__, dev, LSM303D_SPI_BUF_SIZE); + + return false; + } + + reg &= 0x7f; + + // first byte in output is the register address + mosi[0] = addr; + + // shift data one byte right, first byte in output is the register address + for (int i = 0; i < len; i++) + mosi[i+1] = data[i]; + + #ifdef LSM303D_DEBUG_LEVEL_2 + printf("LSM303D %s: Write the following bytes to reg %02x: ", __FUNCTION__, reg); + for (int i = 1; i < len+1; i++) + printf("%02x ", mosi[i]); + printf("\n"); + #endif + + if (!spi_transfer_pf (dev->bus, dev->cs, mosi, NULL, len+1)) + { + error_dev ("Could not write data to SPI.", __FUNCTION__, dev); + dev->error_code |= LSM303D_SPI_WRITE_FAILED; + return false; + } + + return true; +} + + +#define I2C_AUTO_INCREMENT (0x80) + +static bool lsm303d_i2c_read(lsm303d_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + debug_dev ("Read %d byte from i2c slave register %02x.", __FUNCTION__, dev, len, reg); + + if (len > 1) + reg |= I2C_AUTO_INCREMENT; + + int result = i2c_slave_read(dev->bus, dev->addr, ®, data, len); + + if (result) + { + dev->error_code |= (result == -EBUSY) ? LSM303D_I2C_BUSY : LSM303D_I2C_READ_FAILED; + error_dev ("Error %d on read %d byte from I2C slave register %02x.", + __FUNCTION__, dev, result, len, reg); + return false; + } + +# ifdef LSM303D_DEBUG_LEVEL_2 + printf("LSM303D %s: Read following bytes: ", __FUNCTION__); + printf("%02x: ", reg & 0x7f); + for (int i=0; i < len; i++) + printf("%02x ", data[i]); + printf("\n"); +# endif + + return true; +} + + +static bool lsm303d_i2c_write(lsm303d_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len) +{ + if (!dev || !data) return false; + + debug_dev ("Write %d byte to i2c slave register %02x.", __FUNCTION__, dev, len, reg); + + if (len > 1) + reg |= I2C_AUTO_INCREMENT; + + int result = i2c_slave_write(dev->bus, dev->addr, ®, data, len); + + if (result) + { + dev->error_code |= (result == -EBUSY) ? LSM303D_I2C_BUSY : LSM303D_I2C_WRITE_FAILED; + error_dev ("Error %d on write %d byte to i2c slave register %02x.", + __FUNCTION__, dev, result, len, reg); + return false; + } + +# ifdef LSM303D_DEBUG_LEVEL_2 + printf("LSM303D %s: Wrote the following bytes: ", __FUNCTION__); + printf("%02x: ", reg & 0x7f); + for (int i=0; i < len; i++) + printf("%02x ", data[i]); + printf("\n"); +# endif + + return true; +} diff --git a/extras/lsm303d/lsm303d.h b/extras/lsm303d/lsm303d.h new file mode 100644 index 0000000..514469a --- /dev/null +++ b/extras/lsm303d/lsm303d.h @@ -0,0 +1,587 @@ +/** + * Driver for LSM303D 3-axes digital accelerometer and magnetometer connected + * either to I2C or SPI. + * + * This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos) + * [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32 + * and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux + * based systems using a wrapper library for ESP8266 functions. + * + * --------------------------------------------------------------------------- + * + * The BSD License (3-clause license) + * + * Copyright (c) 2018 Gunar Schorcht (https://github.com/gschorcht) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LSM303D_H__ +#define __LSM303D_H__ + +// Uncomment one of the following defines to enable debug output +// #define LSM303D_DEBUG_LEVEL_1 // only error messages +// #define LSM303D_DEBUG_LEVEL_2 // debug and error messages + +// LSM303D addresses +#define LSM303D_I2C_ADDRESS_1 0x1e // SDO pin is low +#define LSM303D_I2C_ADDRESS_2 0x1d // SDO pin is high + +// LSM303D chip id +#define LSM303D_CHIP_ID 0x49 // LSM303D_REG_WHO_AM_I<7:0> + +// Definition of error codes +#define LSM303D_OK 0 +#define LSM303D_NOK -1 + +#define LSM303D_INT_ERROR_MASK 0x000f +#define LSM303D_DRV_ERROR_MASK 0xfff0 + +// Error codes for I2C and SPI interfaces ORed with LSM303D driver error codes +#define LSM303D_I2C_READ_FAILED 1 +#define LSM303D_I2C_WRITE_FAILED 2 +#define LSM303D_I2C_BUSY 3 +#define LSM303D_SPI_WRITE_FAILED 4 +#define LSM303D_SPI_READ_FAILED 5 +#define LSM303D_SPI_BUFFER_OVERFLOW 6 + +// LSM303D driver error codes ORed with error codes for I2C and SPI interfaces +#define LSM303D_WRONG_CHIP_ID ( 1 << 8) +#define LSM303D_WRONG_BANDWIDTH ( 2 << 8) +#define LSM303D_GET_RAW_A_DATA_FAILED ( 3 << 8) +#define LSM303D_GET_RAW_A_DATA_FIFO_FAILED ( 4 << 8) +#define LSM303D_GET_RAW_M_DATA_FAILED ( 5 << 8) +#define LSM303D_GET_RAW_T_DATA_FAILED ( 6 << 8) +#define LSM303D_INT_TYPE_WRONG ( 8 << 8) +#define LSM303D_INT_ENABLE_FAILED ( 9 << 8) +#define LSM303D_CONFIG_INT_SIGNALS_FAILED (10 << 8) +#define LSM303D_GET_INT_DATA_SOURCE_FAILED (11 << 8) +#define LSM303D_SET_M_THRESH_CONFIG_FAILED (12 << 8) +#define LSM303D_GET_M_THRESH_CONFIG_FAILED (13 << 8) +#define LSM303D_GET_M_THRESH_SOURCE_FAILED (14 << 8) +#define LSM303D_SET_EVENT_CONFIG_FAILED (15 << 8) +#define LSM303D_GET_EVENT_CONFIG_FAILED (16 << 8) +#define LSM303D_GET_EVENT_SOURCE_FAILED (17 << 8) +#define LSM303D_SET_CLICK_CONFIG_FAILED (18 << 8) +#define LSM303D_GET_CLICK_CONFIG_FAILED (19 << 8) +#define LSM303D_GET_CLICK_SOURCE_FAILED (20 << 8) +#define LSM303D_CONFIG_HPF_FAILED (21 << 8) +#define LSM303D_SET_HPF_REF_FAILED (22 << 8) +#define LSM303D_GET_HPF_REF_FAILED (23 << 8) +#define LSM303D_SET_M_OFFSET_FAILED (24 << 8) +#define LSM303D_GET_M_OFFSET_FAILED (25 << 8) +#define LSM303D_GET_ADC_DATA_FAILED (26 << 8) +#define LSM303D_SENSOR_IN_BYPASS_MODE (27 << 8) +#define LSM303D_SENSOR_IN_FIFO_MODE (28 << 8) +#define LSM303D_ODR_TOO_HIGH (29 << 8) +#define LSM303D_FIFO_THRESHOLD_INVALID (30 << 8) +#define LSM303D_FIFO_GET_SRC_FAILED (31 << 8) + +#include "lsm303d_platform.h" +#include "lsm303d_types.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/** + * @brief Initialize the sensor + * + * Reset the sensor and switch to power down mode. All registers are reset to + * default values. FIFO is cleared. + * + * @param bus I2C or SPI bus at which LSM303D sensor is connected + * @param addr I2C addr of the LSM303D sensor, 0 for using SPI + * @param cs SPI CS GPIO, ignored for I2C + * @return pointer to sensor data structure, or NULL on error + */ +lsm303d_sensor_t* lsm303d_init_sensor (uint8_t bus, uint8_t addr, uint8_t cs); + + +/** + * @brief Set accelerator sensor mode + * + * @param dev pointer to the sensor device data structure + * @param odr accelerator output data rate (ODR) + * @param bw accelerator anti-alias filter bandwidth + * @param x true enable x-axis, false disable x-axis + * @param y true enable y-axis, false disable y-axis + * @param z true enable z-axis, false disable z-axis + * @return true on success, false on error + */ +bool lsm303d_set_a_mode (lsm303d_sensor_t* dev, + lsm303d_a_odr_t odr, lsm303d_a_aaf_bw_t bw, + bool x, bool y, bool z); + +/** + * @brief Set magnetometer sensor mode + * + * @param dev pointer to the sensor device data structure + * @param odr magnetometer output data rate (ODR) + * @param res magnetometer resolution + * @param mode magnetometer mode (ODR) + * @return true on success, false on error + */ +bool lsm303d_set_m_mode (lsm303d_sensor_t* dev, + lsm303d_m_odr_t odr, + lsm303d_m_resolution_t res, + lsm303d_m_mode_t mode); + +/** + * @brief Set accelerator scale (full scale) + * + * @param dev pointer to the sensor device data structure + * @param scale full scale (default 2 g) + * @return true on success, false on error + */ +bool lsm303d_set_a_scale (lsm303d_sensor_t* dev, lsm303d_a_scale_t scale); + + +/** + * @brief Set magnetometer scale (full scale) + * + * @param dev pointer to the sensor device data structure + * @param scale full scale (default 4 Gauss) + * @return true on success, false on error + */ +bool lsm303d_set_m_scale (lsm303d_sensor_t* dev, lsm303d_m_scale_t scale); + + +/** + * @brief Test whether new acceleration data samples are available + * + * When the FIFO is used, it returns true if at least one acceleration + * data sample is stored in the FIFO. Otherwise it returns true when new + * acceleration data are available in the output registers. + * + * @param dev pointer to the sensor device data structure + * @return true on new data, otherwise false + */ +bool lsm303d_new_a_data (lsm303d_sensor_t* dev); + + +/** + * @brief Test whether new magnetometer data samples are available + * + * @param dev pointer to the sensor device data structure + * @return true on new data, otherwise false + */ +bool lsm303d_new_m_data (lsm303d_sensor_t* dev); + + +/** + * @brief Get one acceleration data sample as floating point values (unit g) + * + * Function works only in bypass mode and fails in FIFO modes. In FIFO modes, + * function *lsm303d_get_a_float_data_fifo* has to be used instead to get data. + * + * @param dev pointer to the sensor device data structure + * @param data pointer to float data structure filled with g values + * @return true on success, false on error + */ +bool lsm303d_get_float_a_data (lsm303d_sensor_t* dev, + lsm303d_float_a_data_t* data); + + +/** + * @brief Get all samples of acceleration data stored in the FIFO (unit g) + * + * In bypass mode, it returns only one sensor data sample. + * + * @param dev pointer to the sensor device data structure + * @param data array of 32 float data structures filled with g values + * @return number of data sets read from fifo on success or 0 on error + */ +uint8_t lsm303d_get_float_a_data_fifo (lsm303d_sensor_t* dev, + lsm303d_float_a_data_fifo_t data); + + +/** + * @brief Get one magnetic data sample as floating point values (unit Gauss) + * + * @param dev pointer to the sensor device data structure + * @param data pointer to float data structure filled with magnetic values + * @return true on success, false on error + */ +bool lsm303d_get_float_m_data (lsm303d_sensor_t* dev, + lsm303d_float_m_data_t* data); + + +/** + * @brief Get one sample of raw acceleration data as 16 bit two's complements + * + * Function works only in bypass mode and fails in FIFO modes. In FIFO modes, + * function *lsm303d_get_a_raw_data_fifo* has to be used instead to get data. + * + * @param dev pointer to the sensor device data structure + * @param raw pointer to raw data structure filled with values + * @return true on success, false on error + */ +bool lsm303d_get_raw_a_data (lsm303d_sensor_t* dev, lsm303d_raw_a_data_t* raw); + + +/** + * @brief Get all samples of raw sensor data stored in the FIFO + * + * In bypass mode, it returns only one raw data sample. + * + * @param dev pointer to the sensor device data structure + * @param raw array of 32 raw data structures + * @return number of data sets read from fifo on success or 0 on error + */ +uint8_t lsm303d_get_raw_a_data_fifo (lsm303d_sensor_t* dev, + lsm303d_raw_a_data_fifo_t raw); + + +/** + * @brief Get one sample of raw magnetic data as 16 bit two's complements + * + * @param dev pointer to the sensor device data structure + * @param raw pointer to raw data structure filled with values + * @return true on success, false on error + */ +bool lsm303d_get_raw_m_data (lsm303d_sensor_t* dev, lsm303d_raw_m_data_t* raw); + + +/** + * @brief Set FIFO mode (for acceleration data only) + * + * FIFO threshold can be used to generate an interrupt when FIFO content + * exceeds the value. It is ignored in bypass mode. + * + * @param dev pointer to the sensor device data structure + * @param mode FIFO mode + * @param thresh FIFO threshold (ignored in bypass mode) + * @return true on success, false on error + */ +bool lsm303d_set_fifo_mode (lsm303d_sensor_t* dev, lsm303d_fifo_mode_t mode, + uint8_t thresh); + + +/** + * @brief Enable / disable an interrupt on signal INT1 or INT2 + * + * @param dev pointer to the sensor device data structure + * @param type interrupt to be enabled or disabled + * @param signal interrupt signal that is activated for the interrupt + * @param value true to enable or false to disable the interrupt + * @return true on success, false on error + */ +bool lsm303d_enable_int (lsm303d_sensor_t* dev, + lsm303d_int_type_t type, + lsm303d_int_signal_t signal, bool value); + + +/** + * @brief Get the source of data ready and FIFO interrupts on INT1 or INT2 + * + * @param dev pointer to the sensor device data structure + * @param source pointer to the interrupt source + * @return true on success, false on error + */ +bool lsm303d_get_int_data_source (lsm303d_sensor_t* dev, + lsm303d_int_data_source_t* source); + +/** + * @brief Set the configuration of the magnetic threshold interrupt generator + * + * @param dev pointer to the sensor device data structure + * @param config pointer to the interrupt generator configuration + * @return true on success, false on error + */ +bool lsm303d_set_int_m_thresh_config (lsm303d_sensor_t* dev, + lsm303d_int_m_thresh_config_t* config); + + +/** + * @brief Get the configuration of the magnetic threshold interrupt generator + * + * @param dev pointer to the sensor device data structure + * @param config pointer to the interrupt generator configuration + * @return true on success, false on error + */ +bool lsm303d_get_int_m_thresh_config (lsm303d_sensor_t* dev, + lsm303d_int_m_thresh_config_t* config); + + +/** + * @brief Get the source of the magnetic threshold interrupt on INT/INT2 + * + * Returns a byte with flags that indicate the value(s) that triggered + * the interrupt signal (see INT_SRC_M register in datasheet for details) + * + * @param dev pointer to the sensor device data structure + * @param source pointer to the interrupt source + * @return true on success, false on error + */ +bool lsm303d_get_int_m_thresh_source (lsm303d_sensor_t* dev, + lsm303d_int_m_thresh_source_t* source); + + +/** + * @brief Set the configuration of an inertial event interrupt generator + * + * Inertial interrupt generators produce interrupts when certain inertial event + * occures (event interrupts), that is, the acceleration of defined axes is + * higher or lower than a defined threshold and one of the following event is + * recognized: axis movement or 6D/4D orientation detection. + * + * @param dev pointer to the sensor device data structure + * @param config pointer to the interrupt generator configuration + * @param gen interrupt generator to which the function is applied + * @return true on success, false on error + */ +bool lsm303d_set_int_event_config (lsm303d_sensor_t* dev, + lsm303d_int_event_config_t* config, + lsm303d_int_event_gen_t gen); + + +/** + * @brief Get the configuration of an inertial interrupt generator + * + * Inertial interrupt generators produce interrupts when certain inertial event + * occures (event interrupts), that is, the acceleration of defined axes is + * higher or lower than a defined threshold and one of the following event is + * recognized: axis movement or 6D/4D orientation detection. + * + * @param dev pointer to the sensor device data structure + * @param config pointer to the interrupt generator configuration + * @param gen interrupt generator to which the function is applied + * @return true on success, false on error + */ +bool lsm303d_get_int_event_config (lsm303d_sensor_t* dev, + lsm303d_int_event_config_t* config, + lsm303d_int_event_gen_t gen); + + +/** + * @brief Get the source of an inertial event interrupt on signal INT1/INT2 + * + * Returns a byte with flags that indicate the event that triggered + * the interrupt signal (see IG_SRCx register in datasheet for details) + * + * @param dev pointer to the sensor device data structure + * @param source pointer to the interrupt source data structure + * @param gen interrupt generator to which the function is applied + * @return true on success, false on error + */ +bool lsm303d_get_int_event_source (lsm303d_sensor_t* dev, + lsm303d_int_event_source_t* source, + lsm303d_int_event_gen_t gen); + + +/** + * @brief Set the configuration of the click detection interrupt generator + * + * Set the configuration for interrupts that are generated when single or + * double clicks are detected. + * + * @param dev pointer to the sensor device data structure + * @param config pointer to the interrupt generator configuration + * @return true on success, false on error + */ +bool lsm303d_set_int_click_config (lsm303d_sensor_t* dev, + lsm303d_int_click_config_t* config); + +/** + * @brief Get the configuration of the click detection interrupt generator + * + * Set the configuration for interrupts that are generated when single or + * double clicks are detected. + * + * @param dev pointer to the sensor device data structure + * @param config pointer to the interrupt generator configuration + * @return true on success, false on error + */ +bool lsm303d_get_int_click_config (lsm303d_sensor_t* dev, + lsm303d_int_click_config_t* config); + + +/** + * @brief Get the source of the click detection interrupt on signal INT1/INT2 + * + * Returns a byte with flags that indicate the activity which triggered + * the interrupt signal (see CLICK_SRC register in datasheet for details) + * + * @param dev pointer to the sensor device data structure + * @param source pointer to the interrupt source + * @return true on success, false on error + */ +bool lsm303d_get_int_click_source (lsm303d_sensor_t* dev, + lsm303d_int_click_source_t* source); + + +/** + * @brief Set signal configuration for INT1 and INT2 signals + * + * @param dev pointer to the sensor device data structure + * @param type define interrupt signal as pushed/pulled or open drain + * @return true on success, false on error + */ +bool lsm303d_config_int_signals (lsm303d_sensor_t* dev, + lsm303d_int_signal_type_t type); + + +/** + * @brief Configure HPF (high pass filter) for acceleration data + * + * The function resets implicitly reset the reference by a dummy read. + * + * @param dev pointer to the sensor device data structure + * @param mode filter mode + * @param data if true, use filtered data as sensor output + * @param click if true, use filtered data for CLICK function + * @param int1 if true, use filtered data for interrupt generator INT1 + * @param int2 if true, use filtered data for interrupt generator INT2 + * @return true on success, false on error + */ +bool lsm303d_config_a_hpf (lsm303d_sensor_t* dev, + lsm303d_hpf_mode_t mode, + bool data, bool click, bool int1, bool int2); + + +/** + * @brief Set HPF (high pass filter) reference for acceleration data + * + * Used to set the reference of HPF in reference mode *lsm303d_hpf_reference*. + * Used to reset the HPF in autoreset mode *lsm303d_hpf_autoreset*. + * Reference is given as two's complement. + * + * @param dev pointer to the sensor device data structure + * @param x_ref x reference *lsm303d_hpf_reference* mode, otherwise ignored + * @param y_ref y reference *lsm303d_hpf_reference* mode, otherwise ignored + * @param z_ref z reference *lsm303d_hpf_reference* mode, otherwise ignored + * @return true on success, false on error + */ +bool lsm303d_set_a_hpf_ref (lsm303d_sensor_t* dev, + int8_t x_ref, int8_t y_ref, int8_t z_ref); + + +/** + * @brief Get HPF (high pass filter) reference + * + * Used to reset the HPF in normal mode *lsm303d_hpf_normal*. + * + * @param dev pointer to the sensor device data structure + * @param x_ref pointer to variable filled with x reference + * @param y_ref pointer to variable filled with y reference + * @param z_ref pointer to variable filled with z reference + * @return true on success, false on error + */ +bool lsm303d_get_a_hpf_ref (lsm303d_sensor_t* dev, + int8_t* x_ref, int8_t* y_ref, int8_t* z_ref); + + +/** + * @brief Set magnetic offset + * + * @param dev pointer to the sensor device data structure + * @param x magnetic offset for x axis + * @param y magnetic offset for y axis + * @param z magnetic offset for z axis + * @return true on success, false on error + */ +bool lsm303d_set_m_offset (lsm303d_sensor_t* dev, + int16_t x, int16_t y, int16_t z); + + +/** + * @brief Get magnetic offset + * + * @param dev pointer to the sensor device data structure + * @param x magnetic offset for x axis + * @param y magnetic offset for y axis + * @param z magnetic offset for z axis + * @return true on success, false on error + */ +bool lsm303d_get_m_offset (lsm303d_sensor_t* dev, + int16_t* x, int16_t* y, int16_t* z); + + +/** + * @brief Enable/Disable temperature sensor + * + * @param dev pointer to the sensor device data structure + * @param enable if true, temperature sensor is enabled + * @return true on success, false on error + */ +bool lsm303d_enable_temperature (lsm303d_sensor_t* dev, bool enable); + + +/** + * @brief Get temperature + * + * @param dev pointer to the sensor device data structure + * @return temperature in degree + */ +float lsm303d_get_temperature (lsm303d_sensor_t* dev); + + +// ---- Low level interface functions ----------------------------- + +/** + * @brief Direct write to register + * + * PLEASE 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 dev pointer to the sensor device data structure + * @param reg address of the first register to be changed + * @param data pointer to the data to be written to the register + * @param len number of bytes to be written to the register + * @return true on success, false on error + */ +bool lsm303d_reg_write (lsm303d_sensor_t* dev, + uint8_t reg, uint8_t *data, uint16_t len); + +/** + * @brief Direct read from register + * + * PLEASE 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 dev pointer to the sensor device data structure + * @param reg address of the first register to be read + * @param data pointer to the data to be read from the register + * @param len number of bytes to be read from the register + * @return true on success, false on error + */ +bool lsm303d_reg_read (lsm303d_sensor_t* dev, + uint8_t reg, uint8_t *data, uint16_t len); + +#ifdef __cplusplus +} +#endif /* End of CPP guard */ + +#endif /* __LSM303D_H__ */ diff --git a/extras/lsm303d/lsm303d_platform.c b/extras/lsm303d/lsm303d_platform.c new file mode 100644 index 0000000..118d295 --- /dev/null +++ b/extras/lsm303d/lsm303d_platform.c @@ -0,0 +1,82 @@ +/** + * Driver for LSM303D 3-axes digital accelerometer and magnetometer connected + * either to I2C or SPI. + * + * This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos) + * [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32 + * and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux + * based systems using a wrapper library for ESP8266 functions. + * + * --------------------------------------------------------------------------- + * + * The BSD License (3-clause license) + * + * Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Platform file: platform specific definitions, includes and functions + */ + +#include "lsm303d_platform.h" + +// platform specific SPI functions + +static const spi_settings_t bus_settings = { + .mode = SPI_MODE0, + .freq_divider = SPI_FREQ_DIV_1M, + .msb = true, + .minimal_pins = false, + .endianness = SPI_LITTLE_ENDIAN +}; + +bool spi_device_init (uint8_t bus, uint8_t cs) +{ + gpio_enable(cs, GPIO_OUTPUT); + gpio_write (cs, true); + return true; +} + +size_t spi_transfer_pf(uint8_t bus, uint8_t cs, const uint8_t *mosi, uint8_t *miso, uint16_t len) +{ + spi_settings_t old_settings; + + spi_get_settings(bus, &old_settings); + spi_set_settings(bus, &bus_settings); + gpio_write(cs, false); + + size_t transfered = spi_transfer (bus, (const void*)mosi, (void*)miso, len, SPI_8BIT); + + gpio_write(cs, true); + spi_set_settings(bus, &old_settings); + + return transfered; +} + diff --git a/extras/lsm303d/lsm303d_platform.h b/extras/lsm303d/lsm303d_platform.h new file mode 100644 index 0000000..7daac4e --- /dev/null +++ b/extras/lsm303d/lsm303d_platform.h @@ -0,0 +1,81 @@ +/** + * Driver for LSM303D 3-axes digital accelerometer and magnetometer connected + * either to I2C or SPI. + * + * This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos) + * [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32 + * and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux + * based systems using a wrapper library for ESP8266 functions. + * + * --------------------------------------------------------------------------- + * + * The BSD License (3-clause license) + * + * Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Platform file: platform specific definitions, includes and functions + */ + +#ifndef __LSM303D_PLATFORM_H__ +#define __LSM303D_PLATFORM_H__ + +#if !defined(ESP_OPEN_RTOS) +#define ESP_OPEN_RTOS 1 +#endif + +#ifdef ESP_OPEN_RTOS // ESP8266 + +// platform specific includes + +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" + +#include "espressif/esp_common.h" +#include "espressif/sdk_private.h" + +#include "esp/uart.h" +#include "esp/spi.h" +#include "i2c/i2c.h" + +// platform specific SPI functions + +#define spi_bus_init(bus,sck,miso,mosi) // not needed on ESP8266 + +extern bool spi_device_init (uint8_t bus, uint8_t cs); +extern size_t spi_transfer_pf (uint8_t bus, uint8_t cs, + const uint8_t *mosi, uint8_t *miso, + uint16_t len); + +#endif // ESP_OPEN_RTOS + +#endif // __LSM303D_PLATFORM_H__ diff --git a/extras/lsm303d/lsm303d_types.h b/extras/lsm303d/lsm303d_types.h new file mode 100644 index 0000000..8b6787f --- /dev/null +++ b/extras/lsm303d/lsm303d_types.h @@ -0,0 +1,483 @@ +/** + * Driver for LSM303D 3-axes digital accelerometer and magnetometer connected + * either to I2C or SPI. + * + * This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos) + * [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32 + * and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux + * based systems using a wrapper library for ESP8266 functions. + * + * --------------------------------------------------------------------------- + * + * The BSD License (3-clause license) + * + * Copyright (c) 2018 Gunar Schorcht (https://github.com/gschorcht) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO Activity SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LSM303D_TYPES_H__ +#define __LSM303D_TYPES_H__ + +#include "stdint.h" +#include "stdbool.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/** + * @brief Accelerator output data rates (A_ODR) + */ +typedef enum { + + lsm303d_a_power_down = 0, // power down mode (default) + lsm303d_a_odr_3_125, // normal power mode 3.125 Hz + lsm303d_a_odr_6_25, // normal power mode 6.25 Hz + lsm303d_a_odr_12_5, // normal power mode 12.5 Hz + lsm303d_a_odr_25, // normal power mode 25 Hz + lsm303d_a_odr_50, // normal power mode 50 Hz + lsm303d_a_odr_100, // normal power mode 100 Hz + lsm303d_a_odr_200, // normal power mode 200 Hz + lsm303d_a_odr_400, // normal power mode 400 Hz + lsm303d_a_odr_800, // normal power mode 800 Hz + lsm303d_a_odr_1600, // normal power mode 1.6 kHz + +} lsm303d_a_odr_t; + +/** + * @brief Accelerator anti-alias filter (A_AAF) bandwidth (BW) in Hz + */ +typedef enum { + + lsm303d_a_aaf_bw_773 = 0, // default + lsm303d_a_aaf_bw_194, + lsm303d_a_aaf_bw_362, + lsm303d_a_aaf_bw_50 + +} lsm303d_a_aaf_bw_t; + +/** + * @brief Accelerator full scale ranges (A_SCALE) in g + */ +typedef enum { + + lsm303d_a_scale_2_g = 0, // default + lsm303d_a_scale_4_g, + lsm303d_a_scale_6_g, + lsm303d_a_scale_8_g, + lsm303d_a_scale_16_g + +} lsm303d_a_scale_t; + +/** + * @brief Magnetometer output data rates (M_ODR) + */ +typedef enum { + + lsm303d_m_odr_3_125 = 0, // normal power mode at 3.125 Hz + lsm303d_m_odr_6_25, // normal power mode at 6.25 Hz + lsm303d_m_odr_12_5, // normal power mode at 12.5 Hz + lsm303d_m_odr_25, // normal power mode at 25 Hz + lsm303d_m_odr_50, // normal power mode at 50 Hz + lsm303d_m_odr_100, // normal power mode at 100 Hz + lsm303d_m_do_not_use, // power down mode (default) + lsm303d_m_low_power // low power mode at 3.125 Hz + +} lsm303d_m_odr_t; + +/** + * @brief Magnetometer sensor mode (M_MODE) + */ +typedef enum { + + lsm303d_m_continuous = 0, // continuous conversion mode + lsm303d_m_single, // single conversion mode (default) + lsm303d_m_power_down // power-down mode + +} lsm303d_m_mode_t; + + +/** + * @brief Magnetometer resolution selection + */ +typedef enum { + + lsm303d_m_low_res, // low resolution (default) + lsm303d_m_high_res // high resolution + +} lsm303d_m_resolution_t; + +/** + * @brief Magnetometer full scale ranges (M_SCALE) in Gauss (Gs) + */ +typedef enum { + + lsm303d_m_scale_2_Gs = 0, + lsm303d_m_scale_4_Gs, // default + lsm303d_m_scale_8_Gs, + lsm303d_m_scale_12_Gs + +} lsm303d_m_scale_t; + +/** + * @brief FIFO mode for accelerator data + */ +typedef enum { + + lsm303d_bypass = 0, // default + lsm303d_fifo, + lsm303d_stream, + lsm303d_stream_to_fifo, + lsm303d_bypass_to_stream + +} lsm303d_fifo_mode_t; + +/** + * @brief Interrupt signals + */ +typedef enum { + + lsm303d_int1_signal = 0, + lsm303d_int2_signal = 1 + +} lsm303d_int_signal_t; + + +/** + * @brief INT1, INT2 signal type + */ +typedef enum { + + lsm303d_push_pull = 0, + lsm303d_open_drain + +} lsm303d_int_signal_type_t; + + +/** + * @brief Inertial event interrupt generators + */ +typedef enum { + + lsm303d_int_event1_gen = 0, + lsm303d_int_event2_gen = 1 + +} lsm303d_int_event_gen_t; + + +/** + * @brief Interrupt types for interrupt signals INT1/INT2 + */ +typedef enum { + + lsm303d_int_a_data_ready, // acceleration data ready for read interrupt + lsm303d_int_m_data_ready, // magnetic data ready for read interrupt + + lsm303d_int_fifo_empty, // FIFO is empty (only INT1) + lsm303d_int_fifo_thresh, // FIFO exceeds the threshold (only INT2) + lsm303d_int_fifo_overrun, // FIFO is completely filled (only INT2) + + lsm303d_int_event1, // inertial event interrupt 1 + lsm303d_int_event2, // inertial event interrupt 2 + + lsm303d_int_click, // click detection interrupt + + lsm303d_int_m_thresh // magnetic threshold interrupt + +} lsm303d_int_type_t; + + +/** + * @brief Data ready and FIFO interrupt source for INT1/INT2 + */ +typedef struct { + + bool a_data_ready; // true when acceleration data are ready to read + bool m_data_ready; // true when magnetic data are ready to read + + bool fifo_empty; // true when FIFO is empty + bool fifo_thresh; // true when FIFO exceeds the FIFO threshold + bool fifo_overrun; // true when FIFO is completely filled + +} lsm303d_int_data_source_t; + + +/** + * @brief Magnetic threshold interrupt configuration for INT1/INT2 signals + */ +typedef struct { + + uint16_t threshold; // threshold used for interrupt generation + + bool x_enabled; // true - x exceeds threshold on positive side + bool y_enabled; // true - y exceeds threshold on positive side + bool z_enabled; // true - z exceeds threshold on positive side + + bool latch; // true - latch the interrupt until the interrupt + // source has been read + enum + { + lsm303d_low_active = 0, + lsm303d_high_active = 1 + + } signal_level; // level of interrupt signal + +} lsm303d_int_m_thresh_config_t; + + +/** + * @brief Magnetic threshold interrupt source of INT1/INT2 signals + */ +typedef struct { + + bool x_pos :1; // true - x exceeds threshold on positive side + bool y_pos :1; // true - y exceeds threshold on positive side + bool z_pos :1; // true - z exceeds threshold on positive side + + bool x_neg :1; // true - x exceeds threshold on negative side + bool y_neg :1; // true - y exceeds threshold on negative side + bool z_neg :1; // true - z exceeds threshold on negative side + + bool mroi :1; // true - internal measurement range overflow + bool active:1; // true - interrupt event occured + +} lsm303d_int_m_thresh_source_t; + + +/** + * @brief Inertial interrupt generator configuration for INT1/INT2 + * + * Inertial events are: axis movement and 6D/4D detection. + */ +typedef struct { + + enum // interrupt mode + { // AOI (IG_CFGx), 6D (IG_CFGx), 4D (INT_CTRL_M) + + lsm303d_or, // AOI = 0, 6D = 0, 4D = X + lsm303d_and, // AOI = 1, 6D = 0, 4D = X + + lsm303d_6d_movement, // AOI = 0, 6D = 1, 4D = 0 + lsm303d_6d_position, // AOI = 1, 6D = 1, 4D = 0 + + lsm303d_4d_movement, // AOI = 0, 6D = 1, 4D = 1 + lsm303d_4d_position, // AOI = 1, 6D = 1, 4D = 1 + + } mode; + + uint8_t threshold; // threshold used for comparison for all axes + + bool x_low_enabled; // x lower than threshold interrupt enabled + bool x_high_enabled; // x higher than threshold interrupt enabled + + bool y_low_enabled; // y lower than threshold interrupt enabled + bool y_high_enabled; // y higher than threshold interrupt enabled + + bool z_low_enabled; // z lower than threshold interrupt enabled + bool z_high_enabled; // z higher than threshold interrupt enabled + + bool latch; // latch the interrupt when true until the + // interrupt source has been read + + uint8_t duration; // duration in 1/ODR an interrupt condition has + // to be given before the interrupt is generated + +} lsm303d_int_event_config_t; + + +/** + * @brief Inertial event source type for interrupt generator INT1/INT2 + */ +typedef struct { + + bool active:1; // true - one ore more events occured + + bool x_low :1; // true - x is lower than threshold event + bool x_high:1; // true - x is higher than threshold event + + bool y_low :1; // true - z is lower than threshold event + bool y_high:1; // true - z is higher than threshold event + + bool z_low :1; // true - z is lower than threshold event + bool z_high:1; // true - z is higher than threshold event + +} lsm303d_int_event_source_t; + + +/** + * @brief Click interrupt configuration for interrupt signals INT1/INT2 + */ +typedef struct { + + bool x_single; // x-axis single tap interrupt enabled + bool x_double; // x-axis double tap interrupt enabled + + bool y_single; // y-axis single tap interrupt enabled + bool y_double; // y-axis double tap interrupt enabled + + bool z_single; // z-axis single tap interrupt enabled + bool z_double; // z-axis double tap interrupt enabled + + uint8_t threshold; // threshold used for comparison for all axes + + bool latch; // latch the interrupt when true until the + // interrupt source has been read + + uint8_t time_limit; // maximum time interval between the start and the + // end of a cick (accel increases and falls back) + uint8_t time_latency; // click detection is disabled for that time after + // a was click detected (in 1/ODR) + uint8_t time_window; // time interval in which the second click has to + // to be detected in double clicks (in 1/ODR) + +} lsm303d_int_click_config_t; + + +/** + * @brief Click interrupt source for interrupt signals INT1/INT2 + */ +typedef struct { + + bool x_click:1; // click detected in x direction + bool y_click:1; // click detected in y direction + bool z_click:1; // click detected in z direction + + bool sign :1; // click sign (0 - posisitive, 1 - negative) + + bool s_click:1; // single click detected + bool d_click:1; // double click detected + + bool active :1; // true - one ore more event occured + +} lsm303d_int_click_source_t; + + +/** + * @brief HPF (high pass filter) modes for acceleration data + */ +typedef enum { + + lsm303d_hpf_normal = 0, // normal mode (reset by reading references) + lsm303d_hpf_reference, // reference signal used for filtering + lsm303d_hpf_normal_x, // normal mode + lsm303d_hpf_autoreset // autoreset on interrupt event + +} lsm303d_hpf_mode_t; + + +/** + * @brief Raw accelerations data set of as two complements + */ +typedef struct { + + int16_t ax; // acceleration on x axis + int16_t ay; // acceleration on y axis + int16_t az; // acceleration on z axis + +} lsm303d_raw_a_data_t; + + +/** + * @brief Raw acceleration data FIFO type + */ +typedef lsm303d_raw_a_data_t lsm303d_raw_a_data_fifo_t[32]; + + +/** + * @brief Floating point accelerations output value set in g + */ +typedef struct { + + float ax; // acceleration on x axis + float ay; // acceleration on y axis + float az; // acceleration on z axis + +} lsm303d_float_a_data_t; + + +/** + * @brief Floating point accelerations output value FIFO type + */ +typedef lsm303d_float_a_data_t lsm303d_float_a_data_fifo_t[32]; + + +/** + * @brief Raw magnetic data set as two's complements + */ +typedef struct { + + int16_t mx; // magnetic value on x axis + int16_t my; // magnetic value on y axis + int16_t mz; // magnetic value on z axis + +} lsm303d_raw_m_data_t; + + +/** + * @brief Floating point magnetic output value set in Gauss + */ +typedef struct { + + float mx; // magnetic value on x axis + float my; // magnetic value on y axis + float mz; // magnetic value on z axis + +} lsm303d_float_m_data_t; + + +/** + * @brief LSM303D sensor device data structure type + */ +typedef struct { + + int error_code; // error code of last operation + + uint8_t bus; // I2C = x, SPI = 1 + uint8_t addr; // I2C = slave address, SPI = 0 + + uint8_t cs; // ESP8266, ESP32: GPIO used as SPI CS + // __linux__: device index + + lsm303d_a_scale_t a_scale; // acceleration full scale (default 2 g) + lsm303d_m_scale_t m_scale; // magnetic full scale (default 4 Gauss) + lsm303d_m_resolution_t m_res; // magnetic resolution (default low) + + lsm303d_fifo_mode_t fifo_mode; // FIFO operation mode (default bypass) + bool fifo_first; // first FIFO access + +} lsm303d_sensor_t; + + +#ifdef __cplusplus +} +#endif /* End of CPP guard */ + +#endif /* __LSM303D_TYPES_H__ */