#![cfg_attr(not(test), no_std)]
pub const BH1730FVC_ADDR: u8 = 0x29;
const BH1730FVC_RESET_CMD: u8 = 0xE4;
const TINT_US: f32 = 2.8;
#[derive(Copy, Clone, Debug)]
pub struct BH1730FVC<I2C, D> {
_delay: core::marker::PhantomData<D>,
_i2c: core::marker::PhantomData<I2C>,
gain: Gain,
integration_time_ms: f32,
}
impl<I2C, D> BH1730FVC<I2C, D>
where
D: embedded_hal::blocking::delay::DelayMs<u32>,
I2C: embedded_hal::blocking::i2c::Read
+ embedded_hal::blocking::i2c::Write
+ embedded_hal::blocking::i2c::WriteRead,
{
pub fn new(delay: &mut D, i2c: &mut I2C) -> Result<Self> {
let result = i2c.write(BH1730FVC_ADDR, &[BH1730FVC_RESET_CMD]);
if result.is_err() {
return Err(BH1730FVCError::WriteI2CError);
}
delay.delay_ms(10);
Ok(Self {
_delay: core::marker::PhantomData::default(),
_i2c: core::marker::PhantomData::default(),
gain: Gain::X1,
integration_time_ms: 102.6,
})
}
pub fn get_ambient_light_intensity_single_shot(
&mut self,
delay: &mut D,
i2c: &mut I2C,
) -> Result<f32> {
self.set_mode(Mode::SingleShot, i2c)?;
delay.delay_ms(self.integration_time_ms as u32);
self.read_ambient_light_intensity(i2c)
}
pub fn start_continuous_measurement(&mut self, i2c: &mut I2C) -> Result<()> {
self.set_mode(Mode::Continuous, i2c)
}
pub fn stop_continuous_measurement(&mut self, i2c: &mut I2C) -> Result<()> {
self.set_mode(Mode::PowerDown, i2c)
}
pub fn get_last_ambient_light_intensity(&mut self, i2c: &mut I2C) -> Result<f32> {
self.read_ambient_light_intensity(i2c)
}
pub fn set_integration_time(&mut self, time_ms: f32, i2c: &mut I2C) -> Result<()> {
self.integration_time_ms = time_ms;
self.write_register(DataRegister::Timing, Self::itime_ms_to_itime(time_ms), i2c)
}
pub fn read_integration_time(&mut self, i2c: &mut I2C) -> Result<f32> {
let itime = self.read_register(DataRegister::Timing, i2c)?;
Ok(Self::itime_to_itime_ms(itime))
}
pub fn set_gain(&mut self, gain: Gain, i2c: &mut I2C) -> Result<()> {
self.gain = gain;
self.write_register(DataRegister::Gain, gain.into_reg_value(), i2c)
}
pub fn read_gain(&mut self, i2c: &mut I2C) -> Result<Gain> {
let gain = self.read_register(DataRegister::Gain, i2c)?;
Ok(match gain & 0x07 {
0x0 => Gain::X1,
0x1 => Gain::X2,
0x2 => Gain::X64,
0x3 => Gain::X128,
_ => panic!("Invalid gain value"),
})
}
pub fn set_mode(&mut self, mode: Mode, i2c: &mut I2C) -> Result<()> {
let control_register_value = match mode {
Mode::PowerDown => 0x00,
Mode::SingleShot => 0x0b,
Mode::Continuous => 0x03,
};
self.write_register(DataRegister::Control, control_register_value, i2c)
}
pub fn read_ambient_light_intensity(&mut self, i2c: &mut I2C) -> Result<f32> {
if (self.read_register(DataRegister::Control, i2c)? & 0x10) == 0 {
return Err(BH1730FVCError::NoDataAvailable);
}
let (data0, data1) = self.read_light_raw_values(i2c)?;
let lux = self.calculate_lux(data0, data1);
Ok(lux)
}
fn calculate_lux(&mut self, data0: u16, data1: u16) -> f32 {
let data0 = data0 as f32;
let data1 = data1 as f32;
let ratio = data1 / data0;
let lux = if ratio < 0.26 {
(1.290 * data0 - 2.733 * data1)
/ (f32::from(self.gain) * 102.6 / self.integration_time_ms)
} else if ratio < 0.55 {
(0.795 * data0 - 0.859 * data1)
/ (f32::from(self.gain) * 102.6 / self.integration_time_ms)
} else if ratio < 1.09 {
(0.510 * data0 - 0.345 * data1)
/ (f32::from(self.gain) * 102.6 / self.integration_time_ms)
} else if ratio < 2.13 {
(0.276 * data0 - 0.130 * data1)
/ (f32::from(self.gain) * 102.6 / self.integration_time_ms)
} else {
0.0
};
lux
}
pub fn write_register(
&mut self,
register: DataRegister,
data: u8,
i2c: &mut I2C,
) -> Result<()> {
let register_address = register as u8;
let command_reg_value = register_address | 0x80; let write_data = [command_reg_value, data];
i2c.write(BH1730FVC_ADDR, &write_data)
.map_err(|_| BH1730FVCError::WriteI2CError)?;
Ok(())
}
pub fn read_register(&mut self, register: DataRegister, i2c: &mut I2C) -> Result<u8> {
let register_address = register as u8;
let command_reg_value = register_address | 0x80; let mut read_data = [0; 1];
i2c.write_read(BH1730FVC_ADDR, &[command_reg_value], &mut read_data)
.map_err(|_| BH1730FVCError::ReadI2CError)?;
Ok(read_data[0])
}
pub fn read_light_raw_values(&mut self, i2c: &mut I2C) -> Result<(u16, u16)> {
let mut read_data = [0; 4];
i2c.write_read(
BH1730FVC_ADDR,
&[DataRegister::Data0Low as u8 | 0x80],
&mut read_data,
)
.map_err(|_| BH1730FVCError::ReadI2CError)?;
log::info!("Read raw values: {:?}", read_data);
let data0 = ((read_data[1] as u16) << 8) | read_data[0] as u16;
let data1 = ((read_data[3] as u16) << 8) | read_data[2] as u16;
Ok((data0, data1))
}
pub fn read_id(&mut self, i2c: &mut I2C) -> Result<(u8, u8)> {
let id_raw = self.read_register(DataRegister::ID, i2c)?;
Ok((id_raw >> 4, id_raw & 0x0F))
}
pub(crate) fn itime_ms_to_itime(itime_ms: f32) -> u8 {
let itime = 256.0 - (itime_ms * 1000.0 / (TINT_US * 964.0));
itime as u8
}
pub(crate) fn itime_to_itime_ms(itime: u8) -> f32 {
let itime_ms = (TINT_US * 964.0 * (256.0 - itime as f32)) / 1000.0;
itime_ms
}
}
type Result<T> = core::result::Result<T, BH1730FVCError>;
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum BH1730FVCError {
ReadI2CError,
WriteI2CError,
NoDataAvailable,
}
pub enum DataRegister {
Control = 0x00,
Timing = 0x01,
Interrupt = 0x02,
InterruptLowThresholdLow = 0x03,
InterruptLowThresholdHigh = 0x04,
InterruptHighThresholdLow = 0x05,
InterruptHighThresholdHigh = 0x06,
Gain = 0x07,
ID = 0x12,
Data0Low = 0x14,
Data0High = 0x15,
Data1Low = 0x16,
Data1High = 0x17,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum Gain {
X1 = 0x0,
X2 = 0x1,
X64 = 0x2,
X128 = 0x3,
}
impl From<Gain> for f32 {
fn from(gain: Gain) -> Self {
match gain {
Gain::X1 => 1.0,
Gain::X2 => 2.0,
Gain::X64 => 64.0,
Gain::X128 => 128.0,
}
}
}
impl Gain {
pub fn into_reg_value(self) -> u8 {
self as u8
}
}
#[derive(Copy, Clone, Debug)]
pub enum Mode {
PowerDown = 0x00,
SingleShot = 0x01,
Continuous = 0x02,
}
#[cfg(test)]
mod tests {
use super::*;
use embedded_hal_mock::i2c::Mock as I2cMock;
use embedded_hal_mock::MockError;
use embedded_hal_mock::{delay::MockNoop as DelayMock, i2c::Transaction as I2cTransaction};
#[test]
fn test_get_ambient_light_intensity_single_shot() {
let expectations = [
I2cTransaction::write(BH1730FVC_ADDR, [0xe4].to_vec()),
I2cTransaction::write(BH1730FVC_ADDR, [0x80, 0x0b].to_vec()),
I2cTransaction::write_read(BH1730FVC_ADDR, [0x80].to_vec(), [0x10].to_vec()),
I2cTransaction::write_read(
BH1730FVC_ADDR,
[(0x14 | 0x80)].to_vec(),
[0xA4, 0x0, 0x0, 0x1].to_vec(),
),
];
let mut i2c_mock = I2cMock::new(&expectations);
let mut delay_mock = DelayMock::new();
let mut bh1730fvc = BH1730FVC::new(&mut delay_mock, &mut i2c_mock).unwrap();
let lux_val = bh1730fvc
.get_ambient_light_intensity_single_shot(&mut delay_mock, &mut i2c_mock)
.unwrap();
assert_eq!(lux_val, 11.984001);
i2c_mock.done();
}
#[test]
fn test_continous_measurement() {
let expectations = [
I2cTransaction::write(BH1730FVC_ADDR, [0xe4].to_vec()),
I2cTransaction::write(BH1730FVC_ADDR, [0x80, 0x03].to_vec()),
I2cTransaction::write_read(BH1730FVC_ADDR, [0x80].to_vec(), [0x10].to_vec()),
I2cTransaction::write_read(
BH1730FVC_ADDR,
[(0x14 | 0x80)].to_vec(),
[0xA4, 0x0, 0x0, 0x1].to_vec(),
),
I2cTransaction::write_read(BH1730FVC_ADDR, [0x80].to_vec(), [0x0f].to_vec()),
I2cTransaction::write(BH1730FVC_ADDR, [0x80, 0x00].to_vec()),
];
let mut i2c_mock = I2cMock::new(&expectations);
let mut delay_mock = DelayMock::new();
let mut bh1730fvc = BH1730FVC::new(&mut delay_mock, &mut i2c_mock).unwrap();
bh1730fvc
.start_continuous_measurement(&mut i2c_mock)
.unwrap();
let lux_val = bh1730fvc
.get_last_ambient_light_intensity(&mut i2c_mock)
.unwrap();
assert_eq!(lux_val, 11.984001);
let result = bh1730fvc.get_last_ambient_light_intensity(&mut i2c_mock);
assert!(result.is_err());
bh1730fvc
.stop_continuous_measurement(&mut i2c_mock)
.unwrap();
i2c_mock.done();
}
#[test]
fn test_calculate_lux() {
let expectations = [
I2cTransaction::write(BH1730FVC_ADDR, [0xE4].to_vec()),
I2cTransaction::write(BH1730FVC_ADDR, [0x07 | 0x80, 0x01].to_vec()),
I2cTransaction::write(BH1730FVC_ADDR, [0x01 | 0x80, 0xB5].to_vec()),
];
let mut i2c_mock = I2cMock::new(&expectations); let mut delay_mock = DelayMock::new();
let mut sensor = BH1730FVC::new(&mut delay_mock, &mut i2c_mock).unwrap();
let integration_time_ms = 200.0;
sensor.set_gain(Gain::X2, &mut i2c_mock).unwrap();
sensor
.set_integration_time(integration_time_ms, &mut i2c_mock)
.unwrap();
let data0 = 100;
let data1 = 20;
let expected_lux = (1.290 * data0 as f32 - 2.733 * data1 as f32)
/ (f32::from(sensor.gain) * 102.6 / sensor.integration_time_ms);
assert_eq!(sensor.calculate_lux(data0, data1), expected_lux);
let data0 = 100;
let data1 = 40;
let expected_lux = (0.795 * data0 as f32 - 0.859 * data1 as f32)
/ (f32::from(sensor.gain) * 102.6 / sensor.integration_time_ms);
assert_eq!(sensor.calculate_lux(data0, data1), expected_lux);
let data0 = 100;
let data1 = 70;
let expected_lux = (0.510 * data0 as f32 - 0.345 * data1 as f32)
/ (f32::from(sensor.gain) * 102.6 / sensor.integration_time_ms);
assert_eq!(sensor.calculate_lux(data0, data1), expected_lux);
let data0 = 100;
let data1 = 120;
let expected_lux = (0.276 * data0 as f32 - 0.130 * data1 as f32)
/ (f32::from(sensor.gain) * 102.6 / sensor.integration_time_ms);
assert_eq!(sensor.calculate_lux(data0, data1), expected_lux);
let data0 = 100;
let data1 = 250;
let expected_lux = 0.0;
assert_eq!(sensor.calculate_lux(data0, data1), expected_lux);
}
#[test]
fn test_write_error() {
let expectations = [
I2cTransaction::write(BH1730FVC_ADDR, [0xE4].to_vec()),
I2cTransaction::write(BH1730FVC_ADDR, [0x80, 0x00].to_vec()),
I2cTransaction::write(BH1730FVC_ADDR, [0x80, 0x00].to_vec())
.with_error(MockError::Io(std::io::ErrorKind::Other)),
];
let mut i2c_mock = I2cMock::new(&expectations);
let mut delay_mock = DelayMock::new();
let mut sensor = BH1730FVC::new(&mut delay_mock, &mut i2c_mock).unwrap();
let result = sensor.write_register(DataRegister::Control, 0x00, &mut i2c_mock);
assert_eq!(result, Ok(()));
let result = sensor.write_register(DataRegister::Control, 0x00, &mut i2c_mock);
assert_eq!(result, Err(BH1730FVCError::WriteI2CError));
i2c_mock.done();
}
#[test]
fn test_read_error() {
let expectations = [
I2cTransaction::write(BH1730FVC_ADDR, [0xE4].to_vec()),
I2cTransaction::write_read(BH1730FVC_ADDR, [0x80].to_vec(), [0x00].to_vec()),
I2cTransaction::write_read(BH1730FVC_ADDR, [0x80].to_vec(), [0x00].to_vec())
.with_error(MockError::Io(std::io::ErrorKind::Other)),
];
let mut i2c_mock = I2cMock::new(&expectations);
let mut delay_mock = DelayMock::new();
let mut sensor = BH1730FVC::new(&mut delay_mock, &mut i2c_mock).unwrap();
let result = sensor.read_register(DataRegister::Control, &mut i2c_mock);
assert_eq!(result, Ok(0x00));
let result = sensor.read_register(DataRegister::Control, &mut i2c_mock);
assert_eq!(result, Err(BH1730FVCError::ReadI2CError));
i2c_mock.done();
}
#[test]
fn test_read_id() {
let expectations = [
I2cTransaction::write(BH1730FVC_ADDR, [0xE4].to_vec()),
I2cTransaction::write_read(BH1730FVC_ADDR, [0x92].to_vec(), [0x12].to_vec()),
];
let mut i2c_mock = I2cMock::new(&expectations);
let mut delay_mock = DelayMock::new();
let mut sensor = BH1730FVC::new(&mut delay_mock, &mut i2c_mock).unwrap();
let result = sensor.read_id(&mut i2c_mock);
assert_eq!(result, Ok((0x1, 0x2)));
i2c_mock.done();
}
#[test]
fn test_set_integration_time() {
let expectations = [
I2cTransaction::write(BH1730FVC_ADDR, [0xE4].to_vec()),
I2cTransaction::write(BH1730FVC_ADDR, [0x81, 0xC8].to_vec()),
];
let mut i2c_mock = I2cMock::new(&expectations);
let mut delay_mock = DelayMock::new();
let mut sensor = BH1730FVC::new(&mut delay_mock, &mut i2c_mock).unwrap();
let result = sensor.set_integration_time(150.0, &mut i2c_mock);
assert_eq!(result, Ok(()));
i2c_mock.done();
}
#[test]
fn test_read_integration_time() {
let expectations = [
I2cTransaction::write(BH1730FVC_ADDR, [0xE4].to_vec()),
I2cTransaction::write_read(BH1730FVC_ADDR, [0x81].to_vec(), [0x2a].to_vec()),
];
let mut i2c_mock = I2cMock::new(&expectations);
let mut delay_mock = DelayMock::new();
let mut sensor = BH1730FVC::new(&mut delay_mock, &mut i2c_mock).unwrap();
let result = sensor.read_integration_time(&mut i2c_mock);
assert_eq!(result, Ok(577.6288));
i2c_mock.done();
}
#[test]
fn test_set_gain() {
let expectations = [
I2cTransaction::write(BH1730FVC_ADDR, [0xE4].to_vec()),
I2cTransaction::write(BH1730FVC_ADDR, [0x87, 0x02].to_vec()),
];
let mut i2c_mock = I2cMock::new(&expectations);
let mut delay_mock = DelayMock::new();
let mut sensor = BH1730FVC::new(&mut delay_mock, &mut i2c_mock).unwrap();
let result = sensor.set_gain(Gain::X64, &mut i2c_mock);
assert_eq!(result, Ok(()));
i2c_mock.done();
}
#[test]
fn test_read_gain() {
let expectations = [
I2cTransaction::write(BH1730FVC_ADDR, [0xE4].to_vec()),
I2cTransaction::write_read(BH1730FVC_ADDR, [0x87].to_vec(), [0x03].to_vec()),
];
let mut i2c_mock = I2cMock::new(&expectations);
let mut delay_mock = DelayMock::new();
let mut sensor = BH1730FVC::new(&mut delay_mock, &mut i2c_mock).unwrap();
let result = sensor.read_gain(&mut i2c_mock).unwrap();
assert_eq!(result, Gain::X128);
i2c_mock.done();
}
#[test]
fn test_itime() {
let itime_ms = 250.0;
let itime: u8 = BH1730FVC::<I2cMock, DelayMock>::itime_ms_to_itime(itime_ms);
assert_eq!(itime, 0xA3);
let itime_ms = BH1730FVC::<I2cMock, DelayMock>::itime_to_itime_ms(itime);
assert!((itime_ms - 250.0).abs() < 2.5);
}
}