Files
as7265x-rust/src/lib.rs

661 lines
16 KiB
Rust

#![no_std]
#[cfg(not(feature = "async"))]
use embedded_hal::{
delay::DelayNs,
i2c::{self},
};
#[cfg(feature = "async")]
use embedded_hal_async::{
delay::DelayNs,
i2c::{self},
};
use maybe_async::maybe_async;
const AS7265X_ADDR: u8 = 0x49;
const STATUS_REG: u8 = 0x00;
const WRITE_REG: u8 = 0x01;
const READ_REG: u8 = 0x02;
const TX_VALID: u8 = 0x02;
const RX_VALID: u8 = 0x01;
const HW_VERSION_HIGH_ADDR: u8 = 0x00;
const HW_VERSION_LOW_ADDR: u8 = 0x01;
const FW_VERSION_HIGH_ADDR: u8 = 0x02;
const FW_VERSION_LOW_ADDR: u8 = 0x03;
const CONFIG_ADDR: u8 = 0x04;
const INTEGRATION_ADDR: u8 = 0x05;
const TEMP_ADDR: u8 = 0x06;
const LED_ADDR: u8 = 0x07;
const DEV_SELECT_ADDR: u8 = 0x4F;
const SOFT_RESET: u8 = 0b10000000;
const ENABLE_INTERRUPT: u8 = 0b01000000;
const CONFIG_GAIN_MASK: u8 = 0x30;
const CONFIG_GAIN_SHIFT: u8 = 4;
const CONFIG_BANK_MASK: u8 = 0x0C;
const CONFIG_BANK_SHIFT: u8 = 2;
const CONFIG_DATA_RDY: u8 = 0x02;
const LED_DRV_ICL_MASK: u8 = 0x30;
const LED_DRV_ICL_SHIFT: u8 = 4;
const LED_DRV_ENABLE: u8 = 0x08;
const LED_IND_ICL_MASK: u8 = 0x06;
const LED_IND_ICL_SHIFT: u8 = 1;
const LED_IND_ENABLE: u8 = 0x01;
#[derive(Debug, Clone, Copy)]
pub enum Channel {
/// 410nm - AS72653
A,
/// 435nm - AS72653
B,
/// 460nm - AS72653
C,
/// 485nm - AS72653
D,
/// 510nm - AS72653
E,
/// 535nm - AS72653
F,
/// 560nm - AS72652
G,
/// 585nm - AS72652
H,
/// 610nm - AS72651
R,
/// 645nm - AS72652
I,
/// 680nm - AS72651
S,
/// 705nm - AS72652
J,
/// 730nm - AS72651
T,
/// 760nm - AS72651
U,
/// 810nm - AS72651
V,
/// 860nm - AS72651
W,
/// 900nm - AS72652
K,
/// 940nm - AS72652
L,
}
impl Channel {
/// Each channel is only read from one of three sensors
pub fn get_device_for_channel(&self) -> Device {
match self {
Channel::A | Channel::B | Channel::C | Channel::D | Channel::E | Channel::F => {
Device::Uv
}
Channel::G | Channel::H | Channel::I | Channel::J | Channel::K | Channel::L => {
Device::Visable
}
Channel::R | Channel::S | Channel::T | Channel::U | Channel::V | Channel::W => {
Device::Nir
}
}
}
/// Get the virtual register address for the raw value of the channel
fn get_raw_address(&self) -> u8 {
match self {
Channel::A | Channel::G | Channel::R => 0x08,
Channel::B | Channel::H | Channel::S => 0x0A,
Channel::C | Channel::I | Channel::T => 0x0C,
Channel::D | Channel::J | Channel::U => 0x0E,
Channel::E | Channel::K | Channel::V => 0x10,
Channel::F | Channel::L | Channel::W => 0x12,
}
}
/// Get the virtual register address for the calibrated value of the channel
fn get_cal_address(&self) -> u8 {
let raw = self.get_raw_address();
((raw - 0x08) / 2) * 4 + 0x14
}
}
/// The AS7265X has three sensor devices
#[derive(Debug, Clone, Copy)]
pub enum Device {
Visable = 0x00,
Nir = 0x01,
Uv = 0x02,
}
/// The three main LEDs on the device
#[derive(Debug, Clone, Copy)]
pub enum Led {
White,
Ir,
Uv,
}
impl Led {
/// Apparently the LEDs don't match the device the are for
fn get_device(&self) -> Device {
match self {
Led::White => Device::Nir,
Led::Ir => Device::Visable,
Led::Uv => Device::Uv,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum GainConfig {
/// x1
Gain1x,
/// x3.7
Gain3_7x,
/// x16
Gain16x,
/// x64
Gain64x,
}
impl GainConfig {
/// To byte that can be written to the virtual register
fn to_byte(self) -> u8 {
match self {
GainConfig::Gain1x => 0b00,
GainConfig::Gain3_7x => 0b01,
GainConfig::Gain16x => 0b10,
GainConfig::Gain64x => 0b11,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum MeasurementMode {
/// 4 channels
Mode0,
/// 4 channels
Mode1,
/// All 6 channels
Mode2Continuous,
/// One-Shot operation of mode 2
Mode3OneShot,
}
impl MeasurementMode {
/// To byte that can be written to the virtual register
fn to_byte(self) -> u8 {
match self {
MeasurementMode::Mode0 => 0b00,
MeasurementMode::Mode1 => 0b01,
MeasurementMode::Mode2Continuous => 0b10,
MeasurementMode::Mode3OneShot => 0b11,
}
}
}
#[derive(Debug, Clone)]
pub struct AS7265XConfig {
/// Enable the onboard interrupt pin
pub enable_interrupt: bool,
pub gain: GainConfig,
pub measurement_mode: MeasurementMode,
/// Integration time: value * 2.8ms
pub integration_time: u8,
}
impl Default for AS7265XConfig {
fn default() -> Self {
Self {
enable_interrupt: true,
gain: GainConfig::Gain64x,
measurement_mode: MeasurementMode::Mode3OneShot,
integration_time: 49,
}
}
}
impl AS7265XConfig {
/// Convert to byte to write to the config virtual register
fn to_byte(&self) -> u8 {
let mut byte = 0u8;
if self.enable_interrupt {
byte |= ENABLE_INTERRUPT;
}
byte |= (self.gain.to_byte() << CONFIG_GAIN_SHIFT) & CONFIG_GAIN_MASK;
byte |= (self.measurement_mode.to_byte() << CONFIG_BANK_SHIFT) & CONFIG_BANK_MASK;
byte
}
}
#[derive(Debug, Clone, Copy)]
pub enum LedDriverCurrent {
/// 12.5 mA
Ma12_5,
/// 25 mA
Ma25,
/// 50 mA
Ma50,
/// 100 mA
Ma100,
}
impl LedDriverCurrent {
fn to_bits(self) -> u8 {
match self {
LedDriverCurrent::Ma12_5 => 0b00,
LedDriverCurrent::Ma25 => 0b01,
LedDriverCurrent::Ma50 => 0b10,
LedDriverCurrent::Ma100 => 0b11,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum LedIndicatorCurrent {
/// 1 mA
Ma1,
/// 2 mA
Ma2,
/// 4 mA
Ma4,
/// 8 mA
Ma8,
}
impl LedIndicatorCurrent {
fn to_bits(self) -> u8 {
match self {
LedIndicatorCurrent::Ma1 => 0b00,
LedIndicatorCurrent::Ma2 => 0b01,
LedIndicatorCurrent::Ma4 => 0b10,
LedIndicatorCurrent::Ma8 => 0b11,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct LedConfig {
pub drv_enabled: bool,
pub drv_current: LedDriverCurrent,
pub ind_enabled: bool,
pub ind_current: LedIndicatorCurrent,
}
impl LedConfig {
/// To byte that can be written to the virtual register
fn to_byte(self) -> u8 {
let mut byte = 0u8;
if self.drv_enabled {
byte |= LED_DRV_ENABLE;
}
byte |= (self.drv_current.to_bits() << LED_DRV_ICL_SHIFT) & LED_DRV_ICL_MASK;
if self.ind_enabled {
byte |= LED_IND_ENABLE;
}
byte |= (self.ind_current.to_bits() << LED_IND_ICL_SHIFT) & LED_IND_ICL_MASK;
byte
}
}
impl Default for LedConfig {
fn default() -> Self {
Self {
drv_enabled: false,
drv_current: LedDriverCurrent::Ma12_5,
ind_enabled: false,
ind_current: LedIndicatorCurrent::Ma8,
}
}
}
#[derive(Debug)]
pub enum Error<E> {
I2c(E),
Timeout,
}
impl<E> From<E> for Error<E> {
fn from(e: E) -> Self {
Error::I2c(e)
}
}
/// The AS7265X device
pub struct AS7265X<I, D>
where
I: i2c::I2c,
D: DelayNs,
{
device: I,
delay: D,
config: AS7265XConfig,
}
#[maybe_async]
impl<I, D> AS7265X<I, D>
where
I: i2c::I2c,
D: DelayNs,
{
/// Create a new AS7265X device
pub fn new(device: I, config: AS7265XConfig, delay: D) -> Self {
Self {
device,
config,
delay,
}
}
/// Initialize the device.
/// Writes config, integration time and led config.
/// Ok to call multiple times & most likly not need but the cpp driver also does this.
#[maybe_async]
pub async fn init(&mut self) -> Result<(), Error<I::Error>> {
self.write_config().await?;
self.set_integration_time(self.config.integration_time)
.await?;
self.set_led_config(
Led::White,
LedConfig {
..Default::default()
},
)
.await?;
self.set_led_config(
Led::Ir,
LedConfig {
..Default::default()
},
)
.await?;
self.set_led_config(
Led::Uv,
LedConfig {
..Default::default()
},
)
.await?;
Ok(())
}
/// Perform a soft reset
#[maybe_async]
pub async fn soft_reset(&mut self) -> Result<(), Error<I::Error>> {
self.write_virtual_register(CONFIG_ADDR, SOFT_RESET).await?;
self.delay.delay_ms(100).await;
Ok(())
}
/// Write the config to the device
/// Also need to fire the oneshot mode again
#[maybe_async]
async fn write_config(&mut self) -> Result<(), Error<I::Error>> {
let config_byte = self.config.to_byte();
self.write_virtual_register(CONFIG_ADDR, config_byte).await
}
/// Sets the integration time value
/// value * 2.8ms
#[maybe_async]
pub async fn set_integration_time(&mut self, time: u8) -> Result<(), Error<I::Error>> {
self.config.integration_time = time;
self.write_virtual_register(INTEGRATION_ADDR, time).await
}
/// Write the LED specific config
#[maybe_async]
async fn set_led_config(&mut self, led: Led, config: LedConfig) -> Result<(), Error<I::Error>> {
self.select_device(led.get_device()).await?;
let config_byte = config.to_byte();
self.write_virtual_register(LED_ADDR, config_byte).await
}
/// Get the hardware info from the hw version register
#[maybe_async]
pub async fn get_device_type(&mut self) -> Result<u8, Error<I::Error>> {
self.read_virtual_register(HW_VERSION_HIGH_ADDR).await
}
/// Get the hardware info from the hw version register
#[maybe_async]
pub async fn get_hardware_version(&mut self) -> Result<u8, Error<I::Error>> {
self.read_virtual_register(HW_VERSION_LOW_ADDR).await
}
/// Alot of function only work if you have the correct device selected
#[maybe_async]
async fn select_device(&mut self, device: Device) -> Result<(), Error<I::Error>> {
self.write_virtual_register(DEV_SELECT_ADDR, device as u8)
.await
}
/// Read the integrated temperature sensor in °C
#[maybe_async]
pub async fn read_temperature(&mut self) -> Result<i8, Error<I::Error>> {
let temp = self.read_virtual_register(TEMP_ADDR).await?;
Ok(temp as i8)
}
/// Check if the internal register reports data ready
#[maybe_async]
pub async fn is_data_ready(&mut self) -> Result<bool, Error<I::Error>> {
let config = self.read_virtual_register(CONFIG_ADDR).await?;
Ok((config & CONFIG_DATA_RDY) != 0)
}
/// Run a messurement
/// Only needed if messurement mode is set to One-Shot
/// Basicly rewrites the config and waits the current integration time
#[maybe_async]
pub async fn measure(&mut self) -> Result<(), Error<I::Error>> {
self.write_config().await?;
let wait_time = (self.config.integration_time as u32) * 28 / 10 * 2;
self.delay.delay_ms(wait_time).await;
Ok(())
}
/// Same as measure but enable LED befor and disable after
#[maybe_async]
pub async fn mesure_with_bulb(&mut self) -> Result<(), Error<I::Error>> {
self.set_led_config(
Led::White,
LedConfig {
drv_enabled: true,
..Default::default()
},
)
.await?;
self.set_led_config(
Led::Ir,
LedConfig {
drv_enabled: true,
..Default::default()
},
)
.await?;
self.set_led_config(
Led::Uv,
LedConfig {
drv_enabled: true,
..Default::default()
},
)
.await?;
self.measure().await?;
self.set_led_config(
Led::White,
LedConfig {
..Default::default()
},
)
.await?;
self.set_led_config(
Led::Ir,
LedConfig {
..Default::default()
},
)
.await?;
self.set_led_config(
Led::Uv,
LedConfig {
..Default::default()
},
)
.await?;
Ok(())
}
/// Read the raw value for a channel
#[maybe_async]
pub async fn read_raw_measurement(&mut self, channel: Channel) -> Result<u16, Error<I::Error>> {
let dev = channel.get_device_for_channel();
self.select_device(dev).await?;
self.read_raw_channel(channel.get_raw_address()).await
}
/// Read the calibrated value for a channel
#[maybe_async]
pub async fn read_calibrated_messurement(
&mut self,
channel: Channel,
) -> Result<f32, Error<I::Error>> {
let dev = channel.get_device_for_channel();
self.select_device(dev).await?;
self.read_calibrated_channel(channel.get_cal_address())
.await
}
/// Read u16 value from address
#[maybe_async]
async fn read_raw_channel(&mut self, high_byte_addr: u8) -> Result<u16, Error<I::Error>> {
let high = self.read_virtual_register(high_byte_addr).await?;
let low = self.read_virtual_register(high_byte_addr + 1).await?;
Ok(((high as u16) << 8) | (low as u16))
}
/// Read f32 value from address
#[maybe_async]
async fn read_calibrated_channel(
&mut self,
high_byte_addr: u8,
) -> Result<f32, Error<I::Error>> {
let b0 = self.read_virtual_register(high_byte_addr).await?;
let b1 = self.read_virtual_register(high_byte_addr + 1).await?;
let b2 = self.read_virtual_register(high_byte_addr + 2).await?;
let b3 = self.read_virtual_register(high_byte_addr + 3).await?;
let bits = u32::from_be_bytes([b0, b1, b2, b3]);
Ok(f32::from_bits(bits))
}
/// Write to a virtual register
#[maybe_async]
async fn write_virtual_register(
&mut self,
virtual_addr: u8,
data: u8,
) -> Result<(), Error<I::Error>> {
const MAX_RETRIES: u8 = 100;
for _ in 0..MAX_RETRIES {
let mut status = [0u8; 1];
self.device
.write_read(AS7265X_ADDR, &[STATUS_REG], &mut status)
.await?;
if (status[0] & TX_VALID) == 0 {
break;
}
self.delay.delay_ms(5).await;
}
self.device
.write(AS7265X_ADDR, &[WRITE_REG, 0x80 | virtual_addr])
.await?;
for _ in 0..MAX_RETRIES {
let mut status = [0u8; 1];
self.device
.write_read(AS7265X_ADDR, &[STATUS_REG], &mut status)
.await?;
if (status[0] & TX_VALID) == 0 {
break;
}
self.delay.delay_ms(5).await;
}
self.device.write(AS7265X_ADDR, &[WRITE_REG, data]).await?;
Ok(())
}
/// Read from a virtual register
#[maybe_async]
async fn read_virtual_register(&mut self, virtual_addr: u8) -> Result<u8, Error<I::Error>> {
const MAX_RETRIES: u8 = 100;
for _ in 0..MAX_RETRIES {
let mut status = [0u8; 1];
self.device
.write_read(AS7265X_ADDR, &[STATUS_REG], &mut status)
.await?;
if (status[0] & TX_VALID) == 0 {
break;
}
self.delay.delay_ms(5).await;
}
self.device
.write(AS7265X_ADDR, &[WRITE_REG, virtual_addr])
.await?;
for _ in 0..MAX_RETRIES {
let mut status = [0u8; 1];
self.device
.write_read(AS7265X_ADDR, &[STATUS_REG], &mut status)
.await?;
if (status[0] & RX_VALID) != 0 {
break;
}
self.delay.delay_ms(5).await;
}
let mut data = [0u8; 1];
self.device
.write_read(AS7265X_ADDR, &[READ_REG], &mut data)
.await?;
Ok(data[0])
}
}