mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
468 lines
16 KiB
C
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 "timex.h"
|
|
#include "ztimer.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. */
|
|
ztimer_sleep(ZTIMER_USEC, _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. */
|
|
ztimer_sleep(ZTIMER_USEC, SI1133_STARTUP_TIME_MS * US_PER_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;
|
|
ztimer_now_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 ztimer for commands that are immediate. */
|
|
if (retry) {
|
|
if (ztimer_now(ZTIMER_USEC) - start_time > SI1133_COMMAND_TIMEOUT_USEC) {
|
|
DEBUG("[si1133] Command 0x%.2x timeout.\n", (unsigned)command);
|
|
return SI1133_ERR_LOGIC;
|
|
}
|
|
}
|
|
else {
|
|
retry = true;
|
|
start_time = ztimer_now(ZTIMER_USEC);
|
|
}
|
|
}
|
|
if (retry) {
|
|
DEBUG("[si1133] Command overtime: %" PRIu32 " us.\n",
|
|
ztimer_now(ZTIMER_USEC) - 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. */
|
|
ztimer_sleep(ZTIMER_USEC, SI1133_STARTUP_TIME_MS * US_PER_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;
|
|
}
|