Compare commits

..

3 Commits

Author SHA1 Message Date
Astro 1849e6f5e7 cargosha256.nix: update 2020-09-13 23:24:44 +02:00
Astro b80fcc430b channel_state: move adc_calibration into ChannelState 2020-09-13 23:15:48 +02:00
Astro bb26490153 unit: replace with uom 2020-09-13 23:13:51 +02:00
8 changed files with 100 additions and 126 deletions

11
Cargo.lock generated
View File

@ -402,6 +402,7 @@ dependencies = [
"smoltcp", "smoltcp",
"stm32-eth", "stm32-eth",
"stm32f4xx-hal", "stm32f4xx-hal",
"uom",
"usb-device", "usb-device",
"usbd-serial", "usbd-serial",
] ]
@ -418,6 +419,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 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]] [[package]]
name = "usb-device" name = "usb-device"
version = "0.2.5" version = "0.2.5"

View File

@ -32,6 +32,7 @@ num-traits = { version = "0.2", default-features = false, features = ["libm"] }
usb-device = "0.2" usb-device = "0.2"
usbd-serial = "0.1" usbd-serial = "0.1"
nb = "0.1" nb = "0.1"
uom = { version = "0.29", default-features = false, features = ["autoconvert", "si", "f64"] }
[patch.crates-io] [patch.crates-io]
stm32f4xx-hal = { git = "https://github.com/stm32-rs/stm32f4xx-hal.git" } stm32f4xx-hal = { git = "https://github.com/stm32-rs/stm32f4xx-hal.git" }

View File

@ -1 +1 @@
"06adm0s65h5qxb1a1gfjcwnl8lny52p90nrk3anj52iwnqlc5qwr" "0wa1xknnmmax278pwvcj134g62y00mr7k54cmvis64y27qlbg64h"

View File

