commit c52b21d95183310eae38bd7ce6c0355e77628fa1 Author: Niklas Kapelle Date: Sat Dec 13 22:54:53 2025 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c7dfcf2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "as7265x-rust" +version = "0.1.0" +dependencies = [ + "embedded-hal", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b00bd74 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "as7265x-rust" +version = "0.1.0" +edition = "2024" + +[dependencies] +embedded-hal = "1.0.0" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5c613e3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,597 @@ +#![no_std] + +use embedded_hal::{ + delay::DelayNs, + i2c::{self}, +}; + +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 { + pub fn get_device_for_chanel(&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 + } + } + } + + 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, + } + } + + fn get_cal_address(&self) -> u8 { + let raw = self.get_raw_address(); + ((raw - 0x08) / 2) * 4 + 0x14 + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Device { + Visable = 0x00, + Nir = 0x01, + Uv = 0x02, +} + +#[derive(Debug, Clone, Copy)] +pub enum Led { + White, + Ir, + Uv, +} + +impl Led { + 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 { + fn to_bits(self) -> u8 { + match self { + GainConfig::Gain1x => 0b00, + GainConfig::Gain3_7x => 0b01, + GainConfig::Gain16x => 0b10, + GainConfig::Gain64x => 0b11, + } + } +} + +/// Measurement mode options +#[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 { + fn to_bits(self) -> u8 { + match self { + MeasurementMode::Mode0 => 0b00, + MeasurementMode::Mode1 => 0b01, + MeasurementMode::Mode2Continuous => 0b10, + MeasurementMode::Mode3OneShot => 0b11, + } + } +} + +#[derive(Debug, Clone)] +pub struct AS7265XConfig { + pub enable_interrupt: bool, + pub gain: GainConfig, + pub measurement_mode: MeasurementMode, + pub integration_time: u8, +} + +impl Default for AS7265XConfig { + fn default() -> Self { + Self { + enable_interrupt: false, + gain: GainConfig::Gain64x, + measurement_mode: MeasurementMode::Mode3OneShot, + integration_time: 20, + } + } +} + +impl AS7265XConfig { + fn to_config_byte(&self) -> u8 { + let mut byte = 0u8; + + if self.enable_interrupt { + byte |= ENABLE_INTERRUPT; + } + + byte |= (self.gain.to_bits() << CONFIG_GAIN_SHIFT) & CONFIG_GAIN_MASK; + byte |= (self.measurement_mode.to_bits() << CONFIG_BANK_SHIFT) & CONFIG_BANK_MASK; + + byte + } +} + +#[derive(Debug, Clone, Copy)] +pub enum LedCurrent { + /// 12.5 mA + Ma12_5, + + /// 25 mA + Ma25, + + /// 50 mA + Ma50, + + /// 100 mA + Ma100, +} + +impl LedCurrent { + fn to_bits(self) -> u8 { + match self { + LedCurrent::Ma12_5 => 0b00, + LedCurrent::Ma25 => 0b01, + LedCurrent::Ma50 => 0b10, + LedCurrent::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: LedCurrent, + pub ind_enabled: bool, + pub ind_current: LedIndicatorCurrent, +} + +impl LedConfig { + fn config_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: LedCurrent::Ma12_5, + ind_enabled: false, + ind_current: LedIndicatorCurrent::Ma8, + } + } +} + +#[derive(Debug)] +pub enum Error { + I2c(E), + Timeout, +} + +impl From for Error { + fn from(e: E) -> Self { + Error::I2c(e) + } +} + +pub struct AS7265X +where + I: i2c::I2c, + D: DelayNs, +{ + device: I, + delay: D, + config: AS7265XConfig, +} + +impl AS7265X +where + I: i2c::I2c, + D: DelayNs, +{ + pub fn new(device: I, config: AS7265XConfig, delay: D) -> Self { + Self { + device, + config, + delay, + } + } + + pub async fn init(&mut self) -> Result<(), 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(()) + } + + pub async fn soft_reset(&mut self) -> Result<(), Error> { + self.write_virtual_register(CONFIG_ADDR, SOFT_RESET).await?; + self.delay.delay_ms(100); + Ok(()) + } + + async fn write_config(&mut self) -> Result<(), Error> { + let config_byte = self.config.to_config_byte(); + self.write_virtual_register(CONFIG_ADDR, config_byte).await + } + + pub async fn set_integration_time(&mut self, time: u8) -> Result<(), Error> { + self.write_virtual_register(INTEGRATION_ADDR, time).await + } + + async fn set_led_config(&mut self, led: Led, config: LedConfig) -> Result<(), Error> { + self.select_device(led.get_device()).await?; + let config_byte = config.config_byte(); + self.write_virtual_register(LED_ADDR, config_byte).await + } + + pub async fn get_device_type(&mut self) -> Result> { + self.read_virtual_register(HW_VERSION_HIGH_ADDR).await + } + + pub async fn get_hardware_version(&mut self) -> Result> { + self.read_virtual_register(HW_VERSION_LOW_ADDR).await + } + + pub async fn get_firmware_version(&mut self) -> Result<(u8, u8), Error> { + let high = self.read_virtual_register(FW_VERSION_HIGH_ADDR).await?; + let low = self.read_virtual_register(FW_VERSION_LOW_ADDR).await?; + Ok((high, low)) + } + + async fn select_device(&mut self, device: Device) -> Result<(), Error> { + self.write_virtual_register(DEV_SELECT_ADDR, device as u8) + .await + } + + pub async fn read_temperature(&mut self) -> Result> { + let temp = self.read_virtual_register(TEMP_ADDR).await?; + Ok(temp as i8) + } + + pub async fn is_data_ready(&mut self) -> Result> { + let config = self.read_virtual_register(CONFIG_ADDR).await?; + Ok((config & CONFIG_DATA_RDY) != 0) + } + + pub async fn measure(&mut self) -> Result<(), Error> { + self.write_config().await?; + let wait_time = (self.config.integration_time as u32) * 28 / 10 * 2; + self.delay.delay_ms(wait_time); + Ok(()) + } + + pub async fn mesure_with_bulb(&mut self) -> Result<(), 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(()) + } + + pub async fn read_raw_measurement(&mut self, channel: Channel) -> Result> { + let dev = channel.get_device_for_chanel(); + self.select_device(dev).await?; + self.read_raw_channel(channel.get_raw_address()).await + } + + pub async fn read_calibrated_messurement( + &mut self, + channel: Channel, + ) -> Result> { + let dev = channel.get_device_for_chanel(); + self.select_device(dev).await?; + + self.read_calibrated_channel(channel.get_cal_address()) + .await + } + + async fn write_virtual_register( + &mut self, + virtual_addr: u8, + data: u8, + ) -> Result<(), 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)?; + if (status[0] & TX_VALID) == 0 { + break; + } + self.delay.delay_ms(5); + } + + self.device + .write(AS7265X_ADDR, &[WRITE_REG, 0x80 | virtual_addr])?; + + for _ in 0..MAX_RETRIES { + let mut status = [0u8; 1]; + self.device + .write_read(AS7265X_ADDR, &[STATUS_REG], &mut status)?; + if (status[0] & TX_VALID) == 0 { + break; + } + self.delay.delay_ms(5); + } + + self.device.write(AS7265X_ADDR, &[WRITE_REG, data])?; + + Ok(()) + } + + async fn read_virtual_register(&mut self, virtual_addr: u8) -> Result> { + 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)?; + if (status[0] & TX_VALID) == 0 { + break; + } + self.delay.delay_ms(5); + } + + self.device + .write(AS7265X_ADDR, &[WRITE_REG, virtual_addr])?; + + for _ in 0..MAX_RETRIES { + let mut status = [0u8; 1]; + self.device + .write_read(AS7265X_ADDR, &[STATUS_REG], &mut status)?; + if (status[0] & RX_VALID) != 0 { + break; + } + self.delay.delay_ms(5); + } + + let mut data = [0u8; 1]; + self.device + .write_read(AS7265X_ADDR, &[READ_REG], &mut data)?; + + Ok(data[0]) + } + + async fn read_raw_channel(&mut self, high_byte_addr: u8) -> Result> { + 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)) + } + + async fn read_calibrated_channel( + &mut self, + high_byte_addr: u8, + ) -> Result> { + 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)) + } +}