From 51bf9d4d7d4f3b22838f4cf8c0752e21fd8e707d Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Sun, 28 Nov 2021 12:16:22 +0100 Subject: [PATCH] drivers/vl6180x: driver for VL6180X ranging and ALS --- drivers/Makefile.dep | 4 + drivers/include/vl6180x.h | 1076 ++++++++++++++++++++++ drivers/vl6180x/Makefile | 1 + drivers/vl6180x/Makefile.dep | 17 + drivers/vl6180x/Makefile.include | 9 + drivers/vl6180x/include/vl6180x_params.h | 248 +++++ drivers/vl6180x/include/vl6180x_regs.h | 152 +++ drivers/vl6180x/vl6180x.c | 920 ++++++++++++++++++ 8 files changed, 2427 insertions(+) create mode 100644 drivers/include/vl6180x.h create mode 100644 drivers/vl6180x/Makefile create mode 100644 drivers/vl6180x/Makefile.dep create mode 100644 drivers/vl6180x/Makefile.include create mode 100644 drivers/vl6180x/include/vl6180x_params.h create mode 100644 drivers/vl6180x/include/vl6180x_regs.h create mode 100644 drivers/vl6180x/vl6180x.c diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep index 421f98adad..7f169427a4 100644 --- a/drivers/Makefile.dep +++ b/drivers/Makefile.dep @@ -215,6 +215,10 @@ ifneq (,$(filter vcnl40%0,$(USEMODULE))) USEMODULE += vcnl40x0 endif +ifneq (,$(filter vl6180x_%,$(USEMODULE))) + USEMODULE += vl6180x +endif + ifneq (,$(filter ws281x_%,$(USEMODULE))) USEMODULE += ws281x endif diff --git a/drivers/include/vl6180x.h b/drivers/include/vl6180x.h new file mode 100644 index 0000000000..0d409c2bf5 --- /dev/null +++ b/drivers/include/vl6180x.h @@ -0,0 +1,1076 @@ +/* + * Copyright (C) 2021 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_vl6180x VL6180X Ranging and Ambient Light Sensing (ALS) module + * @ingroup drivers_sensors + * @ingroup drivers_saul + * @brief Device driver for the ST VL6180X Ranging and Ambient Light Sensing (ALS) module + * + * # Overview {#vl6180x_overview} + * + * ## About the sensor {#vl6180x_about} + * + * The ST VL6180X is a low-power **proximity** and **ambient light** sensor + * with an I2C interface that uses time-to-flight technology for distance + * measurements. It can be used for ranging and/or ambient light sensing (ALS). + * Measurements can be automatically performed at user-defined intervals. + * + * To minimize host operations, interrupts can be used either when + * new sensor data are ready to be read or when sensor values exceed + * configured thresholds. + * + * ## Supported Features {#vl6180x_supported} + * + * The driver supports different levels of functionality, which can be + * enabled by using pseudomodules according to the requirements of the + * application. This ensures that the driver only uses as much ROM/RAM + * as really needed. + * + * As basic functionality the driver supports: + * + * - Ranging and ambient light sensing (ALS) in single-shot or + * continuous mode with polling for new sensor data + * - Fixed configuration of the sensor by a default parameter set of + * type #vl6180x_params_t as defined in the file `vl6180x_params.h + * - SAUL sensor interface + * + * The following pseudomodules are used to enable additional functionalities: + *
+ * Pseudomodule | Functionality + * :-------------------|:------------------------------------------------------- + * `vl6180x_irq` | Data ready and event interrupt handling + * `vl6180x_suhtdown` | Power-down and power-up functions + * `vl6180x_config` | Functions for changing configurations at runtime + *
+ *
+ * + * The following table shows the mapping of which modules have to be used + * to enable which functions of the VL6180X. + * + *
+ * Feature | Module + * :------------------------------------------------------------ |:------------- + * Ranging in single-shot or continuous mode | `vl6180x_rng` + * Ambient light sensing (ALS) in single-shot or continuous mode | `vl6180x_als` + * Data ready and event interrupt handling | `vl6180x_irq` + * Power-down and power-up functions | `vl6180x_shutdown` + * Configuration of the sensor at runtime | `vl6180x_config` + *

+ * + * @note + * - If the handling of interrupts for data ready and event interrupts + * is enabled by module `vl6180x_irq`, the GPIO pin for the interrupt + * signal (sensor pin GPIO1) must be defined by the configuration parameter + * vl6180x_params_t::int_pin. The default configuration of this GPIO pin + * is defined by #VL6180X_PARAM_INT_PIN that can be overridden by the + * board definition. The interrupt signal is LOW active. + * - If power-down and power-up functions are enabled by module + * `vl6180x_shutdown`, the GPIO pin for the shutdown signal (sensor pin + * GPIO0/CE) must be defined by the configuration parameter + * vl6180x_params_t::shutdown_pin. The default configuration of this GPIO pin + * is defined by #VL6180X_PARAM_SHUTDOWN_PIN that can be overridden by the + * board definition. The shutdown signal is LOW active. + * + * # Using the driver {#vl6180x_using_driver} + * + * ## Initialization {#vl6180x_initialization} + * + * The **easiest way to use the driver** is simply to initialize the sensor + * with function #vl6180x_init using the default configuration parameter set + * #vl6180x_params as defined in file vl6180x_params.h. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * static vl6180x_t dev; + * + * if (vl6180x_init(&dev, &vl6180x_params[0]) != VL6180X_OK) { + * ... // error handling + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * After initialization, the sensor is configured according to the standard + * configuration parameters and is fully operational. + * + * ## Operation modes {#vl6180x_operation_mode} + * + * The VL6180X can be used in two modes + * + * - **Single-shot mode**
+ * In this mode the sensor is in software standby and a single measurement + * is started explicitly either with the function #vl6180x_rng_start_single + * or the function #vl6180x_als_start_single. After finishing the single + * measurement the sensor returns to software standby. In software standby, + * the power consumption of the sensor is less than 1 uA. + * - **Continuous mode**
+ * In this mode the sensor automatically performs measurements with the + * measurement period configured by parameter vl6180x_params_t::period. + * Between these measurements it returns to the software standby.
+ * If range and ALS measurements are used (the modules `vl6180x_rng` and + * `vl6180x_als` are both used), the so-called **interleaved mode** is + * automatically used, where an ALS measurement is immediately followed + * by a range measurement and repeated with the defined period. + * The continuous mode can be stopped with function #vl6180x_stop_cont, + * for example to start single measurements. It is also possible to + * restart it using function #vl6180x_start_cont. + * + * @note If the configured measurement period is 0, the single-shot mode + * is enabled after initialization for both the range and ALS measurements. + * The functions #vl6180x_rng_start_single and #vl6180x_als_start_single must + * then be used to start a single measurement.

+ * Otherwise, the continuous mode is activated for both measurements and + * continuous measurements started automatically after sensor initialization + * with the configured measurement period. + * + * ## Fetching data {#vl6180x_fetching_data} + * + * To get data, the user task can use either + * + * - the #vl6180x_rng_data_ready and #vl6180x_als_data_ready functions to + * periodically check if new data are ready to be read, and the + * #vl6180x_rng_read and #vl6180x_als_read functions to read the data + * (following example), or + * - the data ready interrupt which is triggered as soon as new output data + * are available, see section [Using Interrupts](#vl6180x_using_interrupts). + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * while (1) + * { + * uint16_t als; + * uint16_t lux; + * uint8_t rng; + * + * // execute task every 20 ms + * xtimer_usleep(20 * US_PER_MS); + * ... + * // test for new data and fetch them if available + * if (vl6180x_als_data_ready(&dev) == VL6180X_OK && + * vl6180x_als_read(&dev, &als, &lux) == VL6180X_OK) { + * ... + * } + * if (vl6180x_rng_data_ready(&dev) == VL6180X_OK) { + * if (vl6180x_rng_read(&dev, &rng) == VL6180X_OK) { + * ... + * } + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ## Output data format + * + * Function #vl6180x_als_read returns one ALS data sample as raw count value + * and, if required, as illuminance in Lux. The range of the count value + * depends on + * + * - the ALS analog gain defined by vl6180x_params_t::als_gain, + * - the integration time defined by vl6180x_params_t::als_int_time, and + * - the lux resolution defined by vl6180x_params_t::als_lux_res. + * + * The count value is returned in parameter \p raw while the illuminance + * is returned in parameter \p lux. For either \p raw or \p lux also `NULL` + * can be passed, if only one value is of interest. + * + * If ALS value is invalid because of a measurement error, #VL6180X_ERROR_ALS + * is returned. The #vl6180x_als_status function can then be used to get an + * error code of type #vl6180x_als_status_t. + * + * Function vl6180x_rng_read returns the ranging data in millimeters. If + * ranging value is invalid because of a measurement error, #VL6180X_ERROR_RNG + * is returned and function #vl6180x_rng_status function can then be used to + * get an error code of type #vl6180x_rng_status_t. + * + * # Using Interrupts {#vl6180x_using_interrupts} + * + * The VL6180X sensor allows the use of different types of interrupts on signal + * GPIO1 for range and ALS measurements: + * + * - data ready interrupts when data become available + * - different event interrupts when sensor data cross configured thresholds + * + * @note Interrupts are only supported when module `vl6180x_irq` is used. + * + * ## Interrupt configuration {#vl6180x_interrupt_configuration} + * + * These interrupts can be enabled separately for the range and ALS + * measurements by the interrupt mode of type #vl6180x_int_mode_t + * + *
+ * | Interrupt mode | Driver symbol | + * |:-----------------------------------------------------------------------|:------------------| + * | New data are ready to be read | #VL6180X_INT_DRDY | + * | Sensor data are below the lower threshold | #VL6180X_INT_LOW | + * | Sensor data are above the upper threshold | #VL6180X_INT_HIGH | + * | Sensor data are below the lower threshold or above the upper threshold | #VL6180X_INT_OUT | + *

+ * + * @warning Only one of the interrupt modes must be enabled at the same time + * for the same measurement. + * + * For event interrupts, upper and lower thresholds have to be defined, + * with the upper and lower thresholds defining a threshold window of type + * #vl6180x_int_thresh_t. + * + * In default configuration, #VL6180X_INT_DRDY is used both for range and + * ALS measurements if module `vl6180x_irq` is used. + * + * The enabled interrupts can be changed with the #vl6180x_int_enable + * function which takes a parameter of type #vl6180x_int_config_t which + * simply contains the interrupt mode of type #vl6180x_int_mode_t for range + * and ALS measurements. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * vl6180x_int_config_t mode = { .rng_int = VL6180X_INT_OUT, + * .als_int = VL6180X_INT_DRDY }; + * vl6180x_int_enable(&dev, mode); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * If module `vl6180x_config` is used, the thresholds for event interrupts + * can be changed using function #vl6180x_int_config, for example: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * vl6180x_int_thresh_t thresh; + * + * thresh.rng_low = 30; + * thresh.rng_high = 100; + * thresh.als_low = 10; + * thresh.als_high = 1000; + * + * vl6180x_int_config(&dev, thresh); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ## Interrupt usage {#vl6180x_interrupt_sources} + * + * All functions of the driver require direct access to the sensor via + * I2C which does not work in interrupt context. + * + * Therefore, the driver prevents the direct use of the interrupts and + * application specific ISRs. The only way to use interrupts is to call + * the function #vl6180x_int_wait which enables the interrupt signal + * for the configured MCU GPIO and then blocks the calling thread + * until an interrupt is triggered. + * + * Once an interrupt is triggered, the driver handles the interrupt with + * an internal ISR and then returns from the #vl6180x_int_wait function + * with the interrupt source. The interrupt mode of type #vl6180x_int_mode_t + * respectively the composite type #vl6180x_int_config_t which is used for + * defining enabled interrupts is also used for specifying the interrupt + * source. It contains a flag for each possible interrupt source which + * can be tested for true. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * vl6180x_int_config_t src; + * + * vl6180x_int_wait(&dev, &src); + * + * if (src.rng_int == VL6180X_INT_DRDY) { + * vl6180x_rng_read(&dev, &rng); + * printf("RNG: %u [mm]\n", rng); + * } + * else if (src.rng_int == VL6180X_INT_OUT) { + * printf("RNG: out of window\n"); + * } + * else if (src.rng_int == VL6180X_INT_RNG_LOW) { + * printf("RNG: low level\n"); + * } + * else if (src.rng_int == VL6180X_INT_RNG_HIGH) { + * printf("RNG: high level\n"); + * } + * + * if (src.als_int == VL6180X_INT_DRDY) { + * vl6180x_als_read(&dev, &als, &lux); + * printf("ALS: %u [cnts], %u [lux]\n", als, lux); + * } + * else if (src.als_int == VL6180X_INT_OUT) { + * printf("ALS: out of window\n"); + * } + * else if (src.als_int == VL6180X_INT_LOW) { + * printf("ALS: low level\n"); + * } + * else if (src.als_int == VL6180X_INT_HIGH) { + * printf("ALS: high level\n"); + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * # Power Saving {#vl6180x_power_saving} + * + * If module `vl6180x_shutdown` is used, the VL6180X sensor can be shutdown + * when no measurements are required using the function #vl6180x_power_down. + * The power consumption is then reduced to less than 1 uA. To restart the + * VL6180X in previous measurement mode, the #vl6180x_power_up function can + * be used. + * + * @note To use these functions, the MCU GPIO pin connected to the GPIO0/CE + * pin of the sensor has to be defined by the vl6180x_params_t::pin_shutdown + * parameter. + * + * # Low level functions {#vl6180x_low_level} + * + * Low level level interface functions that allow direct read and write + * access to the registers of the sensor. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} + * bool vl6180x_reg_read(const vl6180x_t* dev, uint16_t reg, uint8_t *data, uint8_t len); + * bool vl6180x_reg_write(const vl6180x_t* dev, uint16_t reg, const uint8_t *data, uint8_t len); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * @warning + * These functions should only be used to do something special that + * is not covered by the high level interface AND if you exactly + * know what you do and what it might affect. Please be aware that + * it might affect the high level interface. + * + * # Configuration + * + * ## Default configuration + * + * Default sensor hardware configurations are set in file `vl6180x_params.h` + * using the following defines: + * + *
+ * | Hardware configuration | Driver name | Default Value | + * |:-----------------------|:----------------------------|:------------------| + * | I2C device | #VL6180X_PARAM_DEV | I2C_DEV(0) | + * | I2C address | #VL6180X_PARAM_ADDR | #VL6180X_I2C_ADDR | + * | Interrupt pin | #VL6180X_PARAM_INT_PIN | GPIO_PIN(0,1) | + * | Shutdown pin | #VL6180X_PARAM_SHUTDOWN_PIN | GPIO_PIN(0,2) | + *

+ * + * These hardware configurations can be overridden either by the board + * definition or by defining them in the `CFLAGS` variable in the make + * command, for example: + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * USEMODULE='vl6180x_rng vl6180x_als vl6180x_irq` \ + * CLFAGS='-DVL6180X_PARAM_INT_PIN=GPIO_PIN\(0,5\)' \ + * BOARD=... make -C tests/driver_vl6180x + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * The default configuration of the sensor is defined in file + * `vl6180x_params.h` using the following defines: + * + * Parameter | Default Value | Define to be overridden + * :----------------------------------|:-------------------|:--------------------------- + * Period of continuous measurements | 200 ms | #CONFIG_VL6180X_MEAS_PERIOD + * Ranging maximum convergence time | 50 ms | #CONFIG_VL6180X_RNG_MAX_TIME + * Ranging interrupt mode | VL6180X_INT_DRDY | #CONFIG_VL6180X_RNG_INT + * Ranging lower threshold | 20 mm | #CONFIG_VL6180X_RNG_THRESH_LOW + * Ranging upper threshold | 90 mm | #CONFIG_VL6180X_RNG_THRESH_HIGH + * ALS integration time | 100 ms | #CONFIG_VL6180X_ALS_INT_TIME + * ALS analogue gain | VL6180X_ALS_GAIN_1 | #CONFIG_VL6180X_ALS_GAIN + * ALS lux resolution lux/count*1000 | 320 | #CONFIG_VL6180X_ALS_LUX_RES + * ALS interrupt mode | VL6180X_INT_DRDY | #CONFIG_VL6180X_ALS_INT + * ALS lower threshold | 50 counts | #CONFIG_VL6180X_ALS_THRESH_LOW + * ALS upper threshold | 2000 counts | #CONFIG_VL6180X_ALS_THRESH_HIGH + * + * Single or all parameters of the default configuration can be overridden + * either by placing a modified file `vl6180x_params.h` in the application + * directory or by defining them in the variable `CFLAGS` in the make command + * line. For example to configure a measurement period of 500 ms and a maximum + * convergence time for ranging of 60 ms, the following command could be used: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * USEMODULE='vl6180x_rng vl6180x_als` \ + * CLFAGS='-DCONFIG_VL6180X_MEAS_PERIOD=50 -DCONFIG_VL6180X_RNG_MAX_TIME=60' \ + * BOARD=... make -C tests/driver_vl6180x + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ## Configuration at runtime + * + * If module `vl6180x_config` is used, the following functions can be used to + * change the default sensor configuration at runtime. + * + * | Function | Functionality | + * |:--------------------|:------------------------------------------------------| + * | #vl6180x_rng_config | Changes the range measurement parameter configuration | + * | #vl6180x_als_config | Changes the ALS measurement parameter configuration | + * | #vl6180x_int_config | Changes the thresholds for event interrupts | + * + * @{ + * + * @author Gunar Schorcht + * @file + */ + +#ifndef VL6180X_H +#define VL6180X_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +#include "periph/gpio.h" +#include "periph/i2c.h" + +#include "vl6180x_regs.h" + +#define VL6180X_I2C_ADDR (0x29) /**< VNCL6180 default I2C slave address */ + +/** + * @brief Error codes + */ +typedef enum { + VL6180X_OK = 0, /**< Success */ + VL6180X_ERROR_I2C = 1, /**< I2C communication error */ + VL6180X_ERROR_WRONG_ID = 2, /**< Wrong id read */ + VL6180X_ERROR_NO_PIN = 3, /**< Pin not defined */ + VL6180X_ERROR_NO_DATA = 4, /**< No data available */ + VL6180X_ERROR_RNG = 5, /**< Ranging error */ + VL6180X_ERROR_ALS = 6, /**< Ambient light sensing (ALS) error */ + VL6180X_ERROR_NOT_READY = 7, /**< Device not ready */ +} vl6180x_error_t; + +/** + * @brief Analogue gain for ALS measurements + */ +typedef enum { + VL6180X_ALS_GAIN_20 = 0, /**< 20 x gain (actual analogue gain of 20) */ + VL6180X_ALS_GAIN_10 = 1, /**< 10 x gain (actual analogue gain of 10.32) */ + VL6180X_ALS_GAIN_5 = 2, /**< 5 x gain (actual analogue gain of 5.21) */ + VL6180X_ALS_GAIN_2_5 = 3, /**< 2.5 x gain (actual analogue gain of 2.6) */ + VL6180X_ALS_GAIN_1_67 = 4, /**< 1.67 x gain (actual analogue gain of 1.72) */ + VL6180X_ALS_GAIN_1_25 = 5, /**< 1.25 x gain (actual analogue gain of 1.28) */ + VL6180X_ALS_GAIN_1 = 6, /**< 1 x gain (actual analogue gain of 1.01), default */ + VL6180X_ALS_GAIN_40 = 7, /**< 40 x gain (actual analogue gain of 40) */ +} vl6180x_als_gain_t; + +/** + * @brief Range measurement status + */ +typedef enum { + VL6180X_RNG_OK = 0, /**< No error */ + VL6180X_RNG_VCSEL_CONT_TEST = 1, /**< VCSEL continuity Test */ + VL6180X_RNG_VCSEL_WD_TEST = 2, /**< VCSEL watchdog test */ + VL6180X_RNG_VCSEL_WD = 3, /**< VCSEL watchdog */ + VL6180X_RNG_PLL1_LOCK = 4, /**< PLL1 lock */ + VL6180X_RNG_PLL2_LOCK = 5, /**< PLL2 lock */ + VL6180X_RNG_EARLY_CONV_EST = 6, /**< Early convergence estimate */ + VL6180X_RNG_MAX_CONV = 7, /**< Maximum convergence time reached */ + VL6180X_RNG_NO_TARGET = 8, /**< No target, ignore */ + VL6180X_RNG_MAX_SNR = 11, /**< Maximum SNR reached */ + VL6180X_RNG_RAW_ALGO_UNDERFLOW = 12, /**< Raw ranging algorithm underflow */ + VL6180X_RNG_RAW_ALGO_OVERFLOW = 13, /**< Raw ranging algorithn overflow */ + VL6180X_RNG_ALGO_UNDERFLOW = 14, /**< Ranging algorithm underflow */ + VL6180X_RNG_ALGO_OVERFLOW = 15, /**< Ranging algorithm overflow */ +} vl6180x_rng_status_t; + +/** + * @brief Ambient light sensing (ALS) status + */ +typedef enum { + VL6180X_ALS_OK = 0, /**< No error */ + VL6180X_ALS_OVERFLOW = 1, /**< ALS measurement overflow */ + VL6180X_ALS_UNDERFLOW = 2, /**< ALS measurement underflow */ +} vl6180x_als_status_t; + +/** + * @brief Interrupt mode + * + * The interrupt mode defines the different sources that can trigger an + * interrupt on the GPIO1 pin of the sensor. The interrupt mode is defined for + * range and ALS measurements separately. Interrupts can be triggered either + * + * - in each measurement cycle when new data become available (data ready interrupts) or + * - only when values exceed a threshold configured (event interrupts). + * + * For threshold interrupts, upper and lower thresholds have to be defined, + * with the upper and lower thresholds defining a threshold window, see + * also #vl6180x_int_thresh_t. + * + * @note Interrupts are only supported when module `vl6180x_irq` is used. + * @warning Only one of the interrupt modes must be enabled at the same time. + */ +typedef enum { + VL6180X_INT_NONE = 0, /**< Interrupt is disabled */ + + VL6180X_INT_LOW = 1, /**< Interrupt is triggered when values are below + the lower threshold */ + VL6180X_INT_HIGH = 2, /**< Interrupt is triggered when values are above + the upper threshold */ + VL6180X_INT_OUT = 3, /**< Interrupt is triggered when values are below + the lower threshold or above the upper threshold + (value leave the threshold window) */ + VL6180X_INT_DRDY = 4, /**< Interrupt is triggered when new data are ready + to be read */ +} vl6180x_int_mode_t; + +/** + * @brief Interrupt config + * + * This type defines the interrupt mode for both range measurements and + * ALS measurements. It is used on the one hand as parameter \p mode in the + * function #vl6180x_int_enable to enable the interrupt for the respective + * measurement and on the other hand to return the source of an interrupt + * in function #vl6180x_int_wait when an interrupt was triggered. + * + * The interrupt mode is defined for range and ALS measurements separately. + * + * @note Interrupts are only supported when module `vl6180x_irq` is used. + */ +typedef struct { +#if IS_USED(MODULE_VL6180X_RNG) || DOXYGEN + vl6180x_int_mode_t rng_int; /**< Interrupt mode for range measurements */ +#endif +#if IS_USED(MODULE_VL6180X_ALS) || DOXYGEN + vl6180x_int_mode_t als_int; /**< Interrupt mode for ALS measurements */ +#endif +} vl6180x_int_config_t; + +/** + * @brief Interrupt threshold configuration + * + * Threshold configurations are used for event interrupts only. + * If event interrupts are enabled by the corresponding interrupt + * mode for range and/or ALS measurements, the lower and/or upper threshold + * values are used to generate an interrupt if the values of the respective + * measurement exceed these threshold values. + * + * The unit of threshold values for range measurements is millimeters. + * The unit of threshold values for ALS measurements is counts. + * + * @note Interrupts are only supported when module `vl6180x_irq` is used. + */ +typedef struct { +#if IS_USED(MODULE_VL6180X_RNG) || DOXYGEN + uint8_t rng_high; /**< upper threshold for range values */ + uint8_t rng_low; /**< lower threshold for range values */ +#endif +#if IS_USED(MODULE_VL6180X_ALS) || DOXYGEN + uint16_t als_high; /**< upper threshold for ALS values */ + uint16_t als_low; /**< lower threshold for ALS values */ +#endif +} vl6180x_int_thresh_t; + +/** + * @brief VL6180X device configuration + */ +typedef struct { + /** + * @name Hardware configuration + * @{ + */ + unsigned i2c_dev; /**< I2C device, default I2C_DEV(0) */ + uint8_t i2c_addr; /**< I2C slave address */ + +#if IS_USED(MODULE_VL6180X_SHUTDOWN) || DOXYGEN + gpio_t shutdown_pin; /**< Shutdown pin, LOW active */ +#endif /* IS_USED(MODULE_VL6180X_SHUTDOWN) || DOXYGEN */ + +#if IS_USED(MODULE_VL6180X_IRQ) || DOXYGEN + gpio_t int_pin; /**< Interrupt pin, LOW active */ + vl6180x_int_config_t int_cfg; /**< Interrupt mode configuration */ + vl6180x_int_thresh_t int_thresh; /**< Interrupt threshold configuration */ +#endif /* IS_USED(MODULE_VL6180X_IRQ) || DOXYGEN */ + + /** @} */ + + /** + * @brief Measurement period in continuous mode in + * steps of 10 ms (default 20 = 200 ms). + * + * The measurement period also controls the measurement mode used after + * sensor initialization. If the configured measurement period is 0, + * the single-shot mode is enabled for both the range and ALS + * measurements. The functions vl6180x_rng_start_single and + * vl6180x_als_start_single must then be used to start a single measurement. + * Otherwise, the continuous mode is activated for both measurements, + * which are started immediately after sensor initialization with the + * configured measurement period. This also applies to the initialization + * after a power-down and power-up cycle. + * + * @note When ALS and range measurements are used in continuous mode, + * the so-called interleaved mode is used automatically, where an ALS + * measurement is immediately followed by a range measurement and + * repeated with the defined period. + */ + uint8_t period; + +#if IS_USED(MODULE_VL6180X_RNG) || DOXYGEN + /** + * @name Range measurement configuration + * @{ + */ + uint8_t rng_max_time; /**< Maximum convergence time in ms [1...63] given + to the sensor to perform a range measurement + (default 50 = 50 ms) */ + /** @} */ +#endif /* IS_USED(MODULE_VL6180X_RNG) || DOXYGEN */ + +#if IS_USED(MODULE_VL6180X_ALS) || DOXYGEN + /** + * @name ALS measurement configuration + * @{ + */ + uint16_t als_int_time; /**< ALS integration time in ms [1...512] + (default 100 = 100 ms, **recommended**) */ + uint16_t als_lux_res; /**< ALS lux resolution multiplied by factor 1000 + (default 0.32 lux/count is the factory + calibrated lux resolution without cover glas) */ + vl6180x_als_gain_t als_gain; /**< ALS analogue gain for light channel + (default VL6180X_ALS_GAIN_1_0) */ + /** @} */ +#endif /* IS_USED(MODULE_VL6180X_ALS) || DOXYGEN */ + +} vl6180x_params_t; + +/** + * @brief VL6180X sensor device data structure type + */ +typedef struct { + + vl6180x_params_t params; /**< Device initialization parameters */ + bool cont_meas; /**< Continuous mode running */ +#if IS_USED(MODULE_VL6180X_RNG) || DOXYGEN + vl6180x_rng_status_t rng_status; /**< Status of last range measurement */ +#endif /* IS_USED(MODULE_VL6180X_RNG) || DOXYGEN */ +#if IS_USED(MODULE_VL6180X_ALS) || DOXYGEN + vl6180x_als_status_t als_status; /**< Status of last ALS measurement */ +#endif /* IS_USED(MODULE_VL6180X_ALS) || DOXYGEN */ +} vl6180x_t; + +/** + * @brief Initialize the VL6180X sensor device + * + * After initialization, the sensor is configured according to the standard + * configuration parameters and is fully functional. + * + * If the configured measurement period is 0, the single-shot mode + * is enabled for both the range and ALS measurements. The functions + * vl6180x_rng_start_single and vl6180x_als_start_single must then be used + * to start a single measurement. Otherwise, the continuous mode is activated + * for both measurements, which are started immediately after sensor + * initialization with the configured measurement period. + * + * @param[in] dev Device descriptor of VL6180X sensor to be initialized + * @param[in] params Configuration parameters, see #vl6180x_params_t + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_init(vl6180x_t *dev, const vl6180x_params_t *params); + +/** + * @brief Start measurements in continuous mode + * + * Range and/or ALS measurements are started in continuous mode with same + * measurement period as defined in configuration parameter + * vl6180x_params_t::period. + * + * @note Continuous mode cannot be started separately for range and ALS + * measurements. + * + * @pre + * - Measurement period vl6180x_params_t::period must not be zero. + * - Measurements must not yet be started in continuous mode when called. + * + * @param[in] dev Device descriptor of VL6180X sensor to be initialized + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_start_cont(vl6180x_t *dev); + +/** + * @brief Stop measurements in continuous mode + * + * Continuous range and ALS measurements are stopped. Once continuous + * measurements are stopped, vl6180x_rng_start_single or + * vl6180x_als_start_single can be used to start single-shot measurements + * separately. + * + * @pre + * - Measurement period vl6180x_params_t::period must not be zero. + * - Measurements must be started in continuous mode when called. + * + * @param[in] dev Device descriptor of VL6180X sensor to be initialized + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_stop_cont(vl6180x_t *dev); + +#if IS_USED(MODULE_VL6180X_RNG) || DOXYGEN +/** + * @brief Range data ready status function + * + * The function can be used for polling to know when new ranging data are ready. + * + * @note This function is only available when module `vl6180x_rng` is used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * + * @retval VL6180X_OK new ranging data are ready + * @retval VL6180X_ERROR_NO_DATA no new ranging data available + * @retval VL6180X_ERROR_* a negative error code on any other error, see + * #vl6180x_error_t + */ +int vl6180x_rng_data_ready(const vl6180x_t *dev); + +/** + * @brief Read one ranging data sample in mm + * + * This function returns the ranging data in millimeters. If ranging value + * is invalid because of a measurement error, #VL6180X_ERROR_RNG is returned. + * The #vl6180x_rng_status function can then be used to get an error code of + * type #vl6180x_rng_status_t. + * + * @note + * - This function is only available when module `vl6180x_rng` is used. + * - The function clears the interrupt if ambient light sensing interrupts + * are used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * @param[out] mm Ranging data in mm [0...100] + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_RNG on error during range measurement + * @retval VL6180X_ERROR_* a negative error code on any other error + * see #vl6180x_error_t + */ +int vl6180x_rng_read(vl6180x_t *dev, uint8_t *mm); + +/** + * @brief Get status of last range measurement + * + * @note This function is only available when module `vl6180x_rng` is used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * @retval status of type vl6180x_rng_status_t + */ +vl6180x_rng_status_t vl6180x_rng_status(const vl6180x_t *dev); + +/** + * @brief Start a single-shot range measurement + * + * @pre Measurements must not be started in continuous mode when called. + * + * @note This function is only available when module `vl6180x_rng` is used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_rng_start_single(const vl6180x_t *dev); + +#if IS_USED(MODULE_VL6180X_CONFIG) || DOXYGEN +/** + * @brief Reconfigure range measurements at runtime + * + * This function can be used to overwrite the default configuration of range + * measurements defined by #vl6180x_params_t at runtime. + * + * For this purpose, the running range measurement is stopped and restarted + * after the reconfiguration if continuous mode is used (\p period is not 0). + * + * @note + * - This function is only available when modules `vl6180x_rng` and + * `vl6180x_config` are used. + * - Since parameter \p period is used for continuous mode, in which + * measurements are performed in interleaved mode, setting the period + * with this function also affects the ALS measurements in continuous mode. + * + * @param[in] dev Device descriptor of VL6180X sensor + * @param[in] period Period in continuous measurement mode in steps + * of 10 ms. It controls also the measurement mode + * enabled after the initialization. If 0, single-shot + * mode is enabled, otherwise the continuous + * mode is enabled and measurement are started + * automatically. + * @param[in] max_time Maximum convergence time in ms [1...63] given + * to the sensor to perform range measurements + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_rng_config(vl6180x_t *dev, uint8_t period, uint8_t max_time); + +#endif /* IS_USED(MODULE_VL6180X_CONFIG) || DOXYGEN */ +#endif /* IS_USED(MODULE_VL6180X_RNG) || DOXYGEN */ + +#if IS_USED(MODULE_VL6180X_ALS) || DOXYGEN +/** + * @brief ALS data ready status function + * + * @note This function is only available when module `vl6180x_als` is used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * + * @retval VL6180X_OK new ALS data are ready + * @retval VL6180X_ERROR_NO_DATA no new ALS data available + * @retval VL6180X_ERROR_* a negative error code on any other error, + * see #vl6180x_error_t + */ +int vl6180x_als_data_ready(const vl6180x_t *dev); + +/** + * @brief Read one ambient light sensing (ALS) data sample + * + * This function returns one ALS data sample as raw count value and, if + * required, as illuminance in Lux. The range of the count value + * depends on + * + * - the ALS analog gain defined by vl6180x_params_t::als_gain, + * - the integration time defined by vl6180x_params_t::als_int_time, and + * - the lux resolution defined by vl6180x_params_t::als_res. + * + * The count value is returned in parameter \p raw while the illuminance + * is returned in parameter \p lux. For either \p raw or \p lux also `NULL` + * can be passed, if only one value is of interest. + * + * If ALS value is invalid because of a measurement error, #VL6180X_ERROR_ALS + * is returned. The #vl6180x_als_status function can then be used to get an + * error code of type #vl6180x_als_status_t. + * + * @note + * - This function is only available when module `vl6180x_als` is used. + * - The function clears the interrupt if ambient light sensing interrupts + * are used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * @param[out] raw Ambient light raw data as count value + * @param[out] lux Ambient light in Lux + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_als_read(vl6180x_t *dev, uint16_t *raw, uint16_t *lux); + +/** + * @brief Get status of last ALS measurement + * + * @note This function is only available when module `vl6180x_als` is used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * @retval status of type vl6180x_als_status_t + */ +vl6180x_als_status_t vl6180x_als_status(const vl6180x_t *dev); + +/** + * @brief Start a single-shot ALS measurement + * + * @pre Measurements must not be started in continuous mode when called. + * + * @note This function is only available when module `vl6180x_als` is used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_als_start_single(const vl6180x_t *dev); + +#if IS_USED(MODULE_VL6180X_CONFIG) || DOXYGEN +/** + * @brief Reconfigure ambient light sensing (ALS) during runtime + * + * This function can be used to overwrite the default configuration of ambient + * light sensing defined by #vl6180x_params_t during runtime. + * + * For this purpose, the running ambient light sensing (ALS) is stopped and + * restarted after the reconfiguration if continuous mode is used + * (\p period is not 0). + * + * @note + * - This function is only available when modules `vl6180x_als` and + * `vl6180x_config` are used. + * - Since parameter \p period is used for continuous mode, in which + * measurements are performed in interleaved mode, setting the period + * with this function also affects the range measurements in continuous mode. + * + * @param[in] dev Device descriptor of VL6180X sensor + * @param[in] period Period in continuous measurement mode in steps + * of 10 ms. It controls also the measurement mode + * enabled after the initialization. If 0, single-shot + * mode is enabled, otherwise the continuous + * mode is enabled and measurement are started + * automatically. + * @param[in] int_time ALS integration time in ms [0...511] + * @param[in] gain ALS analogue gain for light channel + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_als_config(vl6180x_t *dev, uint8_t period, uint8_t int_time, + vl6180x_als_gain_t gain); + +#endif /* IS_USED(MODULE_VL6180X_CONFIG) || DOXYGEN */ +#endif /* IS_USED(MODULE_VL6180X_ALS) || DOXYGEN */ + +#if IS_USED(MODULE_VL6180X_SHUTDOWN) || DOXYGEN +/** + * @brief Power down the sensor + * + * @pre This function requires that a GPIO connected to sensor's GPIO0/CE pin is + * defined by parameter vl6180x_params_t::pin_shutdown. + * + * @note This function is only available if the `vl6180x_shutdown` module + * is used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_power_down(const vl6180x_t *dev); + +/** + * @brief Power down the sensor + * + * @pre This function requires that a GPIO connected to sensor's GPIO0/CE pin is + * defined by parameter vl6180x_params_t::pin_shutdown. + * + * @note This function is only available if the `vl6180x_shutdown` module + * is used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_power_up(vl6180x_t *dev); + +#endif /* IS_USED(MODULE_VL6180X_SHUTDOWN) || DOXYGEN */ + +#if IS_USED(MODULE_VL6180X_IRQ) || DOXYGEN + +/** + * @brief Wait for configured interrupts and return the interrupt sources + * + * To avoid I2C bus access in interrupt context, the driver prevents the + * direct use of interrupts and application specific ISRs. Rather, this + * function has to be used to wait for an interrupt. It enables the interrupt + * signal for the configured MCU GPIO and then blocks the calling thread + * until an interrupt is triggered. + * + * Once an interrupt is triggered, the driver handles the interrupt with an + * internal ISR and then returns. When the function returns, the data structure + * of type vl6180x_int_config_t to which the \p src parameter points contains + * the source of the triggered interrupt. It contains a flag for each possible + * interrupt source which can be tested for true. + * + * @pre + * - Configuration parameter vl6180x_params_t::int_pin has to be defined. + * - vl6180x_int_config_t::rng_int and vl6180x_int_config_t::als_int + * must only define one interrupt mode each. + * - If threshold interrupts are enabled, thresholds have to be valid. + * + * @note This function is only available when module `vl6180x_irq` is used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * @param[out] src Interrupt sources, see #vl6180x_int_config_t + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_int_wait(const vl6180x_t *dev, vl6180x_int_config_t *src); + +/** + * @brief Enable and disable interrupts + * + * Configured interrupts can be enabled or disabled with this function. + * + * @pre + * - Configuration parameter vl6180x_params_t::int_pin has to be defined + * - vl6180x_int_config_t::rng_int and vl6180x_int_config_t::als_int + * must only define one interrupt mode each. + * + * @note + * - To disable intertupts, set vl6180x_int_config_t::rng_int and + * vl6180x_int_config_t::als_int to #VL6180X_INT_NONE. + * - This function is only available when module `vl6180x_irq` is used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * @param[in] mode Interrupts to be enabled, must be only one for each + * measurement type (range and ALS) + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_int_enable(vl6180x_t *dev, vl6180x_int_config_t mode); + +#if IS_USED(MODULE_VL6180X_CONFIG) || DOXYGEN +/** + * @brief Configure thresholds for event interrupts at runtime + * + * @note This function is only available when modules `vl6180x_irq` and + * `vl6180x_config` are used. + * + * @param[in] dev Device descriptor of VL6180X sensor + * @param[in] thresh Threshold configuration for event interrupts, + * see #vl6180x_int_thresh_t + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_int_config(vl6180x_t *dev, vl6180x_int_thresh_t thresh); + +#endif /* IS_USED(MODULE_VL6180X_CONFIG) || DOXYGEN */ +#endif /* IS_USED(MODULE_VL6180X_IRQ) || DOXYGEN */ + +/** + * @name Low level interface functions + * @{ + */ + +/** + * @brief Direct write to register + * + * @note This function should only be used to do something special that + * is not covered by the high level interface AND if you exactly know what you + * do and what effects it might have. Please be aware that it might affect the + * high level interface. + * + * @param[in] dev Device descriptor of VL6180X sensor + * @param[in] reg Address of the first register to be changed + * @param[in] data Pointer to the data to be written to the register + * @param[in] len Number of bytes to be written to the register + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_reg_write(const vl6180x_t *dev, + uint16_t reg, const uint8_t *data, uint8_t len); + +/** + * @brief Direct read from register + * + * @note This function should only be used to do something special that + * is not covered by the high level interface AND if you exactly know what you + * do and what effects it might have. Please be aware that it might affect the + * high level interface. + * + * @param[in] dev Device descriptor of VL6180X sensor + * @param[in] reg address of the first register to be read + * @param[out] data pointer to the data to be read from the register + * @param[in] len number of bytes to be read from the register + * + * @retval VL6180X_OK on success + * @retval VL6180X_ERROR_* a negative error code on error, see + * #vl6180x_error_t + */ +int vl6180x_reg_read(const vl6180x_t *dev, + uint16_t reg, uint8_t *data, uint8_t len); +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /* VL6180X_H */ +/** @} */ diff --git a/drivers/vl6180x/Makefile b/drivers/vl6180x/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/drivers/vl6180x/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/drivers/vl6180x/Makefile.dep b/drivers/vl6180x/Makefile.dep new file mode 100644 index 0000000000..f0875e9371 --- /dev/null +++ b/drivers/vl6180x/Makefile.dep @@ -0,0 +1,17 @@ +ifneq (,$(filter vl6180x_irq_%,$(USEMODULE))) + USEMODULE += vl6180x_irq +endif + +ifneq (,$(filter vl6180x,$(USEMODULE))) + FEATURES_REQUIRED += periph_i2c + USEMODULE += ztimer_msec +endif + +ifneq (,$(filter vl6180x_shutdown,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio +endif + +ifneq (,$(filter vl6180x_irq,$(USEMODULE))) + FEATURES_REQUIRED += periph_gpio + FEATURES_REQUIRED += periph_gpio_irq +endif diff --git a/drivers/vl6180x/Makefile.include b/drivers/vl6180x/Makefile.include new file mode 100644 index 0000000000..701100d760 --- /dev/null +++ b/drivers/vl6180x/Makefile.include @@ -0,0 +1,9 @@ +# include variants of VL6180x drivers as pseudomodules +PSEUDOMODULES += vl6180x_rng +PSEUDOMODULES += vl6180x_als +PSEUDOMODULES += vl6180x_irq +PSEUDOMODULES += vl6180x_shutdown +PSEUDOMODULES += vl6180x_config + +USEMODULE_INCLUDES_vl6180x:= $(LAST_MAKEFILEDIR)/include +USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_vl6180x) diff --git a/drivers/vl6180x/include/vl6180x_params.h b/drivers/vl6180x/include/vl6180x_params.h new file mode 100644 index 0000000000..7e47f9bc23 --- /dev/null +++ b/drivers/vl6180x/include/vl6180x_params.h @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2021 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_vl6180x + * @brief Default configuration for ST VL6180X Ranging and Ambient Light Sensing (ALS) module + * @author Gunar Schorcht + * @file + * @{ + */ + +#ifndef VL6180X_PARAMS_H +#define VL6180X_PARAMS_H + +#include "board.h" +#include "saul_reg.h" +#include "vl6180x.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Default hardware configuration + * @{ + */ +#ifndef VL6180X_PARAM_DEV +/** Default I2C_DEV(0) device */ +#define VL6180X_PARAM_DEV I2C_DEV(0) +#endif + +#ifndef VL6180X_PARAM_ADDR +/** Default I2C device address */ +#define VL6180X_PARAM_ADDR (VL6180X_I2C_ADDR) +#endif + +#ifndef VL6180X_PARAM_INT_PIN +/** Default interrupt pin */ +#define VL6180X_PARAM_INT_PIN (GPIO_PIN(0, 1)) +#endif + +#ifndef VL6180X_PARAM_SHUTDOWN_PIN +/** Default shutdown pin */ +#define VL6180X_PARAM_SHUTDOWN_PIN (GPIO_PIN(0, 2)) +#endif +/** @} */ + +/** + * @name Default sensor configuration parameters + * @{ + */ +#if !DOXYGEN +/* Mapping of Kconfig defines to the respective driver enumeration values */ + +#ifdef CONFIG_VL6180X_ALS_GAIN_1 +#define CONFIG_VL6180X_ALS_GAIN (VL6180X_ALS_GAIN_1) +#elif CONFIG_VL6180X_ALS_GAIN_1_25 +#define CONFIG_VL6180X_ALS_GAIN (VL6180X_ALS_GAIN_1_25) +#elif CONFIG_VL6180X_ALS_GAIN_1_67 +#define CONFIG_VL6180X_ALS_GAIN (VL6180X_ALS_GAIN_1_67) +#elif CONFIG_VL6180X_ALS_GAIN_2_5 +#define CONFIG_VL6180X_ALS_GAIN (VL6180X_ALS_GAIN_2_5) +#elif CONFIG_VL6180X_ALS_GAIN_5 +#define CONFIG_VL6180X_ALS_GAIN (VL6180X_ALS_GAIN_5) +#elif CONFIG_VL6180X_ALS_GAIN_10 +#define CONFIG_VL6180X_ALS_GAIN (VL6180X_ALS_GAIN_10) +#elif CONFIG_VL6180X_ALS_GAIN_20 +#define CONFIG_VL6180X_ALS_GAIN (VL6180X_ALS_GAIN_20) +#elif CONFIG_VL6180X_ALS_GAIN_40 +#define CONFIG_VL6180X_ALS_GAIN (VL6180X_ALS_GAIN_40) +#endif + +#ifdef CONFIG_VL6180X_RNG_INT_DRDY +#define CONFIG_VL6180X_RNG_INT (VL6180X_INT_DRDY) +#elif CONFIG_VL6180X_RNG_INT_LOW +#define CONFIG_VL6180X_RNG_INT (VL6180X_INT_LOW) +#elif CONFIG_VL6180X_RNG_INT_HIGH +#define CONFIG_VL6180X_RNG_INT (VL6180X_INT_HIGH) +#elif CONFIG_VL6180X_RNG_INT_OUT +#define CONFIG_VL6180X_RNG_INT (VL6180X_INT_OUT) +#endif + +#ifdef CONFIG_VL6180X_ALS_INT_DRDY +#define CONFIG_VL6180X_ALS_INT (VL6180X_INT_DRDY) +#elif CONFIG_VL6180X_ALS_INT_LOW +#define CONFIG_VL6180X_ALS_INT (VL6180X_INT_LOW) +#elif CONFIG_VL6180X_ALS_INT_HIGH +#define CONFIG_VL6180X_ALS_INT (VL6180X_INT_HIGH) +#elif CONFIG_VL6180X_ALS_INT_OUT +#define CONFIG_VL6180X_ALS_INT (VL6180X_INT_OUT) +#endif + +#endif /* !DOXYGEN */ + +#ifndef CONFIG_VL6180X_MEAS_PERIOD +/** Default period for range and ALS measurements in steps of 10 ms: 200 ms */ +#define CONFIG_VL6180X_MEAS_PERIOD (20) +#endif + +#ifndef CONFIG_VL6180X_RNG_MAX_TIME +/** Default ranging maximum convergence time: 50 ms */ +#define CONFIG_VL6180X_RNG_MAX_TIME (50) +#endif + +#ifndef CONFIG_VL6180X_RNG_INT +/** Default interrupt mode for ranging: VL6180X_INT_DRDY */ +#define CONFIG_VL6180X_RNG_INT (VL6180X_INT_DRDY) +#endif + +#ifndef CONFIG_VL6180X_RNG_THRESH_LOW +/** Default low threshold value for ranging comparison: 20 mm */ +#define CONFIG_VL6180X_RNG_THRESH_LOW (20) +#endif + +#ifndef CONFIG_VL6180X_RNG_THRESH_HIGH +/** Default high threshold value for ranging comparison: 90 mm */ +#define CONFIG_VL6180X_RNG_THRESH_HIGH (90) +#endif + +#ifndef CONFIG_VL6180X_ALS_INT_TIME +/** Default ALS integration time: 100 ms (recommended by the datasheet) */ +#define CONFIG_VL6180X_ALS_INT_TIME (100) +#endif + +#ifndef CONFIG_VL6180X_ALS_GAIN +/** Default ALS analogue light channel gain: 1.0 */ +#define CONFIG_VL6180X_ALS_GAIN (VL6180X_ALS_GAIN_1) +#endif + +#ifndef CONFIG_VL6180X_ALS_LUX_RES +/** Default ALS lux resolution specified as lux/count*1000: 0.32 count/lux is factory calibrated */ +#define CONFIG_VL6180X_ALS_LUX_RES 320 +#endif + +#ifndef CONFIG_VL6180X_ALS_INT +/** Default interrupt mode for ranging: VL6180X_INT_DRDY */ +#define CONFIG_VL6180X_ALS_INT (VL6180X_INT_DRDY) +#endif + +#ifndef CONFIG_VL6180X_ALS_THRESH_LOW +/** Default low threshold value for ALS comparison: 50 counts */ +#define CONFIG_VL6180X_ALS_THRESH_LOW (50) +#endif + +#ifndef CONFIG_VL6180X_ALS_THRESH_HIGH +/** Default high threshold value for ALS comparison: 2000 counts */ +#define CONFIG_VL6180X_ALS_THRESH_HIGH (2000) +#endif + +#if IS_USED(MODULE_VL6180X_RNG) || DOXYGEN +/** Range measurement configuration parameters */ +#define VL6180X_PARAM_RANGE .rng_max_time = CONFIG_VL6180X_RNG_MAX_TIME, +#else +#define VL6180X_PARAM_RANGE +#endif + +#if IS_USED(MODULE_VL6180X_ALS) || DOXYGEN +/** ALS measurement configuration parameters */ +#define VL6180X_PARAM_ALS .als_int_time = CONFIG_VL6180X_ALS_INT_TIME, \ + .als_gain = CONFIG_VL6180X_ALS_GAIN, \ + .als_lux_res = CONFIG_VL6180X_ALS_LUX_RES, +#else +#define VL6180X_PARAM_ALS +#endif + +#if IS_USED(MODULE_VL6180X_SHUTDOWN) || DOXYGEN +/** Shutdown hardware configuration */ +#define VL6180X_PARAM_SHUTDOWN .shutdown_pin = VL6180X_PARAM_SHUTDOWN_PIN, +#else +#define VL6180X_PARAM_SHUTDOWN +#endif + +#if IS_USED(MODULE_VL6180X_IRQ) || DOXYGEN +/** Interrupt pin configuration */ +#define VL6180X_PARAM_INT .int_pin = VL6180X_PARAM_INT_PIN, + +#if IS_USED(MODULE_VL6180X_RNG) || DOXYGEN +/** Interrupt configuration for ranging */ +#define VL6180X_PARAM_INT_RNG_CFG .int_cfg.rng_int = CONFIG_VL6180X_RNG_INT, \ + .int_thresh.rng_low = CONFIG_VL6180X_RNG_THRESH_LOW, \ + .int_thresh.rng_high = CONFIG_VL6180X_RNG_THRESH_HIGH, +#else /* IS_USED(MODULE_VL6180X_RNG) || DOXYGEN */ +#define VL6180X_PARAM_INT_RNG_CFG +#endif /* IS_USED(MODULE_VL6180X_RNG) || DOXYGEN */ + +#if IS_USED(MODULE_VL6180X_ALS) || DOXYGEN +/** Interrupt configuration for ALS */ +#define VL6180X_PARAM_INT_ALS_CFG .int_cfg.als_int = CONFIG_VL6180X_ALS_INT, \ + .int_thresh.als_low = CONFIG_VL6180X_ALS_THRESH_LOW, \ + .int_thresh.als_high = CONFIG_VL6180X_ALS_THRESH_HIGH, +#else /* IS_USED(MODULE_VL6180X_ALS) || DOXYGEN */ +#define VL6180X_PARAM_INT_ALS_CFG +#endif /* IS_USED(MODULE_VL6180X_ALS) || DOXYGEN */ + +#else /* IS_USED(MODULE_VL6180X_IRQ) || DOXYGEN */ +#define VL6180X_PARAM_INT +#define VL6180X_PARAM_INT_RNG_CFG +#define VL6180X_PARAM_INT_ALS_CFG +#endif /* IS_USED(MODULE_VL6180X_IRQ) || DOXYGEN */ + +#if !VL6180X_PARAMS || DOXYGEN +/** Default configuration parameter set */ +#define VL6180X_PARAMS { \ + .i2c_dev = VL6180X_PARAM_DEV, \ + .i2c_addr = VL6180X_PARAM_ADDR, \ + .period = CONFIG_VL6180X_MEAS_PERIOD, \ + VL6180X_PARAM_RANGE \ + VL6180X_PARAM_ALS \ + VL6180X_PARAM_SHUTDOWN \ + VL6180X_PARAM_INT \ + VL6180X_PARAM_INT_RNG_CFG \ + VL6180X_PARAM_INT_ALS_CFG \ + } +#endif /* !VL6180X_PARAMS */ + +#if !defined(VL6180X_SAUL_INFO) || DOXYGEN +/** Default SAUL information */ +#define VL6180X_SAUL_INFO { .name = "vl6180x" } +#endif +/**@}*/ + +/** + * @brief Allocate some memory to store the actual configuration + */ +static const vl6180x_params_t vl6180x_params[] = +{ + VL6180X_PARAMS +}; + +/** + * @brief Additional meta information to keep in the SAUL registry + */ +static const saul_reg_info_t vl6180x_saul_info[] = +{ + VL6180X_SAUL_INFO +}; + +#ifdef __cplusplus +} +#endif + +#endif /* VL6180X_PARAMS_H */ +/** @} */ diff --git a/drivers/vl6180x/include/vl6180x_regs.h b/drivers/vl6180x/include/vl6180x_regs.h new file mode 100644 index 0000000000..067cf02045 --- /dev/null +++ b/drivers/vl6180x/include/vl6180x_regs.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_vl6180x + * @brief Register definitions for ST VL6180X Ranging and Ambient Light Sensing (ALS) module + * @author Gunar Schorcht + * @file + * @{ + */ + +#ifndef VL6180X_REGS_H +#define VL6180X_REGS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @name Register addresses + * @{ + */ +#define VL6180X_REG_MODEL_ID (0x00) /**< Device ID */ +#define VL6180X_REG_MODEL_REV_MAJOR (0x01) /**< Device revision (major) */ +#define VL6180X_REG_MODEL_REV_MINOR (0x02) /**< Device revision (minor) */ +#define VL6180X_REG_MODULE_REV_MAJOR (0x03) /**< Module revision (major) */ +#define VL6180X_REG_MODULE_REV_MINOR (0x04) /**< Module revision (minor) */ + +#define VL6180X_REG_GPIO0_MODE (0x10) /**< GPIO0 mode definition */ +#define VL6180X_REG_GPIO1_MODE (0x11) /**< GPIO1 mode definition */ +#define VL6180X_REG_HISTORY_CTRL (0x12) /**< ALS and Ranging history control */ +#define VL6180X_REG_INT_CONFIG (0x14) /**< Interrupt mode config */ +#define VL6180X_REG_INT_CLR (0x15) /**< Interrupt clear */ +#define VL6180X_REG_FRESH_RST (0x16) /**< Fresh out of reset bit */ + +#define VL6180X_REG_RNG_START (0x18) /**< Range measurement start */ +#define VL6180X_REG_RNG_THRESH_HI (0x19) /**< Range measurement high threshold */ +#define VL6180X_REG_RNG_THRESH_LO (0x1a) /**< Range measurement low threshold */ +#define VL6180X_REG_RNG_PERIOD (0x1b) /**< Range measurement period in continuous mode */ +#define VL6180X_REG_RNG_MAX_TIME (0x1c) /**< Range measurement time limit */ +#define VL6180X_REG_RNG_VALUE (0x62) /**< Range 8-bit value in mm */ + +#define VL6180X_REG_ALS_START (0x38) /**< ALS measurement start */ +#define VL6180X_REG_ALS_THRESH_HI (0x3a) /**< ALS measurement high threshold */ +#define VL6180X_REG_ALS_THRESH_LO (0x3c) /**< ALS measurement low threshold */ +#define VL6180X_REG_ALS_PERIOD (0x3e) /**< ALS measurement period in continuous mode */ +#define VL6180X_REG_ALS_GAIN (0x3f) /**< ALS analogue gain */ +#define VL6180X_REG_ALS_INT_TIME (0x40) /**< ALS integration time */ +#define VL6180X_REG_ALS_VALUE (0x50) /**< ALS 16-bit count value */ + +#define VL6180X_REG_RNG_STATUS (0x4d) /**< Range measurement status */ +#define VL6180X_REG_ALS_STATUS (0x4e) /**< ALS measurement status */ +#define VL6180X_REG_INT_STATUS (0x4f) /**< Interrupt status */ + +#define VL6180X_REG_I2C_ADDR (0x212) /**< Programmable device address */ +#define VL6180X_REG_INTERLEAVED_MODE (0x2a3) /**< Interleaved mode enable */ +/** @} */ + +/** + * @name Register structures + * @{ + */ + +/* VL6180X_REG_RNG_START */ +#define VL6180X_RNG_MODE_CONT (0x02) /**< Continuous range measurement mode */ +#define VL6180X_RNG_START_STOP (0x01) /**< Start/stop range measurement */ + +/* VL6180X_REG_ALS_START */ +#define VL6180X_ALS_MODE_CONT (0x02) /**< ALS measurement mode */ +#define VL6180X_ALS_START_STOP (0x01) /**< Start/stop ALS measurement */ + +/* VL6180X_REG_RNG_STATUS */ +#define VL6180X_RNG_ERR_CODE (0xf0) /**< Range measurement error code mask */ +#define VL6180X_RNG_ERR_CODE_S (4) /**< Range measurement error code shift */ +#define VL6180X_RNG_DEVICE_RDY (0x01) /**< Range device ready */ + +/* VL6180X_REG_ALS_STATUS */ +#define VL6180X_ALS_ERR_CODE (0xf0) /**< ALS measurement error code mask */ +#define VL6180X_ALS_ERR_CODE_S (4) /**< ALS measurement error code shift */ +#define VL6180X_ALS_DEVICE_RDY (0x01) /**< ALS device ready */ + +/* VL6180X_REG_ALS_GAIN */ +#define VL6180X_ALS_GAIN_LIGHT (0x07) /**< ALS analogue gain mask (light channel) */ + +/* VL6180X_REG_INT_CONFIG and VL6180X_REG_INT_STATUS */ +#define VL6180X_INT_RNG (0x07) /**< RNG interrupt mask */ +#define VL6180X_INT_RNG_S (0) /**< RNG interrupt shift */ +#define VL6180X_INT_ALS (0x38) /**< ALS interrupt mask */ +#define VL6180X_INT_ALS_S (3) /**< ALS interrupt shift */ +#define VL6180X_ERR_INT (0xc0) /**< Error interrupt mask (VL6180X_REG_INT_STATUS only) */ +#define VL6180X_ERR_INT_S (6) /**< Error interrupt shift */ + +#define VL6180X_INT_RNG_LOW (0x01) /**< range < lower threshold */ +#define VL6180X_INT_RNG_HIGH (0x02) /**< range > upper threshold */ +#define VL6180X_INT_RNG_OUT (0x03) /**< range < lower threshold or range > upper threshold */ +#define VL6180X_INT_RNG_DRDY (0x04) /**< new range data are ready to be read */ +#define VL6180X_INT_ALS_LOW (0x08) /**< ALS < lower threshold */ +#define VL6180X_INT_ALS_HIGH (0x10) /**< ALS > upper threshold */ +#define VL6180X_INT_ALS_OUT (0x18) /**< ALS < lower threshold or ALS > upper threshold */ +#define VL6180X_INT_ALS_DRDY (0x20) /**< new ALS data are ready to be read */ +#define VL6180X_INT_ERR_LASER (0x40) /**< Laser safety error */ +#define VL6180X_INT_ERR_PLL (0x80) /**< PLL error */ + +/* VL6180X_REG_INT_CLR */ +#define VL6180X_CLR_ERR_INT (0x04) /**< Clear error interrupt */ +#define VL6180X_CLR_ALS_INT (0x02) /**< Clear ALS interrupt */ +#define VL6180X_CLR_RNG_INT (0x01) /**< Clear range interrupt */ +#define VL6180X_CLR_ALL_INT (0x07) /**< Clear all interrupts */ + +/* VL6180X_REG_GPIO0_MODE */ +#define VL6180X_GPIO0_SHUT (0x40) /**< GPIO0 shutdown function mask */ +#define VL6180X_GPIO0_SHUT_ON (0x40) /**< GPIO0 shutdown function enabled */ +#define VL6180X_GPIO0_SHUT_OFF (0x00) /**< GPIO0 shutdown function disabled */ +#define VL6180X_GPIO0_POL (0x20) /**< GPIO0 polarity mask */ +#define VL6180X_GPIO0_POL_LOW (0x00) /**< GPIO0 polarity is low */ +#define VL6180X_GPIO0_POL_HIGH (0x20) /**< GPIO0 polarity is high */ +#define VL6180X_GPIO0_FUNC (0x1e) /**< GPIO0 function mask */ +#define VL6180X_GPIO0_FUNC_OFF (0x00) /**< GPIO0 function off*/ +#define VL6180X_GPIO0_FUNC_ON (0x10) /**< GPIO0 function on */ + +/* VL6180X_REG_GPIO1_MODE */ +#define VL6180X_GPIO1_POL (0x20) /**< GPIO1 polarity mask */ +#define VL6180X_GPIO1_POL_LOW (0x00) /**< GPIO1 polarity is low */ +#define VL6180X_GPIO1_POL_HIGH (0x20) /**< GPIO1 polarity is high */ +#define VL6180X_GPIO1_FUNC (0x1e) /**< GPIO1 function mask */ +#define VL6180X_GPIO1_FUNC_OFF (0x00) /**< GPIO1 function off */ +#define VL6180X_GPIO1_FUNC_ON (0x10) /**< GPIO1 function on */ + +/** @} */ + +/** + * @name Default register values + * + * These values are the register values after reset or overwritten at + * boot-up by NVM contents. + * @{ + */ +#define VL6180X_MODEL_ID (0xb4) /**< VNCL6180 Device ID */ +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* VL6180X_REGS_H */ +/** @} */ diff --git a/drivers/vl6180x/vl6180x.c b/drivers/vl6180x/vl6180x.c new file mode 100644 index 0000000000..5941a99953 --- /dev/null +++ b/drivers/vl6180x/vl6180x.c @@ -0,0 +1,920 @@ +/* + * Copyright (C) 2021 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_vl6180x + * @brief Device driver for the ST VL6180X Ranging and Ambient Light Sensing (ALS) module + * @author Gunar Schorcht + * @file + * @{ + */ + +#include +#include + +#include "vl6180x_regs.h" +#include "vl6180x.h" + +#include "irq.h" +#include "log.h" +#include "ztimer.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#if ENABLE_DEBUG + +#define ASSERT_PARAM(cond) \ + do { \ + if (!(cond)) { \ + DEBUG("[vl6180x] %s: %s\n", \ + __func__, "parameter condition (" # cond ") not fulfilled"); \ + assert(cond); \ + } \ + } while (0) + +#define DEBUG_DEV(f, d, ...) \ + DEBUG("[vl6180x] %s i2c dev=%d addr=%02x: " f "\n", \ + __func__, d->params.i2c_dev, d->params.i2c_addr, ## __VA_ARGS__) + +#else /* ENABLE_DEBUG */ + +#define ASSERT_PARAM(cond) assert(cond) +#define DEBUG_DEV(f, d, ...) + +#endif /* ENABLE_DEBUG */ + +#define ERROR_DEV(f, d, ...) \ + LOG_ERROR("[vl6180x] %s i2c dev=%d addr=%02x: " f "\n", \ + __func__, d->params.i2c_dev, d->params.i2c_addr, ## __VA_ARGS__) + +#define _SET_REG_VALUE(r, m, v) r = (r & ~m) | (((v) << m ## _S) & m) +#define _GET_REG_VALUE(r, m) ((r & ~m) >> m ## _S) + +/** Forward declaration of functions for internal use */ +static int _is_available(const vl6180x_t *dev); +static int _reset(const vl6180x_t *dev); +static int _init(vl6180x_t *dev); + +inline static void _acquire(const vl6180x_t *dev); +inline static void _release(const vl6180x_t *dev); + +static int _read(const vl6180x_t *dev, + uint16_t reg, uint8_t *pdata, uint8_t len); +static int _write(const vl6180x_t *dev, + uint16_t reg, const uint8_t *pdata, uint8_t len); + +inline static int _read_word(const vl6180x_t *dev, uint16_t reg, uint16_t *word); +inline static int _write_byte(const vl6180x_t *dev, uint16_t reg, uint8_t byte); +inline static int _write_word(const vl6180x_t *dev, uint16_t reg, uint16_t word); + +inline static int _read_word_x(const vl6180x_t *dev, uint16_t reg, uint16_t *word); +inline static int _write_byte_x(const vl6180x_t *dev, uint16_t reg, uint8_t byte); +inline static int _write_word_x(const vl6180x_t *dev, uint16_t reg, uint16_t word); + +int vl6180x_init(vl6180x_t *dev, const vl6180x_params_t *params) +{ + /* some parameter sanity checks */ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(params != NULL); + + DEBUG_DEV("params=%p", dev, params); + +#if IS_USED(MODULE_VL6180X_RNG) + ASSERT_PARAM(params->rng_max_time > 0 && params->rng_max_time < 64); +#endif +#if IS_USED(MODULE_VL6180X_ALS) + ASSERT_PARAM(params->als_int_time > 0 && params->als_int_time < 512); +#endif + + /* init sensor data structure */ + dev->params = *params; + +#if IS_USED(MODULE_VL6180X_SHUTDOWN) + /* if shutdown is used, the pin nust not be undefined */ + ASSERT_PARAM(gpio_is_valid(params->shutdown_pin)); + + /* shutdown pin is initialized and set to HIGH */ + gpio_init(params->shutdown_pin, GPIO_OUT); + gpio_write(params->shutdown_pin, 1); +#endif /* IS_USED(MODULE_VL6180X_SHUTDOWN) */ + + /* init the sensor and start measurement if periodic measurements used */ + return _init(dev); +} + +int vl6180x_start_cont(vl6180x_t *dev) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(dev->params.period != 0); /* only possible of period != 0 */ + ASSERT_PARAM(!dev->cont_meas); /* continuous mode is not yet started */ + + /* cppcheck-suppress unusedVariable + * (reason: only unused if neither vl6180x_rng nor vl6180x_als is used) */ + uint8_t status; + int res = VL6180X_OK; + + _acquire(dev); + +#if IS_USED(MODULE_VL6180X_ALS) + res |= _read(dev, VL6180X_REG_ALS_STATUS, &status, 1); + if ((status & VL6180X_ALS_DEVICE_RDY) == 0) { + res |= _write_byte(dev, VL6180X_REG_ALS_START, VL6180X_ALS_START_STOP); + } + while ((status & VL6180X_ALS_DEVICE_RDY) == 0) { + /* start measurement only when device is ready to start*/ + res |= _read(dev, VL6180X_REG_ALS_STATUS, &status, 1); + } + /* + * Start either only ALS measurements or both ALS and range + * measurements in continuous mode. According to the data sheet, + * the interleaved mode should be used in latter case. This means + * that the INTERLEAVED_MODE__ENABLE register must be set to 0x01 + * and the ALS measurement must be started as configured. The same + * period is then used for both measurements. + */ + res |= _write_byte(dev, VL6180X_REG_INTERLEAVED_MODE, + IS_USED(MODULE_VL6180X_RNG)); + res |= _write_byte(dev, VL6180X_REG_ALS_START, + VL6180X_ALS_START_STOP | VL6180X_ALS_MODE_CONT); +#elif IS_USED(MODULE_VL6180X_RNG) + res |= _read(dev, VL6180X_REG_RNG_STATUS, &status, 1); + if ((status & VL6180X_RNG_DEVICE_RDY) == 0) { + res |= _write_byte(dev, VL6180X_REG_RNG_START, VL6180X_RNG_START_STOP); + } + while ((status & VL6180X_RNG_DEVICE_RDY) == 0) { + /* start measurement only when device is ready to start*/ + res |= _read(dev, VL6180X_REG_RNG_STATUS, &status, 1); + } + if ((status & VL6180X_RNG_DEVICE_RDY) == 0) { + /* start measurement only when device is ready to start*/ + return -VL6180X_ERROR_NOT_READY; + } + + /* start only range measurements in continuous mode */ + res |= _write_byte(dev, VL6180X_REG_INTERLEAVED_MODE, 0); + res |= _write_byte(dev, VL6180X_REG_RNG_START, + VL6180X_RNG_START_STOP | VL6180X_RNG_MODE_CONT); +#endif /* IS_USED(MODULE_VL6180X_ALS) */ + + _release(dev); + + dev->cont_meas = (res == VL6180X_OK) ? true : dev->cont_meas; + + return res; +} + +int vl6180x_stop_cont(vl6180x_t *dev) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(dev->params.period != 0); /* operation is only valid if period != 0 */ + ASSERT_PARAM(dev->cont_meas); /* continuous mode is started */ + + /* cppcheck-suppress unusedVariable + * (reason: only unused if neither vl6180x_rng nor vl6180x_als is used) */ + uint8_t status; + int res = VL6180X_OK; + + _acquire(dev); + +#if IS_USED(MODULE_VL6180X_ALS) + res |= _read(dev, VL6180X_REG_ALS_STATUS, &status, 1); + if ((status & VL6180X_ALS_DEVICE_RDY) == 0) { + /* stop only when device is not ready, otherwise it is already stopped */ + res |= _write_byte(dev, VL6180X_REG_ALS_START, VL6180X_ALS_START_STOP); + } + /* wait that the device becomes ready */ + do { + res |= _read(dev, VL6180X_REG_ALS_STATUS, &status, 1); + } while ((res != VL6180X_OK) || ((status & VL6180X_ALS_DEVICE_RDY) == 0)); + +#elif IS_USED(MODULE_VL6180X_RNG) + res |= _read(dev, VL6180X_REG_RNG_STATUS, &status, 1); + if ((status & VL6180X_RNG_DEVICE_RDY) == 0) { + /* stop only when device is not ready, otherwise it is already stopped */ + res |= _write_byte(dev, VL6180X_REG_RNG_START, VL6180X_RNG_START_STOP); + } + /* wait that the device becomes ready */ + do { + res |= _read(dev, VL6180X_REG_RNG_STATUS, &status, 1); + } while ((res != VL6180X_OK) || ((status & VL6180X_RNG_DEVICE_RDY) == 0)); +#endif + + res |= _write_byte(dev, VL6180X_REG_INTERLEAVED_MODE, 0); + _release(dev); + + dev->cont_meas = (res == VL6180X_OK) ? false : dev->cont_meas; + + return res; +} + +#if IS_USED(MODULE_VL6180X_RNG) + +int vl6180x_rng_data_ready (const vl6180x_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + uint8_t byte; + + int res = vl6180x_reg_read(dev, VL6180X_REG_INT_STATUS, &byte, 1); + if (res != VL6180X_OK) { + return res; + } + + return ((byte & VL6180X_INT_RNG_DRDY) != 0) ? VL6180X_OK + : -VL6180X_ERROR_NO_DATA; +} + +int vl6180x_rng_read(vl6180x_t *dev, uint8_t *mm) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(mm != NULL); + DEBUG_DEV("mm=%p", dev, mm); + + _acquire(dev); + + /* read range results */ + int res; + res = _read(dev, VL6180X_REG_RNG_VALUE, mm, 1); + + /* read range measurement status */ + uint8_t status; + res |= _read(dev, VL6180X_REG_RNG_STATUS, &status, 1); + + _release(dev); + + dev->rng_status = (status & VL6180X_ALS_ERR_CODE) >> VL6180X_RNG_ERR_CODE_S; + + if (res != VL6180X_OK) { + return res; + } + else { + /* return with error code if status isn't zero */ + return (dev->rng_status) ? -VL6180X_ERROR_RNG : VL6180X_OK; + } +} + +vl6180x_rng_status_t vl6180x_rng_status(const vl6180x_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + return dev->rng_status; +} + +int vl6180x_rng_start_single(const vl6180x_t *dev) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(!dev->cont_meas); /* continuous mode is not started */ + + uint8_t status; + int res; + + _acquire(dev); + res = _read(dev, VL6180X_REG_RNG_STATUS, &status, 1); + if ((res == VL6180X_OK) && ((status & VL6180X_RNG_DEVICE_RDY) == 1)) { + /* start measurement only when device is ready to start*/ + res = _write_byte_x(dev, VL6180X_REG_RNG_START, VL6180X_RNG_START_STOP); + } + _release(dev); + + return res; +} + +#if IS_USED(MODULE_VL6180X_CONFIG) + +int vl6180x_rng_config(vl6180x_t *dev, uint8_t period, uint8_t max_time) +{ + /* some parameter sanity checks */ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(max_time < 64); + + DEBUG_DEV("period=%u max_time=%u", dev, period, max_time); + + int res; + + /* write new ranging configuration */ + dev->params.period = period; + dev->params.rng_max_time = max_time; + period -= 1; /* register value is decremented by 1 */ + + _acquire(dev); + res = _write(dev, VL6180X_REG_RNG_PERIOD, &period, 1); + res |= _write(dev, VL6180X_REG_RNG_MAX_TIME, &max_time, 1); + _release(dev); + + /* if running in continuous mode, stop and restart measurements */ + if ((res == VL6180X_OK) && dev->cont_meas) { + res = vl6180x_stop_cont(dev); + if ((res == VL6180X_OK) && period) { + res = vl6180x_start_cont(dev); + } + } + + return res; +} + +#endif /* IS_USED(MODULE_VL6180X_CONFIG) */ +#endif /* IS_USED(MODULE_VL6180X_RNG) */ + +#if IS_USED(MODULE_VL6180X_ALS) + +int vl6180x_als_data_ready(const vl6180x_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + uint8_t byte; + + int res = vl6180x_reg_read(dev, VL6180X_REG_INT_STATUS, &byte, 1); + if (res != VL6180X_OK) { + return res; + } + + return ((byte & VL6180X_INT_ALS_DRDY) != 0) ? VL6180X_OK + : -VL6180X_ERROR_NO_DATA; +} + +static const uint16_t _als_gains[] = { +/* 20 10.32 5.21 2.56 1.72 1.28 1.01 40 actual analogue gain */ + 20000, 10320, 5210, 2560, 1720, 1280, 1010, 40000 +}; + +int vl6180x_als_read(vl6180x_t *dev, uint16_t *raw, uint16_t *lux) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(raw != NULL || lux != NULL); + DEBUG_DEV("raw=%p lux=%p", dev, raw, lux); + + int res; + + _acquire(dev); + + /* read raw ALS results */ + uint16_t cnt = 0; + res = _read_word(dev, VL6180X_REG_ALS_VALUE, &cnt); + + /* read range measurement status */ + uint8_t status; + res |= _read(dev, VL6180X_REG_ALS_STATUS, &status, 1); + + _release(dev); + + if (res) { + return res; + } + + dev->als_status = (status & VL6180X_ALS_ERR_CODE_S) >> VL6180X_ALS_ERR_CODE_S; + + if (dev->als_status == VL6180X_ALS_OK) { + if (raw) { + *raw = cnt; + } + if (lux) { + /* lux = lux_res * (cnt / als_gain) * (100 / als_int_time) */ + uint32_t lux_tmp; + lux_tmp = dev->params.als_lux_res * cnt * 100; + lux_tmp /= _als_gains[dev->params.als_gain]; + lux_tmp /= dev->params.als_int_time; + *lux = lux_tmp; + } + + } + + /* return with error code if status isn't zero */ + return (dev->als_status) ? -VL6180X_ERROR_ALS : VL6180X_OK; +} + +vl6180x_als_status_t vl6180x_als_status(const vl6180x_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + return dev->als_status; +} + +int vl6180x_als_start_single(const vl6180x_t *dev) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(!dev->cont_meas); /* continuous mode is not started */ + + uint8_t status; + int res; + + _acquire(dev); + + res = _read(dev, VL6180X_REG_ALS_STATUS, &status, 1); + if ((res == VL6180X_OK) && ((status & VL6180X_ALS_DEVICE_RDY) == 1)) { + /* start measurement only when device is ready to start*/ + res = _write_byte(dev, VL6180X_REG_ALS_START, VL6180X_ALS_START_STOP); + } + + _release(dev); + + return res; +} + +#if IS_USED(MODULE_VL6180X_CONFIG) + +int vl6180x_als_config(vl6180x_t *dev, uint8_t period, uint8_t int_time, + vl6180x_als_gain_t gain) +{ + /* some parameter sanity checks */ + ASSERT_PARAM(dev != NULL); + + DEBUG_DEV("period=%u int_time=%u gain=%u", dev, period, int_time, gain); + + /* write new ambient light sensing (ALS) configuration */ + dev->params.period = period; + dev->params.als_int_time = int_time; + dev->params.als_gain = gain; + + int res; + + _acquire(dev); + res = _write_byte(dev, VL6180X_REG_ALS_PERIOD, period - 1); + res |= _write_word(dev, VL6180X_REG_ALS_INT_TIME, int_time - 1); + res |= _write_byte(dev, VL6180X_REG_ALS_GAIN, 0x40 + gain); + _release(dev); + + /* if running in continuous mode, stop and restart measurements */ + if ((res == VL6180X_OK) && dev->cont_meas) { + res = vl6180x_stop_cont(dev); + if ((res == VL6180X_OK) && period) { + res = vl6180x_start_cont(dev); + } + } + + return res; +} +#endif /* IS_USED(MODULE_VL6180X_CONFIG) */ +#endif /* IS_USED(MODULE_VL6180X_ALS) */ + +#if IS_USED(MODULE_VL6180X_SHUTDOWN) +int vl6180x_power_down(const vl6180x_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + /* assert shutdown pin */ + gpio_clear(dev->params.shutdown_pin); + return VL6180X_OK; +} + +int vl6180x_power_up(vl6180x_t *dev) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + /* deactivate shutdown pin */ + gpio_set(dev->params.shutdown_pin); + + /* init the sensor and start measurement */ + _init(dev); + + return VL6180X_OK; +} +#endif /* IS_USED(MODULE_VL6180X_SHUTDOWN) */ + +#if IS_USED(MODULE_VL6180X_IRQ) + +static void _isr(void *lock) +{ + mutex_unlock(lock); +} + +int vl6180x_int_wait(const vl6180x_t *dev, vl6180x_int_config_t *src) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(src != NULL); + ASSERT_PARAM(gpio_is_valid(dev->params.int_pin)); + + mutex_t lock = MUTEX_INIT_LOCKED; + + /* enable interrupt pin */ + gpio_init_int(dev->params.int_pin, GPIO_IN_PU, GPIO_FALLING, _isr, &lock); + + /* wait for interrupt */ + mutex_lock(&lock); + + /* disable interrupt pin */ + gpio_irq_disable(dev->params.int_pin); + + /* read interrupt sources */ + uint8_t byte; + int res; + + _acquire(dev); + res = _read(dev, VL6180X_REG_INT_STATUS, &byte, 1); +#if IS_USED(MODULE_VL6180X_RNG) + src->rng_int = (byte & VL6180X_INT_RNG) >> VL6180X_INT_RNG_S; +#endif +#if IS_USED(MODULE_VL6180X_ALS) + src->als_int = (byte & VL6180X_INT_ALS) >> VL6180X_INT_ALS_S; +#endif + + /* clear all interrupt flags */ + res |= _write_byte(dev, VL6180X_REG_INT_CLR, VL6180X_CLR_ALL_INT); + _release(dev); + + return res; +} + +#if IS_USED(MODULE_VL6180X_CONFIG) +int vl6180x_int_enable(vl6180x_t *dev, vl6180x_int_config_t mode) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + dev->params.int_cfg = mode; + + uint8_t byte = 0; +#if IS_USED(MODULE_VL6180X_RNG) + byte |= (mode.rng_int << VL6180X_INT_RNG_S) & VL6180X_INT_RNG; +#endif +#if IS_USED(MODULE_VL6180X_ALS) + byte |= (mode.als_int << VL6180X_INT_ALS_S) & VL6180X_INT_ALS; +#endif + + int res; + _acquire(dev); + res = _write_byte(dev, VL6180X_REG_INT_CONFIG, byte); + res |= _write_byte(dev, VL6180X_REG_GPIO1_MODE, + VL6180X_GPIO1_POL_LOW | VL6180X_GPIO1_FUNC_ON); + res |= _write_byte(dev, VL6180X_REG_INT_CLR, VL6180X_CLR_ALL_INT); + _release(dev); + + return res; +} + +int vl6180x_int_config(vl6180x_t *dev, vl6180x_int_thresh_t thresh) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("", dev); + + dev->params.int_thresh = thresh; + + int res = VL6180X_OK; + + _acquire(dev); +#if IS_USED(MODULE_VL6180X_RNG) + res |= _write_byte(dev, VL6180X_REG_RNG_THRESH_HI, thresh.rng_high); + res |= _write_byte(dev, VL6180X_REG_RNG_THRESH_LO, thresh.rng_low); +#endif + +#if IS_USED(MODULE_VL6180X_ALS) + res |= _write_word(dev, VL6180X_REG_ALS_THRESH_HI, thresh.als_high); + res |= _write_word(dev, VL6180X_REG_ALS_THRESH_LO, thresh.als_low); +#endif + _release(dev); + + return res; +} +#endif /* IS_USED(MODULE_VL6180X_CONFIG) */ + +#endif /* IS_USED(MODULE_VL6180X_IRQ) */ + +int vl6180x_reg_read(const vl6180x_t *dev, + uint16_t reg, uint8_t *pdata, uint8_t len) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("reg=%04x pdata=%p len=%u", dev, reg, pdata, len); + + int res; + + _acquire(dev); + res = _read(dev, reg, pdata, len); + _release(dev); + + return res; +} + +int vl6180x_reg_write(const vl6180x_t *dev, + uint16_t reg, const uint8_t *pdata, uint8_t len) +{ + ASSERT_PARAM(dev != NULL); + DEBUG_DEV("reg=%04x pdata=%p len=%u", dev, reg, pdata, len); + + int res; + + _acquire(dev); + res = _write(dev, reg, pdata, len); + _release(dev); + + return res; +} + +/** Functions for internal use only */ + +/** + * @brief Check the chip ID to test whether sensor is available + */ +static int _is_available(const vl6180x_t *dev) +{ + DEBUG_DEV("", dev); + + uint8_t data[5]; + + /* read the chip id from VL6180X_REG_ID_X */ + if (_read(dev, VL6180X_REG_MODEL_ID, data, 5) != VL6180X_OK) { + return VL6180X_ERROR_I2C; + } + + if (data[0] != VL6180X_MODEL_ID) { + DEBUG_DEV("sensor is not available, wrong device id %02x, " + "should be %02x", dev, data[0], VL6180X_MODEL_ID); + return -VL6180X_ERROR_WRONG_ID; + } + + DEBUG_DEV("rev=%u.%u, module rev=%d.%d", + dev, data[1], data[2], data[3], data[4]); + + return VL6180X_OK; +} + +static int _init(vl6180x_t *dev) +{ +#if IS_USED(MODULE_VL6180X_RNG) + dev->rng_status = VL6180X_RNG_OK; +#endif /* IS_USED(MODULE_VL6180X_RNG) */ +#if IS_USED(MODULE_VL6180X_ALS) + dev->als_status = VL6180X_ALS_OK; +#endif /* IS_USED(MODULE_VL6180X_ALS) */ + + dev->cont_meas = false; + + /* we have to wait at least 400 us after power on, with msec timer 1 ms */ + ztimer_sleep(ZTIMER_MSEC, 1); + + int res; + + _acquire(dev); + + /* check availability of the sensor */ + res = _is_available(dev); + if (res != VL6180X_OK) { + _release(dev); + return res; + } + + /* reset the device to recommended configuration */ + res = _reset(dev); + + /* clear all interrupt flags */ + res |= _write_byte(dev, VL6180X_REG_INT_CLR, VL6180X_CLR_ALL_INT); + + /* set range measurement configuration */ + uint8_t rng_int = 0; + +#if IS_USED(MODULE_VL6180X_RNG) +#if IS_USED(MODULE_VL6180X_IRQ) + res |= _write_byte(dev, VL6180X_REG_RNG_THRESH_HI, dev->params.int_thresh.rng_high); + res |= _write_byte(dev, VL6180X_REG_RNG_THRESH_LO, dev->params.int_thresh.rng_low); + rng_int = (dev->params.int_cfg.rng_int << VL6180X_INT_RNG_S) & VL6180X_INT_RNG; +#else + rng_int = VL6180X_INT_RNG_DRDY; +#endif /* IS_USED(MODULE_VL6180X_IRQ) */ + res |= _write_byte(dev, VL6180X_REG_RNG_PERIOD, dev->params.period - 1); + res |= _write_byte(dev, VL6180X_REG_RNG_MAX_TIME, dev->params.rng_max_time); +#endif /* IS_USED(MODULE_VL6180X_RNG) */ + + /* set ALS measurement configuration */ + uint8_t als_int = 0; + +#if IS_USED(MODULE_VL6180X_ALS) +#if IS_USED(MODULE_VL6180X_IRQ) + res |= _write_byte(dev, VL6180X_REG_ALS_THRESH_HI, dev->params.int_thresh.als_high); + res |= _write_byte(dev, VL6180X_REG_ALS_THRESH_LO, dev->params.int_thresh.als_low); + als_int = (dev->params.int_cfg.als_int << VL6180X_INT_ALS_S) & VL6180X_INT_ALS; +#else /* IS_USED(MODULE_VL6180X_IRQ) */ + als_int = VL6180X_INT_ALS_DRDY; +#endif /* IS_USED(MODULE_VL6180X_IRQ) */ + res |= _write_byte(dev, VL6180X_REG_ALS_PERIOD, dev->params.period - 1); + res |= _write_byte(dev, VL6180X_REG_ALS_INT_TIME, dev->params.als_int_time - 1); + res |= _write_byte(dev, VL6180X_REG_ALS_GAIN, 0x40 + dev->params.als_gain); +#endif /* IS_USED(MODULE_VL6180X_ALS) */ + +#if IS_USED(MODULE_VL6180X_IRQ) + res |= _write_byte(dev, VL6180X_REG_GPIO1_MODE, + VL6180X_GPIO1_POL_LOW | VL6180X_GPIO1_FUNC_ON); +#endif + /* cppcheck-suppress knownConditionTrueFalse + * (reason: it is not a condition but a bitwise OR) */ + res |= _write_byte(dev, VL6180X_REG_INT_CONFIG, rng_int | als_int); + _release(dev); + + if (res != VL6180X_OK) { + return res; + } + + /* if period is defined, continuous mode measurements are started */ + if (dev->params.period) { + return vl6180x_start_cont(dev); + } + + return VL6180X_OK; +} + +typedef struct { + uint16_t reg; + uint8_t value; +} _vl6180x_reg_value_t; + +/* + * Private registers and standard register initialization values that have to + * be set after power on reset, see [Application Note AN4545, section 9] + * (https://www.st.com/resource/en/application_note/dm00122600.pdf) + */ +static const _vl6180x_reg_value_t _init_values [] = { + /* Mandatory : private registers */ + { .reg = 0x0207, .value = 0x01 }, + { .reg = 0x0208, .value = 0x01 }, + { .reg = 0x0096, .value = 0x00 }, + { .reg = 0x0097, .value = 0xfd }, + { .reg = 0x00e3, .value = 0x01 }, + { .reg = 0x00e4, .value = 0x03 }, + { .reg = 0x00e5, .value = 0x02 }, + { .reg = 0x00e6, .value = 0x01 }, + { .reg = 0x00e7, .value = 0x03 }, + { .reg = 0x00f5, .value = 0x02 }, + { .reg = 0x00d9, .value = 0x05 }, + { .reg = 0x00db, .value = 0xce }, + { .reg = 0x00dc, .value = 0x03 }, + { .reg = 0x00dd, .value = 0xf8 }, + { .reg = 0x009f, .value = 0x00 }, + { .reg = 0x00a3, .value = 0x3c }, + { .reg = 0x00b7, .value = 0x00 }, + { .reg = 0x00bb, .value = 0x3c }, + { .reg = 0x00b2, .value = 0x09 }, + { .reg = 0x00ca, .value = 0x09 }, + { .reg = 0x0198, .value = 0x01 }, + { .reg = 0x01b0, .value = 0x17 }, + { .reg = 0x01ad, .value = 0x00 }, + { .reg = 0x00ff, .value = 0x05 }, + { .reg = 0x0100, .value = 0x05 }, + { .reg = 0x0199, .value = 0x05 }, + { .reg = 0x01a6, .value = 0x1b }, + { .reg = 0x01ac, .value = 0x3e }, + { .reg = 0x01a7, .value = 0x1f }, + { .reg = 0x0030, .value = 0x00 }, + + /* Recommended : Public registers - See data sheet for more detail */ + { .reg = 0x0011, .value = 0x10 }, /* GPIO1 is HIGH active and OFF (Hi-Z), polling enabled */ + { .reg = 0x010a, .value = 0x30 }, /* Range averaging period is 4.3 ms*/ + { .reg = 0x003f, .value = 0x46 }, /* ALS analogue gain is 1.01, dark gain upper nibble */ + { .reg = 0x0031, .value = 0xff }, /* Calibration every 255 measurements */ + { .reg = 0x0041, .value = 0x63 }, /* ALS integration time is 100 ms */ + { .reg = 0x002e, .value = 0x01 }, /* Perform temperature calibration */ + + /* Optional: Public registers - See data sheet for more detail */ + { .reg = 0x001b, .value = 0x13 }, /* Range measurement period is 200 ms */ + { .reg = 0x003e, .value = 0x13 }, /* ALS measurement period is 200 ms */ + { .reg = 0x0014, .value = 0x00 }, /* Interrupts disabled */ + { .reg = 0x02a3, .value = 0x00 }, /* Interleave mode disabled */ + + /* change fresh out of status to 0 */ + { .reg = 0x0016, .value = 0x00 }, +}; + +static int _reset(const vl6180x_t *dev) +{ + uint8_t byte; + int res = VL6180X_OK; + + /* read the fresh out from reset status */ + res |= _read(dev, VL6180X_REG_FRESH_RST, &byte, 1); + + /* if not fresh out from reset, stop measurements if necessary */ + if ((byte & 0x01) == 0) { + uint8_t status; + res |= _read(dev, VL6180X_REG_RNG_STATUS, &status, 1); + if ((status & VL6180X_RNG_DEVICE_RDY) == 0) { + /* stop measurement when device is not ready */ + res |= _write_byte(dev, VL6180X_REG_RNG_START, VL6180X_RNG_START_STOP); + } + } + /* load initial register value set */ + for (unsigned i = 0; i < ARRAY_SIZE(_init_values); i++) { + res |= _write_byte(dev, _init_values[i].reg, _init_values[i].value); + } + + return res; +} + +inline static void _acquire(const vl6180x_t *dev) +{ + i2c_acquire(dev->params.i2c_dev); +} + +inline static void _release(const vl6180x_t *dev) +{ + i2c_release(dev->params.i2c_dev); +} + +#define VL6180X_BUFSIZ 32 +static uint8_t _buffer[VL6180X_BUFSIZ] = {}; + +static int _write(const vl6180x_t *dev, + uint16_t reg, const uint8_t *pdata, uint8_t len) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(pdata != NULL); + ASSERT_PARAM(len <= (VL6180X_BUFSIZ - 2)); + DEBUG_DEV("reg=%04x pdata=%p len=%u", dev, reg, pdata, len); + +#if ENABLE_DEBUG + printf("[vl6180x] %s i2c dev=%d addr=%02x: ", + __func__, dev->params.i2c_dev, dev->params.i2c_addr); + for (uint8_t i = 0; i < len; i++) { + printf("%02x ", pdata[i]); + } + printf("\n"); +#endif /* ENABLE_DEBUG */ + + /* fill the first 2 bytes of the buffer with the reg */ + _buffer[0] = (reg >> 8) & 0xff; + _buffer[1] = reg & 0xff; + + /* fill the buffer with data bytes */ + memcpy(_buffer + 2, pdata, len); + + int res = i2c_write_bytes(dev->params.i2c_dev, + dev->params.i2c_addr, _buffer, len + 2, 0); + if (res) { + DEBUG_DEV("I2C communication error: %d", dev, res); + return -VL6180X_ERROR_I2C; + } + + return VL6180X_OK; +} + +inline static int _write_byte(const vl6180x_t *dev, uint16_t reg, uint8_t byte) +{ + return _write(dev, reg, &byte, 1); +} + +inline static int _write_word(const vl6180x_t *dev, uint16_t reg, uint16_t data) +{ + uint8_t bytes[2] = { (data >> 8) & 0xff, data & 0xff }; + return _write(dev, reg, (uint8_t*)&bytes, 2); +} + +static int _read(const vl6180x_t *dev, + uint16_t reg, uint8_t *pdata, uint8_t len) +{ + ASSERT_PARAM(dev != NULL); + ASSERT_PARAM(pdata != NULL); + ASSERT_PARAM(len <= VL6180X_BUFSIZ); + DEBUG_DEV("reg=%04x pdata=%p len=%u", dev, reg, pdata, len); + + uint8_t bytes[2] = { (reg >> 8) & 0xff, reg & 0xff }; + + /* write the register reg and if successful, read the data */ + if (i2c_write_bytes(dev->params.i2c_dev, dev->params.i2c_addr, bytes, 2, 0) || + i2c_read_bytes(dev->params.i2c_dev, dev->params.i2c_addr, pdata, len, 0)) { + DEBUG_DEV("I2C communication error", dev); + return -VL6180X_ERROR_I2C; + } + +#if ENABLE_DEBUG + printf("[vl6180x] %s i2c dev=%d addr=%02x: ", + __func__, dev->params.i2c_dev, dev->params.i2c_addr); + for (uint8_t i = 0; i < len; i++) { + printf("%02x ", pdata[i]); + } + printf("\n"); +#endif /* ENABLE_DEBUG */ + + return VL6180X_OK; +} + +inline static int _read_word(const vl6180x_t *dev, uint16_t reg, uint16_t *word) +{ + uint8_t bytes[2]; + int res = _read(dev, reg, bytes, 2); + *word = (bytes[0] << 8) + (bytes[1]); + return res; +} + +inline static int _read_word_x(const vl6180x_t *dev, uint16_t reg, uint16_t *word) +{ + uint8_t bytes[2]; + int res = vl6180x_reg_read(dev, reg, bytes, 2); + *word = (bytes[0] << 8) + (bytes[1]); + return res; +} + +inline static int _write_byte_x(const vl6180x_t *dev, uint16_t reg, uint8_t byte) +{ + return vl6180x_reg_write(dev, reg, &byte, 1); +} + +inline static int _write_word_x(const vl6180x_t *dev, uint16_t reg, uint16_t data) +{ + uint8_t bytes[2] = { (data >> 8) & 0xff, data & 0xff }; + return vl6180x_reg_write(dev, reg, (uint8_t*)&bytes, 2); +}