diff --git a/README.md b/README.md index 955c638..6c9c81e 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ formatted as line-delimited JSON. | `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 0 to 100, where 0 is auto mode (TODO) | +| `fan ` | Set fan power with values from 0 to 100, where 0 is auto mode | ## USB @@ -271,3 +271,13 @@ with the following keys. ## PID Tuning The thermostat implements a PID control loop for each of the TEC channels, more details on setting up the PID control loop can be found [here](./doc/PID%20tuning.md). + +## Fan control + +Fan control is available for the thermostat revisions with integrated fan system. For this purpose two commands are available: +1. `fan` - show fan stats: `fan_pwm`, `tacho`, `abs_max_tec_i`, `auto_mode`. Please note that `tacho` shows *approximate* value, which +linearly correlates with the actual fan speed. +2. `fan ` - set the fan power with the value from `0` to `100`. Since there is no hardware way to disable the fan, +`0` value is used for enabling automatic fan control mode, which correlates with the square of the TEC's current. +Values from `1` to `100` are used for setting the power from minimum to maximum respectively. +Please note that power doesn't correlate with the actual speed linearly. diff --git a/src/channels.rs b/src/channels.rs index b40656b..40927be 100644 --- a/src/channels.rs +++ b/src/channels.rs @@ -26,6 +26,9 @@ pub const CHANNELS: usize = 2; pub const R_SENSE: f64 = 0.05; // DAC chip outputs 0-5v, which is then passed through a resistor dividor to provide 0-3v range const DAC_OUT_V_MAX: f64 = 3.0; +const MAX_TEC_I: f64 = 3.0; // as stated in the schemes +const MAX_FAN_PWM: f64 = 100.0; +const MIN_FAN_PWM: f64 = 1.0; #[derive(Serialize, Copy, Clone)] pub struct HWRev { @@ -41,7 +44,8 @@ pub struct Channels { /// stm32f4 integrated adc pins_adc: pins::PinsAdc, pub pwm: pins::PwmPins, - pub hw_rev: HWRev + hw_rev: HWRev, + fan_auto: bool } impl Channels { @@ -63,7 +67,8 @@ impl Channels { let channel1 = Channel::new(pins.channel1, adc_calibration1); let pins_adc = pins.pins_adc; let pwm = pins.pwm; - let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm, hw_rev: Self::detect_hw_rev(&pins.hwrev) }; + let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm, + hw_rev: Self::detect_hw_rev(&pins.hwrev), fan_auto: true }; for channel in 0..CHANNELS { channels.channel_state(channel).vref = channels.read_vref(channel); channels.calibrate_dac_value(channel); @@ -407,7 +412,7 @@ impl Channels { (self.read_tec_u_meas(channel) - ElectricPotential::new::(1.5)) * 4.0 } - pub fn set_pwm(&mut self, channel: usize, pin: PwmPin, duty: f64) -> f64 { + fn set_pwm(&mut self, channel: usize, pin: PwmPin, duty: f64) -> f64 { fn set>(pin: &mut P, duty: f64) -> f64 { let max = pin.get_max_duty(); let value = ((duty * (max as f64)) as u16).min(max); @@ -458,12 +463,16 @@ impl Channels { } pub fn set_fan_pwm(&mut self, fan_pwm: u32) -> f64 { - let duty = fan_pwm as f64 / 100.0; + let duty = fan_pwm as f64 / MAX_FAN_PWM; self.set_pwm(0, PwmPin::Fan, duty) } pub fn get_fan_pwm(&mut self) -> u32 { - (self.get_pwm(0, PwmPin::Fan) * 100.0) as u32 + (self.get_pwm(0, PwmPin::Fan) * MAX_FAN_PWM) as u32 + } + + pub fn set_fan_auto_mode(&mut self, fan_auto: bool) { + self.fan_auto = fan_auto; } fn report(&mut self, channel: usize) -> Report { @@ -557,12 +566,19 @@ impl Channels { serde_json_core::to_vec(&summaries) } + fn current_abs_max_tec_i(&mut self) -> f64 { + max_by(self.get_tec_i(0).abs().get::(), + self.get_tec_i(1).abs().get::(), + |a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)) + } + pub fn fan_summary(&mut self, tacho: Option) -> Result { if self.fan_available() { let summary = FanSummary { fan_pwm: self.get_fan_pwm(), tacho: tacho.unwrap_or(u32::MAX), - abs_max_tec_i: max_by(self.get_tec_i(0).abs().value, self.get_tec_i(1).abs().value, |a, b| a.partial_cmp(b).unwrap()) + abs_max_tec_i: self.current_abs_max_tec_i(), + auto_mode: self.fan_auto }; serde_json_core::to_vec(&summary) } else { @@ -570,6 +586,16 @@ impl Channels { serde_json_core::to_vec(&summary) } } + + pub fn fan_ctrl(&mut self) { + if self.fan_auto && self.fan_available() { + let scaled_current = self.current_abs_max_tec_i() / MAX_TEC_I; + let pwm = max_by(scaled_current * scaled_current * MAX_FAN_PWM, + MIN_FAN_PWM, + |a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)) as u32; + self.set_fan_pwm(pwm); + } + } } type JsonBuffer = Vec; @@ -650,4 +676,5 @@ pub struct FanSummary { fan_pwm: u32, tacho: u32, abs_max_tec_i: f64, + auto_mode: bool, } diff --git a/src/command_handler.rs b/src/command_handler.rs index 0fcf252..79a2b5a 100644 --- a/src/command_handler.rs +++ b/src/command_handler.rs @@ -347,13 +347,14 @@ impl Handler { fn fan (socket: &mut TcpSocket, channels: &mut Channels, fan_pwm: Option, tacho_value: Option) -> Result { match fan_pwm { Some(val) => { + channels.set_fan_auto_mode(val == 0); channels.set_fan_pwm(val); - Ok(Handler::Handled) }, None => { match channels.fan_summary(tacho_value) { Ok(buf) => { send_line(socket, &buf); + return Ok(Handler::Handled); } Err(e) => { error!("unable to serialize fan summary: {:?}", e); @@ -361,9 +362,10 @@ impl Handler { return Err(Error::ReportError); } }; - Ok(Handler::Handled) } - } + }; + send_line(socket, b"{}"); + Ok(Handler::Handled) } pub fn handle_command (command: Command, socket: &mut TcpSocket, channels: &mut Channels, session: &Session, leds: &mut Leds, store: &mut FlashStore, ipv4_config: &mut Ipv4Config, tacho_value: Option) -> Result { diff --git a/src/command_parser.rs b/src/command_parser.rs index 82069a2..b87b40a 100644 --- a/src/command_parser.rs +++ b/src/command_parser.rs @@ -303,8 +303,7 @@ fn pwm_setup(input: &[u8]) -> IResult<&[u8], Result<(PwmPin, f64), Error>> { ) ), result_with_pin(PwmPin::MaxV) - ), - ) + )) )(input) } diff --git a/src/main.rs b/src/main.rs index 77f6d8b..7314c43 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,14 +19,14 @@ use stm32f4xx_hal::{ stm32::{CorePeripherals, Peripherals, SCB}, time::{U32Ext, MegaHertz}, watchdog::IndependentWatchdog, + gpio::{Edge, ExtiPin}, + syscfg::SysCfgExt }; use smoltcp::{ time::Instant, socket::TcpSocket, wire::EthernetAddress, }; -use stm32f4xx_hal::gpio::{Edge, ExtiPin}; -use stm32f4xx_hal::syscfg::SysCfgExt; mod init_log; use init_log::init_log; @@ -220,6 +220,8 @@ fn main() -> ! { } } + channels.fan_ctrl(); + cortex_m::interrupt::free(net::clear_pending); server.poll(instant) .unwrap_or_else(|e| {