From 32bd49b258a9c29f6ad76b0d33b3d326e3f4b718 Mon Sep 17 00:00:00 2001 From: atse Date: Tue, 20 Aug 2024 17:42:44 +0800 Subject: [PATCH 1/2] Add a command to reverse the output polarity Effectively flips all values related to the directionality of current through the TEC, including current maximums. The reversed status is stored in the flash store and will be loaded on reset once saved. The command is "pwm polarity ", where the "normal" polarity is indicated by the front panel markings. This is needed for IDC cable connections to the Sinara 5432 DAC "Zotino", since the TEC pins of the 10-pin connector on the Thermostat and Zotino have opposite polarities. --- README.md | 73 ++++++++++++++++++++++-------------------- src/channel_state.rs | 4 ++- src/channels.rs | 51 +++++++++++++++++++++++------ src/command_handler.rs | 10 +++++- src/command_parser.rs | 26 +++++++++++++++ src/config.rs | 7 ++-- 6 files changed, 124 insertions(+), 47 deletions(-) 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()); } } -- 2.44.2 From 6224486662a6958475d86c61679f9b44d5952be5 Mon Sep 17 00:00:00 2001 From: atse Date: Mon, 30 Sep 2024 10:45:42 +0800 Subject: [PATCH 2/2] Show polarity in pwm command --- pytec/pytec/client.py | 2 ++ src/channels.rs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/pytec/pytec/client.py b/pytec/pytec/client.py index 5ac2095..159fd41 100644 --- a/pytec/pytec/client.py +++ b/pytec/pytec/client.py @@ -57,12 +57,14 @@ class Client: 'max_i_neg': {'max': 3.0, 'value': 3.0}, 'max_v': {'max': 5.988, 'value': 5.988}, 'max_i_pos': {'max': 3.0, 'value': 3.0}}, + 'polarity': 'normal', {'channel': 1, 'center': 'vref', 'i_set': {'max': 2.9802790335151985, 'value': -0.02002179650216762}, 'max_i_neg': {'max': 3.0, 'value': 3.0}, 'max_v': {'max': 5.988, 'value': 5.988}, 'max_i_pos': {'max': 3.0, 'value': 3.0}} + 'polarity': 'normal', ] """ return self._get_conf("pwm") diff --git a/src/channels.rs b/src/channels.rs index 94dc7b3..802c245 100644 --- a/src/channels.rs +++ b/src/channels.rs @@ -545,6 +545,7 @@ impl Channels { max_v: self.get_max_v(channel).into(), max_i_pos: self.get_max_i_pos(channel).into(), max_i_neg: self.get_max_i_neg(channel).into(), + polarity: PolarityJson(self.channel_state(channel).polarity.clone()), } } @@ -626,6 +627,21 @@ impl Serialize for CenterPointJson { } } +pub struct PolarityJson(Polarity); + +// used in JSON encoding, not for config +impl Serialize for PolarityJson { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(match self.0 { + Polarity::Normal => "normal", + Polarity::Reversed => "reversed", + }) + } +} + #[derive(Serialize)] pub struct PwmSummaryField { value: T, @@ -646,6 +662,7 @@ pub struct PwmSummary { max_v: PwmSummaryField, max_i_pos: PwmSummaryField, max_i_neg: PwmSummaryField, + polarity: PolarityJson, } #[derive(Serialize)] -- 2.44.2