1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/drivers/si1133/si1133.c
iosabi ce76125a22 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.
2020-12-14 03:13:23 +00:00

468 lines
16 KiB
C

/*
* 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;
}