Compare commits

..

No commits in common. "8d70c03520e1cc221e40232cad3f4beb4fec5199" and "d3606d25b696f765f4791e7c2e4855bbc3c5d8f3" have entirely different histories.

8 changed files with 55 additions and 209 deletions

1
Cargo.lock generated
View File

@ -492,6 +492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bb593f5252356bfb829112f8fca2d0982d48588d2d6bb5a92553b0dfc4c9aba" checksum = "8bb593f5252356bfb829112f8fca2d0982d48588d2d6bb5a92553b0dfc4c9aba"
dependencies = [ dependencies = [
"num-traits", "num-traits",
"serde",
"typenum", "typenum",
] ]

View File

@ -31,7 +31,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"] } uom = { version = "0.29", default-features = false, features = ["autoconvert", "si", "f64", "use_serde"] }
eeprom24x = "0.3" eeprom24x = "0.3"
serde = { version = "1.0", default-features = false, features = ["derive"] } serde = { version = "1.0", default-features = false, features = ["derive"] }
postcard = "0.5.1" postcard = "0.5.1"

View File

@ -7,6 +7,7 @@ use uom::si::{
electrical_resistance::ohm, electrical_resistance::ohm,
ratio::ratio, ratio::ratio,
}; };
use log::info;
use crate::{ use crate::{
ad5680, ad5680,
ad7172, ad7172,
@ -103,7 +104,7 @@ impl Channels {
CenterPoint::Vref => CenterPoint::Vref =>
state.vref, state.vref,
CenterPoint::Override(center_point) => CenterPoint::Override(center_point) =>
ElectricPotential::new::<volt>(center_point.into()), ElectricPotential::new::<volt>(center_point),
}; };
let r_sense = ElectricalResistance::new::<ohm>(R_SENSE); let r_sense = ElectricalResistance::new::<ohm>(R_SENSE);
let (voltage, max) = self.get_dac(channel); let (voltage, max) = self.get_dac(channel);
@ -137,7 +138,7 @@ impl Channels {
CenterPoint::Vref => CenterPoint::Vref =>
state.vref, state.vref,
CenterPoint::Override(center_point) => CenterPoint::Override(center_point) =>
ElectricPotential::new::<volt>(center_point.into()), ElectricPotential::new::<volt>(center_point),
}; };
let r_sense = ElectricalResistance::new::<ohm>(R_SENSE); let r_sense = ElectricalResistance::new::<ohm>(R_SENSE);
let voltage = i_tec * 10.0 * r_sense + center_point; let voltage = i_tec * 10.0 * r_sense + center_point;
@ -155,6 +156,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);
info!("dac0_fb: {}/{:03X}", mv, sample);
ElectricPotential::new::<millivolt>(mv as f64) ElectricPotential::new::<millivolt>(mv as f64)
} }
1 => { 1 => {
@ -163,6 +165,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);
info!("dac1_fb: {}/{:03X}", mv, sample);
ElectricPotential::new::<millivolt>(mv as f64) ElectricPotential::new::<millivolt>(mv as f64)
} }
_ => unreachable!(), _ => unreachable!(),

View File

@ -126,7 +126,7 @@ pub enum PwmPin {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CenterPoint { pub enum CenterPoint {
Vref, Vref,
Override(f32), Override(f64),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -312,7 +312,7 @@ fn center_point(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
value(Ok(CenterPoint::Vref), tag("vref")), value(Ok(CenterPoint::Vref), tag("vref")),
|input| { |input| {
let (input, value) = float(input)?; let (input, value) = float(input)?;
Ok((input, value.map(|value| CenterPoint::Override(value as f32)))) Ok((input, value.map(CenterPoint::Override)))
} }
))(input)?; ))(input)?;
end(input)?; end(input)?;

View File

