Compare commits

...

8 Commits

8 changed files with 211 additions and 57 deletions

1
Cargo.lock generated
View File

@ -492,7 +492,6 @@ 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", "use_serde"] } uom = { version = "0.29", default-features = false, features = ["autoconvert", "si", "f64"] }
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,7 +7,6 @@ use uom::si::{
electrical_resistance::ohm, electrical_resistance::ohm,
ratio::ratio, ratio::ratio,
}; };
use log::info;
use crate::{ use crate::{
ad5680, ad5680,
ad7172, ad7172,
@ -104,7 +103,7 @@ impl Channels {
CenterPoint::Vref => CenterPoint::Vref =>
state.vref, state.vref,
CenterPoint::Override(center_point) => CenterPoint::Override(center_point) =>
ElectricPotential::new::<volt>(center_point), ElectricPotential::new::<volt>(center_point.into()),
}; };
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);
@ -138,7 +137,7 @@ impl Channels {
CenterPoint::Vref => CenterPoint::Vref =>
state.vref, state.vref,
CenterPoint::Override(center_point) => CenterPoint::Override(center_point) =>
ElectricPotential::new::<volt>(center_point), ElectricPotential::new::<volt>(center_point.into()),
}; };
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;
@ -156,7 +155,6 @@ 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 => {
@ -165,7 +163,6 @@ 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(f64), Override(f32),
} }
#[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(CenterPoint::Override))) Ok((input, value.map(|value| CenterPoint::Override(value as f32))))
} }
))(input)?; ))(input)?;
end(input)?; end(input)?;

View File

@ -1,15 +1,42 @@
use serde::{Serialize, Deserialize};
use postcard::{from_bytes, to_slice}; use postcard::{from_bytes, to_slice};
use serde::{Serialize, Deserialize};
use stm32f4xx_hal::i2c;
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(Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Config { pub struct Config {
channels: [ChannelConfig; CHANNELS], channels: [ChannelConfig; CHANNELS],
} }
@ -18,8 +45,8 @@ impl Config {
pub fn new(channels: &mut Channels) -> Self { pub fn new(channels: &mut Channels) -> Self {
Config { Config {
channels: [ channels: [
ChannelConfig::new(channels.channel_state(0usize)), ChannelConfig::new(channels, 0),
ChannelConfig::new(channels.channel_state(1usize)), ChannelConfig::new(channels, 1),
], ],
} }
} }
@ -33,23 +60,143 @@ impl Config {
} }
} }
#[derive(Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ChannelConfig { pub struct ChannelConfig {
center: CenterPoint, center: CenterPoint,
pid: pid::Parameters, pid: pid::Parameters,
pid_target: f64, pid_target: f32,
sh: steinhart_hart::Parameters, sh: SteinhartHartConfig,
// TODO: pwm limits pwm: PwmLimits,
} }
impl ChannelConfig { impl ChannelConfig {
pub fn new(state: &ChannelState) -> Self { pub fn new(channels: &mut Channels, channel: usize) -> 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, pid_target: state.pid.target as f32,
sh: state.sh.clone(), sh: (&state.sh).into(),
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,6 +73,9 @@ 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;
@ -388,19 +391,19 @@ fn main() -> ! {
pid.reset(); pid.reset();
} }
KP => KP =>
pid.parameters.kp = value, pid.parameters.kp = value as f32,
KI => KI =>
pid.parameters.ki = value, pid.parameters.ki = value as f32,
KD => KD =>
pid.parameters.kd = value, pid.parameters.kd = value as f32,
OutputMin => OutputMin =>
pid.parameters.output_min = value, pid.parameters.output_min = value as f32,
OutputMax => OutputMax =>
pid.parameters.output_max = value, pid.parameters.output_max = value as f32,
IntegralMin => IntegralMin =>
pid.parameters.integral_min = value, pid.parameters.integral_min = value as f32,
IntegralMax => IntegralMax =>
pid.parameters.integral_max = value, pid.parameters.integral_max = value as f32,
} }
let _ = writeln!(socket, "PID parameter updated"); let _ = writeln!(socket, "PID parameter updated");
} }
@ -429,16 +432,25 @@ fn main() -> ! {
} }
} }
} }
Command::Load => {} Command::Load => {
Command::Save => { match Config::load(&mut eeprom) {
let config = Config::new(&mut channels); Ok(config) => {
let mut buf = [0; 128]; config.apply(&mut channels);
match config.encode(&mut buf) { let _ = writeln!(socket, "Config loaded from EEPROM.");
Ok(buf) => {
let _ = writeln!(socket, "Encoded {}: {:?}", buf.len(), buf);
} }
Err(e) => { Err(e) => {
let _ = writeln!(socket, "Error encoding configuration: {}", e); let _ = writeln!(socket, "Error: {:?}", e);
}
}
}
Command::Save => {
let config = Config::new(&mut channels);
match config.save(&mut eeprom) {
Ok(()) => {
let _ = writeln!(socket, "Config saved to EEPROM.");
}
Err(e) => {
let _ = writeln!(socket, "Error saving config: {:?}", e);
} }
} }
} }

View File

@ -1,14 +1,14 @@
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Parameters { pub struct Parameters {
pub kp: f64, pub kp: f32,
pub ki: f64, pub ki: f32,
pub kd: f64, pub kd: f32,
pub output_min: f64, pub output_min: f32,
pub output_max: f64, pub output_max: f32,
pub integral_min: f64, pub integral_min: f32,
pub integral_max: f64 pub integral_max: f32
} }
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 = self.parameters.kp * error; let p = f64::from(self.parameters.kp) * error;
//integral //integral
self.integral += error; self.integral += error;
if self.integral < self.parameters.integral_min { if self.integral < self.parameters.integral_min.into() {
self.integral = self.parameters.integral_min; self.integral = self.parameters.integral_min.into();
} }
if self.integral > self.parameters.integral_max { if self.integral > self.parameters.integral_max.into() {
self.integral = self.parameters.integral_max; self.integral = self.parameters.integral_max.into();
} }
let i = self.parameters.ki * self.integral; let i = f64::from(self.parameters.ki) * f64::from(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) => self.parameters.kd * (last_input - input) Some(last_input) => f64::from(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 { if output < self.parameters.output_min.into() {
output = self.parameters.output_min; output = self.parameters.output_min.into();
} }
if output > self.parameters.output_max { if output > self.parameters.output_max.into() {
output = self.parameters.output_max; output = self.parameters.output_max.into();
} }
self.last_output = Some(output); self.last_output = Some(output);
output output

View File

@ -8,10 +8,9 @@ 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, Serialize, Deserialize)] #[derive(Clone, Debug)]
pub struct Parameters { pub struct Parameters {
/// Base temperature /// Base temperature
pub t0: ThermodynamicTemperature, pub t0: ThermodynamicTemperature,