#![doc = include_str!("../README.md")]
#![no_std]
#![forbid(missing_docs)]
#![forbid(unsafe_code)]
use device_driver::ll::register::RegisterInterface;
use device_driver::{create_low_level_device, implement_registers, Bit};
use embedded_hal::blocking::delay::DelayMs;
use embedded_hal::blocking::i2c::{Read, Write};
use num_enum::{IntoPrimitive, TryFromPrimitive};
pub const DEFAULT_ADDRESS: u8 = 0x15;
const RA_FW: RegisterAddress = RegisterAddress {
address: 0x1389,
read_code: Some(ModbusFunctionCode::ReadInternalRegisters),
write_code: None,
};
const RA_STATUS: RegisterAddress = RegisterAddress {
address: 0x138A,
read_code: Some(ModbusFunctionCode::ReadInternalRegisters),
write_code: None,
};
const RA_GAS_PPM: RegisterAddress = RegisterAddress {
address: 0x138B,
read_code: Some(ModbusFunctionCode::ReadInternalRegisters),
write_code: None,
};
const RA_RESET: RegisterAddress = RegisterAddress {
address: 0x03E8,
read_code: None,
write_code: Some(ModbusFunctionCode::ForceSingleCoil),
};
const RA_SPC: RegisterAddress = RegisterAddress {
address: 0x03EC,
read_code: None,
write_code: Some(ModbusFunctionCode::ForceSingleCoil),
};
const RA_SLAVE_ADDR: RegisterAddress = RegisterAddress {
address: 0x0FA5,
read_code: Some(ModbusFunctionCode::ReadHoldingRegisters),
write_code: Some(ModbusFunctionCode::PresetSingleRegister),
};
const RA_ABC: RegisterAddress = RegisterAddress {
address: 0x03EE,
read_code: None, write_code: Some(ModbusFunctionCode::ForceSingleCoil),
};
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
pub enum ModbusFunctionCode {
ReadCoilStatus = 0x01,
ReadHoldingRegisters = 0x03,
ReadInternalRegisters = 0x04,
ForceSingleCoil = 0x05,
PresetSingleRegister = 0x06,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct RegisterAddress {
pub address: u16,
pub read_code: Option<ModbusFunctionCode>,
pub write_code: Option<ModbusFunctionCode>,
}
#[repr(u16)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
pub enum AbcLogic {
Enabled = 0xFF00,
Disabled = 0x0000,
}
#[repr(u16)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
pub enum SinglePointCalibration {
Start = 0xFF00,
Stop = 0x0000,
}
#[repr(u16)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
pub enum Reset {
Reset = 0xFF00,
}
#[derive(Debug)]
pub enum InterfaceError {
CommunicationError,
InvalidCommand,
ModbusError(u8),
}
pub struct T67xxInterface<I2C: Read + Write, D: DelayMs<u8>> {
communication_interface: I2C,
delay: D,
addr: u8,
}
impl<I2C: Read + Write, D: DelayMs<u8>> T67xxInterface<I2C, D> {
pub fn new(communication_interface: I2C, delay: D, address: Option<u8>) -> Self {
T67xxInterface {
communication_interface,
delay,
addr: address.unwrap_or(DEFAULT_ADDRESS),
}
}
pub fn free(self) -> (I2C, D) {
(self.communication_interface, self.delay)
}
}
impl<I2C: Read + Write, D: DelayMs<u8>> RegisterInterface for T67xxInterface<I2C, D> {
type Address = RegisterAddress;
type InterfaceError = InterfaceError;
fn read_register(
&mut self,
address: Self::Address,
value: &mut [u8],
) -> Result<(), Self::InterfaceError> {
if value.len() != 2 {
return Err(InterfaceError::InvalidCommand);
}
let mut buf: [u8; 4] = [0; 4];
self.communication_interface
.write(
self.addr,
&[
address.read_code.ok_or(InterfaceError::InvalidCommand)? as u8,
(address.address >> 8) as u8,
(address.address & 0xff) as u8,
0x00,
0x01,
],
)
.map_err(|_| InterfaceError::CommunicationError)?;
self.delay.delay_ms(10);
self.communication_interface
.read(self.addr, &mut buf[..])
.map_err(|_| InterfaceError::CommunicationError)?;
if buf[0] != address.read_code.unwrap() as u8 {
return Err(InterfaceError::ModbusError(buf[0]));
}
value.copy_from_slice(&buf[2..]);
Ok(())
}
fn write_register(
&mut self,
address: Self::Address,
value: &[u8],
) -> Result<(), Self::InterfaceError> {
if value.len() != 2 {
return Err(InterfaceError::InvalidCommand);
}
let mut buf = [
address.write_code.ok_or(InterfaceError::InvalidCommand)? as u8,
(address.address >> 8) as u8,
(address.address & 0xff) as u8,
0,
0,
];
buf[3..].copy_from_slice(value);
self.communication_interface
.write(self.addr, &buf)
.map_err(|_| InterfaceError::CommunicationError)?;
self.delay.delay_ms(10);
if address != RA_RESET {
self.communication_interface
.read(self.addr, &mut buf[..])
.map_err(|_| InterfaceError::CommunicationError)?;
if buf[0] != address.write_code.unwrap() as u8 {
return Err(InterfaceError::ModbusError(buf[0]));
}
}
Ok(())
}
}
impl<I2C: Read + Write, D: DelayMs<u8>> HardwareInterface for T67xxInterface<I2C, D> {}
create_low_level_device!(
T67xxLL {
errors: [InterfaceError],
hardware_interface_requirements: { RegisterInterface<Address = RegisterAddress, InterfaceError = InterfaceError> },
hardware_interface_capabilities: {}
}
);
implement_registers!(
T67xxLL.registers<RegisterAddress> = {
#[generate(Debug)]
firmware_revision(RO, RA_FW, 2) = {
firmware_revision: u16:BE = RO 0..16,
},
#[generate(Debug)]
status(RO, RA_STATUS, 2) = MSB {
error_condition: u8 as Bit = RO 15..=15,
flash_error: u8 as Bit = RO 14..=14,
calibration_error: u8 as Bit = RO 13..=13,
rs232: u8 as Bit = RO 7..=7,
rs485: u8 as Bit = RO 6..=6,
i2c: u8 as Bit = RO 5..=5,
warm_up_mode: u8 as Bit = RO 4..=4,
single_point_calibration: u8 as Bit = RO 0..=0,
},
#[generate(Debug)]
gas_ppm(RO, RA_GAS_PPM, 2) = {
gas_ppm: u16:BE = RO 0..16,
},
reset_device(WO, RA_RESET, 2) = {
reset_device: u16:BE as Reset = WO 0..16,
},
start_single_point_cal(WO, RA_SPC, 2) = {
start_single_point_cal: u16:BE as SinglePointCalibration = WO 0..16,
},
#[generate(Debug)]
slave_address(RW, RA_SLAVE_ADDR, 2) = {
slave_address: u8 = RW 8..16,
},
abc_logic(WO, RA_ABC, 2) = {
abc_logic: u16:BE as AbcLogic = WO 0..16,
},
}
);