#![no_std]
#![allow(non_snake_case)]
#[macro_use(block)]
extern crate nb;
use ads1x1x::{
self,
channel::{DifferentialA0A1, SingleA2},
ic::{Ads1115, Resolution16Bit},
interface::I2cInterface,
Ads1x1x, SlaveAddr,
};
use embedded_hal::{
adc::OneShot,
blocking::i2c::{Read, Write, WriteRead},
};
const PH_TEMP_C: f32 = -0.05694;
pub type Adc<I> = Ads1x1x<
I2cInterface<I>,
Ads1115,
Resolution16Bit,
ads1x1x::mode::OneShot,
>;
#[derive(Debug, Clone)]
pub enum Smoothing {
NoSmoothing,
Little,
Lot,
}
#[derive(Debug, Clone, Copy)]
pub enum CalSlot {
One,
Two,
Three,
}
#[derive(Debug, Clone)]
pub struct CalPt {
V: f32,
pH: f32,
T: f32,
}
impl CalPt {
pub fn new(V: f32, pH: f32, T: f32) -> Self {
Self { V, pH, T }
}
}
pub struct PhSensor<I2C: Read<Error = E> + Write<Error = E> + WriteRead<Error = E>, E> {
adc: Ads1x1x<
ads1x1x::interface::I2cInterface<I2C>,
Ads1115,
Resolution16Bit,
ads1x1x::mode::OneShot,
>,
pub smoothing: Smoothing,
cal_1: CalPt,
cal_2: CalPt,
cal_3: Option<CalPt>,
}
impl<I2C: WriteRead<Error = E> + Write<Error = E> + Read<Error = E>, E> PhSensor<I2C, E> {
pub fn new(i2c: I2C) -> Self {
let adc = Ads1x1x::new_ads1115(i2c, SlaveAddr::default());
Self {
adc,
smoothing: Smoothing::Little,
cal_1: CalPt::new(0., 7., 23.),
cal_2: CalPt::new(-0.18, 4., 23.),
cal_3: None,
}
}
pub fn read(&mut self) -> Result<f32, ads1x1x::Error<E>> {
let T = temp_from_voltage(voltage_from_adc(
block!(self.adc.read(&mut SingleA2))? as f32
));
Ok(ph_from_voltage(
voltage_from_adc(block!(self.adc.read(&mut DifferentialA0A1))? as f32),
T,
&self.cal_1,
&self.cal_2,
&self.cal_3,
))
}
pub fn read_voltage(&mut self) -> Result<f32, ads1x1x::Error<E>> {
Ok(voltage_from_adc(
block!(self.adc.read(&mut DifferentialA0A1))? as f32,
))
}
pub fn read_temp(&mut self) -> Result<f32, ads1x1x::Error<E>> {
Ok(temp_from_voltage(voltage_from_adc(
block!(self.adc.read(&mut SingleA2))? as f32
)))
}
pub fn calibrate(&mut self, slot: CalSlot, pH: f32) -> Result<(), ads1x1x::Error<E>> {
let T = temp_from_voltage(voltage_from_adc(
block!(self.adc.read(&mut SingleA2))? as f32
));
let V = voltage_from_adc(block!(self.adc.read(&mut DifferentialA0A1))? as f32);
let pt = CalPt::new(V, pH, T);
match slot {
CalSlot::One => self.cal_1 = pt,
CalSlot::Two => self.cal_2 = pt,
CalSlot::Three => self.cal_3 = Some(pt),
}
Ok(())
}
pub fn calibrate_all(&mut self, pt0: CalPt, pt1: CalPt, pt2: Option<CalPt>) {
self.cal_1 = pt0;
self.cal_2 = pt1;
self.cal_3 = pt2;
}
pub fn reset_calibration(&mut self) {
self.cal_1 = CalPt::new(0., 7., 25.);
self.cal_2 = CalPt::new(-0.18, 4., 25.);
self.cal_3 = None;
}
}
pub fn voltage_from_adc(digi: f32) -> f32 {
let vref = 2.048;
(digi / 32_768.) * vref
}
fn lg(pt0: (f32, f32), pt1: (f32, f32), pt2: (f32, f32), X: f32) -> f32 {
let mut result = 0.;
let x = [pt0.0, pt1.0, pt2.0];
let y = [pt0.1, pt1.1, pt2.1];
for j in 0..3 {
let mut c = 1.;
for i in 0..3 {
if j == i {
continue;
}
c *= (X - x[i]) / (x[j] - x[i]);
}
result += y[j] * c;
}
result
}
fn ph_from_voltage(V: f32, temp: f32, cal_0: &CalPt, cal_1: &CalPt, cal_2: &Option<CalPt>) -> f32 {
let T_diff = temp - cal_0.T;
let T_comp = PH_TEMP_C * T_diff;
match cal_2 {
Some(c2) => {
let result = lg((cal_0.V, cal_0.pH), (cal_1.V, cal_1.pH), (c2.V, c2.pH), V);
result + T_comp * V
}
None => {
let a = (cal_1.pH - cal_0.pH) / (cal_1.V - cal_0.V);
let b = cal_1.pH - a * cal_1.V;
(a + T_comp) * V + b
}
}
}
pub fn temp_from_voltage(V: f32) -> f32 {
100. * V - 60.
}