diff --git a/drivers/include/mlx90393.h b/drivers/include/mlx90393.h new file mode 100644 index 000000000000..42d0a236be14 --- /dev/null +++ b/drivers/include/mlx90393.h @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2023 Michael Ristau + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_mlx90393 MLX90393 3-axis magnetometer + * @ingroup drivers_sensors + * @ingroup drivers_saul + * @brief Device driver for the MLX90393 3-axis magnetometer + * + * # Overview + * + * ## About the sensor + * + * The MLX90393 is a magnetic field sensor offering a 16-bit output proportional + * to the magnetic flux density sensed along the X, Y, and Z axes. + * In addition, the sensor offers a 16-bit temperature output signal. These values + * are available via I2C and SPI. The sensor can be run in single shot and continuous + * modes, see below. + * To minimize host operations, interrupts can be used either when new sensor data is + * ready to be read or when sensor values exceed configured thresholds. + * + * ## Supported Features + * + * Functionality the driver supports: + * - Magnetic field and temperature sensing in single-shot or continuous mode + * - Configurable thresholds for triggering interrupt + * - Configurable output data rate + * - Configurable analog chain gain + * - Configurable 16-bit output value from the 19-bit ADC + * - Configurable oversampling ratio for meagnetic and temperature sensor + * - Digital filter applicable to ADC + * - Support for I2C and SPI + * - Measurement retrieval via interrupt or polling + * - Setting the sensor into idle (sleep) mode + * - Fixed configuration of the sensor by a default parameter set of + * type #mlx90393_params_t as defined in the file `mlx90393_params.h + * - SAUL sensor interface + * + * The following pseudomodules are used to choose the communication bus or add + * additional functionalities: + *
+ * Pseudomodule | Functionality + * :-------------------|:------------------------------------------------------- + * `mlx90393_i2c` | Use I2C bus + * `mlx90393_spi` | Use SPI bus + * `mlx90393_int` | Data ready interrupt handling + * `mlx90393_woc` | Wake-up on change mode + *
+ *
+ * + * @note + * - If the handling of data ready interrupts is enabled by module `mlx90393_int`, + * the GPIO pin for the interrupt signal must be defined by the configuration parameter + * mlx90393_params_t::int_pin. The default configuration of this GPIO pin + * is defined by #MLX90393_PARAM_INT_PIN that can be overridden by the + * board definition. The interrupt signal is HIGH active. With the use of the module + * `mlx90393_int` polling is no longer supported. + * - For the Wake-up on change measurement mode the use of interrupt is mandatory. + * If the module `mlx90393_woc` is used, the module `mlx90393_int` is added automatically. + * + * # Using the driver + * + * ## Operation modes + * + * The MLX90393 can be used in three modes: + * + * - **Single measurement mode**
+ * The master will ask for data via the corresponding interface (I2C or SPI), + * waking up the mlx90393 to make a single conversion, immediately followed by an + * automatic return to sleep mode (IDLE) until the next polling of the master. + * + * - **Burst mode**
+ * When the sensor is operating in burst mode, it will make continuous conversions at + * configurable time intervals. Whenever the MLX90393 has made the selected conversions, + * the DRDY signal will be set (active High) on the INT and/or INT/TRG pin to indicate that + * the data is ready for read. + * + * - **Wake-up on change mode**
+ * The Wake-Up on Change (WOC) functionality can be set to only receive interrupts when a + * certain threshold is crossed. In WOC mode, the sensor is operating in burst + * mode and compares each new burst value with a reference value to assert an + * interrupt if the difference between both exceeds a user-defined threshold. + * The reference value is defined as one of the following: + * - The first measurement of WOC mode is stored once as reference value. This measurement at + * “t=0” is then the basis for comparison (Absolute mode). + * - The reference for acquisition(t) is always acquisition(t-1) (Relative mode). + * For this mode the use of interrupt is mandatory. + * + * ## Initialization + * + * The **easiest way to use the driver** is simply to initialize the sensor + * with function #mlx90393_init using the default configuration parameter set + * #mlx90393_params as defined in file mlx90393_params.h. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * static mlx90393_t dev; + * + * if (mlx90393_init(&dev, &mlx90393_params[0]) != 0) { + * ... // error handling + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * After initialization, the sensor is configured according to the standard + * configuration parameters and is fully operational. + * + * ## Fetching data + * + * - In single measurement mode the #mlx90393_read function starts the measurement. + * - When interrupt is configured the #mlx90393_read function locks a mutex + * and sleeps until a measurement is ready and the sensor triggers an interrupt. + * In the corresponding ISR the mutex is unlocked and the function proceeds reading + * and converting the data. + * - If interrupt is not used, the function either sleeps for the conversion time + * in single measurement mode or performs continuous queries for data in burst mode, + * with sleep intervals of 10 ms (polling). + * - For the wake-up on change mode the use of interrupt is mandatory. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * while (1) + * { + * mlx90393_data_t mag_data; + * + * if (mlx90393_read(&dev, &mag_data) != 0) { + * ... // error handling + * } + * ... + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ## Output data format + * + * Function #mlx90393_read returns magnetic field data in micro tesla and temperature + * in deci celsius in parameter \p data. The range of the magnetic field value depends on: + * - the analog chain gain defined by mlx90393_params_t::gain + * - the resolution defined by mlx90393_params_t::resolution + * + * ## Using Interrupts + * + * The MLX90393 sensor supports the use of data-ready interrupts to signal when data is available. + * To use interrupts, add the module `mlx90393_int`, connect the INT pin of the MLX90393 to the + * designated interrupt pin on the MCU, as configured in the parameters. + * In single measurement and burst mode, when calling the function #mlx90393_read, + * it will block the calling thread until an interrupt is triggered. Once an interrupt is + * triggered, the driver handles the interrupt with an internal ISR and then proceeds with + * reading and converting data and finally returns from the #mlx90393_read function. + * In wake-up on change mode the #mlx90393_enable_woc function can be used to activate the + * interrupt which is triggered, when the user defined threshold is crossed. + * Additionally a callback function for handling the interrupt and its argument are + * passed to the function. + * @warning + * The passed callback function is called in interrupt context and should not be used to access + * the sensor or execute time-consuming code. + * + * ## Power Saving + * + * The MLX90393 sensor can be shutdown into idle mode when no continuous measurements are + * required using the function #mlx90393_stop_cont. The power consumption is then reduced to + * a maximum of 5 uA. To restart the MLX90393 in previous continuous measurement mode, + * the #mlx90393_start_cont function can be used. + * In single measurement mode, once the measurement is completed, the sensor transitions into + * idle mode, awaiting a new command from the master to initiate another acquisition. + * + * @{ + * + * @file + * + * @author Michael Ristau + */ + +#ifndef MLX90393_H +#define MLX90393_H + +#include "periph/gpio.h" +#if IS_USED(MODULE_MLX90393_SPI) +#include "periph/spi.h" +#elif IS_USED(MODULE_MLX90393_I2C) +#include "periph/i2c.h" +#endif + +/* Add header includes here */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Data container of the MLX90393 sensor + */ +typedef struct { + int32_t x_axis; /**< Magnometer data from x-axis */ + int32_t y_axis; /**< Magnometer data from y_axis */ + int32_t z_axis; /**< Magnometer data from z_axis */ + int16_t temp; /**< Temperature */ +} mlx90393_data_t; + +/** + * @brief Measurement mode of the MLX90393 sensor + */ +typedef enum { + MLX90393_MODE_BURST, /**< Burst mode */ + MLX90393_MODE_SINGLE_MEASUREMENT, /**< Single measurement mode */ +#if IS_USED(MODULE_MLX90393_WOC) || DOXYGEN + MLX90393_MODE_WOC_ABSOLUTE, /**< Wake-up on change mode, + compared to first measurement */ + MLX90393_MODE_WOC_RELATIVE, /**< Wake-up on change mode, + compared to previous measurement */ +#endif +} mlx90393_mode_t; + +/** + * @brief Output data rate (sample rate) of the MLX90393 sensor + * + * @note When MLX90393_ODR_MAX is used, the output data rata depends only + * on the configuration of oversampling ratio and digital filter. + */ +typedef enum { + MLX90393_ODR_MAX = 0x00, /**< Maximum output data rate */ + MLX90393_ODR_50HZ = 0x01, /**< Output data rate 50 Hz */ + MLX90393_ODR_25HZ = 0x02, /**< Output data rate 25 Hz */ + MLX90393_ODR_12_5HZ = 0x04, /**< Output data rate 12.5 Hz */ + MLX90393_ODR_10HZ = 0x05, /**< Output data rate 10 Hz */ + MLX90393_ODR_6_25HZ = 0x08, /**< Output data rate 6.25 Hz */ + MLX90393_ODR_5HZ = 0x0A, /**< Output data rate 5 Hz */ + MLX90393_ODR_2_5HZ = 0x14, /**< Output data rate 2.5 Hz */ + MLX90393_ODR_2HZ = 0x19, /**< Output data rate 2 Hz */ + MLX90393_ODR_1_25HZ = 0x28, /**< Output data rate 1.25 Hz */ + MLX90393_ODR_1HZ = 0x32, /**< Output data rate 1 Hz */ +} mlx90393_odr_t; + +/** + * @brief Analog chain gain of the MLX90393 sensor + */ +typedef enum { + MLX90393_GAIN_5X, /**< gain factor 5 */ + MLX90393_GAIN_4X, /**< gain factor 4 */ + MLX90393_GAIN_3X, /**< gain factor 3 */ + MLX90393_GAIN_2_5X, /**< gain factor 2.5 */ + MLX90393_GAIN_2X, /**< gain factor 2 */ + MLX90393_GAIN_1_67X, /**< gain factor 1.67 */ + MLX90393_GAIN_1_33X, /**< gain factor 1.33 */ + MLX90393_GAIN_1X, /**< gain factor 1 */ +} mlx90393_gain_t; + +/** + * @brief Desired 16-bit output value from the 19-bit ADC of the MLX90393 sensor + */ +typedef enum { + MLX90393_RES_16, /**< resolution 16 bit */ + MLX90393_RES_17, /**< resolution 17 bit */ + MLX90393_RES_18, /**< resolution 18 bit */ + MLX90393_RES_19, /**< resolution 19 bit */ +} mlx90393_resolution_t; + +/** + * @brief Oversampling ratio of the MLX90393 sensor + */ +typedef enum { + MLX90393_OSR_0, /**< Oversampling ratio 0 */ + MLX90393_OSR_1, /**< Oversampling ratio 1 */ + MLX90393_OSR_2, /**< Oversampling ratio 2 */ + MLX90393_OSR_3, /**< Oversampling ratio 3 */ +} mlx90393_oversampling_ratio_t; + +/** + * @brief Digital filter applicable to ADC of the MLX90393 sensor + */ +typedef enum { + MLX90393_DIG_FILT_0, /**< Digital filter 0 */ + MLX90393_DIG_FILT_1, /**< Digital filter 1 */ + MLX90393_DIG_FILT_2, /**< Digital filter 2 */ + MLX90393_DIG_FILT_3, /**< Digital filter 3 */ + MLX90393_DIG_FILT_4, /**< Digital filter 4 */ + MLX90393_DIG_FILT_5, /**< Digital filter 5 */ + MLX90393_DIG_FILT_6, /**< Digital filter 6 */ + MLX90393_DIG_FILT_7, /**< Digital filter 7 */ +} mlx90393_digital_filter_t; + +/** + * @brief Oversampling rates of magnetic and temperature sensors + */ +typedef struct { + mlx90393_oversampling_ratio_t mag; /**< Oversampling ratio magnetic sensor */ + mlx90393_oversampling_ratio_t temp; /**< Oversampling ratio temperature sensor */ +} mlx90393_oversampling_t; + +#if IS_USED(MODULE_MLX90393_WOC) || DOXYGEN +/** + * @brief Thresholds for wake-up on change mode + */ +typedef struct { + int xy; /**< Threshold for x and y axes */ + int z; /**< Threshold for z axis */ + int temp; /**< Threshold for temperature */ +} mlx90393_threshold_t; + +/** + * @brief Signature of callback function triggered from wake-up on change interrupt + * + * @param[in] arg argument for the callback + */ +typedef void (*mlx90393_cb_t)(void *arg); + +#endif + +/** + * @brief Device configuration parameters + */ +typedef struct { +#if IS_USED(MODULE_MLX90393_SPI) || DOXYGEN + spi_t spi; /**< SPI bus */ + gpio_t cs_pin; /**< Connected chip select pin */ + spi_clk_t clk; /**< clock speed for the SPI bus */ +#elif IS_USED(MODULE_MLX90393_I2C) || DOXYGEN + i2c_t i2c; /**< I2C device */ + uint8_t addr; /**< Magnometer I2C address */ +#endif + mlx90393_mode_t mode; /**< Measurement mode */ +#if IS_USED(MODULE_MLX90393_INT) || DOXYGEN + gpio_t int_pin; /**< Interrupt pin */ +#endif + mlx90393_gain_t gain; /**< Analog chain gain */ + mlx90393_resolution_t resolution; /**< Desired 16-bit output value from the 19-bit ADC */ + mlx90393_odr_t odr; /**< Output data rate */ + mlx90393_oversampling_t oversampling; /**< Oversampling ratio */ + mlx90393_digital_filter_t dig_filt; /**< Digital filter applicable to ADC */ +#if IS_USED(MODULE_MLX90393_WOC) || DOXYGEN + mlx90393_threshold_t threshold; /**< Threshold for wake-up on change mode */ +#endif +} mlx90393_params_t; + +/** + * @brief Device descriptor for the MLX90393 magnetometer + */ +typedef struct { + const mlx90393_params_t *params; /**< Device configuration parameters */ + uint16_t ref_temp; /**< Reference temperature for converting raw temp data + in centi celsius */ +#if !IS_USED(MODULE_MLX90393_INT) || DOXYGEN + uint8_t conversion_time; /**< Conversion time for single measurement mode */ +#endif +} mlx90393_t; + +/** + * @brief Initialize the MLX90393 sensor device + * + * This function resets the sensor and initializes it based on the given configuration + * parameters. Burst and wake-up on change modes are activated immediately. In single + * measurement mode, the measurement is not initiated by this function, mlx90393_read + * must be called for that purpose. + * If wake-up on change mode is used, configuring a valid interrupt pin is mandatory. + * + * @note Some configurations of oversampling and digital filter are not permitted: + * - #MLX90393_OSR_0 and #MLX90393_DIG_FILT_0 + * - #MLX90393_OSR_0 and #MLX90393_DIG_FILT_1 + * - #MLX90393_OSR_1 and #MLX90393_DIG_FILT_0 + * In such cases, the function will return with -EINVAL. + * + * @note The conversion time increases with higher oversampling rates and digital filters. + * If the values are too high, it may result in the configured output data rate + * not being achieved. + * + * @param[inout] dev Device descriptor of MLX90393 device to be initialized + * @param[in] params Configuration parameters + * + * @retval 0 on success + * @retval -ENXIO device not available + * @retval -EIO SPI or I2C communication error + * @retval -EINVAL invalid parameter + * @retval -EFAULT device error + */ +int mlx90393_init(mlx90393_t *dev, const mlx90393_params_t *params); + +/** + * @brief Read magnetic and temperature data from the given device and convert it + * to physical values + * + * This function performs a single measurement or retrieves data from continuous + * measurements based on the configured mode. It supports both interrupt-based and + * polling-based measurement retrieval. + * + * If interrupt is not used, the function either sleeps for the conversion time + * in single measurement mode or performs continuous queries for data in burst mode, + * with sleep intervals of 10 ms. When interrupt is used, a mutex is locked, and + * the function sleeps until data is ready, triggering an interrupt. + * + * @note For wake-up on change mode the use of interrupt is mandatory. + * + * @pre + * - Device must be properly initialized using mlx90393_init + * + * @param[in] dev Device descriptor of MLX90393 device to read from + * @param[out] data MLX90393 data in micro Tesla and deci Celsius + * + * @retval 0 on success + * @retval -EIO SPI or I2C communication error + * @retval -EFAULT device error + */ +int mlx90393_read(mlx90393_t *dev, mlx90393_data_t *data); + +/** + * @brief Stop measurements in continuous mode + * + * Burst and Wake-up on change measurement modes are stopped and the device + * is forced into idle mode. Once stopped, the modes can be restarted with mlx90393_start_cont. + * + * @param[in] dev Device descriptor of MLX90393 device + * + * @retval 0 on success + * @retval -EIO SPI or I2C communication error + * @retval -EFAULT device error + */ +int mlx90393_stop_cont(mlx90393_t *dev); + +/** + * @brief Start measurements in continuous mode + * + * Turns the device out of idle mode and starts a previously stopped burst or wake-up on change + * mode with same parameters as defined in configuration parameters. This function does not + * initialize the device and the continuous measurement mode. + * + * @pre + * - Device must be initialized with mlx90393_init + * - Continuous measurement was stopped with mlx90393_stop_cont + * + * @param[in] dev Device descriptor of MLX90393 device + * + * @retval 0 on success + * @retval -EIO SPI or I2C communication error + * @retval -EFAULT device error + */ +int mlx90393_start_cont(mlx90393_t *dev); + +#if IS_USED(MODULE_MLX90393_WOC) || DOXYGEN +/** + * @brief Activate wake-up on change interrupt + * + * Activates the interrupt which is triggered if the user defined woc threshold is crossed. + * The passed callback function is called when the interrupt occurs. After the interrupt + * the measurement data can be read with mlx90393_read. + * + * @warning + * The passed callback function is called in interrupt context and should not be used to access + * the sensor or execute time-consuming code. + * + * @param[in] dev Device descriptor of MLX90393 device + * @param cb Callback to be executed when interrupt occurs + * @param arg Callback argument + */ +void mlx90393_enable_woc(mlx90393_t *dev, mlx90393_cb_t cb, void *arg); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* MLX90393_H */ +/** @} */ diff --git a/drivers/mlx90393/Kconfig b/drivers/mlx90393/Kconfig new file mode 100644 index 000000000000..30fe746ba76a --- /dev/null +++ b/drivers/mlx90393/Kconfig @@ -0,0 +1,15 @@ +# Copyright (c) 2023 mi6527ri +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +config MODULE_MLX90393 + bool "drivers_mlx90393" + depends on TEST_KCONFIG + depends on HAS_PERIPH_GPIO + depends on HAS_PERIPH_GPIO_IRQ + depends on HAS_PERIPH_I2C + select MODULE_PERIPH_GPIO + select MODULE_PERIPH_GPIO_IRQ + select MODULE_PERIPH_I2C diff --git a/drivers/mlx90393/Makefile b/drivers/mlx90393/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /dev/null +++ b/drivers/mlx90393/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/mlx90393/Makefile.dep b/drivers/mlx90393/Makefile.dep new file mode 100644 index 000000000000..c6c84b13f235 --- /dev/null +++ b/drivers/mlx90393/Makefile.dep @@ -0,0 +1,21 @@ +ifneq (,$(filter mlx90393,$(USEMODULE))) + USEMODULE += ztimer + USEMODULE += ztimer_msec +endif + +ifneq (,$(filter mlx90393_i2c,$(USEMODULE))) + FEATURES_REQUIRED += periph_i2c +endif + +ifneq (,$(filter mlx90393_spi,$(USEMODULE))) + FEATURES_REQUIRED += periph_spi +endif + +ifneq (,$(filter mlx90393_woc,$(USEMODULE))) + USEMODULE += mlx90393_int +endif + +ifneq (,$(filter mlx90393_int,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio + FEATURES_REQUIRED += periph_gpio_irq +endif diff --git a/drivers/mlx90393/Makefile.include b/drivers/mlx90393/Makefile.include new file mode 100644 index 000000000000..71f4491b6cae --- /dev/null +++ b/drivers/mlx90393/Makefile.include @@ -0,0 +1,8 @@ +# include variants of the MLX90393 drivers as pseudo modules +PSEUDOMODULES += mlx90393_i2c +PSEUDOMODULES += mlx90393_spi +PSEUDOMODULES += mlx90393_int +PSEUDOMODULES += mlx90393_woc + +USEMODULE_INCLUDES_mlx90393 := $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_mlx90393) diff --git a/drivers/mlx90393/include/mlx90393_constants.h b/drivers/mlx90393/include/mlx90393_constants.h new file mode 100644 index 000000000000..2e9084f2b99d --- /dev/null +++ b/drivers/mlx90393/include/mlx90393_constants.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2023 mi6527ri + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_mlx90393 + * @{ + * + * @file + * @brief MLX90393 internal addresses, registers and constants + * + * @author Michael Ristau + */ + +#ifndef MLX90393_CONSTANTS_H +#define MLX90393_CONSTANTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Register address map + */ +#define MLX90393_REG_CONF0 0x00 /**< Configuration register 0 */ +#define MLX90393_REG_CONF1 0x01 /**< Configuration register 1 */ +#define MLX90393_REG_CONF2 0x02 /**< Configuration register 2 */ +#define MLX90393_REG_SENS_TC 0x03 /**< Sensitivity drift compensation factor */ +#define MLX90393_REG_OFFSET_X 0x04 /**< Offset for x axis */ +#define MLX90393_REG_OFFSET_Y 0x05 /**< Offset for y axis */ +#define MLX90393_REG_OFFSET_Z 0x06 /**< Offset for z axis */ +#define MLX90393_REG_WOXY_THRESHOLD 0x07 /**< Wake up on change threshold for x and y axis */ +#define MLX90393_REG_WOZ_THRESHOLD 0x08 /**< Wake up on change threshold for z axis */ +#define MLX90393_REG_WOT_THRESHOLD 0x09 /**< Wake up on change threshold for temp */ +#define MLX90393_REG_CONN_TEST 0x0A /**< Free available register used for + connectivity test */ +#define MLX90393_REG_REF_TEMP 0x24 /**< Reference temperature */ + +/** + * @brief Configuration parameter bit masks + */ +#define MLX90393_MASK_BDR 0x003F /**< burst data rate */ +#define MLX90393_MASK_WOC_DIFF 0x1000 /**< wake up on change mode (relative or absolute) */ +#define MLX90393_MASK_TCMP_EN 0x0400 /**< enable temperature compensation */ +#define MLX90393_MASK_GAIN_SEL 0x0070 /**< analog chain gain */ +#define MLX90393_MASK_RES_XYZ 0x07E0 /**< xyz resolution */ +#define MLX90393_MASK_COMM_MODE 0x6000 /**< set allowed bus communication (I2C or SPI) */ +#define MLX90393_MASK_OSR 0x0003 /**< oversampling ratio magnetic sensor */ +#define MLX90393_MASK_OSR2 0x1800 /**< oversampling ratio temperature */ +#define MLX90393_MASK_DIG_FILT 0x001C /**< digital filter magnetic sensor */ + +/** + * @brief Number of left shifts in configuration registers + */ +#define MLX90393_SHIFT_OSR2 11 /**< oversampling ratio temperature */ +#define MLX90393_SHIFT_DIG_FILT 2 /**< digital filter magnetic sensor */ +#define MLX90393_SHIFT_GAIN 4 /**< analog chain gain */ +#define MLX90393_SHIFT_RES_X 5 /**< x resolution */ +#define MLX90393_SHIFT_RES_Y 7 /**< y resolution */ +#define MLX90393_SHIFT_RES_Z 9 /**< z resolution */ +#define MLX90393_SHIFT_WOC_MODE 12 /**< wake-up on change mode */ + +/** + * @brief Command Set + */ +#define MLX90393_COMMAND_SB 0x1F /**< start burst mode */ +#define MLX90393_COMMAND_SW 0x2F /**< start wake up on change mode */ +#define MLX90393_COMMAND_SM 0x3F /**< start single measurement mode */ +#define MLX90393_COMMAND_RM 0x4F /**< read measurement */ +#define MLX90393_COMMAND_RR 0x50 /**< read register */ +#define MLX90393_COMMAND_WR 0x60 /**< write register */ +#define MLX90393_COMMAND_EX 0x80 /**< exit mode */ +#define MLX90393_COMMAND_HR 0xD0 /**< memory recall */ +#define MLX90393_COMMAND_HS 0xE0 /**< memory store */ +#define MLX90393_COMMAND_RT 0xF0 /**< reset */ + +/** + * @brief Status byte bit map + */ +#define MLX90393_STATUS_RESET 0x04 /**< reset bit */ +#define MLX90393_STATUS_ERROR 0x10 /**< error bit */ + +/** + * @brief Timeout durations in ms + */ +#define MLX90393_COMMAND_EX_TIMEOUT 1 /**< Timeout after exit command */ +#define MLX90393_COMMAND_RT_TIMEOUT 2 /**< Timeout after reset command */ + +/** + * @brief Temperature conversion constants + */ +#define MLX90393_TEMP_OFFSET 3500 /**< Temperature offset in centi celsius */ +#define MLX90393_TEMP_RESOLUTION 452 /**< Temperature sensor resolution + (45.2 * 10 for avoiding float values) */ + +/** + * @brief Magnetic flux conversion constants + */ +#define MLX90393_XY_SENS 150 /**< xy sensitivity in nT/LSB */ +#define MLX90393_Z_SENS 242 /**< z sensitivity in nT/LSB */ + +/** + * @brief Timing constants + */ +#define MLX90393_T_STBY 264 /**< Time from IDLE to STBY in us */ +#define MLX90393_T_ACTIVE 432 /**< Time from STBY to ACTIVE in us */ +#define MLX90393_T_CONV_END 120 /**< Time to end analog active mode in us */ + +#ifdef __cplusplus +} +#endif + +#endif /* MLX90393_CONSTANTS_H */ +/** @} */ diff --git a/drivers/mlx90393/include/mlx90393_params.h b/drivers/mlx90393/include/mlx90393_params.h new file mode 100644 index 000000000000..ee5cc65a06b2 --- /dev/null +++ b/drivers/mlx90393/include/mlx90393_params.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2023 mi6527ri + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_mlx90393 + * + * @{ + * @file + * @brief Default configuration for MLX90393 device driver + * + * @author Michael Ristau + */ + +#ifndef MLX90393_PARAMS_H +#define MLX90393_PARAMS_H + +#include "board.h" +#include "mlx90393.h" +#include "mlx90393_constants.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Set default configuration parameters for the MLX90393 + * @{ + */ +#ifndef MLX90393_PARAM_MODE +/** + * @brief Default measurement mode + */ +#define MLX90393_PARAM_MODE (MLX90393_MODE_BURST) +#endif + +#if IS_USED(MODULE_MLX90393_INT) || DOXYGEN +#ifndef MLX90393_PARAM_INT_PIN +/** + * @brief Default interrupt pin + */ +#define MLX90393_PARAM_INT_PIN .int_pin = (GPIO_PIN(PORT_C, 8)), +#endif +#else +#define MLX90393_PARAM_INT_PIN +#endif + +#ifndef MLX90393_PARAM_GAIN +/** + * @brief Default gain + */ +#define MLX90393_PARAM_GAIN (MLX90393_GAIN_1X) +#endif + +#ifndef MLX90393_PARAM_RES +/** + * @brief Default resolution + */ +#define MLX90393_PARAM_RES (MLX90393_RES_19) +#endif + +#ifndef MLX90393_PARAM_ODR +/** + * @brief Default sampling rate + */ +#define MLX90393_PARAM_ODR (MLX90393_ODR_10HZ) +#endif + +#ifndef MLX90393_PARAM_OSR +/** + * @brief Default oversampling ratio + */ +#define MLX90393_PARAM_OSR \ +{ \ + .mag = MLX90393_OSR_1, \ + .temp = MLX90393_OSR_1 \ +} +#endif + +#ifndef MLX90393_PARAM_DIG_FILT +/** + * @brief Default digital filter + */ +#define MLX90393_PARAM_DIG_FILT (MLX90393_DIG_FILT_1) +#endif + +#if IS_USED(MODULE_MLX90393_WOC) || DOXYGEN +#ifndef MLX90393_PARAM_THRESHOLD +/** + * @brief Default thresholds for Wake-up On Change mode + */ +#define MLX90393_PARAM_THRESHOLD \ +.threshold = \ +{ \ + .xy = 0xFFFF, \ + .z = 1000, \ + .temp = 0xFFFF \ +}, +#endif +#else +#define MLX90393_PARAM_THRESHOLD +#endif + +/* Default configuration for SPI mode */ +#if IS_USED(MODULE_MLX90393_SPI) || DOXYGEN + +#ifndef MLX90393_PARAM_SPI +/** + * @brief Default SPI device + */ +#define MLX90393_PARAM_SPI (SPI_DEV(0)) +#endif + +#ifndef MLX90393_PARAM_SPI_CS_PIN +/** + * @brief Default SPI chip select pin + */ +#define MLX90393_PARAM_SPI_CS_PIN (GPIO_PIN(PORT_B, 6)) +#endif + +#ifndef MLX90393_PARAM_SPI_CLK +/** + * @brief Default SPI clock speed + */ +#define MLX90393_PARAM_SPI_CLK (SPI_CLK_10MHZ) +#endif + +#ifndef MLX90393_PARAMS_SPI +/** + * @brief Default SPI params + */ +#define MLX90393_PARAMS_SPI { \ + .spi = MLX90393_PARAM_SPI, \ + .cs_pin = MLX90393_PARAM_SPI_CS_PIN, \ + .clk = MLX90393_PARAM_SPI_CLK, \ + .mode = MLX90393_PARAM_MODE, \ + .gain = MLX90393_PARAM_GAIN, \ + .resolution = MLX90393_PARAM_RES, \ + .odr = MLX90393_PARAM_ODR, \ + .oversampling = MLX90393_PARAM_OSR, \ + .dig_filt = MLX90393_PARAM_DIG_FILT, \ + MLX90393_PARAM_INT_PIN \ + MLX90393_PARAM_THRESHOLD \ + } +#endif + +/* Default configuration for I2C mode */ +#elif IS_USED(MODULE_MLX90393_I2C) || DOXYGEN + +#ifndef MLX90393_PARAM_I2C +/** + * @brief Default I2C device + */ +#define MLX90393_PARAM_I2C (I2C_DEV(0)) +#endif + +#ifndef MLX90393_PARAM_I2C_ADDR +/** + * @brief Default I2C device address + */ +#define MLX90393_PARAM_I2C_ADDR (0x0C) +#endif + +#ifndef MLX90393_PARAMS_I2C +/** + * @brief Default I2C params + */ +#define MLX90393_PARAMS_I2C { \ + .i2c = MLX90393_PARAM_I2C, \ + .addr = MLX90393_PARAM_I2C_ADDR, \ + .mode = MLX90393_PARAM_MODE, \ + .gain = MLX90393_PARAM_GAIN, \ + .resolution = MLX90393_PARAM_RES, \ + .odr = MLX90393_PARAM_ODR, \ + .oversampling = MLX90393_PARAM_OSR, \ + .dig_filt = MLX90393_PARAM_DIG_FILT, \ + MLX90393_PARAM_INT_PIN \ + MLX90393_PARAM_THRESHOLD \ + } +#endif +#endif +/**@}*/ + +/** + * @brief Configure params for MLX90393 + */ +static const mlx90393_params_t mlx90393_params[] = +{ +#if IS_USED(MODULE_MLX90393_SPI) + MLX90393_PARAMS_SPI +#elif IS_USED(MODULE_MLX90393_I2C) + MLX90393_PARAMS_I2C +#endif +}; + +#ifdef __cplusplus +} +#endif + +#endif /* MLX90393_PARAMS_H */ +/** @} */ diff --git a/drivers/mlx90393/mlx90393.c b/drivers/mlx90393/mlx90393.c new file mode 100644 index 000000000000..76ea9fb14ed0 --- /dev/null +++ b/drivers/mlx90393/mlx90393.c @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2023 Michael Ristau + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_mlx90393 + * @{ + * + * @file + * @brief Device driver implementation for the MLX90393 + * + * @author Michael Ristau + * + * @} + */ + +#include +#include "mlx90393.h" +#include "mlx90393_constants.h" +#include "mlx90393_params.h" +#include "ztimer.h" + +#if !IS_USED(MODULE_MLX90393_INT) +#include "imath.h" +#endif + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#if IS_USED(MODULE_MLX90393_SPI) +#define DEV_SPI (dev->params->spi) +#define DEV_CS_PIN (dev->params->cs_pin) +#define DEV_CLK (dev->params->clk) +#define SPI_MODE (SPI_MODE_3) +#elif IS_USED(MODULE_MLX90393_I2C) +#define DEV_I2C (dev->params->i2c) +#define DEV_ADDR (dev->params->addr) +#endif + +#if IS_USED(MODULE_MLX90393_INT) +#define DEV_INT_PIN (dev->params->int_pin) +#endif + +#define DEV_MODE (dev->params->mode) +#define DEV_ODR (dev->params->odr) +#define DEV_GAIN (dev->params->gain) +#define DEV_RESOLUTION (dev->params->resolution) +#define DEV_OSR_MAG (dev->params->oversampling.mag) +#define DEV_OSR_TEMP (dev->params->oversampling.temp) +#define DEV_DIG_FILT (dev->params->dig_filt) +#define DEV_REF_TEMP (dev->ref_temp) +#define CONN_TEST_DATA (0xAF03) + +#define MLX90393_BM_READ_TIMEOUT (10) + +/** Forward declaration of functions for internal use */ +static int _init_bus(const mlx90393_t *dev); +static void _acquire(mlx90393_t *dev); +static void _release(mlx90393_t *dev); +static int _write_byte(mlx90393_t *dev, uint8_t data); +static int _read_byte(mlx90393_t *dev, uint8_t *buffer); +static int _write_bytes(mlx90393_t *dev, void *data, size_t len); +static int _read_bytes(mlx90393_t *dev, void *buffer, size_t len); +static int _check_status_byte(mlx90393_t *dev); +static int _write_register(mlx90393_t *dev, uint8_t addr, uint16_t value); +static int _read_register(mlx90393_t *dev, uint8_t addr, uint16_t *value); +static int _write_register_bits(mlx90393_t *dev, uint8_t addr, uint16_t mask, uint16_t value); +static int _calculate_temp(uint16_t raw_temp, uint16_t ref_temp); +static int _get_gain_factor(mlx90393_gain_t gain); +static int _reset(mlx90393_t *dev); +static int _exit(mlx90393_t *dev); +static int _is_avaiable(mlx90393_t *dev); +static int _read_measurement(mlx90393_t *dev, uint8_t *buffer); + +#if !IS_USED(MODULE_MLX90393_INT) +static void _calculate_conv_time(mlx90393_t *dev); +#endif + +int mlx90393_init(mlx90393_t *dev, const mlx90393_params_t *params) +{ + assert(dev); + assert(params); + +#if IS_USED(MODULE_MLX90393_INT) + assert(gpio_is_valid(DEV_INT_PIN)); +#endif + + dev->params = params; + + int error = 0; + + if ((error = _init_bus(dev)) != 0) { + return error; + } + + _acquire(dev); + + /* exit all continuous measurement modes */ + if ((error = _exit(dev)) != 0) { + _release(dev); + return error; + } + ztimer_sleep(ZTIMER_MSEC, MLX90393_COMMAND_EX_TIMEOUT); + /* reset mlx90393 */ + if ((error = _reset(dev)) != 0) { + _release(dev); + return error; + } + ztimer_sleep(ZTIMER_MSEC, MLX90393_COMMAND_RT_TIMEOUT); + /* check availability of the sensor */ + if ((error = _is_avaiable(dev)) != 0) { + _release(dev); + DEBUG("[mlx90393] error: device not available\n"); + return error; + } + /* store ref temp in dev */ + if ((error = _read_register(dev, MLX90393_REG_REF_TEMP, &DEV_REF_TEMP)) != 0) { + _release(dev); + return error; + } + /* check oversampling and digital filter configuration */ + if ((DEV_OSR_MAG == 0 && DEV_DIG_FILT == 0) || + (DEV_OSR_MAG == 0 && DEV_DIG_FILT == 1) || + (DEV_OSR_MAG == 1 && DEV_DIG_FILT == 0)) { + _release(dev); + DEBUG("[mlx90393] error: the configuration of oversampling and digital filter \ + is not permitted\n"); + return -EINVAL; + } + /* magnetic sensor oversampling */ + if ((error = _write_register_bits(dev, MLX90393_REG_CONF2, MLX90393_MASK_OSR, + DEV_OSR_MAG)) != 0) { + _release(dev); + return error; + } + /* magnetic sensor digital filter */ + uint16_t dig_filt_value_mask = DEV_DIG_FILT << MLX90393_SHIFT_DIG_FILT; + if ((error = _write_register_bits(dev, MLX90393_REG_CONF2, MLX90393_MASK_DIG_FILT, + dig_filt_value_mask)) != 0) { + _release(dev); + return error; + } + /* temperature oversampling */ + uint16_t temp_osr_value_mask = DEV_OSR_TEMP << MLX90393_SHIFT_OSR2; + if ((error = _write_register_bits(dev, MLX90393_REG_CONF2, MLX90393_MASK_OSR2, + temp_osr_value_mask)) != 0) { + _release(dev); + return error; + } + /* gain */ + uint16_t gain_value_mask = DEV_GAIN << MLX90393_SHIFT_GAIN; + if ((error = _write_register_bits(dev, MLX90393_REG_CONF0, MLX90393_MASK_GAIN_SEL, + gain_value_mask)) != 0) { + _release(dev); + return error; + } + /* resolution */ + uint16_t xyz_resolution_value_mask = (DEV_RESOLUTION << MLX90393_SHIFT_RES_Z) | + (DEV_RESOLUTION << MLX90393_SHIFT_RES_Y) | + (DEV_RESOLUTION << MLX90393_SHIFT_RES_X); + if ((error = _write_register_bits(dev, MLX90393_REG_CONF2, MLX90393_MASK_RES_XYZ, + xyz_resolution_value_mask)) != 0) { + _release(dev); + return error; + } + /* burst more */ + if (DEV_MODE == MLX90393_MODE_BURST) { + /* set burst data rate */ + if ((error = _write_register_bits(dev, MLX90393_REG_CONF1, MLX90393_MASK_BDR, + DEV_ODR)) != 0) { + _release(dev); + return error; + } + /* start burst mode */ + if ((error = _write_byte(dev, MLX90393_COMMAND_SB)) != 0) { + _release(dev); + return error; + } + if ((error = _check_status_byte(dev)) != 0) { + _release(dev); + return error; + } + } +#if IS_USED(MODULE_MLX90393_WOC) + /* wake up on change mode */ + else if (DEV_MODE == MLX90393_MODE_WOC_ABSOLUTE || DEV_MODE == MLX90393_MODE_WOC_RELATIVE) { + /* set absolute or relative wake up on change mode */ + int woc_mode = 0; + if (DEV_MODE == MLX90393_MODE_WOC_RELATIVE) { + woc_mode = 1; + } + woc_mode <<= MLX90393_SHIFT_WOC_MODE; + if ((error = _write_register_bits(dev, MLX90393_REG_CONF1, MLX90393_MASK_WOC_DIFF, + woc_mode)) != 0) { + _release(dev); + return error; + } + /* set burst data rate */ + if ((error = _write_register_bits(dev, MLX90393_REG_CONF1, MLX90393_MASK_BDR, + DEV_ODR)) != 0) { + _release(dev); + return error; + } + /* set thresholds */ + int gain = _get_gain_factor(DEV_GAIN); + uint16_t raw_xy_threshold = (1000 * dev->params->threshold.xy / (MLX90393_XY_SENS + * (1 << DEV_RESOLUTION) * gain)) * 100; + uint16_t raw_z_threshold = (1000 * dev->params->threshold.z / (MLX90393_Z_SENS + * (1 << DEV_RESOLUTION) * gain)) * 100; + uint16_t raw_temp_threshold = dev->params->threshold.temp * MLX90393_TEMP_RESOLUTION + / 1000; + + if ((error = _write_register(dev, MLX90393_REG_WOXY_THRESHOLD, raw_xy_threshold))) { + _release(dev); + return error; + } + if ((error = _write_register(dev, MLX90393_REG_WOZ_THRESHOLD, raw_z_threshold))) { + _release(dev); + return error; + } + if ((error = _write_register(dev, MLX90393_REG_WOT_THRESHOLD, raw_temp_threshold))) { + _release(dev); + return error; + } + /* start wake up on change mode */ + if ((error = _write_byte(dev, MLX90393_COMMAND_SW)) != 0) { + _release(dev); + return error; + } + if ((error = _check_status_byte(dev)) != 0) { + _release(dev); + return error; + } + } +#endif + +#if !IS_USED(MODULE_MLX90393_INT) + if (DEV_MODE == MLX90393_MODE_SINGLE_MEASUREMENT) { + _calculate_conv_time(dev); + } +#endif + + _release(dev); + return 0; +} + +#if IS_USED(MODULE_MLX90393_INT) + +static void _isr(void *lock) +{ + mutex_unlock(lock); +} + +#endif + +int mlx90393_read(mlx90393_t *dev, mlx90393_data_t *data) +{ + assert(dev); + assert(data); + +#if IS_USED(MODULE_MLX90393_INT) + assert(gpio_is_valid(DEV_INT_PIN)); +#endif + + int error = 0; + + /* start single measurement if used */ + if (DEV_MODE == MLX90393_MODE_SINGLE_MEASUREMENT) { + _acquire(dev); + if ((error = _write_byte(dev, MLX90393_COMMAND_SM)) != 0) { + _release(dev); + return error; + } + if ((error = _check_status_byte(dev)) != 0) { + _release(dev); + return error; + } + _release(dev); + } + + uint8_t buffer[9]; + +#if IS_USED(MODULE_MLX90393_INT) +#if IS_USED(MODULE_MLX90393_WOC) + if (DEV_MODE == MLX90393_MODE_WOC_RELATIVE || DEV_MODE == MLX90393_MODE_WOC_ABSOLUTE) { + if ((error = _read_measurement(dev, buffer)) != 0) { + return error; + } + gpio_irq_disable(DEV_INT_PIN); + } +#endif + if (DEV_MODE == MLX90393_MODE_SINGLE_MEASUREMENT || DEV_MODE == MLX90393_MODE_BURST) { + /* wait for interrupt */ + mutex_t lock = MUTEX_INIT_LOCKED; + gpio_init_int(DEV_INT_PIN, GPIO_IN_PU, GPIO_RISING, _isr, &lock); + mutex_lock(&lock); + gpio_irq_disable(DEV_INT_PIN); + if ((error = _read_measurement(dev, buffer)) != 0) { + return error; + } + } +#else + /* sleep for conversion time in single measurement mode */ + if (DEV_MODE == MLX90393_MODE_SINGLE_MEASUREMENT) { + ztimer_sleep(ZTIMER_MSEC, dev->conversion_time); + if ((error = _read_measurement(dev, buffer)) != 0) { + return error; + } + } + /* polling in burst mode */ + else if (DEV_MODE ==MLX90393_MODE_BURST) { + while (_read_measurement(dev, buffer) != 0) { + ztimer_sleep(ZTIMER_MSEC, MLX90393_BM_READ_TIMEOUT); + } + } +#endif + + /* convert read data according to Table 17 and 21 from Datasheet */ + int16_t raw_x, raw_y, raw_z; + uint16_t raw_temp; + raw_temp = (uint16_t)((buffer[1] << 8) | buffer[2]); + raw_x = (int16_t)((buffer[3] << 8) | buffer[4]); + raw_y = (int16_t)((buffer[5] << 8) | buffer[6]); + raw_z = (int16_t)((buffer[7] << 8) | buffer[8]); + + if (DEV_RESOLUTION == MLX90393_RES_18) { + raw_x -= 0x8000; + raw_y -= 0x8000; + raw_z -= 0x8000; + } + else if (DEV_RESOLUTION == MLX90393_RES_19) { + raw_x -= 0x4000; + raw_y -= 0x4000; + raw_z -= 0x4000; + } + + data->temp = _calculate_temp(raw_temp, DEV_REF_TEMP); + int gain = _get_gain_factor(DEV_GAIN); + data->x_axis = ((raw_x * gain) / 100) * MLX90393_XY_SENS * (1 << DEV_RESOLUTION) / 1000; + data->y_axis = ((raw_y * gain) / 100) * MLX90393_XY_SENS * (1 << DEV_RESOLUTION) / 1000; + data->z_axis = ((raw_z * gain) / 100) * MLX90393_Z_SENS * (1 << DEV_RESOLUTION) / 1000; + + return 0; +} + +#if IS_USED(MODULE_MLX90393_WOC) + +void mlx90393_enable_woc(mlx90393_t *dev, mlx90393_cb_t cb, void *arg) +{ + assert(dev); + assert(gpio_is_valid(DEV_INT_PIN)); + assert(cb); + + gpio_init_int(DEV_INT_PIN, GPIO_IN_PU, GPIO_RISING, cb, arg); +} + +#endif + +int mlx90393_stop_cont(mlx90393_t *dev) +{ + assert(dev); + + int error = 0; + + _acquire(dev); + + if ((error = _exit(dev)) != 0) { + _release(dev); + return error; + } + + _release(dev); + return 0; +} + +int mlx90393_start_cont(mlx90393_t *dev) +{ + assert(dev); + + if (DEV_MODE == MLX90393_MODE_SINGLE_MEASUREMENT) { + return -EFAULT; + } + + int error = 0; + + _acquire(dev); + + if (DEV_MODE == MLX90393_MODE_BURST) { + if ((error = _write_byte(dev, MLX90393_COMMAND_SB)) != 0) { + _release(dev); + return error; + } + } + +#if IS_USED(MODULE_MLX90393_WOC) + else if (DEV_MODE == MLX90393_MODE_WOC_RELATIVE || DEV_MODE == MLX90393_MODE_WOC_ABSOLUTE) { + if ((error = _write_byte(dev, MLX90393_COMMAND_SW)) != 0) { + _release(dev); + return error; + } + } +#endif + + if ((error = _check_status_byte(dev)) != 0) { + _release(dev); + return error; + } + + _release(dev); + return 0; +} + +#if IS_USED(MODULE_MLX90393_SPI) + +static int _init_bus(const mlx90393_t *dev) +{ + if (spi_init_cs(DEV_SPI, DEV_CS_PIN) != SPI_OK) { + DEBUG("[mlx90393] error: unable to configure the chip select pin\n"); + return -EIO; + } + return 0; +} + +static void _acquire(mlx90393_t *dev) +{ + spi_acquire(DEV_SPI, DEV_CS_PIN, SPI_MODE, DEV_CLK); +} + +static void _release(mlx90393_t *dev) +{ + spi_release(DEV_SPI); +} + +static int _write_byte(mlx90393_t *dev, uint8_t data) +{ + spi_transfer_byte(DEV_SPI, DEV_CS_PIN, false, data); + return 0; +} + +static int _read_byte(mlx90393_t *dev, uint8_t *buffer) +{ + *buffer = spi_transfer_byte(DEV_SPI, DEV_CS_PIN, false, 0x0); + return 0; +} + +static int _write_bytes(mlx90393_t *dev, void *data, size_t len) +{ + spi_transfer_bytes(DEV_SPI, DEV_CS_PIN, false, data, NULL, len); + return 0; +} + +static int _read_bytes(mlx90393_t *dev, void *buffer, size_t len) +{ + spi_transfer_bytes(DEV_SPI, DEV_CS_PIN, false, NULL, buffer, len); + return 0; +} + +#elif IS_USED(MODULE_MLX90393_I2C) + +static int _init_bus(const mlx90393_t *dev) +{ + (void)dev; + return 0; +} + +static void _acquire(mlx90393_t *dev) +{ + i2c_acquire(DEV_I2C); +} + +static void _release(mlx90393_t *dev) +{ + i2c_release(DEV_I2C); +} + +static int _write_byte(mlx90393_t *dev, uint8_t data) +{ + return i2c_write_byte(DEV_I2C, DEV_ADDR, data, 0) ? -EIO : 0; +} + +static int _read_byte(mlx90393_t *dev, uint8_t *buffer) +{ + return i2c_read_byte(DEV_I2C, DEV_ADDR, buffer, 0) ? -EIO : 0; +} + +static int _write_bytes(mlx90393_t *dev, void *data, size_t len) +{ + return i2c_write_bytes(DEV_I2C, DEV_ADDR, data, len, 0) ? -EIO : 0; +} + +static int _read_bytes(mlx90393_t *dev, void *buffer, size_t len) +{ + return i2c_read_bytes(DEV_I2C, DEV_ADDR, buffer, len, 0) ? -EIO : 0; +} + +#endif + +static int _check_status_byte(mlx90393_t *dev) +{ + uint8_t status; + int error = 0; + + if ((error = _read_byte(dev, &status)) != 0) { + return error; + } + return (status & MLX90393_STATUS_ERROR) ? -EFAULT : 0; +} + +static int _write_register(mlx90393_t *dev, uint8_t addr, uint16_t value) +{ + uint8_t buffer[4]; + buffer[0] = MLX90393_COMMAND_WR; + buffer[1] = (uint8_t) (value >> 8); + buffer[2] = (uint8_t) (value & 0xFF); + buffer[3] = addr << 2; + + int error = 0; + + if ((error = _write_bytes(dev, buffer, 4)) != 0) { + return error; + } + if ((error = _check_status_byte(dev)) != 0) { + return error; + } + return 0; +} + +static int _read_register(mlx90393_t *dev, uint8_t addr, uint16_t *value) +{ + uint8_t buffer_send[2]; + buffer_send[0] = MLX90393_COMMAND_RR; + buffer_send[1] = addr << 2; + int error = 0; + + if ((error = _write_bytes(dev, buffer_send, 2)) != 0) { + return error; + } + uint8_t buffer_receive[3]; + if ((error = _read_bytes(dev, buffer_receive, 3)) != 0) { + return error; + } + if (buffer_receive[0] & MLX90393_STATUS_ERROR) { + return -EFAULT; + } + *value = (uint16_t)((buffer_receive[1] << 8) | buffer_receive[2]); + + return 0; +} + +static int _write_register_bits(mlx90393_t *dev, uint8_t addr, uint16_t mask, uint16_t value) +{ + uint16_t reg_value; + int error = 0; + + if ((error = _read_register(dev, addr, ®_value)) != 0) { + return error; + } + reg_value &= ~mask; + reg_value |= (mask & value); + if ((error = _write_register(dev, addr, reg_value)) != 0) { + return error; + } + return 0; +} + +static int _calculate_temp(uint16_t raw_temp, uint16_t ref_temp) +{ + /* calculate temp in deci celsius (Application note MLX90393 temperature compensation - v4) */ + return (MLX90393_TEMP_OFFSET + (((raw_temp - ref_temp) * 1000) / MLX90393_TEMP_RESOLUTION)); +} + +static int _get_gain_factor(mlx90393_gain_t gain) +{ + switch (gain) + { + case MLX90393_GAIN_5X: + return 500; + case MLX90393_GAIN_4X: + return 400; + case MLX90393_GAIN_3X: + return 300; + case MLX90393_GAIN_2_5X: + return 250; + case MLX90393_GAIN_2X: + return 200; + case MLX90393_GAIN_1_67X: + return 167; + case MLX90393_GAIN_1_33X: + return 133; + case MLX90393_GAIN_1X: + return 100; + default: + return -1; + } +} + +static int _reset(mlx90393_t *dev) +{ + int error = 0; + + if ((error = _write_byte(dev, MLX90393_COMMAND_RT)) != 0) { + return error; + } + if ((error = _check_status_byte(dev)) != 0) { + return error; + } + return 0; +} + +static int _exit(mlx90393_t *dev) +{ + int error = 0; + + if ((error = _write_byte(dev, MLX90393_COMMAND_EX)) != 0) { + return error; + } + if ((error = _check_status_byte(dev)) != 0) { + return error; + } + return 0; +} + +static int _is_avaiable(mlx90393_t *dev) +{ + int error = 0; + + if ((error = _write_register(dev, MLX90393_REG_CONN_TEST, CONN_TEST_DATA)) != 0) { + return error; + } + uint16_t buffer = 0x00; + if ((error = _read_register(dev, MLX90393_REG_CONN_TEST, &buffer)) != 0) { + return error; + } + return buffer == CONN_TEST_DATA ? 0 : -ENXIO; +} + +#if !IS_USED(MODULE_MLX90393_INT) + +static void _calculate_conv_time(mlx90393_t *dev) +{ + /* calculate single measurement conversion time in ms + (Datasheet table 8: Timing specifications) */ + int conv_mag = 67 + 64 * powi(2, DEV_OSR_MAG) * (2 + powi(2, DEV_DIG_FILT)); + int conv_temp = 67 + 192 * powi(2, DEV_OSR_TEMP); + dev->conversion_time = (MLX90393_T_STBY + MLX90393_T_ACTIVE + 3 * conv_mag + conv_temp + + MLX90393_T_CONV_END) / 1000 + 1; +} + +#endif + +static int _read_measurement(mlx90393_t *dev, uint8_t *buffer) +{ + int error = 0; + + _acquire(dev); + + /* read measurement */ + if ((error = _write_byte(dev, MLX90393_COMMAND_RM)) != 0) { + _release(dev); + return error; + } + /* check status byte */ + if ((error = _read_bytes(dev, buffer, 9)) != 0) { + _release(dev); + return error; + } + if (buffer[0] & MLX90393_STATUS_ERROR) { + DEBUG("Data could not be read out\n\r"); + _release(dev); + return -EFAULT; + } + _release(dev); + return 0; +} diff --git a/drivers/mlx90393/mlx90393_saul.c b/drivers/mlx90393/mlx90393_saul.c new file mode 100644 index 000000000000..164eed397b25 --- /dev/null +++ b/drivers/mlx90393/mlx90393_saul.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 Michael Ristau + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_mlx90393 + * @{ + * + * @file + * @brief MLX90393 adaption to the RIOT actuator/sensor interface + * + * @author Michael Ristau + * + * @} + */ + +#include "saul.h" +#include "mlx90393.h" + +static int read(const void *dev, phydat_t *res) +{ + mlx90393_data_t data; + + if (mlx90393_read((mlx90393_t*) dev, &data) == 0) { + res->val[0] = (int16_t)(data.x_axis / 10); + res->val[1] = (int16_t)(data.y_axis / 10); + res->val[2] = (int16_t)(data.z_axis / 10); + res->unit = UNIT_T; + res->scale = -5; + return 1; + } + return -ECANCELED; +} + +const saul_driver_t mlx90393_saul_driver = { + .read = read, + .write = saul_write_notsup, + .type = SAUL_SENSE_MAG, +}; diff --git a/tests/drivers/mlx90393/Makefile b/tests/drivers/mlx90393/Makefile new file mode 100644 index 000000000000..b3c0a12c49cc --- /dev/null +++ b/tests/drivers/mlx90393/Makefile @@ -0,0 +1,9 @@ +include ../Makefile.drivers_common + +DRIVER ?= mlx90393_i2c + +USEMODULE += mlx90393 +USEMODULE += $(DRIVER) +USEMODULE += ztimer_sec + +include $(RIOTBASE)/Makefile.include diff --git a/tests/drivers/mlx90393/Makefile.ci b/tests/drivers/mlx90393/Makefile.ci new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/drivers/mlx90393/README.md b/tests/drivers/mlx90393/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/drivers/mlx90393/main.c b/tests/drivers/mlx90393/main.c new file mode 100644 index 000000000000..a91a3824ea77 --- /dev/null +++ b/tests/drivers/mlx90393/main.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2023 Michael Ristau + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_mlx90393 + * @{ + * + * @file + * @brief Test application for the MLX90393 magnetometer driver. + * + * @author Michael Ristau + * + * ## About + * + * The test application demonstrates the use of different functions of + * the MLX90393 sensor driver depending on the used modules and configuration params. + * + * Pseudomodule | Functionality + * :-------------------|:------------------------------------------------------- + * `mlx90393_i2c` | Use I2C bus + * `mlx90393_spi` | Use SPI bus + * `mlx90393_int` | Data ready interrupt handling + * `mlx90393_woc` | Wake-up on change mode + * + * By default the test application uses the I2C bus, polling and the default params set + * defined in file mlx90393_params.h. The default params use the Burst mode. + * To use data ready interrupts instead of polling for new data, the `mlx90393_int` module + * has to be used. + * + * ## Usage + * To compile and execute the test application, use command in the test directory: + * make BOARD=... flash + * + * To test the different driver functions you can overwrite the parameters in the + * default configuration set or add modules. + * + * Some examples: + * + * Wake-up on change mode absolute: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * CFLAGS="-DMLX90393_PARAM_MODE=MLX90393_MODE_WOC_ABSOLUTE" \ + * USEMODULE='mlx90393_woc' \ + * make BOARD=... flash + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Single measurement mode: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * CFLAGS="-DMLX90393_PARAM_MODE=MLX90393_MODE_SINGLE_MEASUREMENT" \ + * make BOARD=... flash + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Burst mode with interrupt: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * CFLAGS="-DMLX90393_PARAM_MODE=MLX90393_MODE_BURST" \ + * USEMODULE='mlx90393_int' \ + * make BOARD=... flash + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * To test the sensor with the SPI Interface you can use the mlx90393_spi module: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * DRIVER='mlx90393_spi' \ + * make BOARD=... flash + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * @} + */ + +#include +#include +#include +#include "mlx90393.h" +#include "mlx90393_params.h" +#include "ztimer.h" + +#define BUS_ERROR -EIO +#define DEVICE_NOT_AVAILABLE -ENXIO +#define INVALID_PARAM -EINVAL +#define DEVICE_ERROR -EFAULT + +static void print_error(int error) +{ + switch (error) + { + case 0: + puts("No error"); + break; + case BUS_ERROR: + puts("Communication bus error"); + break; + case DEVICE_NOT_AVAILABLE: + puts("Connectivity error, device not available"); + break; + case INVALID_PARAM: + puts("Invalid configuration parameter"); + break; + case DEVICE_ERROR: + puts("Device error"); + break; + default: + break; + } +} + +#if IS_USED(MODULE_MLX90393_WOC) +void woc_cb(void *arg) +{ + *(int*)arg = 1; +} +#endif + +int main(void) +{ + puts("MLX90393 magnetometer driver test application\n\r"); +#if IS_USED(MODULE_MLX90393_SPI) + printf("Initializing MLX90393 magnetometer at SPI_%i", mlx90393_params[0].spi); +#elif IS_USED(MODULE_MLX90393_I2C) + printf("Initializing MLX90393 magnetometer at I2C_%i", mlx90393_params[0].i2c); +#endif + + mlx90393_t dev; + int error = 0; + +#if IS_USED(MODULE_MLX90393_WOC) + int woc_triggered = 0; +#endif + + if ((error = mlx90393_init(&dev, &mlx90393_params[0])) != 0) { + puts("[FAILED]"); + print_error(error); + return -1; + } + puts("[SUCCESS]"); + + unsigned count = 0; + mlx90393_data_t data; + + puts("Starting read data from the device"); + +#if IS_USED(MODULE_MLX90393_WOC) + mlx90393_enable_woc(&dev, woc_cb, &woc_triggered); +#endif + while (1) { + +#if IS_USED(MODULE_MLX90393_WOC) + if (woc_triggered) { + if ((error = mlx90393_read(&dev, &data)) != 0) { + puts("Failed to read data from the device"); + print_error(error); + return -1; + } + printf("Field strength: X: %ld uT Y: %ld uT Z: %ld uT\n\r", + data.x_axis, data.y_axis, data.z_axis); + printf("Temperature: %d d°C\n\r", data.temp); + count++; + woc_triggered = 0; + mlx90393_enable_woc(&dev, woc_cb, &woc_triggered); + } +#endif + if (dev.params->mode == MLX90393_MODE_BURST || + dev.params->mode == MLX90393_MODE_SINGLE_MEASUREMENT) { + if ((error = mlx90393_read(&dev, &data)) != 0) { + puts("Failed to read data from the device"); + print_error(error); + return -1; + } + printf("Field strength: X: %ld uT Y: %ld uT Z: %ld uT\n\r", + data.x_axis, data.y_axis, data.z_axis); + printf("Temperature: %d d°C\n\r", data.temp); + + if (dev.params->mode == MLX90393_MODE_SINGLE_MEASUREMENT) { + ztimer_sleep(ZTIMER_SEC, 1); + } + count++; + } + + /* + * the continuous measurement is stopped, the sensor is set to idle mode + * and started again after 5 seconds every 50 cycles + */ + if (dev.params->mode != MLX90393_MODE_SINGLE_MEASUREMENT && count == 50) { + mlx90393_stop_cont(&dev); + puts("Measurement stopped and sensor set to idle mode."); + ztimer_sleep(ZTIMER_SEC, 5); + mlx90393_start_cont(&dev); + puts("Measurement started again."); + count = 0; + } + } + return 0; +}