forked from M-Labs/thermostat
unit: replace with uom
This commit is contained in:
parent
4a1ce342a0
commit
bb26490153
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -402,6 +402,7 @@ dependencies = [
|
||||
"smoltcp",
|
||||
"stm32-eth",
|
||||
"stm32f4xx-hal",
|
||||
"uom",
|
||||
"usb-device",
|
||||
"usbd-serial",
|
||||
]
|
||||
@ -418,6 +419,16 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "uom"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bb593f5252356bfb829112f8fca2d0982d48588d2d6bb5a92553b0dfc4c9aba"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usb-device"
|
||||
version = "0.2.5"
|
||||
|
@ -32,6 +32,7 @@ num-traits = { version = "0.2", default-features = false, features = ["libm"] }
|
||||
usb-device = "0.2"
|
||||
usbd-serial = "0.1"
|
||||
nb = "0.1"
|
||||
uom = { version = "0.29", default-features = false, features = ["autoconvert", "si", "f64"] }
|
||||
|
||||
[patch.crates-io]
|
||||
stm32f4xx-hal = { git = "https://github.com/stm32-rs/stm32f4xx-hal.git" }
|
||||
|
@ -1,16 +1,19 @@
|
||||
use smoltcp::time::Instant;
|
||||
use uom::si::{
|
||||
f64::ElectricPotential,
|
||||
electric_potential::volt,
|
||||
};
|
||||
use crate::{
|
||||
ad7172,
|
||||
pid,
|
||||
steinhart_hart as sh,
|
||||
units::Volts,
|
||||
};
|
||||
|
||||
|
||||
pub struct ChannelState {
|
||||
pub adc_data: Option<u32>,
|
||||
pub adc_time: Instant,
|
||||
pub dac_value: Volts,
|
||||
pub dac_value: ElectricPotential,
|
||||
pub pid_engaged: bool,
|
||||
pub pid: pid::Controller,
|
||||
pub sh: sh::Parameters,
|
||||
@ -21,7 +24,7 @@ impl Default for ChannelState {
|
||||
ChannelState {
|
||||
adc_data: None,
|
||||
adc_time: Instant::from_secs(0),
|
||||
dac_value: Volts(0.0),
|
||||
dac_value: ElectricPotential::new::<volt>(0.0),
|
||||
pid_engaged: false,
|
||||
pid: pid::Controller::new(pid::Parameters::default()),
|
||||
sh: sh::Parameters::default(),
|
||||
|
@ -1,4 +1,8 @@
|
||||
use smoltcp::time::Instant;
|
||||
use uom::si::{
|
||||
f64::ElectricPotential,
|
||||
electric_potential::{millivolt, volt},
|
||||
};
|
||||
use log::info;
|
||||
use crate::{
|
||||
ad5680,
|
||||
@ -6,7 +10,6 @@ use crate::{
|
||||
channel::{Channel, Channel0, Channel1},
|
||||
channel_state::ChannelState,
|
||||
pins,
|
||||
units::Volts,
|
||||
};
|
||||
|
||||
pub const CHANNELS: usize = 2;
|
||||
@ -69,7 +72,7 @@ impl Channels {
|
||||
};
|
||||
if let Some(dac_value) = dac_value {
|
||||
// Forward PID output to i_set DAC
|
||||
self.set_dac(channel.into(), Volts(dac_value));
|
||||
self.set_dac(channel.into(), ElectricPotential::new::<volt>(dac_value));
|
||||
}
|
||||
|
||||
channel
|
||||
@ -77,13 +80,13 @@ impl Channels {
|
||||
}
|
||||
|
||||
/// i_set DAC
|
||||
pub fn set_dac(&mut self, channel: usize, voltage: Volts) {
|
||||
pub fn set_dac(&mut self, channel: usize, voltage: ElectricPotential) {
|
||||
let dac_factor = match channel.into() {
|
||||
0 => self.channel0.dac_factor,
|
||||
1 => self.channel1.dac_factor,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let value = (voltage.0 * dac_factor) as u32;
|
||||
let value = (voltage.get::<volt>() * dac_factor) as u32;
|
||||
match channel {
|
||||
0 => {
|
||||
self.channel0.dac.set(value).unwrap();
|
||||
@ -97,7 +100,7 @@ impl Channels {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_dac_feedback(&mut self, channel: usize) -> Volts {
|
||||
pub fn read_dac_feedback(&mut self, channel: usize) -> ElectricPotential {
|
||||
match channel {
|
||||
0 => {
|
||||
let sample = self.pins_adc.convert(
|
||||
@ -106,7 +109,7 @@ impl Channels {
|
||||
);
|
||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
||||
info!("dac0_fb: {}/{:03X}", mv, sample);
|
||||
Volts(mv as f64 / 1000.0)
|
||||
ElectricPotential::new::<millivolt>(mv as f64)
|
||||
}
|
||||
1 => {
|
||||
let sample = self.pins_adc.convert(
|
||||
@ -115,25 +118,25 @@ impl Channels {
|
||||
);
|
||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
||||
info!("dac1_fb: {}/{:03X}", mv, sample);
|
||||
Volts(mv as f64 / 1000.0)
|
||||
ElectricPotential::new::<millivolt>(mv as f64)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_dac_feedback_until_stable(&mut self, channel: usize, tolerance: f64) -> Volts {
|
||||
pub fn read_dac_feedback_until_stable(&mut self, channel: usize, tolerance: ElectricPotential) -> ElectricPotential {
|
||||
let mut prev = self.read_dac_feedback(channel);
|
||||
loop {
|
||||
let current = self.read_dac_feedback(channel);
|
||||
use num_traits::float::Float;
|
||||
if (current - prev).0.abs() < tolerance {
|
||||
if (current - prev).abs() < tolerance {
|
||||
return current;
|
||||
}
|
||||
prev = current;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_itec(&mut self, channel: usize) -> Volts {
|
||||
pub fn read_itec(&mut self, channel: usize) -> ElectricPotential {
|
||||
match channel {
|
||||
0 => {
|
||||
let sample = self.pins_adc.convert(
|
||||
@ -141,7 +144,7 @@ impl Channels {
|
||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||
);
|
||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
||||
Volts(mv as f64 / 1000.0)
|
||||
ElectricPotential::new::<millivolt>(mv as f64)
|
||||
}
|
||||
1 => {
|
||||
let sample = self.pins_adc.convert(
|
||||
@ -149,14 +152,14 @@ impl Channels {
|
||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||
);
|
||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
||||
Volts(mv as f64 / 1000.0)
|
||||
ElectricPotential::new::<millivolt>(mv as f64)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// should be 1.5V
|
||||
pub fn read_vref(&mut self, channel: usize) -> Volts {
|
||||
pub fn read_vref(&mut self, channel: usize) -> ElectricPotential {
|
||||
match channel {
|
||||
0 => {
|
||||
let sample = self.pins_adc.convert(
|
||||
@ -164,7 +167,7 @@ impl Channels {
|
||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||
);
|
||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
||||
Volts(mv as f64 / 1000.0)
|
||||
ElectricPotential::new::<volt>(mv as f64 / 1000.0)
|
||||
}
|
||||
1 => {
|
||||
let sample = self.pins_adc.convert(
|
||||
@ -172,13 +175,13 @@ impl Channels {
|
||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||
);
|
||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
||||
Volts(mv as f64 / 1000.0)
|
||||
ElectricPotential::new::<volt>(mv as f64 / 1000.0)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_tec_u_meas(&mut self, channel: usize) -> Volts {
|
||||
pub fn read_tec_u_meas(&mut self, channel: usize) -> ElectricPotential {
|
||||
match channel {
|
||||
0 => {
|
||||
let sample = self.pins_adc.convert(
|
||||
@ -186,7 +189,7 @@ impl Channels {
|
||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||
);
|
||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
||||
Volts(mv as f64 / 1000.0)
|
||||
ElectricPotential::new::<millivolt>(mv as f64)
|
||||
}
|
||||
1 => {
|
||||
let sample = self.pins_adc.convert(
|
||||
@ -194,7 +197,7 @@ impl Channels {
|
||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||
);
|
||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
||||
Volts(mv as f64 / 1000.0)
|
||||
ElectricPotential::new::<millivolt>(mv as f64)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@ -205,9 +208,9 @@ impl Channels {
|
||||
/// These loops perform a width-first search for the DAC setting
|
||||
/// that will produce a `target_voltage`.
|
||||
pub fn calibrate_dac_value(&mut self, channel: usize) {
|
||||
let target_voltage = Volts(2.5);
|
||||
let target_voltage = ElectricPotential::new::<volt>(2.5);
|
||||
let mut start_value = 1;
|
||||
let mut best_error = Volts(100.0);
|
||||
let mut best_error = ElectricPotential::new::<volt>(100.0);
|
||||
|
||||
for step in (0..18).rev() {
|
||||
let mut prev_value = start_value;
|
||||
@ -222,15 +225,15 @@ impl Channels {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let dac_feedback = self.read_dac_feedback_until_stable(channel, 0.001);
|
||||
let dac_feedback = self.read_dac_feedback_until_stable(channel, ElectricPotential::new::<volt>(0.001));
|
||||
let error = target_voltage - dac_feedback;
|
||||
if error < Volts(0.0) {
|
||||
if error < ElectricPotential::new::<volt>(0.0) {
|
||||
break;
|
||||
} else if error < best_error {
|
||||
best_error = error;
|
||||
start_value = prev_value;
|
||||
|
||||
let dac_factor = value as f64 / dac_feedback.0;
|
||||
let dac_factor = value as f64 / dac_feedback.get::<volt>();
|
||||
match channel {
|
||||
0 => self.channel0.dac_factor = dac_factor,
|
||||
1 => self.channel1.dac_factor = dac_factor,
|
||||
@ -243,6 +246,6 @@ impl Channels {
|
||||
}
|
||||
|
||||
// Reset
|
||||
self.set_dac(channel, Volts(0.0));
|
||||
self.set_dac(channel, ElectricPotential::new::<volt>(0.0));
|
||||
}
|
||||
}
|
||||
|
28
src/main.rs
28
src/main.rs
@ -28,6 +28,18 @@ use smoltcp::{
|
||||
time::Instant,
|
||||
wire::EthernetAddress,
|
||||
};
|
||||
use uom::{
|
||||
fmt::DisplayStyle::Abbreviation,
|
||||
si::{
|
||||
f64::{
|
||||
ElectricPotential,
|
||||
ElectricalResistance,
|
||||
},
|
||||
electric_current::ampere,
|
||||
electric_potential::volt,
|
||||
electrical_resistance::ohm,
|
||||
},
|
||||
};
|
||||
|
||||
mod init_log;
|
||||
use init_log::init_log;
|
||||
@ -45,8 +57,6 @@ use session::{Session, SessionOutput};
|
||||
mod command_parser;
|
||||
use command_parser::{Command, ShowCommand, PwmPin};
|
||||
mod timer;
|
||||
mod units;
|
||||
use units::{Ohms, Volts};
|
||||
mod pid;
|
||||
mod steinhart_hart;
|
||||
mod channels;
|
||||
@ -165,7 +175,7 @@ fn main() -> ! {
|
||||
let dac_feedback = channels.read_dac_feedback(channel);
|
||||
|
||||
let itec = channels.read_itec(channel);
|
||||
let tec_i = -(itec - Volts(1.5)) / Ohms(0.4);
|
||||
let tec_i = -(itec - ElectricPotential::new::<volt>(1.5)) / ElectricalResistance::new::<ohm>(0.4);
|
||||
|
||||
let tec_u_meas = channels.read_tec_u_meas(channel);
|
||||
|
||||
@ -175,9 +185,9 @@ fn main() -> ! {
|
||||
channel,
|
||||
state.adc_time, channel, adc_data,
|
||||
channel, adc_calibration[channel].convert_data(adc_data),
|
||||
vref, dac_feedback,
|
||||
itec, tec_i,
|
||||
tec_u_meas,
|
||||
vref.into_format_args(volt, Abbreviation), dac_feedback.into_format_args(volt, Abbreviation),
|
||||
itec.into_format_args(volt, Abbreviation), tec_i.into_format_args(ampere, Abbreviation),
|
||||
tec_u_meas.into_format_args(volt, Abbreviation),
|
||||
);
|
||||
} else {
|
||||
let _ = writeln!(socket, "channel {}: no adc input", channel);
|
||||
@ -219,7 +229,7 @@ fn main() -> ! {
|
||||
channel,
|
||||
if state.pid_engaged { "engaged" } else { "disengaged" }
|
||||
);
|
||||
let _ = writeln!(socket, "- i_set={}", state.dac_value);
|
||||
let _ = writeln!(socket, "- i_set={}", state.dac_value.into_format_args(volt, Abbreviation));
|
||||
fn show_pwm_channel<S, P>(mut socket: S, name: &str, pin: &P)
|
||||
where
|
||||
S: core::fmt::Write,
|
||||
@ -287,11 +297,11 @@ fn main() -> ! {
|
||||
Command::Pwm { channel, pin: PwmPin::ISet, duty } => {
|
||||
channels.channel_state(channel).pid_engaged = false;
|
||||
leds.g3.off();
|
||||
let voltage = Volts(duty);
|
||||
let voltage = ElectricPotential::new::<volt>(duty);
|
||||
channels.set_dac(channel, voltage);
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: PWM duty cycle manually set to {}",
|
||||
channel, voltage
|
||||
channel, voltage.into_format_args(volt, Abbreviation),
|
||||
);
|
||||
}
|
||||
Command::Pwm { channel, pin, duty } => {
|
||||
|
65
src/units.rs
65
src/units.rs
@ -1,65 +0,0 @@
|
||||
use core::{
|
||||
fmt,
|
||||
ops::{Add, Div, Neg, Sub},
|
||||
};
|
||||
|
||||
macro_rules! impl_add_sub {
|
||||
($Type: ident) => {
|
||||
impl Add<$Type> for $Type {
|
||||
type Output = $Type;
|
||||
fn add(self, rhs: $Type) -> $Type {
|
||||
$Type(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<$Type> for $Type {
|
||||
type Output = $Type;
|
||||
fn sub(self, rhs: $Type) -> $Type {
|
||||
$Type(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for $Type {
|
||||
type Output = $Type;
|
||||
fn neg(self) -> $Type {
|
||||
$Type(-self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct Volts(pub f64);
|
||||
impl_add_sub!(Volts);
|
||||
|
||||
impl fmt::Display for Volts {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:.3}V", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Ohms> for Volts {
|
||||
type Output = Amps;
|
||||
fn div(self, rhs: Ohms) -> Amps {
|
||||
Amps(self.0 / rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct Amps(pub f64);
|
||||
impl_add_sub!(Amps);
|
||||
|
||||
impl fmt::Display for Amps {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:.3}A", self.0)
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct Ohms(pub f64);
|
||||
impl_add_sub!(Ohms);
|
||||
|
||||
impl fmt::Display for Ohms {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:.3}Ω", self.0)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user