@ -1,42 +1,15 @@
use postcard::{from_bytes, to_slice};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use stm32f4xx_hal::i2c; use postcard::{from_bytes, to_slice};
use uom::si::{
electric_potential::volt,
electric_current::ampere,
electrical_resistance::ohm,
f64::{ElectricCurrent, ElectricPotential, ElectricalResistance, ThermodynamicTemperature},
thermodynamic_temperature::degree_celsius,
};
use crate::{ use crate::{
channel_state::ChannelState,
channels::{CHANNELS, Channels}, channels::{CHANNELS, Channels},
command_parser::CenterPoint, command_parser::CenterPoint,
EEPROM_SIZE, EEPROM_PAGE_SIZE,
pid, pid,
pins,
steinhart_hart, steinhart_hart,
}; };
#[derive(Debug)]
pub enum Error {
Eeprom(eeprom24x::Error<i2c::Error>),
Encode(postcard::Error),
}
impl From<eeprom24x::Error<i2c::Error>> for Error {
fn from(e: eeprom24x::Error<i2c::Error>) -> Self {
Error::Eeprom(e)
}
}
impl From<postcard::Error> for Error {
fn from(e: postcard::Error) -> Self {
Error::Encode(e)
}
}
/// Just for encoding/decoding, actual state resides in ChannelState /// Just for encoding/decoding, actual state resides in ChannelState
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Config { pub struct Config {
channels: [ChannelConfig; CHANNELS], channels: [ChannelConfig; CHANNELS],
} }
@ -45,8 +18,8 @@ impl Config {
pub fn new(channels: &mut Channels) -> Self { pub fn new(channels: &mut Channels) -> Self {
Config { Config {
channels: [ channels: [
ChannelConfig::new(channels, 0), ChannelConfig::new(channels.channel_state(0usize)),
ChannelConfig::new(channels, 1), ChannelConfig::new(channels.channel_state(1usize)),
], ],
} }
} }
@ -60,143 +33,23 @@ impl Config {
} }
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct ChannelConfig { pub struct ChannelConfig {
center: CenterPoint, center: CenterPoint,
pid: pid::Parameters, pid: pid::Parameters,
pid_target: f32, pid_target: f64,
sh: SteinhartHartConfig, sh: steinhart_hart::Parameters,
pwm: PwmLimits, // TODO: pwm limits
} }
impl ChannelConfig { impl ChannelConfig {
pub fn new(channels: &mut Channels, channel: usize) -> Self { pub fn new(state: &ChannelState) -> Self {
let pwm = PwmLimits::new(channels, channel);
let state = channels.channel_state(channel);
ChannelConfig { ChannelConfig {
center: state.center.clone(), center: state.center.clone(),
pid: state.pid.parameters.clone(), pid: state.pid.parameters.clone(),
pid_target: state.pid.target as f32, pid_target: state.pid.target,
sh: (&state.sh).into(), sh: state.sh.clone(),
pwm,
} }
} }
pub fn apply(&self, channels: &mut Channels, channel: usize) {
let state = channels.channel_state(channel);
state.center = self.center.clone();
state.pid.parameters = self.pid.clone();
state.pid.target = self.pid_target.into();
state.sh = (&self.sh).into();
self.pwm.apply(channels, channel);
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct SteinhartHartConfig {
t0: f32,
r0: f32,
b: f32,
}
impl From<&steinhart_hart::Parameters> for SteinhartHartConfig {
fn from(sh: &steinhart_hart::Parameters) -> Self {
SteinhartHartConfig {
t0: sh.t0.get::<degree_celsius>() as f32,
r0: sh.r0.get::<ohm>() as f32,
b: sh.b as f32,
}
}
}
impl Into<steinhart_hart::Parameters> for &SteinhartHartConfig {
fn into(self) -> steinhart_hart::Parameters {
steinhart_hart::Parameters {
t0: ThermodynamicTemperature::new::<degree_celsius>(self.t0.into()),
r0: ElectricalResistance::new::<ohm>(self.r0.into()),
b: self.b.into(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct PwmLimits {
max_v: f32,
max_i_pos: f32,
max_i_neg: f32,
}
impl PwmLimits {
pub fn new(channels: &mut Channels, channel: usize) -> Self {
let (max_v, _) = channels.get_max_v(channel);
let (max_i_pos, _) = channels.get_max_i_pos(channel);
let (max_i_neg, _) = channels.get_max_i_neg(channel);
PwmLimits {
max_v: max_v.get::<volt>() as f32,
max_i_pos: max_i_pos.get::<ampere>() as f32,
max_i_neg: max_i_neg.get::<ampere>() as f32,
}
}
pub fn apply(&self, channels: &mut Channels, channel: usize) {
channels.set_max_v(channel, ElectricPotential::new::<volt>(self.max_v.into()));
channels.set_max_i_pos(channel, ElectricCurrent::new::<ampere>(self.max_i_pos.into()));
channels.set_max_i_neg(channel, ElectricCurrent::new::<ampere>(self.max_i_neg.into()));
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_fit_eeprom() {
let channel_config = ChannelConfig {
center: CenterPoint::Override(1.5),
pid: pid::Parameters::default(),
pid_target: 93.7,
sh: (&steinhart_hart::Parameters::default()).into(),
pwm: PwmLimits {
max_v: 1.65,
max_i_pos: 2.1,
max_i_neg: 2.25,
},
};
let config = Config {
channels: [
channel_config.clone(),
channel_config.clone(),
],
};
let mut buffer = [0; EEPROM_SIZE];
let buffer = config.encode(&mut buffer).unwrap();
assert!(buffer.len() <= EEPROM_SIZE);
}
#[test]
fn test_encode_decode() {
let channel_config = ChannelConfig {
center: CenterPoint::Override(1.5),
pid: pid::Parameters::default(),
pid_target: 93.7,
sh: (&steinhart_hart::Parameters::default()).into(),
pwm: PwmLimits {
max_v: 1.65,
max_i_pos: 2.1,
max_i_neg: 2.25,
},
};
let config = Config {
channels: [
channel_config.clone(),
channel_config.clone(),
],
};
let mut buffer = [0; EEPROM_SIZE];
config.encode(&mut buffer).unwrap();
let decoded = Config::decode(&buffer).unwrap();
assert_eq!(decoded, config);
}
} }

View File

@ -73,9 +73,6 @@ const WATCHDOG_INTERVAL: u32 = 1_000;
#[cfg(feature = "semihosting")] #[cfg(feature = "semihosting")]
const WATCHDOG_INTERVAL: u32 = 30_000; const WATCHDOG_INTERVAL: u32 = 30_000;
pub const EEPROM_PAGE_SIZE: usize = 8;
pub const EEPROM_SIZE: usize = 128;
const TCP_PORT: u16 = 23; const TCP_PORT: u16 = 23;
@ -391,19 +388,19 @@ fn main() -> ! {
pid.reset(); pid.reset();
} }
KP => KP =>
pid.parameters.kp = value as f32, pid.parameters.kp = value,
KI => KI =>
pid.parameters.ki = value as f32, pid.parameters.ki = value,
KD => KD =>
pid.parameters.kd = value as f32, pid.parameters.kd = value,
OutputMin => OutputMin =>
pid.parameters.output_min = value as f32, pid.parameters.output_min = value,
OutputMax => OutputMax =>
pid.parameters.output_max = value as f32, pid.parameters.output_max = value,
IntegralMin => IntegralMin =>
pid.parameters.integral_min = value as f32, pid.parameters.integral_min = value,
IntegralMax => IntegralMax =>
pid.parameters.integral_max = value as f32, pid.parameters.integral_max = value,
} }
let _ = writeln!(socket, "PID parameter updated"); let _ = writeln!(socket, "PID parameter updated");
} }
@ -432,25 +429,16 @@ fn main() -> ! {
} }
} }
} }
Command::Load => { Command::Load => {}
match Config::load(&mut eeprom) {
Ok(config) => {
config.apply(&mut channels);
let _ = writeln!(socket, "Config loaded from EEPROM.");
}
Err(e) => {
let _ = writeln!(socket, "Error: {:?}", e);
}
}
}
Command::Save => { Command::Save => {
let config = Config::new(&mut channels); let config = Config::new(&mut channels);
match config.save(&mut eeprom) { let mut buf = [0; 128];
Ok(()) => { match config.encode(&mut buf) {
let _ = writeln!(socket, "Config saved to EEPROM."); Ok(buf) => {
let _ = writeln!(socket, "Encoded {}: {:?}", buf.len(), buf);
} }
Err(e) => { Err(e) => {
let _ = writeln!(socket, "Error saving config: {:?}", e); let _ = writeln!(socket, "Error encoding configuration: {}", e);
} }
} }
} }

