Compare commits

..

3 Commits

Author SHA1 Message Date
0fe2d645e2 Show and save set value for PWM limits
Show in PwmSummary the set value, before all PWM duty calculations
instead of the machine value after the calculations. Also save the set
value into the flash store instead of the machine value.
2024-09-30 17:46:42 +08:00
ee26616c09 README: Document PWM value clamping 2024-09-30 17:44:24 +08:00
c3b423b5f4 PwmSummary: Don't show max PWM values
Putting the maximum in PwmSummary has little use - the maximum never
changes throughout the runtime of the firmware and can be replaced by
documentation elsewhere.
2024-09-30 17:44:20 +08:00
7 changed files with 45 additions and 135 deletions

View File

@ -92,43 +92,40 @@ ADC input data is provided in reports. Query for the latest report with the comm
Send commands as simple text string terminated by `\n`. Responses are Send commands as simple text string terminated by `\n`. Responses are
formatted as line-delimited JSON. formatted as line-delimited JSON.
| Syntax | Function | | Syntax | Function |
|------------------------------------------- |-------------------------------------------------------------------------------| |----------------------------------|-------------------------------------------------------------------------------|
| `report` | Show current input | | `report` | Show current input |
| `report mode` | Show current report mode | | `pwm` | Show current PWM settings |
| `report mode <off/on>` | Set report mode | | `pwm <0/1> max_i_pos <amp>` | Set maximum positive output current, clamped to [0, 2] |
| `pwm` | Show current PWM settings | | `pwm <0/1> max_i_neg <amp>` | Set maximum negative output current, clamped to [0, 2] |
| `pwm <0/1> max_i_pos <amp>` | Set maximum positive output current, clamped to [0, 2] | | `pwm <0/1> max_v <volt>` | Set maximum output voltage, clamped to [0, 4] |
| `pwm <0/1> max_i_neg <amp>` | Set maximum negative output current, clamped to [0, 2] | | `pwm <0/1> i_set <amp>` | Disengage PID, set fixed output current, clamped to [-2, 2] |
| `pwm <0/1> max_v <volt>` | Set maximum output voltage, clamped to [0, 4] | | `pwm <0/1> pid` | Let output current to be controlled by the PID |
| `pwm <0/1> i_set <amp>` | Disengage PID, set fixed output current, clamped to [-2, 2] | | `center <0/1> <volt>` | Set the MAX1968 0A-centerpoint to the specified fixed voltage |
| `pwm <0/1> polarity <normal/reversed>` | Set output current polarity, with 'normal' being the front panel polarity | | `center <0/1> vref` | Set the MAX1968 0A-centerpoint to measure from VREF |
| `pwm <0/1> pid` | Let output current to be controlled by the PID | | `pid` | Show PID configuration |
| `center <0/1> <volt>` | Set the MAX1968 0A-centerpoint to the specified fixed voltage | | `pid <0/1> target <deg_celsius>` | Set the PID controller target temperature |
| `center <0/1> vref` | Set the MAX1968 0A-centerpoint to measure from VREF | | `pid <0/1> kp <value>` | Set proportional gain |
| `pid` | Show PID configuration | | `pid <0/1> ki <value>` | Set integral gain |
| `pid <0/1> target <deg_celsius>` | Set the PID controller target temperature | | `pid <0/1> kd <value>` | Set differential gain |
| `pid <0/1> kp <value>` | Set proportional gain | | `pid <0/1> output_min <amp>` | Set mininum output |
| `pid <0/1> ki <value>` | Set integral gain | | `pid <0/1> output_max <amp>` | Set maximum output |
| `pid <0/1> kd <value>` | Set differential gain | | `s-h` | Show Steinhart-Hart equation parameters |
| `pid <0/1> output_min <amp>` | Set mininum output | | `s-h <0/1> <t0/b/r0> <value>` | Set Steinhart-Hart parameter for a channel |
| `pid <0/1> output_max <amp>` | Set maximum output | | `postfilter` | Show postfilter settings |
| `s-h` | Show Steinhart-Hart equation parameters | | `postfilter <0/1> off` | Disable postfilter |
| `s-h <0/1> <t0/b/r0> <value>` | Set Steinhart-Hart parameter for a channel | | `postfilter <0/1> rate <rate>` | Set postfilter output data rate |
| `postfilter` | Show postfilter settings | | `load [0/1]` | Restore configuration for channel all/0/1 from flash |
| `postfilter <0/1> off` | Disable postfilter | | `save [0/1]` | Save configuration for channel all/0/1 to flash |
| `postfilter <0/1> rate <rate>` | Set postfilter output data rate | | `reset` | Reset the device |
| `load [0/1]` | Restore configuration for channel all/0/1 from flash | | `dfu` | Reset device and enters USB device firmware update (DFU) mode |
| `save [0/1]` | Save configuration for channel all/0/1 to flash | | `ipv4 <X.X.X.X/L> [Y.Y.Y.Y]` | Configure IPv4 address, netmask length, and optional default gateway |
| `reset` | Reset the device | | `fan` | Show current fan settings and sensors' measurements |
| `dfu` | Reset device and enters USB device firmware update (DFU) mode | | `fan <value>` | Set fan power with values from 1 to 100 |
| `ipv4 <X.X.X.X/L> [Y.Y.Y.Y]` | Configure IPv4 address, netmask length, and optional default gateway | | `fan auto` | Enable automatic fan speed control |
| `fan` | Show current fan settings and sensors' measurements | | `fcurve <a> <b> <c>` | Set fan controller curve coefficients (see *Fan control* section) |
| `fan <value>` | Set fan power with values from 1 to 100 | | `fcurve default` | Set fan controller curve coefficients to defaults (see *Fan control* section) |
| `fan auto` | Enable automatic fan speed control | | `hwrev` | Show hardware revision, and settings related to it |
| `fcurve <a> <b> <c>` | Set fan controller curve coefficients (see *Fan control* section) |
| `fcurve default` | Set fan controller curve coefficients to defaults (see *Fan control* section) |
| `hwrev` | Show hardware revision, and settings related to it |
## USB ## USB
@ -185,8 +182,6 @@ postfilter rate can be tuned with the `postfilter` command.
When using a TEC module with the Thermostat, the Thermostat expects the thermal load (where the thermistor is located) to cool down with a positive software current set point, and heat up with a negative current set point. When using a TEC module with the Thermostat, the Thermostat expects the thermal load (where the thermistor is located) to cool down with a positive software current set point, and heat up with a negative current set point.
If the Thermostat is used for temperature control with the Sinara 5432 DAC "Zotino", and is connected via an IDC cable, the TEC polarity may need to be reversed with the `pwm <ch> polarity reversed` TCP command.
Testing heat flow direction with a low set current is recommended before installation of the TEC module. Testing heat flow direction with a low set current is recommended before installation of the TEC module.
### Limits ### Limits

