mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
drivers/si1133: New SI1133 UV/IR/Ambient light driver
The SI1133 from Silicon Labs is a UV Index Sensor and Ambient Light Sensor in a small 2x2 mm DFN package. The sensor can measure independently ultra violet (UV) light, infra red (IR) light and ambient light, however the ambient light is also influenced by the IR light requiring compensation from the IR readings. The SI1133 is quite different from other Silicon Labs chips in RIOT OS and therefore needs its own driver. In particular, the SI1133 has 7 different photodiode configurations to read but only 6 channels to read them in parallel so only some channels can be read each time. This patch implements a new driver allowing to read the data directly and a saul interface for the three kinds of light source. There are many configuration options including interrupts and continous modes that are left out of this initial driver.
This commit is contained in:
parent
e3558a4482
commit
ce76125a22
@ -122,6 +122,10 @@ ifneq (,$(filter sht1%,$(USEMODULE)))
|
||||
USEMODULE += sht1x
|
||||
endif
|
||||
|
||||
ifneq (,$(filter si1133,$(USEMODULE)))
|
||||
USEMODULE += si1133
|
||||
endif
|
||||
|
||||
ifneq (,$(filter si114%,$(USEMODULE)))
|
||||
USEMODULE += si114x
|
||||
endif
|
||||
|
210
drivers/include/si1133.h
Normal file
210
drivers/include/si1133.h
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2020 iosabi
|
||||
*
|
||||
* 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_si1133 Si1133 UV Index/Ambient Light Sensor with I2C
|
||||
* @ingroup drivers_sensors
|
||||
* @ingroup drivers_saul
|
||||
* @brief Device driver for the Si1133 sensor
|
||||
*
|
||||
* The Si1133 is a UV Index Sensor and Ambient Light Sensor with I2C digital
|
||||
* interface and programmable-event interrupt output.
|
||||
*
|
||||
* The I2C protocol implemented in this driver is most similar in registers and
|
||||
* commands to the Si115x family, like the SI1153, however the Si1133 supports
|
||||
* UV index while the Si115x doesn't.
|
||||
*
|
||||
* This driver provides @ref drivers_saul capabilities as well.
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Device driver interface for the Si1133 sensor
|
||||
*
|
||||
* @author iosabi <iosabi@protonmail.com>
|
||||
*/
|
||||
|
||||
#ifndef SI1133_H
|
||||
#define SI1133_H
|
||||
|
||||
#include "periph/i2c.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Driver error return codes
|
||||
*/
|
||||
typedef enum {
|
||||
SI1133_OK = 0, /**< No error. */
|
||||
SI1133_ERR_PARAMS = -1, /**< Invalid parameters. */
|
||||
SI1133_ERR_I2C = -2, /**< I2C communication error. */
|
||||
SI1133_ERR_LOGIC = -3, /**< Device communication logic error. */
|
||||
SI1133_ERR_NODEV = -4, /**< No SI1133 device detected. */
|
||||
SI1133_ERR_OVERFLOW = -5, /**< ADC overflow when sampling. */
|
||||
} si1133_ret_code_t;
|
||||
|
||||
/**
|
||||
* @brief Sensor (photodiode combination) in the Si1133 package.
|
||||
*/
|
||||
typedef enum {
|
||||
SI1133_SENS_SMALL_IR = 1u << 0,
|
||||
SI1133_SENS_MEDIUM_IR = 1u << 1,
|
||||
SI1133_SENS_LARGE_IR = 1u << 2,
|
||||
SI1133_SENS_WHITE = 1u << 3,
|
||||
SI1133_SENS_LARGE_WHITE = 1u << 4,
|
||||
SI1133_SENS_UV = 1u << 5,
|
||||
SI1133_SENS_DEEP_UV = 1u << 6,
|
||||
} si1133_sensor_t;
|
||||
|
||||
/**
|
||||
* @brief Channel configuration the Si1133 sensor.
|
||||
*
|
||||
* The sensor sampling in the Si1133 is done via Analog to Digital "channels"
|
||||
* that read from a given sensor (photodiode combination) and output a numeric
|
||||
* value. The A/D process has some configuration parameters that affect the
|
||||
* acquisition time, the power consumption and the quality of the result.
|
||||
*
|
||||
* The A/D time is controlled by the internal 21 MHz clock. The sampling
|
||||
* duration time is:
|
||||
*
|
||||
* (1 << decimation) * (1 << hw_gain) * 512 / 21000000 s
|
||||
*
|
||||
* where @p decimation is a number between 0 and 3 and @p hw_gain" is between 0
|
||||
* and 11. The shortest A/D sampling time is therefore 24.4 us while the longest
|
||||
* is 400 ms. However, each sample is performed twice internally to cancel ADC
|
||||
* offset and there are processing and sampling start times specified in the
|
||||
* datasheet that increase the total sampling time. Increasing the sampling time
|
||||
* doesn't make the output value be larger.
|
||||
*
|
||||
* The @p hw_gain and @p decimation parameters are configured from a single
|
||||
* @p sample_time_log parameter in this struct, between 0 and 14, preferring the
|
||||
* "normal" decimation when possible.
|
||||
*
|
||||
* An additional software sampling and averaging is possible by selecting a
|
||||
* "sw_gain" value between 0 and 7. This will cause each A/D measurement to be
|
||||
* repeated for a total of (1 << sw_gain) and accumulated in software in the
|
||||
* 24-bit output. The output value will be affected by the sw_gain since it is
|
||||
* a sum of samples and not an average.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t sample_time_log; /**< Log2 of sampling time, 0 to 14. */
|
||||
uint8_t sw_gain; /**< Software gain, 0 to 7. */
|
||||
si1133_sensor_t sensor; /**< Sensor to sample. */
|
||||
} si1133_channel_t;
|
||||
|
||||
/**
|
||||
* @brief Device initialization parameters
|
||||
*/
|
||||
typedef struct {
|
||||
i2c_t i2c_dev; /**< I2C bus the sensor is connected to */
|
||||
/**
|
||||
* @brief sensor address.
|
||||
* Note: it is possible to change the a sensor's I2C address after it has
|
||||
* been initialized to have multiple sensor on the same bus, but this is not
|
||||
* supported by this driver. However, two different addresses can be
|
||||
* selected by hardware.
|
||||
*/
|
||||
uint8_t address;
|
||||
} si1133_params_t;
|
||||
|
||||
/**
|
||||
* @brief Device descriptor for the Si1133 sensor
|
||||
*/
|
||||
typedef struct {
|
||||
/* Initialization parameters */
|
||||
i2c_t i2c_dev; /**< I2C bus the sensor is connected to */
|
||||
uint8_t address; /**< sensor address */
|
||||
/* Internal members */
|
||||
uint8_t cmd_counter; /**< Si1133 command counter */
|
||||
uint8_t num_channels; /**< Number of configured channels. */
|
||||
si1133_channel_t channel[6]; /**< Channel configuration. */
|
||||
} si1133_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize the given Si1133 device
|
||||
*
|
||||
* @param[out] dev Initialized device descriptor of Si1133 device
|
||||
* @param[in] params Initialization parameters
|
||||
*
|
||||
* @return A si1133_ret_code_t error or status code.
|
||||
*/
|
||||
si1133_ret_code_t si1133_init(si1133_t *dev, const si1133_params_t *params);
|
||||
|
||||
/**
|
||||
* @brief Configure the capture channels.
|
||||
*
|
||||
* The Si1133 has up to 6 "channels" that can be configured to capture from the
|
||||
* different sensors (photodiode combinations). See @ref si1133_channel_t for
|
||||
* a description of the channel configuration.
|
||||
*
|
||||
* @param[in] dev Device descriptor of Si1133 device to read from
|
||||
* @param[in] channels Array of @p num_channels channel configuration.
|
||||
* @param[in] num_channels Number of configured channel passed in @p channels.
|
||||
*
|
||||
* @return A si1133_ret_code_t error or status code.
|
||||
*/
|
||||
si1133_ret_code_t si1133_configure_channels(si1133_t *dev,
|
||||
const si1133_channel_t *channels,
|
||||
uint32_t num_channels);
|
||||
|
||||
/**
|
||||
* @brief Convenience function to configure all capture channels.
|
||||
*
|
||||
* This function is a convenience function to configure one channel per selected
|
||||
* sensor in the @p sensor_mask, up to the maximum number of channels, setting
|
||||
* all channels to force-mode only with the same parameters. This is equivalent
|
||||
* to a call to @ref si1133_configure_channels with as many channels as bits
|
||||
* set in the @p sensor_mask.
|
||||
*
|
||||
* The channels are configured in increasing order of the @ref si1133_sensor_t
|
||||
* values.
|
||||
*
|
||||
* @param[in] dev Device descriptor of Si1133 device to read from
|
||||
* @param[in] sensor_mask Combination of up to 6 si1133_sensor_t values.
|
||||
* @param[in] sample_time_log Log2 of sampling time, 0 to 14. See @ref
|
||||
* si1133_channel_t for details.
|
||||
* @param[in] sw_gain Software gain, 0 to 7. See @ref si1133_channel_t
|
||||
* for details.
|
||||
*/
|
||||
si1133_ret_code_t si1133_easy_configure(si1133_t *dev,
|
||||
si1133_sensor_t sensor_mask,
|
||||
uint8_t sample_time_log,
|
||||
uint8_t sw_gain);
|
||||
|
||||
/**
|
||||
* @brief Perform a one-time blocking sample of the configured channels.
|
||||
*
|
||||
* Forces a one-time blocking sample of the sensors configured in the channels
|
||||
* and returns the read values as signed 24-bit integers, sign extended to
|
||||
* 32-bits. The number of channels sampled and configured sensor is set by the
|
||||
* last call to @ref si1133_configure_channels, however after sampling all of
|
||||
* them only up to the first @p num_channels values will be returned by this
|
||||
* function.
|
||||
*
|
||||
* In case of ADC overflow, for example because there's too much light for the
|
||||
* configured sensors the overflown sensor will read @p 0x7fffff and the
|
||||
* function will return @ref SI1133_ERR_OVERFLOW. In case of overflow, try
|
||||
* configuring a smaller sensor, for example @p SI1133_SENS_MEDIUM_IR instead of
|
||||
* @p SI1133_SENS_LARGE_IR, or reduce the @p sw_gain for the given sensor.
|
||||
*
|
||||
* @param[in] dev Device descriptor of Si1133 device to read from
|
||||
* @param[out] values Pointer to the output value buffer.
|
||||
* @param[in] num_channels Maximum number of channel values to return.
|
||||
*
|
||||
* @return A si1133_ret_code_t error or status code.
|
||||
*/
|
||||
si1133_ret_code_t si1133_capture_sensors(si1133_t *dev, int32_t *values,
|
||||
uint32_t num_channels);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SI1133_H */
|
||||
/** @} */
|
94
drivers/saul/init_devs/auto_init_si1133.c
Normal file
94
drivers/saul/init_devs/auto_init_si1133.c
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2020 iosabi
|
||||
*
|
||||
* 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 sys_auto_init_saul
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Auto initialization of SI1133 driver.
|
||||
*
|
||||
* @author iosabi <iosabi@protonmail.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#ifdef MODULE_SI1133
|
||||
|
||||
#include "assert.h"
|
||||
#include "log.h"
|
||||
#include "saul_reg.h"
|
||||
#include "si1133.h"
|
||||
#include "si1133_params.h"
|
||||
|
||||
/**
|
||||
* @brief Define the number of configured sensors
|
||||
*/
|
||||
#define SI1133_NUMOF ARRAY_SIZE(si1133_params)
|
||||
|
||||
/**
|
||||
* @brief Allocation of memory for device descriptors
|
||||
*/
|
||||
static si1133_t si1133_devs[SI1133_NUMOF];
|
||||
|
||||
/**
|
||||
* @brief Memory for the SAUL registry entries
|
||||
*/
|
||||
static saul_reg_t saul_entries[SI1133_NUMOF * 4];
|
||||
|
||||
/**
|
||||
* @brief Define the number of saul info
|
||||
*/
|
||||
#define SI1133_INFO_NUMOF ARRAY_SIZE(si1133_saul_reg_info)
|
||||
|
||||
/**
|
||||
* @name Reference the driver structs
|
||||
* @{
|
||||
*/
|
||||
extern const saul_driver_t si1133_uv_saul_driver;
|
||||
extern const saul_driver_t si1133_ir_saul_driver;
|
||||
extern const saul_driver_t si1133_visible_saul_driver;
|
||||
/** @} */
|
||||
|
||||
void auto_init_si1133(void)
|
||||
{
|
||||
assert(SI1133_INFO_NUMOF == SI1133_NUMOF);
|
||||
unsigned entry = 0;
|
||||
for (unsigned i = 0; i < SI1133_NUMOF; i++) {
|
||||
LOG_DEBUG("[auto_init_saul] initializing SI1133 #%u\n", i);
|
||||
|
||||
si1133_ret_code_t ret = si1133_init(&si1133_devs[i], &si1133_params[i]);
|
||||
if (ret != SI1133_OK) {
|
||||
LOG_ERROR("[auto_init_saul] error initializing SI1133 #%u: %d\n",
|
||||
i, (int)ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* UV index */
|
||||
saul_entries[entry].dev = &si1133_devs[i];
|
||||
saul_entries[entry].name = si1133_saul_reg_info[i].name;
|
||||
saul_entries[entry].driver = &si1133_uv_saul_driver;
|
||||
saul_reg_add(&saul_entries[entry++]);
|
||||
|
||||
/* Infra red */
|
||||
saul_entries[entry].dev = &si1133_devs[i];
|
||||
saul_entries[entry].name = si1133_saul_reg_info[i].name;
|
||||
saul_entries[entry].driver = &si1133_ir_saul_driver;
|
||||
saul_reg_add(&saul_entries[entry++]);
|
||||
|
||||
/* Visible */
|
||||
saul_entries[entry].dev = &si1133_devs[i];
|
||||
saul_entries[entry].name = si1133_saul_reg_info[i].name;
|
||||
saul_entries[entry].driver = &si1133_visible_saul_driver;
|
||||
saul_reg_add(&saul_entries[entry]);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
typedef int dont_be_pedantic;
|
||||
#endif /* MODULE_SI1133 */
|
@ -255,6 +255,10 @@ void saul_init_devs(void)
|
||||
extern void auto_init_sds011(void);
|
||||
auto_init_sds011();
|
||||
}
|
||||
if (IS_USED(MODULE_SI1133)) {
|
||||
extern void auto_init_si1133(void);
|
||||
auto_init_si1133();
|
||||
}
|
||||
if (IS_USED(MODULE_SI114X)) {
|
||||
extern void auto_init_si114x(void);
|
||||
auto_init_si114x();
|
||||
|
1
drivers/si1133/Makefile
Normal file
1
drivers/si1133/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
2
drivers/si1133/Makefile.dep
Normal file
2
drivers/si1133/Makefile.dep
Normal file
@ -0,0 +1,2 @@
|
||||
USEMODULE += xtimer
|
||||
FEATURES_REQUIRED += periph_i2c
|
2
drivers/si1133/Makefile.include
Normal file
2
drivers/si1133/Makefile.include
Normal file
@ -0,0 +1,2 @@
|
||||
USEMODULE_INCLUDES_si1133 := $(LAST_MAKEFILEDIR)/include
|
||||
USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_si1133)
|
198
drivers/si1133/include/si1133_internals.h
Normal file
198
drivers/si1133/include/si1133_internals.h
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 2020 iosabi
|
||||
*
|
||||
* 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_si1133
|
||||
* @brief Internal addresses, registers, constants for the Si1133 sensors
|
||||
* family.
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Internal addresses, registers, constants for the Si1133 sensor.
|
||||
*
|
||||
* @author iosabi <iosabi@protonmail.com>
|
||||
*/
|
||||
|
||||
#ifndef SI1133_INTERNALS_H
|
||||
#define SI1133_INTERNALS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Si1133 I2C address
|
||||
*/
|
||||
#define SI1133_I2C_ADDRESS (0x52) /* or 0x55 */
|
||||
|
||||
/**
|
||||
* @name Si1133 registers
|
||||
* @{
|
||||
*/
|
||||
#define SI1133_REG_PART_ID (0x00)
|
||||
#define SI1133_REG_HW_ID (0x01)
|
||||
#define SI1133_REG_REV_ID (0x02)
|
||||
#define SI1133_REG_INFO0 (0x03)
|
||||
#define SI1133_REG_INFO1 (0x04)
|
||||
#define SI1133_REG_HOSTIN0 (0x0a)
|
||||
#define SI1133_REG_COMMAND (0x0b)
|
||||
#define SI1133_REG_IRQENABLE (0x0f) /* Also RESET in the datasheet.*/
|
||||
#define SI1133_REG_RESPONSE1 (0x10)
|
||||
#define SI1133_REG_RESPONSE0 (0x11)
|
||||
#define SI1133_REG_IRQ_STATUS (0x12)
|
||||
#define SI1133_REG_HOSTOUTx (0x13) /* Ranges from 0x13 to 0x2c */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Si1133 commands
|
||||
* @{
|
||||
*/
|
||||
#define SI1133_CMD_RESET_CMD_CTR (0x00)
|
||||
#define SI1133_CMD_RESET_SW (0x01)
|
||||
#define SI1133_CMD_FORCE (0x11)
|
||||
#define SI1133_CMD_PAUSE (0x12)
|
||||
#define SI1133_CMD_START (0x13)
|
||||
#define SI1133_CMD_PARAM_QUERY (0x40) /* Add to SI1133_PARAM_* */
|
||||
#define SI1133_CMD_PARAM_SET (0x80) /* Add to SI1133_PARAM_* */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Si1133 channel parameters.
|
||||
*
|
||||
* These parameters define how a "channel" is sampled, ADC settings, resolution
|
||||
* timing, etc. These four register values define a single channel. This is a
|
||||
* convenience struct to handle them together in the same order as they appear
|
||||
* in the parameter list below.
|
||||
*/
|
||||
typedef struct __attribute__((packed)) _si1133_channel_params {
|
||||
uint8_t adcconfig; /**< ADCCONFIGx register. */
|
||||
uint8_t adcsens; /**< ADCSENSx register. */
|
||||
uint8_t adcpost; /**< ADCPOSTx register. */
|
||||
uint8_t measconfig; /**< MEASCONFIGx register. */
|
||||
} si1133_channel_params_t;
|
||||
|
||||
/**
|
||||
* @name Si1133 parameters
|
||||
*
|
||||
* @note These parameters are not accessible directly from the I2C registers.
|
||||
* Instead, to access these parameters SI1133_CMD_PARAM_QUERY and
|
||||
* SI1133_CMD_PARAM_SET commands should be used.
|
||||
* @{
|
||||
*/
|
||||
#define SI1133_PARAM_I2C_ADDR (0x00)
|
||||
#define SI1133_PARAM_CHAN_LIST (0x01)
|
||||
#define SI1133_PARAM_ADCCONFIG0 (0x02)
|
||||
#define SI1133_PARAM_ADCSENS0 (0x03)
|
||||
#define SI1133_PARAM_ADCPOST0 (0x04)
|
||||
#define SI1133_PARAM_MEASCONFIG0 (0x05)
|
||||
#define SI1133_PARAM_ADCCONFIG1 (0x06)
|
||||
#define SI1133_PARAM_ADCSENS1 (0x07)
|
||||
#define SI1133_PARAM_ADCPOST1 (0x08)
|
||||
#define SI1133_PARAM_MEASCONFIG1 (0x09)
|
||||
#define SI1133_PARAM_ADCCONFIG2 (0x0a)
|
||||
#define SI1133_PARAM_ADCSENS2 (0x0b)
|
||||
#define SI1133_PARAM_ADCPOST2 (0x0c)
|
||||
#define SI1133_PARAM_MEASCONFIG2 (0x0d)
|
||||
#define SI1133_PARAM_ADCCONFIG3 (0x0e)
|
||||
#define SI1133_PARAM_ADCSENS3 (0x0f)
|
||||
#define SI1133_PARAM_ADCPOST3 (0x10)
|
||||
#define SI1133_PARAM_MEASCONFIG3 (0x11)
|
||||
#define SI1133_PARAM_ADCCONFIG4 (0x12)
|
||||
#define SI1133_PARAM_ADCSENS4 (0x13)
|
||||
#define SI1133_PARAM_ADCPOST4 (0x14)
|
||||
#define SI1133_PARAM_MEASCONFIG4 (0x15)
|
||||
#define SI1133_PARAM_ADCCONFIG5 (0x16)
|
||||
#define SI1133_PARAM_ADCSENS5 (0x17)
|
||||
#define SI1133_PARAM_ADCPOST5 (0x18)
|
||||
#define SI1133_PARAM_MEASCONFIG5 (0x19)
|
||||
#define SI1133_PARAM_MEASRATE_H (0x1a)
|
||||
#define SI1133_PARAM_MEASRATE_L (0x1b)
|
||||
#define SI1133_PARAM_MEASCOUNT0 (0x1c)
|
||||
#define SI1133_PARAM_MEASCOUNT1 (0x1d)
|
||||
#define SI1133_PARAM_MEASCOUNT2 (0x1e)
|
||||
#define SI1133_PARAM_THRESHOLD0_H (0x25)
|
||||
#define SI1133_PARAM_THRESHOLD0_L (0x26)
|
||||
#define SI1133_PARAM_THRESHOLD1_H (0x27)
|
||||
#define SI1133_PARAM_THRESHOLD1_L (0x28)
|
||||
#define SI1133_PARAM_THRESHOLD2_H (0x29)
|
||||
#define SI1133_PARAM_THRESHOLD2_L (0x2a)
|
||||
#define SI1133_PARAM_BURST (0x2b)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Si1133 RESPONSE0 register constants
|
||||
* @{
|
||||
*/
|
||||
#define SI1133_RESP0_COUNTER_MASK (0x0f)
|
||||
#define SI1133_RESP0_CMD_ERR_MASK (0x10)
|
||||
#define SI1133_RESP0_SLEEP_MASK (0x20)
|
||||
#define SI1133_RESP0_SUSPEND_MASK (0x40)
|
||||
#define SI1133_RESP0_RUNNING_MASK (0x80)
|
||||
|
||||
/* Possible error values if SI1133_RESP0_CMD_ERR_MASK is set. */
|
||||
#define SI1133_RESP0_ERR_INVALID_COMMAND (0x01)
|
||||
#define SI1133_RESP0_ERR_INVALID_PARAM_ADDR (0x80)
|
||||
#define SI1133_RESP0_ERR_ADC_OVERFLOW (0x88)
|
||||
#define SI1133_RESP0_ERR_BUFFER_OVERFLOW (0x89)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Si1133 Channel configuration constants
|
||||
* @{
|
||||
*/
|
||||
#define SI1133_ADCCONFIG_DECIM_RATE_MASK (0x60)
|
||||
#define SI1133_ADCCONFIG_DECIM_RATE_SHIFT (5u)
|
||||
#define SI1133_ADCCONFIG_ADCMUX_MASK (0x1f)
|
||||
#define SI1133_ADCCONFIG_ADCMUX_SHIFT (0u)
|
||||
|
||||
#define SI1133_ADCSENS_HSIG_MASK (0x80)
|
||||
#define SI1133_ADCSENS_SW_GAIN_MASK (0x70)
|
||||
#define SI1133_ADCSENS_SW_GAIN_SHIFT (4u)
|
||||
#define SI1133_ADCSENS_HW_GAIN_MASK (0x0f)
|
||||
#define SI1133_ADCSENS_HW_GAIN_SHIFT (0u)
|
||||
|
||||
#define SI1133_ADCPOST_24BIT_OUT_MASK (0x40)
|
||||
#define SI1133_ADCPOST_POSTSHIFT_MASK (0x38)
|
||||
#define SI1133_ADCPOST_POSTSHIFT_SHIFT (3u)
|
||||
#define SI1133_ADCPOST_THRESH_SEL_MASK (0x03)
|
||||
#define SI1133_ADCPOST_THRESH_SEL_SHIFT (0u)
|
||||
|
||||
#define SI1133_MEASCONFIG_COUNTER_IDX_MASK (0xc0)
|
||||
#define SI1133_MEASCONFIG_COUNTER_IDX_SHIFT (6u)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Si1133 photodiode selection values for ADCMUX field.
|
||||
* @{
|
||||
*/
|
||||
#define SI1133_ADCMUX_SMALL_IR (0u)
|
||||
#define SI1133_ADCMUX_MEDIUM_IR (1u)
|
||||
#define SI1133_ADCMUX_LARGE_IR (2u)
|
||||
#define SI1133_ADCMUX_WHITE (11u)
|
||||
#define SI1133_ADCMUX_LARGE_WHITE (13u)
|
||||
#define SI1133_ADCMUX_UV (24u)
|
||||
#define SI1133_ADCMUX_DEEP_UV (25u)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Si1133 constants
|
||||
* @{
|
||||
*/
|
||||
#define SI1133_ID (0x33)
|
||||
#define SI1133_STARTUP_TIME_MS (25u)
|
||||
#define SI1133_NUM_CHANNELS (6u)
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SI1133_INTERNALS_H */
|
||||
/** @} */
|
74
drivers/si1133/include/si1133_params.h
Normal file
74
drivers/si1133/include/si1133_params.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2020 iosabi
|
||||
*
|
||||
* 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_si1133
|
||||
*
|
||||
* @{
|
||||
* @file
|
||||
* @brief Default configuration for SI1133
|
||||
*
|
||||
* @author iosabi <iosabi@protonmail.com>
|
||||
*/
|
||||
|
||||
#ifndef SI1133_PARAMS_H
|
||||
#define SI1133_PARAMS_H
|
||||
|
||||
#include "board.h"
|
||||
#include "si1133.h"
|
||||
#include "saul_reg.h"
|
||||
#include "si1133_internals.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @name Set default configuration parameters for the Si1133
|
||||
* @{
|
||||
*/
|
||||
#ifndef SI1133_PARAM_I2C_DEV
|
||||
#define SI1133_PARAM_I2C_DEV I2C_DEV(0)
|
||||
#endif
|
||||
|
||||
#ifndef SI1133_PARAM_ADDR
|
||||
#define SI1133_PARAM_ADDR SI1133_I2C_ADDRESS
|
||||
#endif
|
||||
|
||||
#ifndef SI1133_PARAMS
|
||||
#define SI1133_PARAMS { .i2c_dev = SI1133_PARAM_I2C_DEV, \
|
||||
.address = SI1133_PARAM_ADDR }
|
||||
#endif
|
||||
|
||||
#ifndef SI1133_SAUL_INFO
|
||||
#define SI1133_SAUL_INFO { .name = "si1133" }
|
||||
#endif
|
||||
/**@}*/
|
||||
|
||||
/**
|
||||
* @brief Configure Si1133
|
||||
*/
|
||||
static const si1133_params_t si1133_params[] =
|
||||
{
|
||||
SI1133_PARAMS
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Allocate and configure entries to the SAUL registry
|
||||
*/
|
||||
saul_reg_t si1133_saul_reg_info[] =
|
||||
{
|
||||
SI1133_SAUL_INFO
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SI1133_PARAMS_H */
|
||||
/** @} */
|
467
drivers/si1133/si1133.c
Normal file
467
drivers/si1133/si1133.c
Normal file
@ -0,0 +1,467 @@
|
||||
/*
|
||||
* Copyright (C) 2020 iosabi
|
||||
*
|
||||
* 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_si1133
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Device driver implementation for the SI1133 UV/IR/Ambient light
|
||||
* sensor with I2C interface.
|
||||
*
|
||||
* @author iosabi <iosabi@protonmail.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "xtimer.h"
|
||||
|
||||
#include "periph/i2c.h"
|
||||
|
||||
#include "si1133.h"
|
||||
#include "si1133_internals.h"
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
/**
|
||||
* @brief The command execution timeout in blocking mode.
|
||||
*
|
||||
* When forcing a measurement (SI1133_CMD_FORCE) the command is not done until
|
||||
* all the measurements from all the channels are taken. This may take some time
|
||||
* depending on the configuration. After the expected sampling time is over we
|
||||
* wait up to this command timeout value for the command to be ready.
|
||||
*/
|
||||
#define SI1133_COMMAND_TIMEOUT_USEC 10000
|
||||
|
||||
/**
|
||||
* @brief Return the expected sampling time in us for a FORCE command.
|
||||
*/
|
||||
static uint32_t _si1133_force_time_us(si1133_t *dev)
|
||||
{
|
||||
/* Sample time measured in 512 clocks of the 21 MHz, or about 24.4 us. */
|
||||
uint32_t sample_time = 0;
|
||||
|
||||
for (uint32_t i = 0; i < dev->num_channels; i++) {
|
||||
/* A forced sample consists of (1 << sw_gain) measurements where each
|
||||
* one has a total 155 us (about 7 * 24.4 us) of processing time, plus
|
||||
* two ADC samples. Each ADC sample requires a t_adstart time (48.8 us
|
||||
* or 2 * 24.4 us) plus the time configured by the sample_time_log. */
|
||||
sample_time += (2 * (2 + (1u << dev->channel[i].sample_time_log)) + 7)
|
||||
<< dev->channel[i].sw_gain;
|
||||
}
|
||||
/* The resulting sample_time value in microseconds is sample_time * 512 / 21
|
||||
* the sample_time is already a 24-bit number so we split the logic to
|
||||
* fit in 32-bit arithmetic using the fact that 512 == 24 * 21 + 8. */
|
||||
return sample_time * 24 + sample_time * 8 / 21;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run a command with no arguments.
|
||||
*
|
||||
* For commands CMD_PARAM_SET or CMD_PARAM_QUERY use @ref _si1133_set_param
|
||||
* and @ref _si1133_get_param respectively instead.
|
||||
*/
|
||||
static int _si1133_run_command(si1133_t *dev, uint8_t command)
|
||||
{
|
||||
DEBUG("[si1133] run_command: 0x%.2x, cmd_counter=%d\n",
|
||||
(unsigned)command, dev->cmd_counter);
|
||||
int ret;
|
||||
ret = i2c_write_reg(dev->i2c_dev, dev->address,
|
||||
SI1133_REG_COMMAND, command, 0 /* flags */);
|
||||
if (ret) {
|
||||
DEBUG("[si1133] write command I2C error: %s\n", strerror(-ret));
|
||||
return SI1133_ERR_I2C;
|
||||
}
|
||||
|
||||
if (command == SI1133_CMD_FORCE) {
|
||||
/* Wait for the expected force acquisition time. */
|
||||
xtimer_usleep(_si1133_force_time_us(dev));
|
||||
}
|
||||
|
||||
if (command == SI1133_CMD_RESET_SW) {
|
||||
/* Reset command sets the command counter of 0x0f. */
|
||||
dev->cmd_counter = 0x0f;
|
||||
/* Reset command puts the device in "Initialization Mode" which requires
|
||||
* us to wait until the device is ready. */
|
||||
xtimer_msleep(SI1133_STARTUP_TIME_MS);
|
||||
}
|
||||
else if (command == SI1133_CMD_RESET_CMD_CTR) {
|
||||
/* The reset cmd_counter command, well, resets it to 0. */
|
||||
dev->cmd_counter = 0;
|
||||
}
|
||||
else {
|
||||
/* Increment the expected 4-bit command counter value. */
|
||||
dev->cmd_counter = (dev->cmd_counter + 1) & SI1133_RESP0_COUNTER_MASK;
|
||||
}
|
||||
|
||||
uint8_t new_cmd_ctr;
|
||||
xtimer_ticks32_t start_time;
|
||||
bool retry = false;
|
||||
while (1) {
|
||||
ret = i2c_read_reg(dev->i2c_dev, dev->address, SI1133_REG_RESPONSE0,
|
||||
&new_cmd_ctr, 0 /* flags */);
|
||||
if (ret) {
|
||||
DEBUG("[si1133] read RESPONSE0 I2C error: %s\n", strerror(-ret));
|
||||
return SI1133_ERR_I2C;
|
||||
}
|
||||
if (new_cmd_ctr & SI1133_RESP0_CMD_ERR_MASK) {
|
||||
DEBUG("[si1133] Command 0x%.2x returned error %d\n",
|
||||
(unsigned)command, new_cmd_ctr & SI1133_RESP0_COUNTER_MASK);
|
||||
/* Error code 2 is "ADC or accumulation overflow", while error code
|
||||
* 3 is output buffer overflow which can only occur in BURST mode.
|
||||
* However, in FORCE mode after the first overflow we can get an
|
||||
* error code 3 if we change the settings in between the overflow
|
||||
* and a new FORCE command, probably due to a silicon bug when
|
||||
* handling sw_gain overflows since it is not possible to write more
|
||||
* than 18 bytes of output in FORCE mode while the output buffer is
|
||||
* 26 bytes long. */
|
||||
if ((new_cmd_ctr & SI1133_RESP0_COUNTER_MASK) >= 2) {
|
||||
return SI1133_ERR_OVERFLOW;
|
||||
}
|
||||
return SI1133_ERR_LOGIC;
|
||||
}
|
||||
/* The reset command is done when the RUNNING flag is clear, the other
|
||||
* commands are done when the command value is set to the expected
|
||||
* one. */
|
||||
if ((command == SI1133_CMD_RESET_SW)
|
||||
? !(new_cmd_ctr & SI1133_RESP0_RUNNING_MASK)
|
||||
: (dev->cmd_counter ==
|
||||
(new_cmd_ctr & SI1133_RESP0_COUNTER_MASK))) {
|
||||
break;
|
||||
}
|
||||
/* The command didn't yet finish in this case so it should be in running
|
||||
* state and we need to retry the loop with a timeout. This avoids
|
||||
* calling xtimer for commands that are immediate. */
|
||||
if (retry) {
|
||||
if (xtimer_usec_from_ticks(xtimer_diff(xtimer_now(), start_time))
|
||||
> SI1133_COMMAND_TIMEOUT_USEC) {
|
||||
DEBUG("[si1133] Command 0x%.2x timeout.\n", (unsigned)command);
|
||||
return SI1133_ERR_LOGIC;
|
||||
}
|
||||
}
|
||||
else {
|
||||
retry = true;
|
||||
start_time = xtimer_now();
|
||||
}
|
||||
}
|
||||
if (retry) {
|
||||
DEBUG("[si1133] Command overtime: %" PRIu32 " us.\n",
|
||||
xtimer_usec_from_ticks(xtimer_diff(xtimer_now(), start_time)));
|
||||
}
|
||||
return SI1133_OK;
|
||||
}
|
||||
|
||||
static int _si1133_set_param(si1133_t *dev, uint8_t param, uint8_t value)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_write_reg(dev->i2c_dev, dev->address,
|
||||
SI1133_REG_HOSTIN0, value, 0 /* flags */);
|
||||
if (ret) {
|
||||
DEBUG("[si1133] write HOSTIN0 I2C error: %s\n", strerror(-ret));
|
||||
return SI1133_ERR_I2C;
|
||||
}
|
||||
ret = _si1133_run_command(dev, param | SI1133_CMD_PARAM_SET);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t resp1;
|
||||
ret = i2c_read_reg(dev->i2c_dev, dev->address,
|
||||
SI1133_REG_RESPONSE1, &resp1, 0);
|
||||
if (ret) {
|
||||
DEBUG("[si1133] read RESPONSE1 I2C error: %s\n", strerror(-ret));
|
||||
return SI1133_ERR_I2C;
|
||||
}
|
||||
if (resp1 != value) {
|
||||
DEBUG("[si1133] Expected to read back value 0x%.2" PRIu8
|
||||
" when setting param 0x%.2" PRIu8 " but got 0x%.2" PRIu8 "\n",
|
||||
value, param, resp1);
|
||||
return SI1133_ERR_LOGIC;
|
||||
}
|
||||
return SI1133_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset the device.
|
||||
*/
|
||||
static int _si1133_reset(si1133_t *dev)
|
||||
{
|
||||
DEBUG("[si1133] reset()\n");
|
||||
dev->num_channels = 0;
|
||||
for (uint32_t i = 0; i < SI1133_NUM_CHANNELS; i++) {
|
||||
/* Initialize the config with invalid values to force the first config
|
||||
* call to update it. */
|
||||
dev->channel[i].sw_gain = 0xff;
|
||||
}
|
||||
int ret = _si1133_run_command(dev, SI1133_CMD_RESET_SW);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return _si1133_run_command(dev, SI1133_CMD_RESET_CMD_CTR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure a single channel with the passed parameters.
|
||||
*/
|
||||
static si1133_ret_code_t _si1133_configure_channel(
|
||||
si1133_t *dev, uint32_t channel_id, const si1133_channel_t *channel)
|
||||
{
|
||||
if (channel->sample_time_log > 14 || channel->sw_gain > 7) {
|
||||
return SI1133_ERR_PARAMS;
|
||||
}
|
||||
|
||||
if (!memcmp(channel, &dev->channel[channel_id], sizeof(si1133_channel_t))) {
|
||||
/* Avoid the I2C roundtrip if the channel configuration didn't
|
||||
* change. */
|
||||
return SI1133_OK;
|
||||
}
|
||||
|
||||
/* Generate the channel configuration. */
|
||||
si1133_channel_params_t config;
|
||||
#define SI1133_SENS_CASE(x) \
|
||||
case SI1133_SENS_ ## x: \
|
||||
config.adcconfig = \
|
||||
SI1133_ADCMUX_ ## x << SI1133_ADCCONFIG_ADCMUX_SHIFT; \
|
||||
break;
|
||||
|
||||
switch (channel->sensor) {
|
||||
SI1133_SENS_CASE(SMALL_IR)
|
||||
SI1133_SENS_CASE(MEDIUM_IR)
|
||||
SI1133_SENS_CASE(LARGE_IR)
|
||||
SI1133_SENS_CASE(WHITE)
|
||||
SI1133_SENS_CASE(LARGE_WHITE)
|
||||
SI1133_SENS_CASE(UV)
|
||||
SI1133_SENS_CASE(DEEP_UV)
|
||||
default:
|
||||
return SI1133_ERR_PARAMS;
|
||||
}
|
||||
/* Use normal decimation (1) except in the extremes where we must use
|
||||
* the other decimation values. */
|
||||
const uint8_t hw_gain = channel->sample_time_log == 0
|
||||
? 0
|
||||
: (channel->sample_time_log <= 11
|
||||
? channel->sample_time_log - 1
|
||||
: 11);
|
||||
/* Select decimation. A value of "0" in this field is the "normal"
|
||||
* decimation, which corresponds to 1 in our "decimation" equation.
|
||||
* The values in this hardware field are offset by 3. */
|
||||
const uint8_t decimation =
|
||||
(channel->sample_time_log - hw_gain + 3) & 3;
|
||||
config.adcconfig |= decimation << SI1133_ADCCONFIG_DECIM_RATE_SHIFT;
|
||||
/* HSIG = 0, SW_GAIN and HW_GAIN as configured. */
|
||||
config.adcsens = (hw_gain << SI1133_ADCSENS_HW_GAIN_SHIFT) |
|
||||
(channel->sw_gain << SI1133_ADCSENS_SW_GAIN_SHIFT);
|
||||
/* 24-bit output, no output shift, no threshold. */
|
||||
config.adcpost = SI1133_ADCPOST_24BIT_OUT_MASK;
|
||||
/* No counter, this will only be used in Force mode. */
|
||||
config.measconfig = 0;
|
||||
|
||||
DEBUG("[si1133] config: %.2x %.2x %.2x %.2x\n",
|
||||
((uint8_t *)&config)[0], ((uint8_t *)&config)[1],
|
||||
((uint8_t *)&config)[2], ((uint8_t *)&config)[3]);
|
||||
|
||||
for (uint8_t i = 0; i < sizeof(config); i++) {
|
||||
uint8_t param = SI1133_PARAM_ADCCONFIG0 + sizeof(config) * channel_id +
|
||||
i;
|
||||
si1133_ret_code_t ret =
|
||||
_si1133_set_param(dev, param, ((uint8_t *)&config)[i]);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
memcpy(&(dev->channel[channel_id]), channel, sizeof(si1133_channel_t));
|
||||
return SI1133_OK;
|
||||
}
|
||||
|
||||
static int _si1133_read_values(si1133_t *dev, int32_t *values,
|
||||
uint32_t num_channels)
|
||||
{
|
||||
/* We can read all registers in a single I2C burst. */
|
||||
uint8_t data[3 * num_channels];
|
||||
|
||||
/* We only request 24-bit values from the device so they are all 3 byte
|
||||
* long. */
|
||||
int ret = i2c_read_regs(dev->i2c_dev, dev->address, SI1133_REG_HOSTOUTx,
|
||||
data, 3 * num_channels, 0 /* flags */);
|
||||
|
||||
if (ret) {
|
||||
return SI1133_ERR_I2C;
|
||||
}
|
||||
uint8_t *offset = data;
|
||||
for (uint8_t i = 0; i < num_channels; i++) {
|
||||
/* Sign-extend the first 8-bit value before shifting. */
|
||||
int32_t value = ((int32_t)(int8_t)*(offset++)) << 16;
|
||||
value |= *(offset++) << 8u;
|
||||
value |= *(offset++);
|
||||
values[i] = value;
|
||||
}
|
||||
return SI1133_OK;
|
||||
}
|
||||
|
||||
si1133_ret_code_t si1133_init(si1133_t *dev, const si1133_params_t *params)
|
||||
{
|
||||
dev->i2c_dev = params->i2c_dev;
|
||||
dev->address = params->address;
|
||||
|
||||
/* After leaving "Off Mode" the SI1133 enters an "Initialization Mode" for
|
||||
* a period of time in which it can't be reached over I2C. After this time
|
||||
* the device will be in Standby Mode. */
|
||||
xtimer_msleep(SI1133_STARTUP_TIME_MS);
|
||||
|
||||
i2c_acquire(params->i2c_dev);
|
||||
|
||||
/* check sensor ID */
|
||||
uint8_t partid = 0;
|
||||
int ret = i2c_read_reg(params->i2c_dev, params->address, SI1133_REG_PART_ID,
|
||||
&partid, 0);
|
||||
if (ret != 0) {
|
||||
DEBUG("[si1133] i2c communication error: %s.\n", strerror(-ret));
|
||||
i2c_release(params->i2c_dev);
|
||||
return SI1133_ERR_I2C;
|
||||
}
|
||||
if (partid != SI1133_ID) {
|
||||
DEBUG("[si1133] Invalid part id: 0x%.2u\n", (unsigned)partid);
|
||||
i2c_release(params->i2c_dev);
|
||||
return SI1133_ERR_NODEV;
|
||||
}
|
||||
|
||||
#if ENABLE_DEBUG
|
||||
uint8_t rev_id, hw_id;
|
||||
i2c_read_reg(params->i2c_dev, params->address, SI1133_REG_REV_ID, &rev_id,
|
||||
0 /* flags */);
|
||||
i2c_read_reg(params->i2c_dev, params->address, SI1133_REG_HW_ID, &hw_id,
|
||||
0 /* flags */);
|
||||
DEBUG("[si1133] impl code: %u, silicon HW rev: %u, rev: %u.%u\n",
|
||||
hw_id & 0x1f, hw_id >> 5, rev_id >> 4, rev_id & 0x0f);
|
||||
#endif /* ENABLE_DEBUG */
|
||||
|
||||
/* We don't know the state in which the device is at this point so we need
|
||||
* to perform a reset, unfortunately this requires another start-up wait. */
|
||||
ret = _si1133_reset(dev);
|
||||
if (ret) {
|
||||
i2c_release(dev->i2c_dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
i2c_release(dev->i2c_dev);
|
||||
return SI1133_OK;
|
||||
}
|
||||
|
||||
si1133_ret_code_t si1133_configure_channels(si1133_t *dev,
|
||||
const si1133_channel_t *channels,
|
||||
uint32_t num_channels)
|
||||
{
|
||||
DEBUG("[si1133] configure_channels(num=%" PRIu32 ")\n", num_channels);
|
||||
if (num_channels > SI1133_NUM_CHANNELS) {
|
||||
return SI1133_ERR_PARAMS;
|
||||
}
|
||||
|
||||
i2c_acquire(dev->i2c_dev);
|
||||
si1133_ret_code_t ret;
|
||||
for (uint32_t i = 0; i < num_channels; i++) {
|
||||
ret = _si1133_configure_channel(dev, i, channels + i);
|
||||
if (ret) {
|
||||
dev->num_channels = 0;
|
||||
i2c_release(dev->i2c_dev);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
/* CHAN_LIST is a bit mask of channels used. */
|
||||
ret = _si1133_set_param(dev, SI1133_PARAM_CHAN_LIST,
|
||||
(1u << num_channels) - 1);
|
||||
if (ret) {
|
||||
num_channels = 0;
|
||||
}
|
||||
dev->num_channels = num_channels;
|
||||
i2c_release(dev->i2c_dev);
|
||||
DEBUG("[si1133] Sample Time %" PRIu32 " us\n", _si1133_force_time_us(dev));
|
||||
return ret;
|
||||
}
|
||||
|
||||
si1133_ret_code_t si1133_easy_configure(si1133_t *dev,
|
||||
si1133_sensor_t sensor_mask,
|
||||
uint8_t sample_time_log,
|
||||
uint8_t sw_gain)
|
||||
{
|
||||
DEBUG("[si1133] easy_configure(0x%.2x)\n", (unsigned)sensor_mask);
|
||||
i2c_acquire(dev->i2c_dev);
|
||||
|
||||
si1133_ret_code_t ret;
|
||||
uint8_t num_channels = 0;
|
||||
for (uint8_t mask = sensor_mask; mask;
|
||||
num_channels++, mask = mask & (mask - 1)) {
|
||||
if (num_channels >= SI1133_NUM_CHANNELS) {
|
||||
dev->num_channels = 0;
|
||||
i2c_release(dev->i2c_dev);
|
||||
return SI1133_ERR_PARAMS;
|
||||
}
|
||||
|
||||
si1133_channel_t channel;
|
||||
channel.sensor = mask ^ (mask & (mask - 1));
|
||||
channel.sample_time_log = sample_time_log;
|
||||
channel.sw_gain = sw_gain;
|
||||
ret = _si1133_configure_channel(dev, num_channels, &channel);
|
||||
if (ret) {
|
||||
dev->num_channels = 0;
|
||||
i2c_release(dev->i2c_dev);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* CHAN_LIST is a bit mask of channels used. */
|
||||
ret = _si1133_set_param(dev, SI1133_PARAM_CHAN_LIST,
|
||||
(1u << num_channels) - 1);
|
||||
if (ret) {
|
||||
num_channels = 0;
|
||||
}
|
||||
dev->num_channels = num_channels;
|
||||
i2c_release(dev->i2c_dev);
|
||||
DEBUG("[si1133] Sample Time %" PRIu32 " us\n", _si1133_force_time_us(dev));
|
||||
return ret;
|
||||
}
|
||||
|
||||
si1133_ret_code_t si1133_capture_sensors(si1133_t *dev, int32_t *values,
|
||||
uint32_t num_channels)
|
||||
{
|
||||
if (!dev->num_channels) {
|
||||
/* Must be configured before calling capture_sensors. */
|
||||
return SI1133_ERR_PARAMS;
|
||||
}
|
||||
|
||||
i2c_acquire(dev->i2c_dev);
|
||||
|
||||
si1133_ret_code_t force_ret;
|
||||
force_ret = _si1133_run_command(dev, SI1133_CMD_FORCE);
|
||||
if (force_ret != SI1133_OK && force_ret != SI1133_ERR_OVERFLOW) {
|
||||
i2c_release(dev->i2c_dev);
|
||||
DEBUG("[si1133] force read command error: %d\n", force_ret);
|
||||
return force_ret;
|
||||
}
|
||||
si1133_ret_code_t ret = SI1133_OK;
|
||||
if (force_ret == SI1133_ERR_OVERFLOW) {
|
||||
/* We need to reset the overflow condition with a RESET_CMD_CTR */
|
||||
ret = _si1133_run_command(dev, SI1133_CMD_RESET_CMD_CTR);
|
||||
}
|
||||
if (ret == SI1133_OK) {
|
||||
ret = _si1133_read_values(dev, values, num_channels);
|
||||
}
|
||||
i2c_release(dev->i2c_dev);
|
||||
/* If there was an error reading the I2C values then return that error,
|
||||
* otherwise we want to return the CMD_FORCE return value because there
|
||||
* could be an overflow non-fatal error to report. */
|
||||
return ret ? ret : force_ret;
|
||||
}
|
107
drivers/si1133/si1133_saul.c
Normal file
107
drivers/si1133/si1133_saul.c
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2020 iosabi
|
||||
*
|
||||
* 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_si1133
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief SAUL adaption for SI1133 devices
|
||||
*
|
||||
* @author iosabi <iosabi@protonmail.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "saul.h"
|
||||
#include "si1133.h"
|
||||
|
||||
static int read_uv(const void *dev, phydat_t *res)
|
||||
{
|
||||
if (si1133_easy_configure((si1133_t *)dev,
|
||||
SI1133_SENS_UV | SI1133_SENS_DEEP_UV,
|
||||
/*sample_time_log=*/ 1,
|
||||
/*sw_gain=*/ 0) != 0) {
|
||||
return -ECANCELED;
|
||||
}
|
||||
|
||||
int32_t values[2];
|
||||
if (si1133_capture_sensors((si1133_t *)dev, values,
|
||||
ARRAY_SIZE(values)) != 0) {
|
||||
return -ECANCELED;
|
||||
}
|
||||
for (uint8_t i = 0; i < ARRAY_SIZE(values); i++) {
|
||||
res->val[i] = values[i];
|
||||
}
|
||||
res->unit = UNIT_NONE; /* UV index */
|
||||
res->scale = 0;
|
||||
return ARRAY_SIZE(values);
|
||||
}
|
||||
|
||||
static int read_ir(const void *dev, phydat_t *res)
|
||||
{
|
||||
if (si1133_easy_configure((si1133_t *)dev,
|
||||
SI1133_SENS_SMALL_IR | SI1133_SENS_MEDIUM_IR |
|
||||
SI1133_SENS_LARGE_IR,
|
||||
/*sample_time_log=*/ 1,
|
||||
/*sw_gain=*/ 0) != 0) {
|
||||
return -ECANCELED;
|
||||
}
|
||||
|
||||
int32_t values[3];
|
||||
if (si1133_capture_sensors((si1133_t *)dev, values,
|
||||
ARRAY_SIZE(values)) != 0) {
|
||||
return -ECANCELED;
|
||||
}
|
||||
for (uint8_t i = 0; i < ARRAY_SIZE(values); i++) {
|
||||
res->val[i] = values[i];
|
||||
}
|
||||
res->unit = UNIT_LUX;
|
||||
res->scale = 0;
|
||||
return ARRAY_SIZE(values);
|
||||
}
|
||||
|
||||
static int read_white(const void *dev, phydat_t *res)
|
||||
{
|
||||
if (si1133_easy_configure((si1133_t *)dev,
|
||||
SI1133_SENS_WHITE | SI1133_SENS_LARGE_WHITE,
|
||||
/*sample_time_log=*/ 1,
|
||||
/*sw_gain=*/ 0) != 0) {
|
||||
return -ECANCELED;
|
||||
}
|
||||
|
||||
int32_t values[2];
|
||||
if (si1133_capture_sensors((si1133_t *)dev, values,
|
||||
ARRAY_SIZE(values)) != 0) {
|
||||
return -ECANCELED;
|
||||
}
|
||||
for (uint8_t i = 0; i < ARRAY_SIZE(values); i++) {
|
||||
res->val[i] = values[i];
|
||||
}
|
||||
res->unit = UNIT_LUX;
|
||||
res->scale = 0;
|
||||
return ARRAY_SIZE(values);
|
||||
}
|
||||
|
||||
const saul_driver_t si1133_uv_saul_driver = {
|
||||
.read = read_uv,
|
||||
.write = saul_notsup,
|
||||
.type = SAUL_SENSE_UV
|
||||
};
|
||||
|
||||
const saul_driver_t si1133_ir_saul_driver = {
|
||||
.read = read_ir,
|
||||
.write = saul_notsup,
|
||||
.type = SAUL_SENSE_LIGHT
|
||||
};
|
||||
|
||||
const saul_driver_t si1133_visible_saul_driver = {
|
||||
.read = read_white,
|
||||
.write = saul_notsup,
|
||||
.type = SAUL_SENSE_LIGHT
|
||||
};
|
7
tests/driver_si1133/Makefile
Normal file
7
tests/driver_si1133/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
include ../Makefile.tests_common
|
||||
|
||||
# required modules
|
||||
USEMODULE += si1133
|
||||
USEMODULE += xtimer
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
8
tests/driver_si1133/Makefile.ci
Normal file
8
tests/driver_si1133/Makefile.ci
Normal file
@ -0,0 +1,8 @@
|
||||
BOARD_INSUFFICIENT_MEMORY := \
|
||||
arduino-duemilanove \
|
||||
arduino-leonardo \
|
||||
arduino-nano \
|
||||
arduino-uno \
|
||||
atmega328p \
|
||||
atmega32u4 \
|
||||
#
|
15
tests/driver_si1133/README.md
Normal file
15
tests/driver_si1133/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Si1133 driver test
|
||||
|
||||
Test application for the Silicon Labs Si1133 I2C device.
|
||||
|
||||
## Usage
|
||||
|
||||
This test will initialize the Si1133 device defined in the `si1133_params.h`
|
||||
header, which can be override by the board or in CLFAGS by setting the following
|
||||
macros:
|
||||
|
||||
* `SI1133_PARAM_I2C_DEV` the I2C device to use, by default `I2C_DEV(0)`.
|
||||
* `SI1133_PARAM_ADDR` the I2C address of the Si1133, either 0x52 or 0x55.
|
||||
|
||||
The automated test checks that the Si1133 responds and all sensor values can be
|
||||
read in blocking mode.
|
159
tests/driver_si1133/main.c
Normal file
159
tests/driver_si1133/main.c
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2020 iosabi
|
||||
*
|
||||
* 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 tests
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Test application for the Si1133 UV, IR and visible light sensor.
|
||||
*
|
||||
* @author iosabi <iosabi@protonmail.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "si1133.h"
|
||||
#include "si1133_params.h"
|
||||
#include "xtimer.h"
|
||||
#include "board.h"
|
||||
|
||||
/* Helper macro to define _si1133_strerr */
|
||||
#define CASE_SI1133_ERROR_STRING(X) \
|
||||
case X: \
|
||||
return #X;
|
||||
|
||||
static const char *_si1133_strerr(si1133_ret_code_t err)
|
||||
{
|
||||
switch (err) {
|
||||
CASE_SI1133_ERROR_STRING(SI1133_OK);
|
||||
CASE_SI1133_ERROR_STRING(SI1133_ERR_PARAMS);
|
||||
CASE_SI1133_ERROR_STRING(SI1133_ERR_I2C);
|
||||
CASE_SI1133_ERROR_STRING(SI1133_ERR_LOGIC);
|
||||
CASE_SI1133_ERROR_STRING(SI1133_ERR_NODEV);
|
||||
CASE_SI1133_ERROR_STRING(SI1133_ERR_OVERFLOW);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define EXPECT_RET_CODE(expected, actual) \
|
||||
do { \
|
||||
si1133_ret_code_t actual_value = (actual); \
|
||||
si1133_ret_code_t expected_value = (expected); \
|
||||
if (actual_value != expected_value) { \
|
||||
printf( \
|
||||
"ERROR: " #actual " = %s\nExpected value " #expected " (%s)\n",\
|
||||
_si1133_strerr(actual_value), _si1133_strerr(expected_value)); \
|
||||
failures++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static si1133_t dev;
|
||||
|
||||
int main(void)
|
||||
{
|
||||
uint32_t failures = 0;
|
||||
|
||||
puts("Testing Si1133 in blocking mode:");
|
||||
static const si1133_params_t blocking_params = {
|
||||
.i2c_dev = SI1133_PARAM_I2C_DEV,
|
||||
.address = SI1133_PARAM_ADDR
|
||||
};
|
||||
EXPECT_RET_CODE(SI1133_OK, si1133_init(&dev, &blocking_params));
|
||||
|
||||
static const si1133_sensor_t sensor_list[] = {
|
||||
SI1133_SENS_SMALL_IR,
|
||||
SI1133_SENS_MEDIUM_IR,
|
||||
SI1133_SENS_LARGE_IR,
|
||||
SI1133_SENS_WHITE,
|
||||
SI1133_SENS_LARGE_WHITE,
|
||||
SI1133_SENS_UV,
|
||||
SI1133_SENS_DEEP_UV,
|
||||
};
|
||||
/* Test reading a sample one by one. */
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(sensor_list); i++) {
|
||||
EXPECT_RET_CODE(SI1133_OK,
|
||||
si1133_easy_configure(&dev, sensor_list[i], 0, 0));
|
||||
int32_t value;
|
||||
EXPECT_RET_CODE(SI1133_OK,
|
||||
si1133_capture_sensors(&dev, &value, 1));
|
||||
if (value >= 0x7fffff) {
|
||||
printf("ERROR: Sensor sample overflow, got %" PRId32 "\n", value);
|
||||
failures++;
|
||||
}
|
||||
printf(" - sensor 0x%.2x: %" PRId32 "\n", (int)sensor_list[i], value);
|
||||
}
|
||||
|
||||
/* Test increasing the sw_gain until we get an overflow. */
|
||||
for (uint32_t sw_gain = 0; sw_gain <= 7; sw_gain++) {
|
||||
uint8_t sensor_mask =
|
||||
SI1133_SENS_LARGE_IR |
|
||||
SI1133_SENS_LARGE_WHITE |
|
||||
SI1133_SENS_UV;
|
||||
EXPECT_RET_CODE(SI1133_OK,
|
||||
si1133_easy_configure(&dev, sensor_mask, 1, sw_gain));
|
||||
int32_t values[3];
|
||||
si1133_ret_code_t ret =
|
||||
si1133_capture_sensors(&dev, values, ARRAY_SIZE(values));
|
||||
printf("INFO: sw_gain=%" PRIu32 " LARGE_IR=%10" PRId32
|
||||
" LARGE_WHITE=%10" PRId32 " UV=%10" PRId32 "\n",
|
||||
sw_gain, values[0], values[1], values[2]);
|
||||
if (ret == SI1133_OK) {
|
||||
continue;
|
||||
}
|
||||
/* If we didn't get an OK we should have an overflow condition. */
|
||||
EXPECT_RET_CODE(SI1133_ERR_OVERFLOW, ret);
|
||||
/* One of the values must be in overflow state. */
|
||||
bool overflowed = false;
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(values); i++) {
|
||||
overflowed = overflowed || values[i] == 0x7fffff;
|
||||
}
|
||||
if (!overflowed) {
|
||||
printf(
|
||||
"ERROR: Sensor overflow but no value in overflowed state.\n");
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(values); i++) {
|
||||
printf(" values[%" PRIu32 "] = %" PRId32 "\n", i, values[i]);
|
||||
}
|
||||
failures++;
|
||||
}
|
||||
else {
|
||||
printf("NOTE: Overflow test OK.\n");
|
||||
}
|
||||
}
|
||||
/* Reading any sensor after overflowing should not fail. */
|
||||
EXPECT_RET_CODE(SI1133_OK,
|
||||
si1133_easy_configure(&dev, SI1133_SENS_SMALL_IR, 1, 0));
|
||||
int32_t value;
|
||||
EXPECT_RET_CODE(SI1133_OK,
|
||||
si1133_capture_sensors(&dev, &value, 1));
|
||||
|
||||
/* Test reading most sensors at once. The maximum is 6 sensors. */
|
||||
uint32_t all = 0;
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(sensor_list); i++) {
|
||||
all |= sensor_list[i];
|
||||
}
|
||||
EXPECT_RET_CODE(SI1133_ERR_PARAMS, si1133_easy_configure(&dev, all, 1, 0));
|
||||
|
||||
/* All except one is lower than the limit of 6. */
|
||||
all &= ~SI1133_SENS_WHITE;
|
||||
EXPECT_RET_CODE(SI1133_OK, si1133_easy_configure(&dev, all, 1, 0));
|
||||
int32_t values[6];
|
||||
EXPECT_RET_CODE(SI1133_OK,
|
||||
si1133_capture_sensors(&dev, values, ARRAY_SIZE(values)));
|
||||
|
||||
if (failures != 0) {
|
||||
printf("Result: FAILED %" PRIu32 "\n", failures);
|
||||
}
|
||||
else {
|
||||
puts("Result: OK\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
23
tests/driver_si1133/tests/01-run.py
Executable file
23
tests/driver_si1133/tests/01-run.py
Executable file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (C) 2020 iosabi
|
||||
#
|
||||
# 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.
|
||||
|
||||
import sys
|
||||
from testrunner import run
|
||||
|
||||
|
||||
def testfunc(child):
|
||||
child.expect_exact('Testing Si1133 in blocking mode:')
|
||||
i = child.expect([r'.*Result: OK\s', r'.*Result: FAILED (\d+)\s'])
|
||||
if i == 1:
|
||||
print('FAILED')
|
||||
return
|
||||
print('SUCCESS')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run(testfunc))
|
Loading…
Reference in New Issue
Block a user