diff --git a/README.md b/README.md index e4558a3..86ab158 100644 --- a/README.md +++ b/README.md @@ -92,40 +92,43 @@ 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 formatted as line-delimited JSON. -| Syntax | Function | -|----------------------------------|-------------------------------------------------------------------------------| -| `report` | Show current input | -| `pwm` | Show current PWM settings | -| `pwm <0/1> max_i_pos ` | Set maximum positive output current | -| `pwm <0/1> max_i_neg ` | Set maximum negative output current | -| `pwm <0/1> max_v ` | Set maximum output voltage | -| `pwm <0/1> i_set ` | Disengage PID, set fixed output current | -| `pwm <0/1> pid` | Let output current to be controlled by the PID | -| `center <0/1> ` | Set the MAX1968 0A-centerpoint to the specified fixed voltage | -| `center <0/1> vref` | Set the MAX1968 0A-centerpoint to measure from VREF | -| `pid` | Show PID configuration | -| `pid <0/1> target ` | Set the PID controller target temperature | -| `pid <0/1> kp ` | Set proportional gain | -| `pid <0/1> ki ` | Set integral gain | -| `pid <0/1> kd ` | Set differential gain | -| `pid <0/1> output_min ` | Set mininum output | -| `pid <0/1> output_max ` | Set maximum output | -| `s-h` | Show Steinhart-Hart equation parameters | -| `s-h <0/1> ` | Set Steinhart-Hart parameter for a channel | -| `postfilter` | Show postfilter settings | -| `postfilter <0/1> off` | Disable postfilter | -| `postfilter <0/1> rate ` | Set postfilter output data rate | -| `load [0/1]` | Restore configuration for channel all/0/1 from flash | -| `save [0/1]` | Save configuration for channel all/0/1 to flash | -| `reset` | Reset the device | -| `dfu` | Reset device and enters USB device firmware update (DFU) mode | -| `ipv4 [Y.Y.Y.Y]` | Configure IPv4 address, netmask length, and optional default gateway | -| `fan` | Show current fan settings and sensors' measurements | -| `fan ` | Set fan power with values from 1 to 100 | -| `fan auto` | Enable automatic fan speed control | -| `fcurve ` | 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 | +| Syntax | Function | +|------------------------------------------- |-------------------------------------------------------------------------------| +| `report` | Show current input | +| `report mode` | Show current report mode | +| `report mode ` | Set report mode | +| `pwm` | Show current PWM settings | +| `pwm <0/1> max_i_pos ` | Set maximum positive output current | +| `pwm <0/1> max_i_neg ` | Set maximum negative output current | +| `pwm <0/1> max_v ` | Set maximum output voltage | +| `pwm <0/1> i_set ` | Disengage PID, set fixed output current | +| `pwm <0/1> polarity ` | Set output current polarity, with 'normal' being the front panel polarity | +| `pwm <0/1> pid` | Let output current to be controlled by the PID | +| `center <0/1> ` | Set the MAX1968 0A-centerpoint to the specified fixed voltage | +| `center <0/1> vref` | Set the MAX1968 0A-centerpoint to measure from VREF | +| `pid` | Show PID configuration | +| `pid <0/1> target ` | Set the PID controller target temperature | +| `pid <0/1> kp ` | Set proportional gain | +| `pid <0/1> ki ` | Set integral gain | +| `pid <0/1> kd ` | Set differential gain | +| `pid <0/1> output_min ` | Set mininum output | +| `pid <0/1> output_max ` | Set maximum output | +| `s-h` | Show Steinhart-Hart equation parameters | +| `s-h <0/1> ` | Set Steinhart-Hart parameter for a channel | +| `postfilter` | Show postfilter settings | +| `postfilter <0/1> off` | Disable postfilter | +| `postfilter <0/1> rate ` | Set postfilter output data rate | +| `load [0/1]` | Restore configuration for channel all/0/1 from flash | +| `save [0/1]` | Save configuration for channel all/0/1 to flash | +| `reset` | Reset the device | +| `dfu` | Reset device and enters USB device firmware update (DFU) mode | +| `ipv4 [Y.Y.Y.Y]` | Configure IPv4 address, netmask length, and optional default gateway | +| `fan` | Show current fan settings and sensors' measurements | +| `fan ` | Set fan power with values from 1 to 100 | +| `fan auto` | Enable automatic fan speed control | +| `fcurve ` | 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 @@ -182,6 +185,8 @@ 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. +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 polarity reversed` TCP command. + Testing heat flow direction with a low set current is recommended before installation of the TEC module. ### Limits diff --git a/src/channel_state.rs b/src/channel_state.rs index 4820131..505c467 100644 --- a/src/channel_state.rs +++ b/src/channel_state.rs @@ -17,7 +17,7 @@ use crate::{ ad7172, pid, steinhart_hart as sh, - command_parser::CenterPoint, + command_parser::{CenterPoint, Polarity}, }; const R_INNER: f64 = 2.0 * 5100.0; @@ -35,6 +35,7 @@ pub struct ChannelState { pub pid_engaged: bool, pub pid: pid::Controller, pub sh: sh::Parameters, + pub polarity: Polarity, } impl ChannelState { @@ -51,6 +52,7 @@ impl ChannelState { pid_engaged: false, pid: pid::Controller::new(pid::Parameters::default()), sh: sh::Parameters::default(), + polarity: Polarity::Normal, } } diff --git a/src/channels.rs b/src/channels.rs index de61567..94dc7b3 100644 --- a/src/channels.rs +++ b/src/channels.rs @@ -17,7 +17,7 @@ use crate::{ ad7172, channel::{Channel, Channel0, Channel1}, channel_state::ChannelState, - command_parser::{CenterPoint, PwmPin}, + command_parser::{CenterPoint, PwmPin, Polarity}, command_handler::JsonBuffer, pins::{self, Channel0VRef, Channel1VRef}, steinhart_hart, @@ -158,6 +158,11 @@ impl Channels { 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); + 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() { 0 => self.channel0.vref_meas, 1 => self.channel1.vref_meas, @@ -165,10 +170,9 @@ impl Channels { }; let center_point = vref_meas; let r_sense = ElectricalResistance::new::(R_SENSE); - let voltage = i_set * 10.0 * r_sense + center_point; + let voltage = negate * i_set * 10.0 * r_sense + center_point; let voltage = self.set_dac(channel, voltage); - let i_set = (voltage - center_point) / (10.0 * r_sense); - self.channel_state(channel).i_set = i_set; + let i_set = negate * (voltage - center_point) / (10.0 * r_sense); i_set } @@ -386,18 +390,28 @@ impl Channels { } pub fn get_max_i_pos(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) { - let duty = self.get_pwm(channel, PwmPin::MaxIPos); + let duty = match self.channel_state(channel).polarity { + Polarity::Normal => self.get_pwm(channel, PwmPin::MaxIPos), + Polarity::Reversed => self.get_pwm(channel, PwmPin::MaxINeg), + }; (duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, MAX_TEC_I) } pub fn get_max_i_neg(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) { - let duty = self.get_pwm(channel, PwmPin::MaxINeg); + let duty = match self.channel_state(channel).polarity { + Polarity::Normal => self.get_pwm(channel, PwmPin::MaxINeg), + Polarity::Reversed => self.get_pwm(channel, PwmPin::MaxIPos), + }; (duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, MAX_TEC_I) } // Get current passing through TEC pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent { - (self.adc_read(channel, PinsAdcReadTarget::ITec, 16) - self.adc_read(channel, PinsAdcReadTarget::VREF, 16)) / ElectricalResistance::new::(0.4) + let tec_i = (self.adc_read(channel, PinsAdcReadTarget::ITec, 16) - self.adc_read(channel, PinsAdcReadTarget::VREF, 16)) / ElectricalResistance::new::(0.4); + match self.channel_state(channel).polarity { + Polarity::Normal => tec_i, + Polarity::Reversed => -tec_i, + } } // Get voltage across TEC @@ -442,17 +456,36 @@ impl Channels { pub fn set_max_i_pos(&mut self, channel: usize, max_i_pos: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) { let max = ElectricCurrent::new::(3.0); let duty = (max_i_pos.min(MAX_TEC_I).max(ElectricCurrent::zero()) / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::(); - let duty = self.set_pwm(channel, PwmPin::MaxIPos, duty); + let duty = match self.channel_state(channel).polarity { + Polarity::Normal => self.set_pwm(channel, PwmPin::MaxIPos, duty), + Polarity::Reversed => self.set_pwm(channel, PwmPin::MaxINeg, duty), + }; (duty * MAX_TEC_I_DUTY_TO_CURRENT_RATE, max) } pub fn set_max_i_neg(&mut self, channel: usize, max_i_neg: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) { let max = ElectricCurrent::new::(3.0); let duty = (max_i_neg.min(MAX_TEC_I).max(ElectricCurrent::zero()) / MAX_TEC_I_DUTY_TO_CURRENT_RATE).get::(); - let duty = self.set_pwm(channel, PwmPin::MaxINeg, duty); + let duty = match self.channel_state(channel).polarity { + Polarity::Normal => self.set_pwm(channel, PwmPin::MaxINeg, duty), + Polarity::Reversed => self.set_pwm(channel, PwmPin::MaxIPos, duty), + }; (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).0; + let max_i_neg = self.get_max_i_neg(channel).0; + 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 { let i_set = self.get_i(channel); let i_tec = self.adc_read(channel, PinsAdcReadTarget::ITec, 16); diff --git a/src/command_handler.rs b/src/command_handler.rs index 3cf3b6e..6fd1d1e 100644 --- a/src/command_handler.rs +++ b/src/command_handler.rs @@ -11,7 +11,8 @@ use super::{ CenterPoint, PidParameter, PwmPin, - ShParameter + ShParameter, + Polarity }, ad7172, CHANNEL_CONFIG_KEY, @@ -170,6 +171,12 @@ impl Handler { Ok(Handler::Handled) } + fn set_polarity(socket: &mut TcpSocket, channels: &mut Channels, channel: usize, polarity: Polarity) -> Result { + 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 { match pin { PwmPin::ISet => { @@ -411,6 +418,7 @@ impl Handler { Command::Show(ShowCommand::PostFilter) => Handler::show_post_filter(socket, channels), Command::Show(ShowCommand::Ipv4) => Handler::show_ipv4(socket, ipv4_config), 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::CenterPoint { channel, center } => Handler::set_center_point(socket, channels, channel, center), Command::Pid { channel, parameter, value } => Handler::set_pid(socket, channels, channel, parameter, value), diff --git a/src/command_parser.rs b/src/command_parser.rs index 56a593e..f8bfd72 100644 --- a/src/command_parser.rs +++ b/src/command_parser.rs @@ -135,6 +135,12 @@ pub enum CenterPoint { Override(f32), } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Polarity { + Normal, + Reversed, +} + #[derive(Debug, Clone, PartialEq)] pub enum Command { Quit, @@ -157,6 +163,10 @@ pub enum Command { PwmPid { channel: usize, }, + PwmPolarity { + channel: usize, + polarity: Polarity, + }, CenterPoint { channel: usize, center: CenterPoint, @@ -297,6 +307,18 @@ fn pwm_pid(input: &[u8]) -> IResult<&[u8], ()> { 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> { let (input, _) = tag("pwm")(input)?; alt(( @@ -309,6 +331,10 @@ fn pwm(input: &[u8]) -> IResult<&[u8], Result> { let (input, ()) = pwm_pid(input)?; Ok((input, Ok(Command::PwmPid { channel }))) }, + |input| { + let (input, polarity) = pwm_polarity(input)?; + Ok((input, Ok(Command::PwmPolarity { channel, polarity }))) + }, |input| { let (input, config) = pwm_setup(input)?; match config { diff --git a/src/config.rs b/src/config.rs index 1649dde..f4253f7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,7 +8,7 @@ use uom::si::{ use crate::{ ad7172::PostFilter, channels::Channels, - command_parser::CenterPoint, + command_parser::{CenterPoint, Polarity}, pid, steinhart_hart, }; @@ -20,6 +20,7 @@ pub struct ChannelConfig { pid_target: f32, pid_engaged: bool, i_set: ElectricCurrent, + polarity: Polarity, sh: steinhart_hart::Parameters, pwm: PwmLimits, /// uses variant `PostFilter::Invalid` instead of `None` to save space @@ -45,7 +46,8 @@ impl ChannelConfig { pid: state.pid.parameters.clone(), pid_target: state.pid.target as f32, pid_engaged: state.pid_engaged, - i_set: i_set, + i_set, + polarity: state.polarity.clone(), sh: state.sh.clone(), pwm, adc_postfilter, @@ -68,6 +70,7 @@ impl ChannelConfig { }; let _ = channels.adc.set_postfilter(channel as u8, adc_postfilter); let _ = channels.set_i(channel, self.i_set); + channels.set_polarity(channel, self.polarity.clone()); } }