View File

@ -1,14 +1,14 @@
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct Parameters { pub struct Parameters {
pub kp: f32, pub kp: f64,
pub ki: f32, pub ki: f64,
pub kd: f32, pub kd: f64,
pub output_min: f32, pub output_min: f64,
pub output_max: f32, pub output_max: f64,
pub integral_min: f32, pub integral_min: f64,
pub integral_max: f32 pub integral_max: f64
} }
impl Default for Parameters { impl Default for Parameters {
@ -50,32 +50,32 @@ impl Controller {
let error = self.target - input; let error = self.target - input;
// partial // partial
let p = f64::from(self.parameters.kp) * error; let p = self.parameters.kp * error;
//integral //integral
self.integral += error; self.integral += error;
if self.integral < self.parameters.integral_min.into() { if self.integral < self.parameters.integral_min {
self.integral = self.parameters.integral_min.into(); self.integral = self.parameters.integral_min;
} }
if self.integral > self.parameters.integral_max.into() { if self.integral > self.parameters.integral_max {
self.integral = self.parameters.integral_max.into(); self.integral = self.parameters.integral_max;
} }
let i = f64::from(self.parameters.ki) * f64::from(self.integral); let i = self.parameters.ki * self.integral;
// derivative // derivative
let d = match self.last_input { let d = match self.last_input {
None => 0.0, None => 0.0,
Some(last_input) => f64::from(self.parameters.kd) * (last_input - input) Some(last_input) => self.parameters.kd * (last_input - input)
}; };
self.last_input = Some(input); self.last_input = Some(input);
// output // output
let mut output = p + i + d; let mut output = p + i + d;
if output < self.parameters.output_min.into() { if output < self.parameters.output_min {
output = self.parameters.output_min.into(); output = self.parameters.output_min;
} }
if output > self.parameters.output_max.into() { if output > self.parameters.output_max {
output = self.parameters.output_max.into(); output = self.parameters.output_max;
} }
self.last_output = Some(output); self.last_output = Some(output);
output output

View File

@ -8,9 +8,10 @@ use uom::si::{
ratio::ratio, ratio::ratio,
thermodynamic_temperature::{degree_celsius, kelvin}, thermodynamic_temperature::{degree_celsius, kelvin},
}; };
use serde::{Serialize, Deserialize};
/// Steinhart-Hart equation parameters /// Steinhart-Hart equation parameters
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Parameters { pub struct Parameters {
/// Base temperature /// Base temperature
pub t0: ThermodynamicTemperature, pub t0: ThermodynamicTemperature,