LIS3MDL 3-axes magnetometer driver added (#547)

This commit is contained in:
Gunar Schorcht 2018-01-20 13:00:35 +01:00 committed by Ruslan V. Uss
parent f5bbff8b87
commit 812794f7d9
9 changed files with 2300 additions and 0 deletions

View file

@ -0,0 +1,3 @@
PROGRAM=LIS3MDL
EXTRA_COMPONENTS = extras/i2c extras/lis3mdl
include ../../common.mk

View file

@ -0,0 +1,268 @@
/**
* 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 | | LIS3MDL |
* | | | |
* | GPIO 14 (SCL) ----> SCL |
* | GPIO 13 (SDA) <---> SDA |
* | GPIO 5 <---- INT |
* | GPIO 4 <---- DRDY |
* +-----------------+ +----------+
*
* SPI
*
* +-----------------+ +----------+ +-----------------+ +----------+
* | ESP8266 | | LIS3MDL | | ESP32 | | LIS3MDL |
* | | | | | | | |
* | 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 <---- INT | | GPIO 5 <---- INT |
* | GPIO 4 <---- DRDY | | GPIO 4 <---- DRDY |
* +-----------------+ +---------+ +-----------------+ +----------+
*/
/* -- use following constants to define the example mode ----------- */
// #define SPI_USED // if defined SPI is used, otherwise I2C
// #define INT_DATA // data ready interrupt used
// #define INT_THRESH // threshold interrupt used
#if defined(INT_DATA) || defined(INT_THRESH)
#define INT_USED
#endif
/* -- includes ----------------------------------------------------- */
#include "lis3mdl.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 PIN_INT 5
#define PIN_DRDY 4
/* -- user tasks --------------------------------------------------- */
static lis3mdl_sensor_t* sensor;
/**
* Common function used to get sensor data.
*/
void read_data ()
{
lis3mdl_float_data_t data;
if (lis3mdl_new_data (sensor) &&
lis3mdl_get_float_data (sensor, &data))
// max. full scale is +-16 g and best resolution is 1 mg, i.e. 5 digits
printf("%.3f LIS3MDL (xyz)[Gs] mx=%+7.3f my=%+7.3f mz=%+7.3f\n",
(double)sdk_system_get_time()*1e-3,
data.mx, data.my, data.mz);
}
#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_num;
while (1)
if (xQueueReceive(gpio_evt_queue, &gpio_num, portMAX_DELAY))
{
if (gpio_num == PIN_DRDY)
{
read_data ();
}
else if (gpio_num == PIN_INT)
{
lis3mdl_int_source_t int_src;
// get the source of the interrupt and reset INT signals
lis3mdl_get_int_source (sensor, &int_src);
// in case of DRDY interrupt or activity interrupt read one data sample
if (int_src.active)
read_data ();
}
}
}
// 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(100/portTICK_PERIOD_MS);
}
}
#endif // INT_USED
/* -- main program ------------------------------------------------- */
void user_init(void)
{
// Set UART Parameter.
uart_set_baud(0, 115200);
// Give the UART some time to settle
vTaskDelay(1);
/** -- MANDATORY PART -- */
#ifdef SPI_USED
// init the sensor connnected to SPI
spi_bus_init (SPI_BUS, SPI_SCK_GPIO, SPI_MISO_GPIO, SPI_MOSI_GPIO);
// init the sensor connected to SPI_BUS with SPI_CS_GPIO as chip select.
sensor = lis3mdl_init_sensor (SPI_BUS, 0, SPI_CS_GPIO);
#else
// init all I2C bus interfaces at which LIS3MDL sensors are connected
i2c_init (I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ);
// init the sensor with slave address LIS3MDL_I2C_ADDRESS_2 connected to I2C_BUS.
sensor = lis3mdl_init_sensor (I2C_BUS, LIS3MDL_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 *INT* and *DRDY* signals and set the interrupt handler
gpio_enable(PIN_INT , GPIO_INPUT);
gpio_enable(PIN_DRDY, GPIO_INPUT);
gpio_set_interrupt(PIN_INT , GPIO_INTTYPE_EDGE_POS, int_signal_handler);
gpio_set_interrupt(PIN_DRDY, GPIO_INTTYPE_EDGE_POS, int_signal_handler);
#endif // !defined(INT_USED)
// -- SENSOR CONFIGURATION PART ---
// Interrupt configuration has to be done before the sensor is set
// into measurement mode
#ifdef INT_THRESH
// enable threshold interrupts on INT1
lis3mdl_int_config_t int_config;
int_config.threshold = 1000;
int_config.x_enabled = true;
int_config.y_enabled = true;
int_config.z_enabled = true;
int_config.latch = true;
int_config.signal_level= lis3mdl_high_active;
lis3mdl_set_int_config (sensor, &int_config);
#endif // INT_THRESH
// LAST STEP: Finally set scale and mode to start measurements
lis3mdl_set_scale(sensor, lis3mdl_scale_4_Gs);
lis3mdl_set_mode (sensor, lis3mdl_lpm_10);
/** -- 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 LIS3MDL sensor\n");
}

694
extras/lis3mdl/README.md Normal file
View file

@ -0,0 +1,694 @@
# Driver for the LIS3MDL 3-axes digital output magnetometer
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/lis3mdl](https://github.com/SuperHouse/esp-open-rtos/tree/master/extras) of original repository, it is not yet merged. Please take a look to branch [lis3mdl](https://github.com/gschorcht/esp-open-rtos/tree/lis3mdl) 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
The LIS3MDL is an ultra-low-power high-performance three-axis magnetic sensor connected to **I2C** or **SPI** with a full scale of up to **±16 Gauss**. It supports different measuring rates.
**Main features** of the sensor are:
- 4 selectable full scales of ±4, ±8, ±12, and ±16 Gauss
- 12 different measuring rates from 0.625 Hz up to 1 kHz
- 16 bit magnetic data output
- interrupt generators for magnetic thresholds
- embedded temperature sensor
- I2C and SPI digital output interface
## Sensor operation
### Sensor operation modes
LIS3MDL provides different operating modes (OM):
- **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.
- **Measurement modes** are a set of operation modes in which measurements are performed with a different output data rates (**ODR**) and different power consumtions.
### Output Data Rates
In measurement modes, measurements are performed at a defined output rate. Following output data rates (ODR) are supported in the different modes operation modes (OM):
Power Mode | Output data rate (ODR) | Driver symbol
:------------- |---------:|:---------------
Power-down mode| - | ```lis3mdl_power_down```
Low-power mode | 0.625 Hz | ```lis3mdl_lpm_0_625```, ```lis3mdl_low_power```
Low-power mode | 1.25 Hz | ```lis3mdl_lpm_1_25```
Low-power mode | 2.5 Hz | ```lis3mdl_lpm_2_5```
Low-power mode | 5 Hz | ```lis3mdl_lpm_5```
Low-power mode | 10 Hz | ```lis3mdl_lpm_10```
Low-power mode | 20 Hz | ```lis3mdl_lpm_20```
Low-power mode | 40 Hz | ```lis3mdl_lpm_40```
Low-power mode | 80 Hz | ```lis3mdl_lpm_80```
Low-power mode | 1000 Hz | ```lis3mdl_lpm_1000```
Medium-performance mode | 560 Hz | ```lis3mdl_mpm_560```
High-performance mode | 300 Hz | ```lis3mdl_hpm_300```
Ultra-high-performance mode | 155 Hz | ```lis3mdl_uhpm_155```
The **easiest way to use the sensor** is to initialize it with the ```lis3mdl_init_sensor``` function and then switch it to any measurement mode with the ```lis3mdl_set_mode``` function to start measurements with the given output data rate (ODR).
```
...
static lis3mdl_sensor_t* sensor = 0;
...
if ((sensor = lis3mdl_init_sensor (I2C_BUS, LIS3MDL_I2C_ADDRESS_2, 0)))
{
...
lis3mdl_set_mode (sensor, lis3mdl_lpm_10);
...
}
...
```
In this example, a LIS3MDL sensor is connected to I2C bus. It is initialized and set to low-power measurement mode with an output data rate (ODR) of 10 Hz to start the measurements.
**Please note:**
- ```lis3mdl_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 ```lis3mdl_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 magnetic values for all axes 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 2s 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. The LIS3MDL allows to select the following full scales:
Full Scale | Resolution | Driver symbol
---------------------:|:-----------|:------
±4 Gauss | 1/6.842 mGauss | ```lis3mdl_scale_4_Gs```
±8 Gauss | 1/3.421 mGauss | ```lis3mdl_scale_8_Gs```
±12 Gauss | 1/2.281 mGauss | ```lis3mdl_scale_12_Gs```
±16 Gauss | 1/1.711 mGauss | ```lis3mdl_scale_16_Gs```
By default, a full scale of ±4 Gauss is used. ```lis3mdl_set_scale``` function can be used to change it.
```
lis3mdl_set_scale(sensor, lis3mdl_scale_4_Gs);
```
### Fetching output data
To get the information whether new data are available, the user task can either use
- the ```lis3mdl_new_data``` function to **check periodically** whether new output data are available, or
- the **data ready interrupt** on ```DRDY``` signal which becomes active as soon as complete sample of new output data are available (see below).
Last measurement results can then be fetched either
- as **raw data** using ```lis3mdl_get_raw_data``` function or
- as **floating point values in Gauss (Gs)** using ```lis3mdl_get_float_data``` function.
It is recommended to use ```lis3mdl_get_float_data``` function since it already converts measurement results to real values according to the selected full scale.
```
void user_task_periodic(void *pvParameters)
{
lis3mdl_float_data_t data;
while (1)
{
// execute task every 10 ms
vTaskDelay (10/portTICK_PERIOD_MS);
...
// test for new data
if (!lis3mdl_new_data (sensor))
continue;
// fetch new data
if (lis3mdl_get_float_data (sensor, &data))
{
// do something with data
...
}
}
}
```
**Please note:**
```lis3mdl_get_float_data``` and ```lis3mdl_get_raw_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.
## Interrupts
The LIS3MDL supports two dedicated interrupt signals for two different types of interrupts:
- **data ready** interrupts on the **```DRDY```** signal, and
- **magnetic threshold** interrupts on the **```INT```** signal.
While magnetic threshold interrupts can be configured as well as enabled or disabled, data-ready interrupts are always enabled and can not be explicitly configured.
### Data ready interrupts
Whenever an interrupt is generated at interrupt signal ```DRDY```, new data are available and can be read with ```lis3mdl_get_float_data``` function or ```lis3mdl_get_raw_data``` function.
```
void drdy_handler ()
{
// fetch new data
if (lis3mdl_get_float_data (sensor, &data))
{
// do something with data
...
}
}
```
### Magnetic threshold interrupts
Magnetic threshold detection of LIS3MDL allows to generate interrupts on ```INT``` signal whenever measured magnetic data exceed a defined threshold value at positive or negative side. It can be enabled for each axis separatly. The defined threshhold is valid for all enabled axes.
Magnetic threshold interrupts can be configured with ```lis3mdl_get_int_config``` function . This function requires configuration of type ```lis3mdl_int_config_t``` as paramater.
```
lis3mdl_int_config_t int_config;
int_config.threshold = 1000;
int_config.x_enabled = true;
int_config.y_enabled = true;
int_config.z_enabled = true;
int_config.latch = true;
int_config.signal_level= lis3mdl_high_active;
lis3mdl_set_int_config (sensor, &int_config);
```
In this example, magnetic threshold detection is enabled for all axes and a threshold of 1000 is defined.
The parameter of type ```lis3mdl_int_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.
```lis3mdl_get_int_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 ```lis3mdl_int_source_t``` which contains a boolean member for each source that can be tested for true.
```
void int_handler ()
{
lis3mdl_int_source_t int_src;
// get the source of the interrupt and reset the INT signal
lis3mdl_get_int_source (sensor, &int_src);
// test the source of the interrupt
if (int_src.active)
{
if (int_src.x_pos || int_src.x_neg) ... ; // do something
if (int_src.y_pos || int_src.y_neg) ... ; // do something
if (int_src.z_pos || int_src.z_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.
## Temperature sensor
The LIS3MDL sensor contains an internal temperature sensor. It can be activated and deactivated with the ```lis3mdl_enable_temperature``` function. Using ```lis3mdl_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 LIS3MDL 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 lis3mdl_reg_read (lis3mdl_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len);
bool lis3mdl_reg_write (lis3mdl_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 drivers'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 | | LIS3MDL |
| | | |
| GPIO 14 (SCL) >-----> SCL |
| GPIO 13 (SDA) <-----> SDA |
| GPIO 5 <------ INT |
| GPIO 4 <------ DRDY |
+-----------------+ +----------+
```
If SPI interface is used, configuration for ESP8266 and ESP32 could look like following.
```
+-----------------+ +----------+ +-----------------+ +----------+
| ESP8266 | | LIS3MDL | | ESP32 | | LIS3MDL |
| | | | | | | |
| 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 <------ INT | | GPIO 5 <------ INT |
| GPIO 4 <------ DRDY | | GPIO 4 <------ DRDY |
+-----------------+ +----------+ +-----------------+ +----------+
```
### 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 PIN_INT 5
#define PIN_DRDY 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 ```lis3mdl_init_sensor``` has to be called for each LIS3MDL 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 lis3mdl_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 = lis3mdl_init_sensor (I2C_BUS, LIS3MDL_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 = lis3mdl_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 ```lis3mdl.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 10 Hz
lis3mdl_set_mode (sensor, lis3mdl_lpm_10);
...
```
#### 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)
{
lis3mdl_float_data_t data;
while (1)
{
// execute task every 10 ms
vTaskDelay (10/portTICK_PERIOD_MS);
...
// test for new data
if (!lis3mdl_new_data (sensor))
continue;
// fetch new data
if (lis3mdl_get_float_data (sensor, &data))
{
// do something with data
...
}
}
}
...
// create a user task that fetches data from sensor periodically
xTaskCreate(user_task_periodic, "user_task_periodic", TASK_STACK_DEPTH, NULL, 2, NULL);
```
The user task simply tests periodically with a 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** ```INT``` or ```DRDY```. 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 data
if (!lis3mdl_new_data (sensor))
continue;
// fetch new data
if (lis3mdl_get_float_data (sensor, &data))
{
// do something with data
...
}
}
}
}
...
// create a task that is triggered only in case of interrupts to fetch the data
xTaskCreate(user_task_interrupt, "user_task_interrupt", TASK_STACK_DEPTH, NULL, 2, NULL);
...
```
In this example, there is
- a task that is fetching data when it receives an event, and
- an interrupt handler that generates the event on interrupt.
Finally, interrupt handlers have to be activated for the GPIOs which are connected to the interrupt signals.
```
// configure interrupt pins for *INT1* and *INT2* signals and set the interrupt handler
gpio_set_interrupt(PIN_INT , GPIO_INTTYPE_EDGE_POS, int_signal_handler);
gpio_set_interrupt(PIN_DRDY, GPIO_INTTYPE_EDGE_POS, int_signal_handler);
```
Furthermore, the interrupts have to be enabled and configured in the LIS3MDL sensor, see section **Interrupts** above.
## Full Example
```
/* -- use following constants to define the example mode ----------- */
// #define SPI_USED // if defined SPI is used, otherwise I2C
// #define INT_DATA // data ready interrupt used
// #define INT_THRESH // threshold interrupt used
#if defined(INT_DATA) || defined(INT_THRESH)
#define INT_USED
#endif
/* -- includes ----------------------------------------------------- */
#include "lis3mdl.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 PIN_INT 5
#define PIN_DRDY 4
/* -- user tasks --------------------------------------------------- */
static lis3mdl_sensor_t* sensor;
/**
* Common function used to get sensor data.
*/
void read_data ()
{
lis3mdl_float_data_t data;
if (lis3mdl_new_data (sensor) &&
lis3mdl_get_float_data (sensor, &data))
// max. full scale is +-16 g and best resolution is 1 mg, i.e. 5 digits
printf("%.3f LIS3MDL (xyz)[Gs] mx=%+7.3f my=%+7.3f mz=%+7.3f\n",
(double)sdk_system_get_time()*1e-3,
data.mx, data.my, data.mz);
}
#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_num;
while (1)
if (xQueueReceive(gpio_evt_queue, &gpio_num, portMAX_DELAY))
{
if (gpio_num == PIN_DRDY)
{
read_data ();
}
else if (gpio_num == PIN_INT)
{
lis3mdl_int_source_t int_src;
// get the source of the interrupt and reset INT signals
lis3mdl_get_int_source (sensor, &int_src);
// in case of DRDY interrupt or activity interrupt read one data sample
if (int_src.active)
read_data ();
}
}
}
// 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(100/portTICK_PERIOD_MS);
}
}
#endif // INT_USED
/* -- main program ------------------------------------------------- */
void user_init(void)
{
// Set UART Parameter.
uart_set_baud(0, 115200);
// Give the UART some time to settle
vTaskDelay(1);
/** -- MANDATORY PART -- */
#ifdef SPI_USED
// init the sensor connnected to SPI
spi_bus_init (SPI_BUS, SPI_SCK_GPIO, SPI_MISO_GPIO, SPI_MOSI_GPIO);
// init the sensor connected to SPI_BUS with SPI_CS_GPIO as chip select.
sensor = lis3mdl_init_sensor (SPI_BUS, 0, SPI_CS_GPIO);
#else
// init all I2C bus interfaces at which LIS3MDL sensors are connected
i2c_init (I2C_BUS, I2C_SCL_PIN, I2C_SDA_PIN, I2C_FREQ);
// init the sensor with slave address LIS3MDL_I2C_ADDRESS_2 connected to I2C_BUS.
sensor = lis3mdl_init_sensor (I2C_BUS, LIS3MDL_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 *INT* and *DRDY* signals and set the interrupt handler
gpio_enable(PIN_INT , GPIO_INPUT);
gpio_enable(PIN_DRDY, GPIO_INPUT);
gpio_set_interrupt(PIN_INT , GPIO_INTTYPE_EDGE_POS, int_signal_handler);
gpio_set_interrupt(PIN_DRDY, GPIO_INTTYPE_EDGE_POS, int_signal_handler);
#endif // !defined(INT_USED)
// -- SENSOR CONFIGURATION PART ---
// Interrupt configuration has to be done before the sensor is set
// into measurement mode
#ifdef INT_THRESH
// enable threshold interrupts on INT1
lis3mdl_int_config_t int_config;
int_config.threshold = 1000;
int_config.x_enabled = true;
int_config.y_enabled = true;
int_config.z_enabled = true;
int_config.latch = true;
int_config.signal_level= lis3mdl_high_active;
lis3mdl_set_int_config (sensor, &int_config);
#endif // INT_THRESH
// LAST STEP: Finally set scale and mode to start measurements
lis3mdl_set_scale(sensor, lis3mdl_scale_4_Gs);
lis3mdl_set_mode (sensor, lis3mdl_lpm_10);
/** -- 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 LIS3MDL sensor\n");
}
```

View file

@ -0,0 +1,10 @@
# Component makefile for extras/lis3mdl
# expected anyone using LIS3MDL driver includes it as 'lis3mld/lis3mld.h'
INC_DIRS += $(lis3mdl_ROOT)..
INC_DIRS += $(lis3mdl_ROOT)
# args for passing into compile rule generation
lis3mdl_SRC_DIR = $(lis3mdl_ROOT)
$(eval $(call component_compile_rules,lis3mdl))

738
extras/lis3mdl/lis3mdl.c Normal file
View file

@ -0,0 +1,738 @@
/*
* Driver for LIS3MDL 3-axes digital magnetometer connected to I2C or SPI.
*
* This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos)
* [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32
* and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux
* based systems using a wrapper library for ESP8266 functions.
*
* ---------------------------------------------------------------------------
*
* The BSD License (3-clause license)
*
* Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The information provided is believed to be accurate and reliable. The
* copyright holder assumes no responsibility for the consequences of use
* of such information nor for any infringement of patents or other rights
* of third parties which may result from its use. No license is granted by
* implication or otherwise under any patent or patent rights of the copyright
* holder.
*/
#include <string.h>
#include <stdlib.h>
#include "lis3mdl.h"
#if defined(LIS3MDL_DEBUG_LEVEL_2)
#define debug(s, f, ...) printf("%s %s: " s "\n", "LIS3MDL", f, ## __VA_ARGS__)
#define debug_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "LIS3MDL", f, d->bus, d->addr, ## __VA_ARGS__)
#else
#define debug(s, f, ...)
#define debug_dev(s, f, d, ...)
#endif
#if defined(LIS3MDL_DEBUG_LEVEL_1) || defined(LIS3MDL_DEBUG_LEVEL_2)
#define error(s, f, ...) printf("%s %s: " s "\n", "LIS3MDL", f, ## __VA_ARGS__)
#define error_dev(s, f, d, ...) printf("%s %s: bus %d, addr %02x - " s "\n", "LIS3MDL", f, d->bus, d->addr, ## __VA_ARGS__)
#else
#define error(s, f, ...)
#define error_dev(s, f, d, ...)
#endif
// register addresses
#define LIS3MDL_REG_WHO_AM_I 0x0f
#define LIS3MDL_REG_CTRL1 0x20
#define LIS3MDL_REG_CTRL2 0x21
#define LIS3MDL_REG_CTRL3 0x22
#define LIS3MDL_REG_CTRL4 0x23
#define LIS3MDL_REG_CTRL5 0x24
#define LIS3MDL_REG_STATUS 0x27
#define LIS3MDL_REG_OUT_X_L 0x28
#define LIS3MDL_REG_OUT_X_H 0x29
#define LIS3MDL_REG_OUT_Y_L 0x2a
#define LIS3MDL_REG_OUT_Y_H 0x2b
#define LIS3MDL_REG_OUT_Z_L 0x2c
#define LIS3MDL_REG_OUT_Z_H 0x2d
#define LIS3MDL_REG_TEMP_OUT_L 0x2e
#define LIS3MDL_REG_TEMP_OUT_H 0x2f
#define LIS3MDL_REG_INT_CFG 0x30
#define LIS3MDL_REG_INT_SRC 0x31
#define LIS3MDL_REG_INT_THS_L 0x32
#define LIS3MDL_REG_INT_THS_H 0x33
// register structure definitions
struct lis3mdl_reg_status
{
uint8_t XDA :1; // STATUS<0> X axis new data available
uint8_t YDA :1; // STATUS<1> Y axis new data available
uint8_t ZDA :1; // STATUS<2> Z axis new data available
uint8_t ZYXDA :1; // STATUS<3> X, Y and Z axis new data available
uint8_t XOR :1; // STATUS<4> X axis data overrun
uint8_t YOR :1; // STATUS<5> Y axis data overrun
uint8_t ZOR :1; // STATUS<6> Z axis data overrun
uint8_t ZYXOR :1; // STATUS<7> X, Y and Z axis data overrun
};
#define LIS3MDL_ANY_DATA_READY 0x0f // LIS3MDL_REG_STATUS<3:0>
struct lis3mdl_reg_ctrl1
{
uint8_t ST :1; // CTRL1<0> Self-test enable
uint8_t FAST_ODR :1; // CTRL1<1> Data rates higher 80 Hz enabled
uint8_t DO :3; // CTRL1<4:2> Output data rate
uint8_t OM :2; // CTRL1<6:5> X and Y axes operative mode
uint8_t TEMP_EN :1; // CTRL1<7> Temperature sensor enabled
};
struct lis3mdl_reg_ctrl2
{
uint8_t unused1 :2; // CTRL2<1:0> unused
uint8_t SOFT_RST :1; // CTRL2<2> configuration and user regs reset
uint8_t REBOOT :1; // CTRL2<3> Reboot memory content
uint8_t unused2 :1; // CTRL2<4> unused
uint8_t FS :2; // CTRL2<6:5>
uint8_t unused3 :1; // CTRL2<7> unused
};
struct lis3mdl_reg_ctrl3
{
uint8_t MD :2; // CTRL3<1:0> Operation mode selection
uint8_t SIM :1; // CTRL3<2> SPI serial interface mode selection
uint8_t unused1 :2; // CTRL3<4:3> unused
uint8_t LP :1; // CTRL3<5> Low power mode configuration
uint8_t unused2 :2; // CTRL3<7:6> unused
};
struct lis3mdl_reg_ctrl4
{
uint8_t unused1 :1; // CTRL4<0> unused
uint8_t BLE :1; // CTRL4<1> Big/litle endian data selection
uint8_t OMZ :2; // CTRL4<3:2> Z axis operative mode
uint8_t unused2 :4; // CTRL4<7:4> unused
};
struct lis3mdl_reg_ctrl5
{
uint8_t unused :6; // CTRL5<5:0> unused
uint8_t BDU :1; // CTRL5<6> Block data update
uint8_t FAST_READ:1; // CTRL5<7> Fast read enabled
};
struct lis3mdl_reg_int_cfg
{
uint8_t IEN :1; // INT_CFG<0> Interrupt enabled
uint8_t LIR :1; // INT_CFG<1> Latch interrupt request
uint8_t IEA :1; // INT_CFG<2> Interrupt active
uint8_t unused :2; // INT_CFG<4:3> unused
uint8_t ZIEN :1; // INT_CFG<5> Z axis threshold interrupt enabled
uint8_t YIEN :1; // INT_CFG<6> Y axis threshold interrupt enabled
uint8_t XIEN :1; // INT_CFG<7> X axis threshold interrupt enabled
};
struct lis3mdl_reg_int_src
{
uint8_t PTH_X :1; // INT_SRC<0> X exceeds threshold on positive side
uint8_t PTH_Y :1; // INT_SRC<1> Y exceeds threshold on positive side
uint8_t PTH_Z :1; // INT_SRC<2> Z exceeds threshold on positive side
uint8_t NTH_X :1; // INT_SRC<3> X exceeds threshold on negative side
uint8_t NTH_Y :1; // INT_SRC<4> Y exceeds threshold on negative side
uint8_t NTH_Z :1; // INT_SRC<5> Z exceeds threshold on negative side
uint8_t MROI :1; // INT_SRC<6> Internal measurement range overflow
uint8_t INT :1; // INT_SRC<7> Interrupt event occurs
};
/** Forward declaration of functions for internal use */
static bool lis3mdl_reset (lis3mdl_sensor_t* dev);
static bool lis3mdl_is_available(lis3mdl_sensor_t* dev);
static bool lis3mdl_i2c_read (lis3mdl_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len);
static bool lis3mdl_i2c_write (lis3mdl_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len);
static bool lis3mdl_spi_read (lis3mdl_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len);
static bool lis3mdl_spi_write (lis3mdl_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 lis3mdl_update_reg(dev,addr,type,elem,value) \
{ \
struct type __reg; \
if (!lis3mdl_reg_read (dev, (addr), (uint8_t*)&__reg, 1)) \
return false; \
__reg.elem = (value); \
if (!lis3mdl_reg_write (dev, (addr), (uint8_t*)&__reg, 1)) \
return false; \
}
lis3mdl_sensor_t* lis3mdl_init_sensor (uint8_t bus, uint8_t addr, uint8_t cs)
{
lis3mdl_sensor_t* dev;
if ((dev = malloc (sizeof(lis3mdl_sensor_t))) == NULL)
return NULL;
// init sensor data structure
dev->bus = bus;
dev->addr = addr;
dev->cs = cs;
dev->error_code = LIS3MDL_OK;
dev->scale = lis3mdl_scale_4_Gs;
// 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 (!lis3mdl_is_available (dev))
{
error_dev ("Sensor is not available.", __FUNCTION__, dev);
free (dev);
return NULL;
}
// reset the sensor
if (!lis3mdl_reset(dev))
{
error_dev ("Could not reset the sensor device.", __FUNCTION__, dev);
free (dev);
return NULL;
}
lis3mdl_update_reg (dev, LIS3MDL_REG_CTRL2, lis3mdl_reg_ctrl2, FS, lis3mdl_scale_4_Gs);
lis3mdl_update_reg (dev, LIS3MDL_REG_CTRL5, lis3mdl_reg_ctrl5, BDU, 1);
return dev;
}
// switching times
// LP 0.90
// MP 1.65
// HP 3.23
// UHP 6.40
bool lis3mdl_set_mode (lis3mdl_sensor_t* dev, lis3mdl_mode_t mode)
{
if (!dev) return false;
dev->error_code = LIS3MDL_OK;
struct lis3mdl_reg_ctrl1 ctrl1;
struct lis3mdl_reg_ctrl3 ctrl3;
struct lis3mdl_reg_ctrl4 ctrl4;
// read current register values
if (!lis3mdl_reg_read (dev, LIS3MDL_REG_CTRL1, (uint8_t*)&ctrl1, 1) ||
!lis3mdl_reg_read (dev, LIS3MDL_REG_CTRL3, (uint8_t*)&ctrl3, 1) ||
!lis3mdl_reg_read (dev, LIS3MDL_REG_CTRL4, (uint8_t*)&ctrl4, 1))
return false;
if (mode < lis3mdl_lpm_1000)
{
ctrl1.FAST_ODR = 0;
ctrl3.MD = 0; // continuous measurement
ctrl3.LP = 0;
ctrl1.DO = mode;
ctrl1.OM = 0;
ctrl4.OMZ = ctrl1.OM;
}
else if (mode < lis3mdl_low_power)
{
ctrl1.FAST_ODR = 1;
ctrl3.MD = 0; // continuous measurement
ctrl3.LP = 0;
ctrl1.DO = 0;
ctrl1.OM = mode - lis3mdl_lpm_1000;
ctrl4.OMZ = ctrl1.OM;
}
else if (mode == lis3mdl_low_power)
{
ctrl1.FAST_ODR = 0;
ctrl3.MD = 0; // continuous measurement
ctrl3.LP = 1; // at lowest data rate 0.625 Hz
ctrl1.DO = 0;
ctrl1.OM = 0;
ctrl4.OMZ = ctrl1.OM;
}
else // lis3mdl_power_down
{
ctrl3.MD = 3;
}
if (!lis3mdl_reg_write (dev, LIS3MDL_REG_CTRL1, (uint8_t*)&ctrl1, 1) ||
!lis3mdl_reg_write (dev, LIS3MDL_REG_CTRL3, (uint8_t*)&ctrl3, 1) ||
!lis3mdl_reg_write (dev, LIS3MDL_REG_CTRL4, (uint8_t*)&ctrl4, 1))
return false;
// wait until mode switch happened
vTaskDelay (50/portTICK_PERIOD_MS);
// dummy read last data register set
lis3mdl_raw_data_t raw;
lis3mdl_get_raw_data (dev, &raw);
return false;
}
bool lis3mdl_set_scale (lis3mdl_sensor_t* dev, lis3mdl_scale_t scale)
{
if (!dev) return false;
dev->error_code = LIS3MDL_OK;
dev->scale = scale;
// read CTRL2 register and write scale
lis3mdl_update_reg (dev, LIS3MDL_REG_CTRL2, lis3mdl_reg_ctrl2, FS, scale);
return true;
}
bool lis3mdl_new_data (lis3mdl_sensor_t* dev)
{
if (!dev) return false;
dev->error_code = LIS3MDL_OK;
struct lis3mdl_reg_status status;
if (!lis3mdl_reg_read (dev, LIS3MDL_REG_STATUS, (uint8_t*)&status, 1))
{
error_dev ("Could not get sensor status", __FUNCTION__, dev);
return false;
}
return status.ZYXDA;
}
/**
* Scaling factors for the conversion of raw sensor data to floating point _Gs
* values. Scaling factors are from mechanical characteristics in datasheet.
*
* scale/sensitivity resolution sensitivity
* +-4 gauss 6842 LSB/gauss 1.461561e-4
* +-8 gauss 3421 LSB/gauss 2,923122e-4
* +-12 gauss 2281 LSB/gauss 4,384042e-4
* +-16 gauss 1711 LSB/gauss 5,844535e-4
*/
const static double LIS3MDL_SCALES[4] = { 1.0/6842, 1.0/3421, 1.0/2281, 1.0/1711 };
bool lis3mdl_get_float_data (lis3mdl_sensor_t* dev, lis3mdl_float_data_t* data)
{
if (!dev || !data) return false;
lis3mdl_raw_data_t raw;
if (!lis3mdl_get_raw_data (dev, &raw))
return false;
data->mx = LIS3MDL_SCALES[dev->scale] * raw.mx;
data->my = LIS3MDL_SCALES[dev->scale] * raw.my;
data->mz = LIS3MDL_SCALES[dev->scale] * raw.mz;
return true;
}
bool lis3mdl_get_raw_data (lis3mdl_sensor_t* dev, lis3mdl_raw_data_t* raw)
{
if (!dev || !raw) return false;
dev->error_code = LIS3MDL_OK;
uint8_t regs[6];
// read raw data sample
if (!lis3mdl_reg_read (dev, LIS3MDL_REG_OUT_X_L, regs, 6))
{
error_dev ("Could not get raw data sample", __FUNCTION__, dev);
dev->error_code |= LIS3MDL_GET_RAW_DATA_FAILED;
return false;
}
raw->mx = ((uint16_t)regs[1] << 8) | regs[0];
raw->my = ((uint16_t)regs[3] << 8) | regs[2];
raw->mz = ((uint16_t)regs[5] << 8) | regs[4];
return true;
}
bool lis3mdl_set_int_config (lis3mdl_sensor_t* dev,
lis3mdl_int_config_t* cfg)
{
if (!dev || !cfg) return false;
dev->error_code = LIS3MDL_OK;
struct lis3mdl_reg_int_cfg int_cfg;
int_cfg.unused = 0;
int_cfg.XIEN = cfg->x_enabled;
int_cfg.YIEN = cfg->y_enabled;
int_cfg.ZIEN = cfg->z_enabled;
int_cfg.LIR = cfg->latch;
int_cfg.IEA = cfg->signal_level;
int_cfg.IEN = cfg->x_enabled | cfg->y_enabled | cfg->z_enabled;
if (// write the threshold to registers INT_THS_*
!lis3mdl_reg_write (dev, LIS3MDL_REG_INT_THS_L, (uint8_t*)&cfg->threshold, 2) ||
// write configuration to INT_CFG
!lis3mdl_reg_write (dev, LIS3MDL_REG_INT_CFG, (uint8_t*)&int_cfg, 1))
{
error_dev ("Could not configure interrupt INT", __FUNCTION__, dev);
dev->error_code |= LIS3MDL_CONFIG_INT_FAILED;
return false;
}
return true;
}
bool lis3mdl_get_int_config (lis3mdl_sensor_t* dev,
lis3mdl_int_config_t* cfg)
{
if (!dev || !cfg) return false;
dev->error_code = LIS3MDL_OK;
struct lis3mdl_reg_int_cfg int_cfg;
if (!lis3mdl_reg_read (dev, LIS3MDL_REG_INT_THS_L, (uint8_t*)&cfg->threshold, 2) ||
!lis3mdl_reg_read (dev, LIS3MDL_REG_INT_CFG , (uint8_t*)&int_cfg, 1))
{
error_dev ("Could not read configuration of interrupt INT from sensor", __FUNCTION__, dev);
dev->error_code |= LIS3MDL_CONFIG_INT_FAILED;
return false;
}
cfg->x_enabled = int_cfg.XIEN;
cfg->y_enabled = int_cfg.YIEN;
cfg->z_enabled = int_cfg.ZIEN;
cfg->latch = int_cfg.LIR;
cfg->signal_level = int_cfg.IEA;
return true;
}
bool lis3mdl_get_int_source (lis3mdl_sensor_t* dev,
lis3mdl_int_source_t* src)
{
if (!dev || !src) return false;
dev->error_code = LIS3MDL_OK;
struct lis3mdl_reg_int_src int_src;
struct lis3mdl_reg_int_cfg int_cfg;
if (!lis3mdl_reg_read (dev, LIS3MDL_REG_INT_SRC, (uint8_t*)&int_src, 1) ||
!lis3mdl_reg_read (dev, LIS3MDL_REG_INT_CFG, (uint8_t*)&int_cfg, 1))
{
error_dev ("Could not read source of interrupt INT from sensor", __FUNCTION__, dev);
dev->error_code |= LIS3MDL_INT_SOURCE_FAILED;
return false;
}
src->active = int_src.INT;
src->x_pos = int_src.PTH_X & int_cfg.XIEN;
src->x_neg = int_src.NTH_X & int_cfg.XIEN;
src->y_pos = int_src.PTH_Y & int_cfg.YIEN;
src->y_neg = int_src.NTH_Y & int_cfg.YIEN;
src->z_pos = int_src.PTH_Z & int_cfg.ZIEN;
src->z_neg = int_src.NTH_Z & int_cfg.ZIEN;
return true;
}
bool lis3mdl_enable_temperature (lis3mdl_sensor_t* dev, bool enable)
{
lis3mdl_update_reg (dev, LIS3MDL_REG_CTRL1, lis3mdl_reg_ctrl1, TEMP_EN, enable);
return true;
}
float lis3mdl_get_temperature (lis3mdl_sensor_t* dev)
{
uint8_t regs[2];
// read raw data sample
if (!lis3mdl_reg_read (dev, LIS3MDL_REG_TEMP_OUT_L, regs, 2))
{
error_dev ("Could not get temperature data sample", __FUNCTION__, dev);
dev->error_code |= LIS3MDL_GET_RAW_DATA_FAILED;
return false;
}
return (((int16_t)((regs[1] << 8) | regs[0])) >> 3) + 25.0;
}
/** Functions for internal use only */
/**
* @brief Check the chip ID to test whether sensor is available
*/
static bool lis3mdl_is_available (lis3mdl_sensor_t* dev)
{
uint8_t chip_id;
if (!dev) return false;
dev->error_code = LIS3MDL_OK;
if (!lis3mdl_reg_read (dev, LIS3MDL_REG_WHO_AM_I, &chip_id, 1))
return false;
if (chip_id != LIS3MDL_CHIP_ID)
{
error_dev ("Chip id %02x is wrong, should be %02x.",
__FUNCTION__, dev, chip_id, LIS3MDL_CHIP_ID);
dev->error_code = LIS3MDL_WRONG_CHIP_ID;
return false;
}
return true;
}
static bool lis3mdl_reset (lis3mdl_sensor_t* dev)
{
if (!dev) return false;
dev->error_code = LIS3MDL_OK;
uint8_t ctrl_regs[5] = { 0x10, 0x00, 0x03, 0x00, 0x00 };
uint8_t int_cfg = 0x00;
// initialize sensor completely including setting in power down mode
lis3mdl_reg_write (dev, LIS3MDL_REG_CTRL1 , ctrl_regs, 5);
lis3mdl_reg_write (dev, LIS3MDL_REG_INT_CFG, &int_cfg , 1);
return true;
}
bool lis3mdl_reg_read(lis3mdl_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len)
{
if (!dev || !data) return false;
return (dev->addr) ? lis3mdl_i2c_read (dev, reg, data, len)
: lis3mdl_spi_read (dev, reg, data, len);
}
bool lis3mdl_reg_write(lis3mdl_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len)
{
if (!dev || !data) return false;
return (dev->addr) ? lis3mdl_i2c_write (dev, reg, data, len)
: lis3mdl_spi_write (dev, reg, data, len);
}
#define LIS3MDL_SPI_BUF_SIZE 64 // SPI register data buffer size of ESP866
#define LIS3MDL_SPI_READ_FLAG 0x80
#define LIS3MDL_SPI_WRITE_FLAG 0x00
#define LIS3MDL_SPI_AUTO_INC_FLAG 0x40
static bool lis3mdl_spi_read(lis3mdl_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len)
{
if (!dev || !data) return false;
if (len >= LIS3MDL_SPI_BUF_SIZE)
{
dev->error_code |= LIS3MDL_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, LIS3MDL_SPI_BUF_SIZE);
return false;
}
uint8_t addr = (reg & 0x3f) | LIS3MDL_SPI_READ_FLAG | LIS3MDL_SPI_AUTO_INC_FLAG;
static uint8_t mosi[LIS3MDL_SPI_BUF_SIZE];
static uint8_t miso[LIS3MDL_SPI_BUF_SIZE];
memset (mosi, 0xff, LIS3MDL_SPI_BUF_SIZE);
memset (miso, 0xff, LIS3MDL_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 |= LIS3MDL_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 LIS3MDL_DEBUG_LEVEL_2
printf("LIS3MDL %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 lis3mdl_spi_write(lis3mdl_sensor_t* dev, uint8_t reg, uint8_t *data, uint16_t len)
{
if (!dev || !data) return false;
uint8_t addr = (reg & 0x3f) | LIS3MDL_SPI_WRITE_FLAG | LIS3MDL_SPI_AUTO_INC_FLAG;
static uint8_t mosi[LIS3MDL_SPI_BUF_SIZE];
if (len >= LIS3MDL_SPI_BUF_SIZE)
{
dev->error_code |= LIS3MDL_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, LIS3MDL_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 LIS3MDL_DEBUG_LEVEL_2
printf("LIS3MDL %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 |= LIS3MDL_SPI_WRITE_FAILED;
return false;
}
return true;
}
#define I2C_AUTO_INCREMENT (0x80)
static bool lis3mdl_i2c_read(lis3mdl_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, &reg, data, len);
if (result)
{
dev->error_code |= (result == -EBUSY) ? LIS3MDL_I2C_BUSY : LIS3MDL_I2C_READ_FAILED;
error_dev ("Error %d on read %d byte from I2C slave register %02x.",
__FUNCTION__, dev, result, len, reg);
return false;
}
# ifdef LIS3MDL_DEBUG_LEVEL_2
printf("LIS3MDL %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 lis3mdl_i2c_write(lis3mdl_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, &reg, data, len);
if (result)
{
dev->error_code |= (result == -EBUSY) ? LIS3MDL_I2C_BUSY : LIS3MDL_I2C_WRITE_FAILED;
error_dev ("Error %d on write %d byte to i2c slave register %02x.",
__FUNCTION__, dev, result, len, reg);
return false;
}
# ifdef LIS3MDL_DEBUG_LEVEL_2
printf("LIS3MDL %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;
}

248
extras/lis3mdl/lis3mdl.h Normal file
View file

@ -0,0 +1,248 @@
/**
* Driver for LIS3MDL 3-axes digital magnetometer connected to I2C or SPI.
*
* This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos)
* [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32
* and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux
* based systems using a wrapper library for ESP8266 functions.
*
* ---------------------------------------------------------------------------
*
* The BSD License (3-clause license)
*
* Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __LIS3MDL_H__
#define __LIS3MDL_H__
// Uncomment one of the following defines to enable debug output
// #define LIS3MDL_DEBUG_LEVEL_1 // only error messages
// #define LIS3MDL_DEBUG_LEVEL_2 // debug and error messages
// LIS3MDL addresses
#define LIS3MDL_I2C_ADDRESS_1 0x1c // SDO pin is low
#define LIS3MDL_I2C_ADDRESS_2 0x1e // SDO pin is high
// LIS3MDL chip id
#define LIS3MDL_CHIP_ID 0x3d // LIS3MDL_REG_WHO_AM_I<7:0>
// Definition of error codes
#define LIS3MDL_OK 0
#define LIS3MDL_NOK -1
#define LIS3MDL_INT_ERROR_MASK 0x000f
#define LIS3MDL_DRV_ERROR_MASK 0xfff0
// Error codes for I2C and SPI interfaces ORed with LIS3MDL driver error codes
#define LIS3MDL_I2C_READ_FAILED 1
#define LIS3MDL_I2C_WRITE_FAILED 2
#define LIS3MDL_I2C_BUSY 3
#define LIS3MDL_SPI_WRITE_FAILED 4
#define LIS3MDL_SPI_READ_FAILED 5
#define LIS3MDL_SPI_BUFFER_OVERFLOW 6
// LIS3MDL driver error codes ORed with error codes for I2C and SPI interfaces
#define LIS3MDL_WRONG_CHIP_ID ( 1 << 8)
#define LIS3MDL_GET_RAW_DATA_FAILED ( 2 << 8)
#define LIS3MDL_CONFIG_INT_FAILED ( 3 << 8)
#define LIS3MDL_INT_SOURCE_FAILED ( 4 << 8)
#define LIS3MDL_GET_ADC_DATA_FAILED ( 5 << 8)
#include "lis3mdl_platform.h"
#include "lis3mdl_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.
*
* @param bus I2C or SPI bus at which LIS3MDL sensor is connected
* @param addr I2C addr of the LIS3MDL sensor, 0 for using SPI
* @param cs SPI CS GPIO, ignored for I2C
* @return pointer to sensor data structure, or NULL on error
*/
lis3mdl_sensor_t* lis3mdl_init_sensor (uint8_t bus, uint8_t addr, uint8_t cs);
/**
* @brief Set sensor operation mode (OM) and output data rate (ODR)
*
* @param dev pointer to the sensor device data structure
* @param mode sensor operation mode (OM) at output data rate (ODR)
* @return true on success, false on error
*/
bool lis3mdl_set_mode (lis3mdl_sensor_t* dev, lis3mdl_mode_t mode);
/**
* @brief Set scale (full scale range)
*
* @param dev pointer to the sensor device data structure
* @param scale full range scale
* @return true on success, false on error
*/
bool lis3mdl_set_scale (lis3mdl_sensor_t* dev, lis3mdl_scale_t scale);
/**
* @brief Test whether new data samples are available
*
* @param dev pointer to the sensor device data structure
* @return true on new data, otherwise false
*/
bool lis3mdl_new_data (lis3mdl_sensor_t* dev);
/**
* @brief Get one sample of sensor data as floating point values (unit Gauss)
*
* @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 lis3mdl_get_float_data (lis3mdl_sensor_t* dev,
lis3mdl_float_data_t* data);
/**
* @brief Get one sample of raw sensor 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 lis3mdl_get_raw_data (lis3mdl_sensor_t* dev, lis3mdl_raw_data_t* raw);
/**
* @brief Set configuration for threshold interrupt signal INT
*
* The function enables the interrupt signal if one of the possible sources
* is enabled for interrupts.
*
* @param dev pointer to the sensor device data structure
* @param config configuration for the specified interrupt signal
* @return true on success, false on error
*/
bool lis3mdl_set_int_config (lis3mdl_sensor_t* dev,
lis3mdl_int_config_t* config);
/**
* @brief Get configuration for threshold interrupt signal INT
*
* @param dev pointer to the sensor device data structure
* @param config configuration for the specified interrupt signal
* @return true on success, false on error
*/
bool lis3mdl_get_int_config (lis3mdl_sensor_t* dev,
lis3mdl_int_config_t* config);
/**
* @brief Get the source of the threshold interrupt signal INT
*
* Returns a byte with flags that indicate the value(s) that triggered
* the interrupt signal (see INT_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 lis3mdl_get_int_source (lis3mdl_sensor_t* dev,
lis3mdl_int_source_t* source);
/**
* @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 lis3mdl_enable_temperature (lis3mdl_sensor_t* dev, bool enable);
/**
* @brief Get temperature
*
* @param dev pointer to the sensor device data structure
* @return temperature in degree
*/
float lis3mdl_get_temperature (lis3mdl_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 lis3mdl_reg_write (lis3mdl_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 lis3mdl_reg_read (lis3mdl_sensor_t* dev,
uint8_t reg, uint8_t *data, uint16_t len);
#ifdef __cplusplus
}
#endif /* End of CPP guard */
#endif /* __LIS3MDL_H__ */

View file

@ -0,0 +1,81 @@
/**
* Driver for LIS3MDL 3-axes digital magnetometer 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 "lis3mdl_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;
}

View file

@ -0,0 +1,80 @@
/**
* Driver for LIS3MDL 3-axes digital magnetometer 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 __LIS3MDL_PLATFORM_H__
#define __LIS3MDL_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 // __LIS3MDL_PLATFORM_H__

View file

@ -0,0 +1,178 @@
/**
* Driver for LIS3MDL 3-axes digital accelerometer connected to I2C or SPI.
*
* This driver is for the usage with the ESP8266 and FreeRTOS (esp-open-rtos)
* [https://github.com/SuperHouse/esp-open-rtos]. It is also working with ESP32
* and ESP-IDF [https://github.com/espressif/esp-idf.git] as well as Linux
* based systems using a wrapper library for ESP8266 functions.
*
* ---------------------------------------------------------------------------
*
* The BSD License (3-clause license)
*
* Copyright (c) 2017 Gunar Schorcht (https://github.com/gschorcht)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO 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 __LIS3MDL_TYPES_H__
#define __LIS3MDL_TYPES_H__
#include "stdint.h"
#include "stdbool.h"
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Operation mode (OM) and output data rates (ODR)
*/
typedef enum {
lis3mdl_lpm_0_625 = 0, // low power mode at 0.625 Hz
lis3mdl_lpm_1_25, // low power mode at 1.25 Hz
lis3mdl_lpm_2_5, // low power mode at 2.5 Hz
lis3mdl_lpm_5, // low power mode at 5 Hz
lis3mdl_lpm_10, // low power mode at 10 Hz
lis3mdl_lpm_20, // low power mode at 20 Hz
lis3mdl_lpm_40, // low power mode at 40 Hz
lis3mdl_lpm_80, // low power mode at 80 Hz
lis3mdl_lpm_1000, // low power mode at 1000 Hz
lis3mdl_mpm_560, // medium performance mode at 560 Hz
lis3mdl_hpm_300, // high performance mode at 300 Hz
lis3mdl_uhpm_155, // ultra high performance mode at 155 Hz
lis3mdl_low_power, // low power mode at 0.625 Hz
lis3mdl_power_down // power down mode
} lis3mdl_mode_t;
/**
* @brief Full scale measurement range in Gauss
*/
typedef enum {
lis3mdl_scale_4_Gs = 0, // default
lis3mdl_scale_8_Gs,
lis3mdl_scale_12_Gs,
lis3mdl_scale_16_Gs
} lis3mdl_scale_t;
/**
* @brief Magnetic threshold interrupt configuration for INT signal
*/
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
{
lis3mdl_low_active = 0,
lis3mdl_high_active = 1
} signal_level; // level of interrupt signal
} lis3mdl_int_config_t;
/**
* @brief Magnetic threshold interrupt source of INT signal
*/
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
} lis3mdl_int_source_t;
/**
* @brief Raw 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
} lis3mdl_raw_data_t;
/**
* @brief Floating point 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
} lis3mdl_float_data_t;
/**
* @brief LIS3MDL 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
lis3mdl_scale_t scale; // full range scale (default 4 Gauss)
} lis3mdl_sensor_t;
#ifdef __cplusplus
}
#endif /* End of CPP guard */
#endif /* __LIS3MDL_TYPES_H__ */