View File

@ -57,14 +57,12 @@ class Client:
'max_i_neg': 2.0, 'max_i_neg': 2.0,
'max_v': 3.988, 'max_v': 3.988,
'max_i_pos': 2.0, 'max_i_pos': 2.0,
'polarity': 'normal',
{'channel': 1, {'channel': 1,
'center': 'vref', 'center': 'vref',
'i_set': -0.02002179650216762, 'i_set': -0.02002179650216762,
'max_i_neg': 2.0, 'max_i_neg': 2.0,
'max_v': 3.988, 'max_v': 3.988,
'max_i_pos': 2.0} 'max_i_pos': 2.0}
'polarity': 'normal',
] ]
""" """
return self._get_conf("pwm") return self._get_conf("pwm")

View File

@ -18,7 +18,7 @@ use crate::{
pid, pid,
config::PwmLimits, config::PwmLimits,
steinhart_hart as sh, steinhart_hart as sh,
command_parser::{CenterPoint, Polarity}, command_parser::CenterPoint,
}; };
const R_INNER: f64 = 2.0 * 5100.0; const R_INNER: f64 = 2.0 * 5100.0;
@ -37,7 +37,6 @@ pub struct ChannelState {
pub pid_engaged: bool, pub pid_engaged: bool,
pub pid: pid::Controller, pub pid: pid::Controller,
pub sh: sh::Parameters, pub sh: sh::Parameters,
pub polarity: Polarity,
} }
impl ChannelState { impl ChannelState {
@ -59,7 +58,6 @@ impl ChannelState {
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(),
polarity: Polarity::Normal,
} }
} }

View File

