Compare commits
8 Commits
d3606d25b6
...
8d70c03520
Author | SHA1 | Date |
---|---|---|
Astro | 8d70c03520 | |
Astro | 3b050347d4 | |
Astro | 254c1c3d73 | |
Astro | 5a293a0ada | |
Astro | daa398cb5e | |
Astro | 17e89b2041 | |
Astro | 58e648b5e0 | |
Astro | c5c0ce5625 |
|
@ -492,7 +492,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8bb593f5252356bfb829112f8fca2d0982d48588d2d6bb5a92553b0dfc4c9aba"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"serde",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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!(),
|
||||
|
|
|
@ -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)?;
|
||||
|
|
171
src/config.rs
171
src/config.rs
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
42
src/main.rs
42
src/main.rs
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
38
src/pid.rs
38
src/pid.rs
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue