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"
dependencies = [
"num-traits",
"serde",
"typenum",
]

View File

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

View File

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

View File

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

View File

@ -1,15 +1,42 @@
use serde::{Serialize, Deserialize};
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::{
channel_state::ChannelState,
channels::{CHANNELS, Channels},
command_parser::CenterPoint,
EEPROM_SIZE, EEPROM_PAGE_SIZE,
pid,
pins,
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
#[derive(Serialize, Deserialize)]
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Config {
channels: [ChannelConfig; CHANNELS],
}
@ -18,8 +45,8 @@ impl Config {
pub fn new(channels: &mut Channels) -> Self {
Config {
channels: [
ChannelConfig::new(channels.channel_state(0usize)),
ChannelConfig::new(channels.channel_state(1usize)),
ChannelConfig::new(channels, 0),
ChannelConfig::new(channels, 1),
],
}
}
@ -33,23 +60,143 @@ impl Config {
}
}
#[derive(Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ChannelConfig {
center: CenterPoint,
pid: pid::Parameters,
pid_target: f64,
sh: steinhart_hart::Parameters,
// TODO: pwm limits
pid_target: f32,
sh: SteinhartHartConfig,
pwm: PwmLimits,
}
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 {
center: state.center.clone(),
pid: state.pid.parameters.clone(),
pid_target: state.pid.target,
sh: state.sh.clone(),
pid_target: state.pid.target as f32,
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")]
const WATCHDOG_INTERVAL: u32 = 30_000;
pub const EEPROM_PAGE_SIZE: usize = 8;
pub const EEPROM_SIZE: usize = 128;
const TCP_PORT: u16 = 23;
@ -388,19 +391,19 @@ fn main() -> ! {
pid.reset();
}
KP =>
pid.parameters.kp = value,
pid.parameters.kp = value as f32,
KI =>
pid.parameters.ki = value,
pid.parameters.ki = value as f32,
KD =>
pid.parameters.kd = value,
pid.parameters.kd = value as f32,
OutputMin =>
pid.parameters.output_min = value,
pid.parameters.output_min = value as f32,
OutputMax =>
pid.parameters.output_max = value,
pid.parameters.output_max = value as f32,
IntegralMin =>
pid.parameters.integral_min = value,
pid.parameters.integral_min = value as f32,
IntegralMax =>
pid.parameters.integral_max = value,
pid.parameters.integral_max = value as f32,
}
let _ = writeln!(socket, "PID parameter updated");
}
@ -429,16 +432,25 @@ fn main() -> ! {
}
}
}
Command::Load => {}
Command::Save => {
let config = Config::new(&mut channels);
let mut buf = [0; 128];
match config.encode(&mut buf) {
Ok(buf) => {
let _ = writeln!(socket, "Encoded {}: {:?}", buf.len(), buf);
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 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};
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Parameters {
pub kp: f64,
pub ki: f64,
pub kd: f64,
pub output_min: f64,
pub output_max: f64,
pub integral_min: f64,
pub integral_max: f64
pub kp: f32,
pub ki: f32,
pub kd: f32,
pub output_min: f32,
pub output_max: f32,
pub integral_min: f32,
pub integral_max: f32
}
impl Default for Parameters {
@ -50,32 +50,32 @@ impl Controller {
let error = self.target - input;
// partial
let p = self.parameters.kp * error;
let p = f64::from(self.parameters.kp) * error;
//integral
self.integral += error;
if self.integral < self.parameters.integral_min {
self.integral = self.parameters.integral_min;
if self.integral < self.parameters.integral_min.into() {
self.integral = self.parameters.integral_min.into();
}
if self.integral > self.parameters.integral_max {
self.integral = self.parameters.integral_max;
if self.integral > self.parameters.integral_max.into() {
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
let d = match self.last_input {
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);
// output
let mut output = p + i + d;
if output < self.parameters.output_min {
output = self.parameters.output_min;
if output < self.parameters.output_min.into() {
output = self.parameters.output_min.into();
}
if output > self.parameters.output_max {
output = self.parameters.output_max;
if output > self.parameters.output_max.into() {
output = self.parameters.output_max.into();
}
self.last_output = Some(output);
output

View File

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