@ -17,7 +17,7 @@ use crate::{
ad7172, ad7172,
channel::{Channel, Channel0, Channel1}, channel::{Channel, Channel0, Channel1},
channel_state::ChannelState, channel_state::ChannelState,
command_parser::{CenterPoint, PwmPin, Polarity}, command_parser::{CenterPoint, PwmPin},
command_handler::JsonBuffer, command_handler::JsonBuffer,
pins::{self, Channel0VRef, Channel1VRef}, pins::{self, Channel0VRef, Channel1VRef},
steinhart_hart, steinhart_hart,
@ -158,11 +158,6 @@ impl Channels {
pub fn set_i(&mut self, channel: usize, i_set: ElectricCurrent) -> ElectricCurrent { pub fn set_i(&mut self, channel: usize, i_set: ElectricCurrent) -> ElectricCurrent {
let i_set = i_set.min(MAX_TEC_I).max(-MAX_TEC_I); let i_set = i_set.min(MAX_TEC_I).max(-MAX_TEC_I);
self.channel_state(channel).i_set = i_set;
let negate = match self.channel_state(channel).polarity {
Polarity::Normal => 1.0,
Polarity::Reversed => -1.0,
};
let vref_meas = match channel.into() { let vref_meas = match channel.into() {
0 => self.channel0.vref_meas, 0 => self.channel0.vref_meas,
1 => self.channel1.vref_meas, 1 => self.channel1.vref_meas,
@ -170,9 +165,10 @@ impl Channels {
}; };
let center_point = vref_meas; let center_point = vref_meas;
let r_sense = ElectricalResistance::new::<ohm>(R_SENSE); let r_sense = ElectricalResistance::new::<ohm>(R_SENSE);
let voltage = negate * i_set * 10.0 * r_sense + center_point; let voltage = i_set * 10.0 * r_sense + center_point;
let voltage = self.set_dac(channel, voltage); let voltage = self.set_dac(channel, voltage);
let i_set = negate * (voltage - center_point) / (10.0 * r_sense); let i_set = (voltage - center_point) / (10.0 * r_sense);
self.channel_state(channel).i_set = i_set;
i_set i_set
} }
@ -371,11 +367,7 @@ impl Channels {
// Get current passing through TEC // Get current passing through TEC
pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent { pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent {
let tec_i = (self.adc_read(channel, PinsAdcReadTarget::ITec, 16) - self.adc_read(channel, PinsAdcReadTarget::VREF, 16)) / ElectricalResistance::new::<ohm>(0.4); (self.adc_read(channel, PinsAdcReadTarget::ITec, 16) - self.adc_read(channel, PinsAdcReadTarget::VREF, 16)) / ElectricalResistance::new::<ohm>(0.4)
match self.channel_state(channel).polarity {
Polarity::Normal => tec_i,
Polarity::Reversed => -tec_i,
}
} }
// Get voltage across TEC // Get voltage across TEC
@ -423,10 +415,7 @@ impl Channels {
let max = ElectricCurrent::new::<ampere>(3.0); let max = ElectricCurrent::new::<ampere>(3.0);
let max_i_pos = max_i_pos.min(MAX_TEC_I).max(ElectricCurrent::zero()); let max_i_pos = max_i_pos.min(MAX_TEC_I).max(ElectricCurrent::zero());
let duty = (max_i_pos / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::<ratio>(); let duty = (max_i_pos / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::<ratio>();
let duty = match self.channel_state(channel).polarity { let duty = self.set_pwm(channel, PwmPin::MaxIPos, duty);
Polarity::Normal => self.set_pwm(channel, PwmPin::MaxIPos, duty),
Polarity::Reversed => self.set_pwm(channel, PwmPin::MaxINeg, duty),
};
self.channel_state(channel).pwm_limits.max_i_pos = max_i_pos.get::<ampere>(); self.channel_state(channel).pwm_limits.max_i_pos = max_i_pos.get::<ampere>();
(duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, max) (duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, max)
} }
@ -435,27 +424,11 @@ impl Channels {
let max = ElectricCurrent::new::<ampere>(3.0); let max = ElectricCurrent::new::<ampere>(3.0);
let max_i_neg = max_i_neg.min(MAX_TEC_I).max(ElectricCurrent::zero()); let max_i_neg = max_i_neg.min(MAX_TEC_I).max(ElectricCurrent::zero());
let duty = (max_i_neg / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::<ratio>(); let duty = (max_i_neg / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::<ratio>();
let duty = match self.channel_state(channel).polarity { let duty = self.set_pwm(channel, PwmPin::MaxINeg, duty);
Polarity::Normal => self.set_pwm(channel, PwmPin::MaxINeg, duty),
Polarity::Reversed => self.set_pwm(channel, PwmPin::MaxIPos, duty),
};
self.channel_state(channel).pwm_limits.max_i_neg = max_i_neg.get::<ampere>(); self.channel_state(channel).pwm_limits.max_i_neg = max_i_neg.get::<ampere>();
(duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, max) (duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, max)
} }
pub fn set_polarity(&mut self, channel: usize, polarity: Polarity) {
if self.channel_state(channel).polarity != polarity {
let i_set = self.channel_state(channel).i_set;
let max_i_pos = self.get_max_i_pos(channel);
let max_i_neg = self.get_max_i_neg(channel);
self.channel_state(channel).polarity = polarity;
self.set_i(channel, i_set);
self.set_max_i_pos(channel, max_i_pos);
self.set_max_i_neg(channel, max_i_neg);
}
}
fn report(&mut self, channel: usize) -> Report { fn report(&mut self, channel: usize) -> Report {
let i_set = self.get_i(channel); let i_set = self.get_i(channel);
let i_tec = self.adc_read(channel, PinsAdcReadTarget::ITec, 16); let i_tec = self.adc_read(channel, PinsAdcReadTarget::ITec, 16);
@ -515,7 +488,6 @@ impl Channels {
max_v: self.get_max_v(channel), max_v: self.get_max_v(channel),
max_i_pos: self.get_max_i_pos(channel), max_i_pos: self.get_max_i_pos(channel),
max_i_neg: self.get_max_i_neg(channel), max_i_neg: self.get_max_i_neg(channel),
polarity: PolarityJson(self.channel_state(channel).polarity.clone()),
} }
} }
@ -597,21 +569,6 @@ impl Serialize for CenterPointJson {
} }
} }
pub struct PolarityJson(Polarity);
// used in JSON encoding, not for config
impl Serialize for PolarityJson {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(match self.0 {
Polarity::Normal => "normal",
Polarity::Reversed => "reversed",
})
}
}
#[derive(Serialize)] #[derive(Serialize)]
pub struct PwmSummary { pub struct PwmSummary {
channel: usize, channel: usize,
@ -620,7 +577,6 @@ pub struct PwmSummary {
max_v: ElectricPotential, max_v: ElectricPotential,
max_i_pos: ElectricCurrent, max_i_pos: ElectricCurrent,
max_i_neg: ElectricCurrent, max_i_neg: ElectricCurrent,
polarity: PolarityJson,
} }
#[derive(Serialize)] #[derive(Serialize)]

View File

@ -11,8 +11,7 @@ use super::{
CenterPoint, CenterPoint,
PidParameter, PidParameter,
PwmPin, PwmPin,
ShParameter, ShParameter
Polarity
}, },
ad7172, ad7172,
CHANNEL_CONFIG_KEY, CHANNEL_CONFIG_KEY,
@ -171,12 +170,6 @@ impl Handler {
Ok(Handler::Handled) Ok(Handler::Handled)
} }
fn set_polarity(socket: &mut TcpSocket, channels: &mut Channels, channel: usize, polarity: Polarity) -> Result<Handler, Error> {
channels.set_polarity(channel, polarity);
send_line(socket, b"{}");
Ok(Handler::Handled)
}
fn set_pwm (socket: &mut TcpSocket, channels: &mut Channels, channel: usize, pin: PwmPin, value: f64) -> Result<Handler, Error> { fn set_pwm (socket: &mut TcpSocket, channels: &mut Channels, channel: usize, pin: PwmPin, value: f64) -> Result<Handler, Error> {
match pin { match pin {
PwmPin::ISet => { PwmPin::ISet => {
@ -418,7 +411,6 @@ impl Handler {
Command::Show(ShowCommand::PostFilter) => Handler::show_post_filter(socket, channels), Command::Show(ShowCommand::PostFilter) => Handler::show_post_filter(socket, channels),
Command::Show(ShowCommand::Ipv4) => Handler::show_ipv4(socket, ipv4_config), Command::Show(ShowCommand::Ipv4) => Handler::show_ipv4(socket, ipv4_config),
Command::PwmPid { channel } => Handler::engage_pid(socket, channels, channel), Command::PwmPid { channel } => Handler::engage_pid(socket, channels, channel),
Command::PwmPolarity { channel, polarity } => Handler::set_polarity(socket, channels, channel, polarity),
Command::Pwm { channel, pin, value } => Handler::set_pwm(socket, channels, channel, pin, value), Command::Pwm { channel, pin, value } => Handler::set_pwm(socket, channels, channel, pin, value),
Command::CenterPoint { channel, center } => Handler::set_center_point(socket, channels, channel, center), Command::CenterPoint { channel, center } => Handler::set_center_point(socket, channels, channel, center),
Command::Pid { channel, parameter, value } => Handler::set_pid(socket, channels, channel, parameter, value), Command::Pid { channel, parameter, value } => Handler::set_pid(socket, channels, channel, parameter, value),

View File

@ -135,12 +135,6 @@ pub enum CenterPoint {
Override(f32), Override(f32),
} }
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Polarity {
Normal,
Reversed,
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Command { pub enum Command {
Quit, Quit,
@ -163,10 +157,6 @@ pub enum Command {
PwmPid { PwmPid {
channel: usize, channel: usize,
}, },
PwmPolarity {
channel: usize,
polarity: Polarity,
},
CenterPoint { CenterPoint {
channel: usize, channel: usize,
center: CenterPoint, center: CenterPoint,
@ -307,18 +297,6 @@ fn pwm_pid(input: &[u8]) -> IResult<&[u8], ()> {
value((), tag("pid"))(input) value((), tag("pid"))(input)
} }
fn pwm_polarity(input: &[u8]) -> IResult<&[u8], Polarity> {
preceded(
tag("polarity"),
preceded(
whitespace,
alt((value(Polarity::Normal, tag("normal")),
value(Polarity::Reversed, tag("reversed"))
))
)
)(input)
}
fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> { fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, _) = tag("pwm")(input)?; let (input, _) = tag("pwm")(input)?;
alt(( alt((
@ -331,10 +309,6 @@ fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
let (input, ()) = pwm_pid(input)?; let (input, ()) = pwm_pid(input)?;
Ok((input, Ok(Command::PwmPid { channel }))) Ok((input, Ok(Command::PwmPid { channel })))
}, },
|input| {
let (input, polarity) = pwm_polarity(input)?;
Ok((input, Ok(Command::PwmPolarity { channel, polarity })))
},
|input| { |input| {
let (input, config) = pwm_setup(input)?; let (input, config) = pwm_setup(input)?;
match config { match config {

View File

@ -8,7 +8,7 @@ use uom::si::{
use crate::{ use crate::{
ad7172::PostFilter, ad7172::PostFilter,
channels::Channels, channels::Channels,
command_parser::{CenterPoint, Polarity}, command_parser::CenterPoint,
pid, pid,
steinhart_hart, steinhart_hart,
}; };
@ -20,7 +20,6 @@ pub struct ChannelConfig {
pid_target: f32, pid_target: f32,
pid_engaged: bool, pid_engaged: bool,
i_set: ElectricCurrent, i_set: ElectricCurrent,
polarity: Polarity,
sh: steinhart_hart::Parameters, sh: steinhart_hart::Parameters,
pwm: PwmLimits, pwm: PwmLimits,
/// uses variant `PostFilter::Invalid` instead of `None` to save space /// uses variant `PostFilter::Invalid` instead of `None` to save space
@ -46,8 +45,7 @@ impl ChannelConfig {
pid: state.pid.parameters.clone(), pid: state.pid.parameters.clone(),
pid_target: state.pid.target as f32, pid_target: state.pid.target as f32,
pid_engaged: state.pid_engaged, pid_engaged: state.pid_engaged,
i_set, i_set: i_set,
polarity: state.polarity.clone(),
sh: state.sh.clone(), sh: state.sh.clone(),
pwm, pwm,
adc_postfilter, adc_postfilter,
@ -70,7 +68,6 @@ impl ChannelConfig {
}; };
let _ = channels.adc.set_postfilter(channel as u8, adc_postfilter); let _ = channels.adc.set_postfilter(channel as u8, adc_postfilter);
let _ = channels.set_i(channel, self.i_set); let _ = channels.set_i(channel, self.i_set);
channels.set_polarity(channel, self.polarity.clone());
} }
} }