@ -1,6 +1,7 @@
use stm32f4xx_hal::hal::digital::v2::OutputPin; use stm32f4xx_hal::hal::digital::v2::OutputPin;
use crate::{ use crate::{
ad5680, ad5680,
ad7172,
channel_state::ChannelState, channel_state::ChannelState,
pins::{ChannelPins, ChannelPinSet}, pins::{ChannelPins, ChannelPinSet},
}; };
@ -27,8 +28,8 @@ pub struct Channel<C: ChannelPins> {
} }
impl<C: ChannelPins> Channel<C> { impl<C: ChannelPins> Channel<C> {
pub fn new(mut pins: ChannelPinSet<C>) -> Self { pub fn new(mut pins: ChannelPinSet<C>, adc_calibration: ad7172::ChannelCalibration) -> Self {
let state = ChannelState::default(); let state = ChannelState::new(adc_calibration);
let mut dac = ad5680::Dac::new(pins.dac_spi, pins.dac_sync); let mut dac = ad5680::Dac::new(pins.dac_spi, pins.dac_sync);
let _ = dac.set(0); let _ = dac.set(0);
// power up TEC // power up TEC

View File

@ -1,43 +1,57 @@
use smoltcp::time::Instant; use smoltcp::time::Instant;
use uom::si::{
f64::ElectricPotential,
electric_potential::volt,
};
use crate::{ use crate::{
ad7172, ad7172,
pid, pid,
steinhart_hart as sh, steinhart_hart as sh,
units::Volts,
}; };
pub struct ChannelState { pub struct ChannelState {
pub adc_data: Option<u32>, pub adc_data: Option<u32>,
pub adc_calibration: ad7172::ChannelCalibration,
pub adc_time: Instant, pub adc_time: Instant,
pub dac_value: Volts, pub dac_value: ElectricPotential,
pub pid_engaged: bool, pub pid_engaged: bool,
pub pid: pid::Controller, pub pid: pid::Controller,
pub sh: sh::Parameters, pub sh: sh::Parameters,
} }
impl Default for ChannelState { impl ChannelState {
fn default() -> Self { pub fn new(adc_calibration: ad7172::ChannelCalibration) -> Self {
ChannelState { ChannelState {
adc_data: None, adc_data: None,
adc_calibration,
adc_time: Instant::from_secs(0), adc_time: Instant::from_secs(0),
dac_value: Volts(0.0), dac_value: ElectricPotential::new::<volt>(0.0),
pid_engaged: false, pid_engaged: false,
pid: pid::Controller::new(pid::Parameters::default()), pid: pid::Controller::new(pid::Parameters::default()),
sh: sh::Parameters::default(), sh: sh::Parameters::default(),
} }
} }
}
impl ChannelState { pub fn update(&mut self, now: Instant, adc_data: u32) {
/// Update PID state on ADC input, calculate new DAC output
pub fn update_pid(&mut self, now: Instant, adc_data: u32) -> f64 {
self.adc_data = Some(adc_data); self.adc_data = Some(adc_data);
self.adc_time = now; self.adc_time = now;
}
/// Update PID state on ADC input, calculate new DAC output
pub fn update_pid(&mut self) -> f64 {
// Update PID controller // Update PID controller
let input = (adc_data as f64) / (ad7172::MAX_VALUE as f64); self.pid.update(self.get_temperature().unwrap())
let temperature = self.sh.get_temperature(input); }
self.pid.update(temperature)
pub fn get_adc(&self) -> Option<ElectricPotential> {
let volts = self.adc_calibration.convert_data(self.adc_data?);
Some(ElectricPotential::new::<volt>(volts))
}
pub fn get_temperature(&self) -> Option<f64> {
let r = self.get_adc()?.get::<volt>();
let temperature = self.sh.get_temperature(r);
Some(temperature)
} }
} }

View File

@ -1,4 +1,8 @@
use smoltcp::time::Instant; use smoltcp::time::Instant;
use uom::si::{
f64::ElectricPotential,
electric_potential::{millivolt, volt},
};
use log::info; use log::info;
use crate::{ use crate::{
ad5680, ad5680,
@ -6,7 +10,6 @@ use crate::{
channel::{Channel, Channel0, Channel1}, channel::{Channel, Channel0, Channel1},
channel_state::ChannelState, channel_state::ChannelState,
pins, pins,
units::Volts,
}; };
pub const CHANNELS: usize = 2; pub const CHANNELS: usize = 2;
@ -23,20 +26,23 @@ pub struct Channels {
impl Channels { impl Channels {
pub fn new(pins: pins::Pins) -> Self { pub fn new(pins: pins::Pins) -> Self {
let channel0 = Channel::new(pins.channel0);
let channel1 = Channel::new(pins.channel1);
let pins_adc = pins.pins_adc;
let pwm = pins.pwm;
let mut adc = ad7172::Adc::new(pins.adc_spi, pins.adc_nss).unwrap(); let mut adc = ad7172::Adc::new(pins.adc_spi, pins.adc_nss).unwrap();
// Feature not used // Feature not used
adc.set_sync_enable(false).unwrap(); adc.set_sync_enable(false).unwrap();
// Setup channels and start ADC // Setup channels and start ADC
adc.setup_channel(0, ad7172::Input::Ain0, ad7172::Input::Ain1).unwrap(); adc.setup_channel(0, ad7172::Input::Ain0, ad7172::Input::Ain1).unwrap();
let adc_calibration0 = adc.get_calibration(0)
.expect("adc_calibration0");
adc.setup_channel(1, ad7172::Input::Ain2, ad7172::Input::Ain3).unwrap(); adc.setup_channel(1, ad7172::Input::Ain2, ad7172::Input::Ain3).unwrap();
let adc_calibration1 = adc.get_calibration(1)
.expect("adc_calibration1");
adc.start_continuous_conversion().unwrap(); adc.start_continuous_conversion().unwrap();
let mut channel0 = Channel::new(pins.channel0, adc_calibration0);
let mut channel1 = Channel::new(pins.channel1, adc_calibration1);
let pins_adc = pins.pins_adc;
let pwm = pins.pwm;
let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm }; let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm };
for channel in 0..CHANNELS { for channel in 0..CHANNELS {
channels.calibrate_dac_value(channel); channels.calibrate_dac_value(channel);
@ -69,7 +75,7 @@ impl Channels {
}; };
if let Some(dac_value) = dac_value { if let Some(dac_value) = dac_value {
// Forward PID output to i_set DAC // 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 channel
@ -77,13 +83,13 @@ impl Channels {
} }
/// i_set DAC /// 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() { let dac_factor = match channel.into() {
0 => self.channel0.dac_factor, 0 => self.channel0.dac_factor,
1 => self.channel1.dac_factor, 1 => self.channel1.dac_factor,
_ => unreachable!(), _ => unreachable!(),
}; };
let value = (voltage.0 * dac_factor) as u32; let value = (voltage.get::<volt>() * dac_factor) as u32;
match channel { match channel {
0 => { 0 => {
self.channel0.dac.set(value).unwrap(); self.channel0.dac.set(value).unwrap();
@ -97,7 +103,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 { match channel {
0 => { 0 => {
let sample = self.pins_adc.convert( let sample = self.pins_adc.convert(
@ -106,7 +112,7 @@ impl Channels {
); );
let mv = self.pins_adc.sample_to_millivolts(sample); let mv = self.pins_adc.sample_to_millivolts(sample);
info!("dac0_fb: {}/{:03X}", mv, sample); info!("dac0_fb: {}/{:03X}", mv, sample);
Volts(mv as f64 / 1000.0) ElectricPotential::new::<millivolt>(mv as f64)
} }
1 => { 1 => {
let sample = self.pins_adc.convert( let sample = self.pins_adc.convert(
@ -115,25 +121,25 @@ impl Channels {
); );
let mv = self.pins_adc.sample_to_millivolts(sample); let mv = self.pins_adc.sample_to_millivolts(sample);
info!("dac1_fb: {}/{:03X}", mv, sample); info!("dac1_fb: {}/{:03X}", mv, sample);
Volts(mv as f64 / 1000.0) ElectricPotential::new::<millivolt>(mv as f64)
} }
_ => unreachable!(), _ => 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); let mut prev = self.read_dac_feedback(channel);
loop { loop {
let current = self.read_dac_feedback(channel); let current = self.read_dac_feedback(channel);
use num_traits::float::Float; use num_traits::float::Float;
if (current - prev).0.abs() < tolerance { if (current - prev).abs() < tolerance {
return current; return current;
} }
prev = current; prev = current;
} }
} }
pub fn read_itec(&mut self, channel: usize) -> Volts { pub fn read_itec(&mut self, channel: usize) -> ElectricPotential {
match channel { match channel {
0 => { 0 => {
let sample = self.pins_adc.convert( let sample = self.pins_adc.convert(
@ -141,7 +147,7 @@ impl Channels {
stm32f4xx_hal::adc::config::SampleTime::Cycles_480 stm32f4xx_hal::adc::config::SampleTime::Cycles_480
); );
let mv = self.pins_adc.sample_to_millivolts(sample); let mv = self.pins_adc.sample_to_millivolts(sample);
Volts(mv as f64 / 1000.0) ElectricPotential::new::<millivolt>(mv as f64)
} }
1 => { 1 => {
let sample = self.pins_adc.convert( let sample = self.pins_adc.convert(
@ -149,14 +155,14 @@ impl Channels {
stm32f4xx_hal::adc::config::SampleTime::Cycles_480 stm32f4xx_hal::adc::config::SampleTime::Cycles_480
); );
let mv = self.pins_adc.sample_to_millivolts(sample); let mv = self.pins_adc.sample_to_millivolts(sample);
Volts(mv as f64 / 1000.0) ElectricPotential::new::<millivolt>(mv as f64)
} }
_ => unreachable!(), _ => unreachable!(),
} }
} }
/// should be 1.5V /// should be 1.5V
pub fn read_vref(&mut self, channel: usize) -> Volts { pub fn read_vref(&mut self, channel: usize) -> ElectricPotential {
match channel { match channel {
0 => { 0 => {
let sample = self.pins_adc.convert( let sample = self.pins_adc.convert(
@ -164,7 +170,7 @@ impl Channels {
stm32f4xx_hal::adc::config::SampleTime::Cycles_480 stm32f4xx_hal::adc::config::SampleTime::Cycles_480
); );
let mv = self.pins_adc.sample_to_millivolts(sample); let mv = self.pins_adc.sample_to_millivolts(sample);
Volts(mv as f64 / 1000.0) ElectricPotential::new::<volt>(mv as f64 / 1000.0)
} }
1 => { 1 => {
let sample = self.pins_adc.convert( let sample = self.pins_adc.convert(
@ -172,13 +178,13 @@ impl Channels {
stm32f4xx_hal::adc::config::SampleTime::Cycles_480 stm32f4xx_hal::adc::config::SampleTime::Cycles_480
); );
let mv = self.pins_adc.sample_to_millivolts(sample); let mv = self.pins_adc.sample_to_millivolts(sample);
Volts(mv as f64 / 1000.0) ElectricPotential::new::<volt>(mv as f64 / 1000.0)
} }
_ => unreachable!(), _ => 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 { match channel {
0 => { 0 => {
let sample = self.pins_adc.convert( let sample = self.pins_adc.convert(
@ -186,7 +192,7 @@ impl Channels {
stm32f4xx_hal::adc::config::SampleTime::Cycles_480 stm32f4xx_hal::adc::config::SampleTime::Cycles_480
); );
let mv = self.pins_adc.sample_to_millivolts(sample); let mv = self.pins_adc.sample_to_millivolts(sample);
Volts(mv as f64 / 1000.0) ElectricPotential::new::<millivolt>(mv as f64)
} }
1 => { 1 => {
let sample = self.pins_adc.convert( let sample = self.pins_adc.convert(
@ -194,7 +200,7 @@ impl Channels {
stm32f4xx_hal::adc::config::SampleTime::Cycles_480 stm32f4xx_hal::adc::config::SampleTime::Cycles_480
); );
let mv = self.pins_adc.sample_to_millivolts(sample); let mv = self.pins_adc.sample_to_millivolts(sample);
Volts(mv as f64 / 1000.0) ElectricPotential::new::<millivolt>(mv as f64)
} }
_ => unreachable!(), _ => unreachable!(),
} }
@ -205,9 +211,9 @@ impl Channels {
/// These loops perform a width-first search for the DAC setting /// These loops perform a width-first search for the DAC setting
/// that will produce a `target_voltage`. /// that will produce a `target_voltage`.
pub fn calibrate_dac_value(&mut self, channel: usize) { 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 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() { for step in (0..18).rev() {
let mut prev_value = start_value; let mut prev_value = start_value;
@ -222,15 +228,15 @@ impl Channels {
_ => unreachable!(), _ => 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; let error = target_voltage - dac_feedback;
if error < Volts(0.0) { if error < ElectricPotential::new::<volt>(0.0) {
break; break;
} else if error < best_error { } else if error < best_error {
best_error = error; best_error = error;
start_value = prev_value; 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 { match channel {
0 => self.channel0.dac_factor = dac_factor, 0 => self.channel0.dac_factor = dac_factor,
1 => self.channel1.dac_factor = dac_factor, 1 => self.channel1.dac_factor = dac_factor,
@ -243,6 +249,6 @@ impl Channels {
} }
// Reset // Reset
self.set_dac(channel, Volts(0.0)); self.set_dac(channel, ElectricPotential::new::<volt>(0.0));
} }
} }

View File

@ -28,6 +28,18 @@ use smoltcp::{
time::Instant, time::Instant,
wire::EthernetAddress, wire::EthernetAddress,
}; };
use uom::{
fmt::DisplayStyle::Abbreviation,
si::{
f64::{
ElectricPotential,
ElectricalResistance,
},
electric_current::ampere,
electric_potential::volt,
electrical_resistance::ohm,
},
};
mod init_log; mod init_log;
use init_log::init_log; use init_log::init_log;
@ -45,8 +57,6 @@ use session::{Session, SessionOutput};
mod command_parser; mod command_parser;
use command_parser::{Command, ShowCommand, PwmPin}; use command_parser::{Command, ShowCommand, PwmPin};
mod timer; mod timer;
mod units;
use units::{Ohms, Volts};
mod pid; mod pid;
mod steinhart_hart; mod steinhart_hart;
mod channels; mod channels;
@ -109,10 +119,6 @@ fn main() -> ! {
usb::State::setup(usb); usb::State::setup(usb);
let mut channels = Channels::new(pins); let mut channels = Channels::new(pins);
let adc_calibration = [
channels.adc.get_calibration(0).unwrap(),
channels.adc.get_calibration(1).unwrap(),
];
#[cfg(not(feature = "generate-hwaddr"))] #[cfg(not(feature = "generate-hwaddr"))]
let hwaddr = EthernetAddress(NET_HWADDR); let hwaddr = EthernetAddress(NET_HWADDR);
@ -165,19 +171,19 @@ fn main() -> ! {
let dac_feedback = channels.read_dac_feedback(channel); let dac_feedback = channels.read_dac_feedback(channel);
let itec = channels.read_itec(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); let tec_u_meas = channels.read_tec_u_meas(channel);
let state = channels.channel_state(channel); let state = channels.channel_state(channel);
let _ = writeln!( let _ = writeln!(
socket, "channel {}: t={} adc_raw{}=0x{:06X} adc{}={:.3}V vref={} dac_feedback={} itec={} tec={} tec_u_meas={}", socket, "channel {}: t={} adc{}={} vref={} dac_feedback={} itec={} tec={} tec_u_meas={} r={:03}",
channel, channel,
state.adc_time, channel, adc_data, state.adc_time, channel, adc_data,
channel, adc_calibration[channel].convert_data(adc_data), vref.into_format_args(volt, Abbreviation), dac_feedback.into_format_args(volt, Abbreviation),
vref, dac_feedback, itec.into_format_args(volt, Abbreviation), tec_i.into_format_args(ampere, Abbreviation),
itec, tec_i, tec_u_meas.into_format_args(volt, Abbreviation),
tec_u_meas, (tec_u_meas / tec_i).into_format_args(ohm, Abbreviation),
); );
} else { } else {
let _ = writeln!(socket, "channel {}: no adc input", channel); let _ = writeln!(socket, "channel {}: no adc input", channel);
@ -219,7 +225,7 @@ fn main() -> ! {
channel, channel,
if state.pid_engaged { "engaged" } else { "disengaged" } 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) fn show_pwm_channel<S, P>(mut socket: S, name: &str, pin: &P)
where where
S: core::fmt::Write, S: core::fmt::Write,
@ -287,11 +293,11 @@ fn main() -> ! {
Command::Pwm { channel, pin: PwmPin::ISet, duty } => { Command::Pwm { channel, pin: PwmPin::ISet, duty } => {
channels.channel_state(channel).pid_engaged = false; channels.channel_state(channel).pid_engaged = false;
leds.g3.off(); leds.g3.off();
let voltage = Volts(duty); let voltage = ElectricPotential::new::<volt>(duty);
channels.set_dac(channel, voltage); channels.set_dac(channel, voltage);
let _ = writeln!( let _ = writeln!(
socket, "channel {}: PWM duty cycle manually set to {}", socket, "channel {}: PWM duty cycle manually set to {}",
channel, voltage channel, voltage.into_format_args(volt, Abbreviation),
); );
} }
Command::Pwm { channel, pin, duty } => { Command::Pwm { channel, pin, duty } => {
@ -388,9 +394,9 @@ fn main() -> ! {
let state = &mut channels.channel_state(usize::from(channel)); let state = &mut channels.channel_state(usize::from(channel));
let adc_data = state.adc_data.unwrap_or(0); let adc_data = state.adc_data.unwrap_or(0);
let _ = writeln!( let _ = writeln!(
socket, "t={} raw{}=0x{:06X} value={:.3}V", socket, "t={} raw{}=0x{:06X} value={}",
state.adc_time, channel, adc_data, state.adc_time, channel, adc_data,
adc_calibration[channel].convert_data(adc_data), state.get_adc().unwrap().into_format_args(volt, Abbreviation),
).map(|_| { ).map(|_| {
session.mark_report_sent(channel); session.mark_report_sent(channel);
}); });

View File

@ -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)
}
}