Forked so that I can make it take I2C as a parameter; on a bus with other I2C things.
Fork of Si1133 by
Si1133.cpp
- Committer:
- stevew817
- Date:
- 2017-11-12
- Revision:
- 2:1e2dd643afa8
- Parent:
- 1:410f61a3900b
File content as of revision 2:1e2dd643afa8:
/***************************************************************************//** * @file Si1133.cpp ******************************************************************************* * @section License * <b>(C) Copyright 2017 Silicon Labs, http://www.silabs.com</b> ******************************************************************************* * * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ #include "Si1133.h" #define SI1133_I2C_ADDRESS (0xAA) /** Hardcoded address for Si1133 sensor */ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ #define X_ORDER_MASK 0x0070 #define Y_ORDER_MASK 0x0007 #define SIGN_MASK 0x0080 #define GET_X_ORDER(m) ( ((m) & X_ORDER_MASK) >> 4) #define GET_Y_ORDER(m) ( ((m) & Y_ORDER_MASK) ) #define GET_SIGN(m) ( ((m) & SIGN_MASK) >> 7) #define UV_INPUT_FRACTION 15 #define UV_OUTPUT_FRACTION 12 #define UV_NUMCOEFF 2 #define ADC_THRESHOLD 16000 #define INPUT_FRACTION_HIGH 7 #define INPUT_FRACTION_LOW 15 #define LUX_OUTPUT_FRACTION 12 #define NUMCOEFF_LOW 9 #define NUMCOEFF_HIGH 4 /** @endcond */ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ /***************************************************************************//** * @brief * Coefficients for lux calculation ******************************************************************************/ const Si1133::LuxCoeff_t Si1133::lk = { { { 0, 209 }, /**< coeff_high[0] */ { 1665, 93 }, /**< coeff_high[1] */ { 2064, 65 }, /**< coeff_high[2] */ { -2671, 234 } }, /**< coeff_high[3] */ { { 0, 0 }, /**< coeff_low[0] */ { 1921, 29053 }, /**< coeff_low[1] */ { -1022, 36363 }, /**< coeff_low[2] */ { 2320, 20789 }, /**< coeff_low[3] */ { -367, 57909 }, /**< coeff_low[4] */ { -1774, 38240 }, /**< coeff_low[5] */ { -608, 46775 }, /**< coeff_low[6] */ { -1503, 51831 }, /**< coeff_low[7] */ { -1886, 58928 } } /**< coeff_low[8] */ }; /***************************************************************************//** * @brief * Coefficients for UV index calculation ******************************************************************************/ const Si1133::Coeff_t Si1133::uk[2] = { { 1281, 30902 }, /**< coeff[0] */ { -638, 46301 } /**< coeff[1] */ }; /**************************************************************************//** * @name Error Codes * @{ ******************************************************************************/ #define SI1133_OK 0x0000 /**< No errors */ #define SI1133_ERROR_I2C_TRANSACTION_FAILED 0x0001 /**< I2C transaction failed */ #define SI1133_ERROR_SLEEP_FAILED 0x0002 /**< Entering sleep mode failed */ /**@}*/ /** @endcond */ Si1133::Si1133(PinName sda, PinName scl, int hz) : m_I2C(sda, scl) { //Set the I2C bus frequency m_I2C.frequency(hz); } Si1133::~Si1133(void) { deinit(); } bool Si1133::open() { //Probe for the Si1133 using a Zero Length Transfer if (m_I2C.write(SI1133_I2C_ADDRESS, NULL, 0)) { //Return success return false; } // initialize sensor if (SI1133_OK == init()) { return true; } return false; } /** Measure the current light level (in lux) on the Si1133 * * @returns The current temperature measurement in Lux. */ float Si1133::get_light_level() { float lux, uvi; measure_lux_uv(&lux, &uvi); return lux; } /** Measure the current UV Index on the Si1133 * * @returns The current UV index. */ float Si1133::get_uv_index() { float lux, uvi; measure_lux_uv(&lux, &uvi); return uvi; } bool Si1133::get_light_and_uv(float *light_level, float *uv_index) { if(measure_lux_uv(light_level, uv_index)) { return false; } return true; } /***************************************************************************//** * @brief * Reads register from the Si1133 sensor * * @param[in] reg * The register address to read from in the sensor. * * @param[out] data * The data read from the sensor * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::read_register(enum Si1133::Register reg, uint8_t *data) { char buf[1]; buf[0] = (char) reg; if (m_I2C.write(SI1133_I2C_ADDRESS, buf, 1, true)) { //Return failure return SI1133_ERROR_I2C_TRANSACTION_FAILED; } if (m_I2C.read(SI1133_I2C_ADDRESS, buf, 1)) { //Return failure return SI1133_ERROR_I2C_TRANSACTION_FAILED; } *data = buf[0]; return SI1133_OK; } /***************************************************************************//** * @brief * Writes register in the Si1133 sensor * * @param[in] reg * The register address to write to in the sensor * * @param[in] data * The data to write to the sensor * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::write_register(enum Si1133::Register reg, uint8_t data) { char buf[2]; buf[0] = (char) reg; buf[1] = (char) data; if (m_I2C.write(SI1133_I2C_ADDRESS, buf, 2)) { //Return failure return SI1133_ERROR_I2C_TRANSACTION_FAILED; } return SI1133_OK; } /***************************************************************************//** * @brief * Writes a block of data to the Si1133 sensor. * * @param[in] reg * The first register to begin writing to * * @param[in] length * The number of bytes to write to the sensor * * @param[in] data * The data to write to the sensor * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::write_register_block(enum Si1133::Register reg, uint8_t length, uint8_t *data) { char buf[length+1]; buf[0] = (char)reg; memcpy(&buf[1], data, length); if (m_I2C.write(SI1133_I2C_ADDRESS, buf, length + 1)) { //Return failure return SI1133_ERROR_I2C_TRANSACTION_FAILED; } return SI1133_OK; } /***************************************************************************//** * @brief * Reads a block of data from the Si1133 sensor. * * @param[in] reg * The first register to begin reading from * * @param[in] length * The number of bytes to write to the sensor * * @param[out] data * The data read from the sensor * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::read_register_block(enum Si1133::Register reg, uint8_t length, uint8_t *data) { char reg_c = (char)reg; if (m_I2C.write(SI1133_I2C_ADDRESS, ®_c, 1, true)) { //Return failure return SI1133_ERROR_I2C_TRANSACTION_FAILED; } if (m_I2C.read(SI1133_I2C_ADDRESS, (char*) data, length)) { //Return failure return SI1133_ERROR_I2C_TRANSACTION_FAILED; } return SI1133_OK; } /***************************************************************************//** * @brief * Reads the interrupt status register of the device * * @param[out] irqStatus * The contentof the IRQ status register * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::get_irq_status(uint8_t *irq_status) { return read_register(REG_IRQ_STATUS, irq_status); } /***************************************************************************//** * @brief * Waits until the Si1133 is sleeping before proceeding * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::wait_until_sleep(void) { uint32_t ret; uint8_t response; size_t count = 0; /* This loops until the Si1133 is known to be in its sleep state */ /* or if an i2c error occurs */ while ( count < 5 ) { ret = read_register(REG_RESPONSE0, &response); if ( (response & (uint8_t)RSP0_CHIPSTAT_MASK) == (uint8_t)RSP0_SLEEP ) { return SI1133_OK; } if ( ret != SI1133_OK ) { return SI1133_ERROR_SLEEP_FAILED; } count++; } return SI1133_ERROR_SLEEP_FAILED; } /***************************************************************************//** * @brief * Resets the Si1133 * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::reset(void) { uint32_t retval; /* Do not access the Si1133 earlier than 25 ms from power-up */ wait_ms(30); /* Perform the Reset Command */ retval = write_register(REG_COMMAND, (uint8_t)CMD_RESET); /* Delay for 10 ms. This delay is needed to allow the Si1133 */ /* to perform internal reset sequence. */ wait_ms(10); return retval; } /***************************************************************************//** * @brief * Helper function to send a command to the Si1133 * * @param[in] command * The command to send to the sensor * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::send_cmd(enum Si1133::Command command) { uint8_t response; uint8_t response_stored; uint8_t count = 0; uint32_t ret; /* Get the response register contents */ ret = read_register(REG_RESPONSE0, &response_stored); if ( ret != SI1133_OK ) { return ret; } response_stored = response_stored & (uint8_t)RSP0_COUNTER_MASK; /* Double-check the response register is consistent */ while ( count < 5 ) { ret = wait_until_sleep(); if ( ret != SI1133_OK ) { return ret; } /* Skip if the command is RESET COMMAND COUNTER */ if ( command == (uint8_t)CMD_RESET_CMD_CTR ) { break; } ret = read_register(REG_RESPONSE0, &response); if ( (response & (uint8_t)RSP0_COUNTER_MASK) == response_stored ) { break; } else { if ( ret != SI1133_OK ) { return ret; } else { response_stored = response & (uint8_t)RSP0_COUNTER_MASK; } } count++; } /* Send the command */ ret = write_register(REG_COMMAND, command); if ( ret != SI1133_OK ) { return ret; } count = 0; /* Expect a change in the response register */ while ( count < 5 ) { /* Skip if the command is RESET COMMAND COUNTER */ if ( command == (uint8_t)CMD_RESET_CMD_CTR ) { break; } ret = read_register(REG_RESPONSE0, &response); if ( (response & (uint8_t)RSP0_COUNTER_MASK) != response_stored ) { break; } else { if ( ret != SI1133_OK ) { return ret; } } count++; } return SI1133_OK; } /***************************************************************************//** * @brief * Sends a RESET COMMAND COUNTER command to the Si1133 * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::reset_cmd_counter(void) { return send_cmd(CMD_RESET_CMD_CTR); } /***************************************************************************//** * @brief * Sends a FORCE command to the Si1133 * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::force_measurement(void) { return send_cmd(CMD_FORCE_CH); } /***************************************************************************//** * @brief * Sends a START command to the Si1133 * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::start_measurement(void) { return send_cmd(CMD_START); } /***************************************************************************//** * @brief * Sends a PAUSE command to the Si1133 * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::pause_measurement(void) { return send_cmd(CMD_PAUSE_CH); } /***************************************************************************//** * @brief * Writes a byte to an Si1133 Parameter * * @param[in] address * The parameter address * * @param[in] value * The byte value to be written to the Si1133 parameter * * @return * Returns zero on OK, non-zero otherwise * * @note * This function ensures that the Si1133 is idle and ready to * receive a command before writing the parameter. Furthermore, * command completion is checked. If setting parameter is not done * properly, no measurements will occur. This is the most common * error. It is highly recommended that host code make use of this * function. ******************************************************************************/ uint32_t Si1133::set_parameter (enum Si1133::Parameter address, uint8_t value) { uint32_t retval; uint8_t buffer[2]; uint8_t response_stored; uint8_t response; size_t count; retval = wait_until_sleep(); if ( retval != SI1133_OK ) { return retval; } read_register(REG_RESPONSE0, &response_stored); response_stored &= (uint8_t)RSP0_COUNTER_MASK; buffer[0] = value; buffer[1] = 0x80 + ((uint8_t)address & 0x3F); retval = write_register_block(REG_HOSTIN0, 2, (uint8_t*) buffer); if ( retval != SI1133_OK ) { return retval; } /* Wait for command to finish */ count = 0; /* Expect a change in the response register */ while ( count < 5 ) { retval = read_register(REG_RESPONSE0, &response); if ( (response & (uint8_t)RSP0_COUNTER_MASK) != response_stored ) { break; } else { if ( retval != SI1133_OK ) { return retval; } } count++; } if (count >= 5) { return SI1133_ERROR_I2C_TRANSACTION_FAILED; } return SI1133_OK; } /***************************************************************************//** * @brief * Reads a parameter from the Si1133 * * @param[in] address * The address of the parameter. * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::read_parameter (enum Si1133::Parameter address) { uint8_t retval; uint8_t cmd; cmd = 0x40 + ((uint8_t)address & 0x3F); retval = send_cmd((enum Si1133::Command)cmd); if ( retval != SI1133_OK ) { return retval; } read_register(REG_RESPONSE1, &retval); return retval; } /**************************************************************************//** * @brief * Initializes the Si1133 chip * * @return * Returns zero on OK, non-zero otherwise *****************************************************************************/ uint32_t Si1133::init (void) { uint32_t retval; /* Allow some time for the part to power up */ wait_ms(5); retval = reset(); wait_ms(10); retval += set_parameter(PARAM_CH_LIST, 0x0f); retval += set_parameter(PARAM_ADCCONFIG0, 0x78); retval += set_parameter(PARAM_ADCSENS0, 0x71); retval += set_parameter(PARAM_ADCPOST0, 0x40); retval += set_parameter(PARAM_ADCCONFIG1, 0x4d); retval += set_parameter(PARAM_ADCSENS1, 0xe1); retval += set_parameter(PARAM_ADCPOST1, 0x40); retval += set_parameter(PARAM_ADCCONFIG2, 0x41); retval += set_parameter(PARAM_ADCSENS2, 0xe1); retval += set_parameter(PARAM_ADCPOST2, 0x50); retval += set_parameter(PARAM_ADCCONFIG3, 0x4d); retval += set_parameter(PARAM_ADCSENS3, 0x87); retval += set_parameter(PARAM_ADCPOST3, 0x40); retval += write_register(REG_IRQ_ENABLE, 0x0f); return retval; } /***************************************************************************//** * @brief * Stops the measurements on all channel and waits until the chip * goes to sleep state. * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::deinit (void) { uint32_t retval; retval = set_parameter(PARAM_CH_LIST, 0x3f); retval += pause_measurement(); retval += wait_until_sleep(); return retval; } /***************************************************************************//** * @brief * Read samples from the Si1133 chip * * @param[out] samples * Retrieves interrupt status and measurement data for channel 0..3 and * converts the data to int32_t format * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::measure (Si1133::Samples_t *samples) { uint8_t buffer[13]; uint32_t retval; retval = read_register_block(REG_IRQ_STATUS, 13, buffer); samples->irq_status = buffer[0]; samples->ch0 = buffer[1] << 16; samples->ch0 |= buffer[2] << 8; samples->ch0 |= buffer[3]; if ( samples->ch0 & 0x800000 ) { samples->ch0 |= 0xFF000000; } samples->ch1 = buffer[4] << 16; samples->ch1 |= buffer[5] << 8; samples->ch1 |= buffer[6]; if ( samples->ch1 & 0x800000 ) { samples->ch1 |= 0xFF000000; } samples->ch2 = buffer[7] << 16; samples->ch2 |= buffer[8] << 8; samples->ch2 |= buffer[9]; if ( samples->ch2 & 0x800000 ) { samples->ch2 |= 0xFF000000; } samples->ch3 = buffer[10] << 16; samples->ch3 |= buffer[11] << 8; samples->ch3 |= buffer[12]; if ( samples->ch3 & 0x800000 ) { samples->ch3 |= 0xFF000000; } return retval; } int32_t Si1133::calculate_polynomial_helper (int32_t input, int8_t fraction, uint16_t mag, int8_t shift) { int32_t value; if ( shift < 0 ) { value = ( (input << fraction) / mag) >> -shift; } else { value = ( (input << fraction) / mag) << shift; } return value; } int32_t Si1133::calculate_polynomial (int32_t x, int32_t y, uint8_t input_fraction, uint8_t output_fraction, uint8_t num_coeff, const Si1133::Coeff_t *kp) { uint8_t info, x_order, y_order, counter; int8_t sign, shift; uint16_t mag; int32_t output = 0, x1, x2, y1, y2; for ( counter = 0; counter < num_coeff; counter++ ) { info = kp->info; x_order = GET_X_ORDER(info); y_order = GET_Y_ORDER(info); shift = ( (uint16_t) kp->info & 0xff00) >> 8; shift ^= 0x00ff; shift += 1; shift = -shift; mag = kp->mag; if ( GET_SIGN(info) ) { sign = -1; } else { sign = 1; } if ( (x_order == 0) && (y_order == 0) ) { output += sign * mag << output_fraction; } else { if ( x_order > 0 ) { x1 = calculate_polynomial_helper(x, input_fraction, mag, shift); if ( x_order > 1 ) { x2 = calculate_polynomial_helper(x, input_fraction, mag, shift); } else { x2 = 1; } } else { x1 = 1; x2 = 1; } if ( y_order > 0 ) { y1 = calculate_polynomial_helper(y, input_fraction, mag, shift); if ( y_order > 1 ) { y2 = calculate_polynomial_helper(y, input_fraction, mag, shift); } else { y2 = 1; } } else { y1 = 1; y2 = 1; } output += sign * x1 * x2 * y1 * y2; } kp++; } if ( output < 0 ) { output = -output; } return output; } /***************************************************************************//** * @brief * Compute UV index * * @param[in] uv * UV sensor raw data * * @param[in] uk * UV calculation coefficients * * @return * UV index scaled by UV_OUPTUT_FRACTION ******************************************************************************/ int32_t Si1133::get_uv (int32_t uv) { int32_t uvi; uvi = calculate_polynomial(0, uv, UV_INPUT_FRACTION, UV_OUTPUT_FRACTION, UV_NUMCOEFF, uk); return uvi; } /***************************************************************************//** * @brief * Compute lux value * * @param[in] vis_high * Visible light sensor raw data * * @param[in] vis_low * Visible light sensor raw data * * @param[in] ir * Infrared sensor raw data * * @param[in] lk * Lux calculation coefficients * * @return * Lux value scaled by LUX_OUPTUT_FRACTION ******************************************************************************/ int32_t Si1133::get_lux (int32_t vis_high, int32_t vis_low, int32_t ir) { int32_t lux; if ( (vis_high > ADC_THRESHOLD) || (ir > ADC_THRESHOLD) ) { lux = calculate_polynomial(vis_high, ir, INPUT_FRACTION_HIGH, LUX_OUTPUT_FRACTION, NUMCOEFF_HIGH, &(lk.coeff_high[0]) ); } else { lux = calculate_polynomial(vis_low, ir, INPUT_FRACTION_LOW, LUX_OUTPUT_FRACTION, NUMCOEFF_LOW, &(lk.coeff_low[0]) ); } return lux; } /***************************************************************************//** * @brief * Measure lux and UV index using the Si1133 sensor * * @param[out] lux * The measured ambient light illuminace in lux * * @param[out] uvi * UV index * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::measure_lux_uv (float *lux, float *uvi) { Si1133::Samples_t samples; uint32_t retval; uint8_t response; /* Force measurement */ retval = force_measurement(); /* Go to sleep while the sensor does the conversion */ wait_ms(200); /* Check if the measurement finished, if not then wait */ retval += read_register(REG_IRQ_STATUS, &response); while ( response != 0x0F ) { wait_ms(5); retval += read_register(REG_IRQ_STATUS, &response); } /* Get the results */ measure(&samples); /* Convert the readings to lux */ *lux = (float) get_lux(samples.ch1, samples.ch3, samples.ch2); *lux = *lux / (1 << LUX_OUTPUT_FRACTION); /* Convert the readings to UV index */ *uvi = (float) get_uv(samples.ch0); *uvi = *uvi / (1 << UV_OUTPUT_FRACTION); return retval; } /***************************************************************************//** * @brief * Reads Hardware ID from the SI1133 sensor * * @param[out] hardwareID * The Hardware ID of the chip (should be 0x33) * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::get_hardware_id (uint8_t *hardware_id) { uint32_t retval; retval = read_register(REG_PART_ID, hardware_id); return retval; } /***************************************************************************//** * @brief * Retrieve the sample values from the chip and convert them * to lux and UV index values * * @param[out] lux * The measured ambient light illuminace in lux * * @param[out] uvi * UV index * * @return * Returns zero on OK, non-zero otherwise ******************************************************************************/ uint32_t Si1133::get_measurement (float *lux, float *uvi) { Si1133::Samples_t samples; uint32_t retval; /* Get the results */ retval = measure(&samples); /* Convert the readings to lux */ *lux = (float) get_lux(samples.ch1, samples.ch3, samples.ch2); *lux = *lux / (1 << LUX_OUTPUT_FRACTION); /* Convert the readings to UV index */ *uvi = (float) get_uv(samples.ch0); *uvi = *uvi / (1 << UV_OUTPUT_FRACTION); return retval; }