/** * @brief Calibrate proximity sensor detection thresholds * * This function measures the proximity sensor output in 3 different steps, * one for each channel in the device. This function should be called when * a sample object has been placed at the desired distance from the device * to define the threshold for near-proximity detection. The measured * proximity sensor value for each channel is stored in non-volatile memory. * These thresholds will later be used to set the threshold during the * device initialization sequence. * * This routine must be called 3 times total, with the "step" parameter * indicating what stage of the calibration is being performed (i.e. which * channel of the proximity sensor). This multi-step mechanism allows * the application to prompt for physical placement of the object to be * detected before this routine is called. * * @param sensor Address of an initialized sensor device descriptor. * @param calib_type The address of a vector storing sensor axis data. * @param step The calibration stage number [1,3]. * @param info Unimplemented (ignored) parameter. * @return bool true if the call succeeds, else false is returned. */ static bool sfh7770_calibrate(sensor_t *sensor, sensor_calibration_t calib_type, int step, void *info) { sensor_hal_t *const hal = sensor->hal; static uint8_t prox_data[3]; uint8_t read_data[3]; /* Validate the specified calibration type */ if (calib_type != MANUAL_CALIBRATE) { sensor->err = SENSOR_ERR_PARAMS; return false; } /* Read proximity sensor for individual channel based on step number. */ switch (step) { case 1: prox_data[0] = sensor_bus_get(hal, SFH7770_PS_DATA_LED1); break; case 2: prox_data[1] = sensor_bus_get(hal, SFH7770_PS_DATA_LED2); break; case 3: prox_data[2] = sensor_bus_get(hal, SFH7770_PS_DATA_LED3); /* Write data */ nvram_write((SFH7770_NVRAM_OFFSET), prox_data, sizeof(prox_data)); /* Read back data and confirm it was written correctly */ nvram_read(SFH7770_NVRAM_OFFSET, read_data, sizeof(read_data)); if (memcmp(prox_data, read_data, sizeof(prox_data))) { sensor->err = SENSOR_ERR_IO; return false; } /* Apply stored proximity thresholds from nvram */ sensor_bus_write(hal, (SFH7770_PS_THR_LED1), read_data, sizeof(read_data)); break; /* Any other step number is invalid */ default: sensor->err = SENSOR_ERR_PARAMS; return false; } return true; }
/** * @brief Perform self-test function * * This function sets up and executes an built-in self test function * within the HMC5883L device. * * @param sensor Address of an initialized sensor descriptor. * @param test_code Address of a numeric code of which test to perform * This location will contain the specific test result code * when the function returns. * @return bool true if the call succeeds, else false is returned. */ bool hmc5883l_selftest(sensor_t *sensor, int *test_code, void *arg) { int count; uint8_t meas_mode; /* test measurement mode */ vector3_t data; bool status = true; sensor_hal_t *const hal = sensor->hal; struct { uint8_t config_reg_a; uint8_t config_reg_b; uint8_t mode_reg; } reg_set; /* Initialize result code */ *test_code = SENSOR_TEST_ERR_NONE; /* Save register values */ count = sensor_bus_read(hal, HMC5883L_CONFIG_REG_A, (uint8_t *)®_set, sizeof(reg_set)); if (count != sizeof(reg_set)) { *test_code = SENSOR_TEST_ERR_READ; return false; } /* Set range (sensitivity) */ sensor_bus_put(hal, HMC5883L_CONFIG_REG_B, HMC5883L_TEST_GAIN); /* Set test mode */ switch (*test_code) { /* which test was specified */ case SENSOR_TEST_DEFAULT: case SENSOR_TEST_BIAS_POS: meas_mode = MEAS_MODE_POS; /* positive bias measurement mode */ break; case SENSOR_TEST_BIAS_NEG: meas_mode = MEAS_MODE_NEG; /* negative bias measurement mode */ break; default: /* bad test code specified */ sensor_bus_put(hal, HMC5883L_CONFIG_REG_B, reg_set.config_reg_b); /* restore reg */ *test_code = SENSOR_TEST_ERR_FUNCTION; return false; } sensor_bus_put(hal, HMC5883L_CONFIG_REG_A, (DATA_RATE_15HZ | MEAS_AVG_1 | meas_mode)); delay_ms(SELF_TEST_DELAY_MSEC); /* Perform test measurement & check results */ sensor_bus_put(hal, HMC5883L_MODE_REG, MODE_SINGLE); /* single meas mode **/ if (hmc5883l_get_data(hal, &data) != true) { *test_code = SENSOR_TEST_ERR_READ; status = false; /* failed to read data registers */ } else { if (arg != NULL) { ((sensor_data_t *)arg)->scaled = false; /* only raw values */ ((sensor_data_t *)arg)->timestamp = 0; /* no timestamp */ ((sensor_data_t *)arg)->axis.x = (int32_t)data.x; /* copy values */ ((sensor_data_t *)arg)->axis.y = (int32_t)data.y; ((sensor_data_t *)arg)->axis.z = (int32_t)data.z; } /* Check range of readings */ if ((HMC5883L_TEST_X_MIN > data.x) || (data.x > HMC5883L_TEST_X_MAX) || (HMC5883L_TEST_Y_MIN > data.y) || (data.y > HMC5883L_TEST_Y_MAX) || (HMC5883L_TEST_Z_MIN > data.z) || (data.z > HMC5883L_TEST_Z_MAX)) { *test_code = SENSOR_TEST_ERR_RANGE; status = false; /* value out of range */ } } /* Restore registers */ count = sensor_bus_write(hal, HMC5883L_CONFIG_REG_A, (uint8_t *)®_set, sizeof(reg_set)); if (count != sizeof(reg_set)) { *test_code = SENSOR_TEST_ERR_WRITE; status = false; } return status; }
/** * @brief InvenSense IMU-3000 gyroscope driver defaults. * * This is a convenience routine for setting the default device configuration * during initialization or after reset. * * @return bool true if the sensor is ready for use, else false. */ static bool imu3000_default_init(sensor_hal_t *hal, sensor_hal_t *aux) { bool status = false; /* Assuming the device is on an I2C bus, the device address and ID * are one and the same. Per the vendor data sheet, AD0 in the * WHO_AM_I (device ID) register is software configurable s.t. two * devices can be installed on the sensor bus. The undefined AD0 * bit is set to zero to select the 110 1000 (AD0=0) address. * * Set the bus address here then verify the setting. */ sensor_bus_put(hal, IMU3000_WHO_AM_I, IMU3000_ID_VAL); if (IMU3000_ID_VAL == sensor_bus_get(hal, IMU3000_WHO_AM_I)) { /* Reset the motion processing unit to a known state then enable * the gyroscope using the Z-axis for clock. */ sensor_bus_put(hal, IMU3000_PWR_MGM, PWR_MGM_H_RESET); sensor_bus_put(hal, IMU3000_WHO_AM_I, IMU3000_ID_VAL); sensor_bus_put(hal, IMU3000_PWR_MGM, PWR_MGM_CLK_SEL_Z); /* \todo Define a method to support I2C master mode. * * Determine whether it is reasonable to use the * "sensor_desc.aux" field to support an external 3-axis accelerometer * configured as an auxiliary device. For example, assume a user * selects this as a build-time configuration option and the "aux" * field points to a sensor_hal_t type describing the accelerometer. */ if (aux) { /* Configure and enable the IMU-3000 auxiliary device * interface, including bus and burst read addresses. */ sensor_bus_put(hal, IMU3000_AUX_SLV_ADDR, AUX_ADDR_CLKOUTEN | aux->bus.addr); sensor_bus_put(hal, IMU3000_AUX_BURST_ADDR, aux->burst_addr); /* Enable the IMU as master to accelerometer (aux) * interface via secondary I2C bus. For an external * processor to communicate directly to an external accelerometer * the AUX_IF_EN bit should be cleared and AUX_IF_RST bit * should be set. The configuration below turns the accelerometer * into a slave device that's not addressed by a separate * sensor_t descriptor and API routines. */ sensor_bus_put(hal, IMU3000_USER_CTRL, USER_CTRL_AUX_IF_RST | USER_CTRL_AUX_IF_EN); } else { /* Reset the auxiliary interface in pass-through mode * s.t. the external processor can communicate directly with * an external accelerometer. */ sensor_bus_put(hal, IMU3000_USER_CTRL, USER_CTRL_AUX_IF_RST); } /* \todo Define a method to dynamically compute DC bias. * * The gyro signed offset registers remove DC bias from the * sensor outputs by subtracting the offset values from gyro sensor * values prior to moving sensor data to output registers. The * device is initialized s.t. DC bias offsets are not applied to * sensor data. */ const uint16_t *const gyro_offsets = (uint16_t [3]) {0}; sensor_bus_write(hal, IMU3000_X_OFFS_USRH, gyro_offsets, 3 * sizeof(uint16_t)); /* Set the sample rate divider, digital low-pass filter, and * full-scale range. * * The digital low-pass filter also determines the internal * sample rate used by the device. The 256 Hz. and 2.1 kHz * filter bandwidths result in an 8 kHz. internal sample rate, while * all other settings result in a 1 kHz. internal sample rate. * * This internal sample rate is then further scaled by the * sample rate divider (SMPLRT_DIV) value to produce the gyro * sample rate according to the formula: * * F_sample = F_internal / (divider + 1) */ uint8_t const dlpf_fs = range_table[sensor_fs_sel].reserved_val | band_table[sensor_dlpf_cfg].reserved_val; sensor_bus_put(hal, IMU3000_SMPLRT_DIV, 9); sensor_bus_put(hal, IMU3000_DLPF_FS, dlpf_fs); } if (STATUS_OK == hal->bus.status) { status = true; } return status; }
/** * @brief Set event threshold value * * @param hal Address of an initialized sensor hardware descriptor. * @param channel Channel (LED) number to set threshold * @param threshold Address of threshold descriptor. * @return bool true if the call succeeds, else false is returned. */ static bool sfh7770_set_threshold(sensor_hal_t *hal, int16_t channel, sensor_threshold_desc_t *threshold) { struct { uint8_t lsb; uint8_t msb; } reg_thresh; uint8_t led_count = 0; uint8_t index; uint8_t value = threshold->value; bool result = false; switch (threshold->type) { /* check threshold type */ case SENSOR_THRESHOLD_NEAR_PROXIMITY: /* high (near) prox. threshold **/ /* Write to sensor register based on current channel (LED * selection) */ switch (channel) { case SENSOR_CHANNEL_ALL: /* "channel -1" = all 3 LEDs */ led_count = 3; index = 0; break; case 1: case 2: case 3: led_count = 1; index = channel - 1; break; default: return false; /* invalid channel selection */ } while (led_count--) { sensor_bus_put(hal, (SFH7770_PS_THR_LED1 + index), (uint8_t)threshold->value); /* Write to nvram */ nvram_write((SFH7770_NVRAM_OFFSET + index), &value, 1); ++index; } result = true; break; case SENSOR_THRESHOLD_LOW_LIGHT: /* lower light level threshold **/ reg_thresh.lsb = (uint8_t)(threshold->value & 0xFF); reg_thresh.msb = (uint8_t)((threshold->value >> 8) & 0xFF); low_light_threshold = (uint16_t)threshold->value; if (sensor_bus_write(hal, SFH7770_ALS_LO_THR_LSB, ®_thresh, sizeof(reg_thresh)) == sizeof(reg_thresh)) { result = true; } break; case SENSOR_THRESHOLD_HIGH_LIGHT: /* upper light level threshold **/ reg_thresh.lsb = (uint8_t)(threshold->value & 0xFF); reg_thresh.msb = (uint8_t)((threshold->value >> 8) & 0xFF); high_light_threshold = (uint16_t)threshold->value; if (sensor_bus_write(hal, SFH7770_ALS_UP_THR_LSB, ®_thresh, sizeof(reg_thresh)) == sizeof(reg_thresh)) { result = true; } break; default: /* Invalid/unsupported threshold type - do nothing, return false */ break; } return result; }
/** * @brief Osram SFH7770 light & proximity sensor driver initialization. * * This is the main initialization function for the SFH7770 device. * * @param sensor Address of a sensor device descriptor. * @param resvd Reserved value. * @return bool true if the call succeeds, else false is returned. */ bool sfh7770_init(sensor_t *sensor, int resvd) { bool status = false; sensor_hal_t *const hal = sensor->hal; /* Proximity threshold values from NVRAM */ struct { uint8_t ps_thr_led1; uint8_t ps_thr_led2; uint8_t ps_thr_led3; } prox_thresholds; /* Read and check part ID register */ uint8_t part_id = sensor_bus_get(hal, SFH7770_PART_ID); if (part_id == (SFH7770_PART_ID_VAL | SFH7770_PART_REV_VAL)) { /* Set the driver function table and capabilities pointer. */ static const sensor_device_t sfh7770_device = { .func.read = sfh7770_read, .func.ioctl = sfh7770_ioctl, .func.calibrate = sfh7770_calibrate, .func.event = sfh7770_event, #if 0 .caps.feature = XXX #endif .caps.vendor = SENSOR_VENDOR_OSRAM, .caps.units = SENSOR_UNITS_lux, .caps.scale = SENSOR_SCALE_one, .caps.name = "SFH7770 Ambient Light & Proximity Sensor" }; sensor->drv = &sfh7770_device; hal->resolution = SFH7770_DATA_RESOLUTION; /* Set the device burst read starting register address. */ hal->burst_addr = SFH7770_ALS_DATA_LSB; /* Reset device during first init call */ if (!sfh7770_initialized) { sensor_bus_put(hal, SFH7770_ALS_CONTROL, ALS_CONTROL_SW_RESET); } /* Init light sensor functions if specified */ if (sensor->type & SENSOR_TYPE_LIGHT) { /* Set light sensor mode & interval */ sensor_bus_put(hal, SFH7770_ALS_CONTROL, ALS_MODE_FREE_RUNNING); sensor_bus_put(hal, SFH7770_ALS_INTERVAL, ALS_INTERVAL_500MS); } /* Init proximity sensor functions if specified */ if (sensor->type & SENSOR_TYPE_PROXIMITY) { /* Set proximity sensor mode & interval */ sensor_bus_put(hal, SFH7770_PS_CONTROL, PS_MODE_FREE_RUNNING); sensor_bus_put(hal, SFH7770_PS_INTERVAL, PS_INTERVAL_100MS); /* Specify which LEDs are active */ uint8_t const active_leds = LED_ACTIVE_ALL; /* XXX one of: */ /* XXX LED_ACTIVE_1 - LED1 only (default) */ /* XXX LED_ACTIVE_1_2 - LED1 & LED2 */ /* XXX LED_ACTIVE_1_3 - LED1 & LED3 */ /* XXX LED_ACTIVE_ALL - all LEDs */ /* Set LED current levels */ uint8_t const led1_curr = I_LED_50MA; /* LED1 current */ uint8_t const led2_curr = I_LED_50MA; /* LED2 current */ uint8_t const led3_curr = I_LED_50MA; /* LED3 current */ sensor_bus_put(hal, SFH7770_I_LED_1_2, (active_leds | (led2_curr << I_LED2_SHIFT) | led1_curr)); sensor_bus_put(hal, SFH7770_I_LED_3, led3_curr); /* Apply stored proximity thresholds from nvram */ nvram_read(SFH7770_NVRAM_OFFSET, &prox_thresholds, sizeof(prox_thresholds)); sensor_bus_write(hal, (SFH7770_PS_THR_LED1), &prox_thresholds, sizeof(prox_thresholds)); } if (!sfh7770_initialized) { /* Set interrupt output polarity & mode(active-low, * latched). */ sensor_bus_put(hal, SFH7770_INT_SET, 0); /* Set up interrupt handler */ if (STATUS_OK == hal->bus.status) { sensor_irq_connect(hal->mcu_sigint, sfh7770_isr, hal); } } sfh7770_initialized = true; status = true; } return status; } /** * @brief Osram SFH7770 driver interrupt service routine. * * This is the common interrupt service routine for all enabled SFH7770 * interrupt events. Three different types of interrupts can be programmed: * high light level, low light level, and near proximity. All share the * same interrupt pin and therefore the same ISR entry. * * @param arg The address of the driver sensor_hal_t descriptor. * @return Nothing. */ static void sfh7770_isr(volatile void *arg) { sensor_hal_t *const hal = (sensor_hal_t *)arg; struct { /* Interrupt register data */ uint8_t als_data_lsb; /* light meas data - least signif byte */ uint8_t als_data_msb; /* light meas data - most signif byte */ uint8_t als_ps_status; /* light & prox sensor status */ uint8_t ps_data_led1; /* proximity meas data - LED 1 */ uint8_t ps_data_led2; /* proximity meas data - LED 2 */ uint8_t ps_data_led3; /* proximity meas data - LED 3 */ uint8_t int_set; /* interrupt status */ } regs; /* Do not wait for a busy bus when reading data. */ hal->bus.no_wait = true; sensor_bus_read(hal, hal->burst_addr, (uint8_t *)®s, sizeof(regs)); hal->bus.no_wait = false; if (STATUS_OK == hal->bus.status) { static sensor_event_data_t event_data = {.data.scaled = true}; event_data.data.timestamp = sensor_timestamp(); event_data.event = SENSOR_EVENT_UNKNOWN; /* * Determine the interrupt source then combine measurement * register values into a single 16-bit measurement value. */ uint8_t const int_source = (regs.int_set & INT_SOURCE_MASK); uint16_t const light_level = ((regs.als_data_msb << 8) | regs.als_data_lsb); switch (int_source) { case INT_SOURCE_ALS: /* Determine if low or high light interrupt */ if (light_level >= high_light_threshold) { event_data.event = SENSOR_EVENT_HIGH_LIGHT; event_data.data.light.value = light_level; (event_cb[2].handler)(&event_data, event_cb[2].arg); } else if (light_level <= low_light_threshold) { event_data.event = SENSOR_EVENT_LOW_LIGHT; event_data.data.light.value = light_level; (event_cb[1].handler)(&event_data, event_cb[1].arg); } return; case INT_SOURCE_LED1: case INT_SOURCE_LED2: case INT_SOURCE_LED3: event_data.event = SENSOR_EVENT_NEAR_PROXIMITY; if (int_source == INT_SOURCE_LED1) { event_data.channel = 1; } else if (int_source == INT_SOURCE_LED2) { event_data.channel = 2; } else { /* INT_SOURCE_LED3 */ event_data.channel = 3; } /* Use internal device threshold status to * determine scaled values. */ event_data.data.proximity.value[0] = (regs.als_ps_status & PS_LED1_THRESH) ? PROXIMITY_NEAR : PROXIMITY_NONE; event_data.data.proximity.value[1] = (regs.als_ps_status & PS_LED2_THRESH) ? PROXIMITY_NEAR : PROXIMITY_NONE; event_data.data.proximity.value[2] = (regs.als_ps_status & PS_LED3_THRESH) ? PROXIMITY_NEAR : PROXIMITY_NONE; (event_cb[0].handler)(&event_data, event_cb[0].arg); } } } /** * @brief Read SFH7770 device ID and revision numbers. * * This function reads the sensor hardware identification registers * and returns these values in the specified data structure. * * @param hal Address of an initialized sensor hardware descriptor. * @param data Address of sensor_data_t structure to return values. * @return bool true if the call succeeds, else false is returned. */ static bool sfh7770_device_id(sensor_hal_t *hal, sensor_data_t *data) { uint8_t const part_id = sensor_bus_get(hal, SFH7770_PART_ID); data->device.id = (uint32_t)(part_id & PART_ID_MASK) >> PART_ID_SHIFT; data->device.version = (uint32_t)(part_id & PART_REV_MASK); return true; }