diff --git a/README.md b/README.md index 7ba5996..975e4d0 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ formatted as line-delimited JSON. | `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 | ## USB diff --git a/src/channels.rs b/src/channels.rs index c594c34..c86305d 100644 --- a/src/channels.rs +++ b/src/channels.rs @@ -1,5 +1,5 @@ use core::cmp::max_by; -use heapless::{consts::{U2, U1024}, Vec}; +use heapless::{consts::U2, Vec}; use serde::{Serialize, Serializer}; use smoltcp::time::Instant; use stm32f4xx_hal::hal; @@ -17,9 +17,9 @@ use crate::{ channel::{Channel, Channel0, Channel1}, channel_state::ChannelState, command_parser::{CenterPoint, PwmPin}, + command_handler::JsonBuffer, pins, steinhart_hart, - fan_ctrl::HWRev, }; pub const CHANNELS: usize = 2; @@ -35,7 +35,6 @@ pub struct Channels { /// stm32f4 integrated adc pins_adc: pins::PinsAdc, pub pwm: pins::PwmPins, - pub hwrev: HWRev, } impl Channels { @@ -57,8 +56,7 @@ 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, - hwrev: HWRev::detect_hw_rev(&pins.hwrev)}; + let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm }; for channel in 0..CHANNELS { channels.channel_state(channel).vref = channels.read_vref(channel); channels.calibrate_dac_value(channel); @@ -458,7 +456,6 @@ impl Channels { tec_i, tec_u_meas: self.get_tec_v(channel), pid_output, - hwrev: self.hwrev } } @@ -531,8 +528,6 @@ impl Channels { } } -pub type JsonBuffer = Vec; - #[derive(Serialize)] pub struct Report { channel: usize, @@ -550,7 +545,6 @@ pub struct Report { tec_i: ElectricCurrent, tec_u_meas: ElectricPotential, pid_output: ElectricCurrent, - hwrev: HWRev, } pub struct CenterPointJson(CenterPoint); diff --git a/src/command_handler.rs b/src/command_handler.rs index 76f507a..50547c0 100644 --- a/src/command_handler.rs +++ b/src/command_handler.rs @@ -1,6 +1,7 @@ use smoltcp::socket::TcpSocket; use log::{error, warn}; use core::fmt::Write; +use heapless::{consts::U1024, Vec}; use super::{ net, command_parser::{ @@ -24,6 +25,7 @@ use super::{ flash_store::FlashStore, session::Session, FanCtrl, + hw_rev::HWRev, }; use uom::{ @@ -56,6 +58,8 @@ pub enum Error { FlashError } +pub type JsonBuffer = Vec; + fn send_line(socket: &mut TcpSocket, data: &[u8]) -> bool { let send_free = socket.send_capacity() - socket.send_queue(); if data.len() > send_free + 1 { @@ -345,7 +349,11 @@ impl Handler { fn set_fan(socket: &mut TcpSocket, fan_pwm: u32, fan_ctrl: &mut FanCtrl) -> Result { fan_ctrl.set_auto_mode(false); fan_ctrl.set_pwm(fan_pwm); - send_line(socket, b"{}"); + if fan_ctrl.is_default_auto() { + send_line(socket, b"{}"); + } else { + send_line(socket, b"{ \"warning\": \"this fan doesn't have full PWM support. Use it on your own risk!\" }"); + } Ok(Handler::Handled) } @@ -365,11 +373,15 @@ impl Handler { fn fan_auto(socket: &mut TcpSocket, fan_ctrl: &mut FanCtrl) -> Result { fan_ctrl.set_auto_mode(true); - send_line(socket, b"{}"); + if fan_ctrl.is_default_auto() { + send_line(socket, b"{}"); + } else { + send_line(socket, b"{ \"warning\": \"this fan doesn't have full PWM support. Use it on your own risk!\" }"); + } Ok(Handler::Handled) } - fn fan_curve(socket: &mut TcpSocket, fan_ctrl: &mut FanCtrl, k_a: f64, k_b: f64, k_c: f64) -> Result { + fn fan_curve(socket: &mut TcpSocket, fan_ctrl: &mut FanCtrl, k_a: f32, k_b: f32, k_c: f32) -> Result { fan_ctrl.set_curve(k_a, k_b, k_c); send_line(socket, b"{}"); Ok(Handler::Handled) @@ -381,7 +393,21 @@ impl Handler { 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, fan_ctrl: &mut FanCtrl) -> Result { + fn show_hwrev(socket: &mut TcpSocket, hwrev: HWRev) -> Result { + match hwrev.summary() { + Ok(buf) => { + send_line(socket, &buf); + Ok(Handler::Handled) + } + Err(e) => { + error!("unable to serialize HWRev summary: {:?}", e); + let _ = writeln!(socket, "{{\"error\":\"{:?}\"}}", e); + Err(Error::ReportError) + } + } + } + + pub fn handle_command(command: Command, socket: &mut TcpSocket, channels: &mut Channels, session: &Session, leds: &mut Leds, store: &mut FlashStore, ipv4_config: &mut Ipv4Config, fan_ctrl: &mut FanCtrl, hwrev: HWRev) -> Result { match command { Command::Quit => Ok(Handler::CloseSocket), Command::Reporting(_reporting) => Handler::reporting(socket), @@ -409,6 +435,7 @@ impl Handler { Command::FanAuto => Handler::fan_auto(socket, fan_ctrl), Command::FanCurve { k_a, k_b, k_c } => Handler::fan_curve(socket, fan_ctrl, k_a, k_b, k_c), Command::FanCurveDefaults => Handler::fan_defaults(socket, fan_ctrl), + Command::ShowHWRev => Handler::show_hwrev(socket, hwrev), } } } \ No newline at end of file diff --git a/src/command_parser.rs b/src/command_parser.rs index 1846eb4..8a32053 100644 --- a/src/command_parser.rs +++ b/src/command_parser.rs @@ -185,11 +185,12 @@ pub enum Command { FanAuto, ShowFan, FanCurve { - k_a: f64, - k_b: f64, - k_c: f64, + k_a: f32, + k_b: f32, + k_c: f32, }, FanCurveDefaults, + ShowHWRev, } fn end(input: &[u8]) -> IResult<&[u8], ()> { @@ -573,7 +574,7 @@ fn fan_curve(input: &[u8]) -> IResult<&[u8], Result> { let (input, k_c) = float(input)?; let (input, _) = end(input)?; if k_a.is_ok() && k_b.is_ok() && k_c.is_ok() { - Ok((input, Ok(Command::FanCurve { k_a: k_a.unwrap(), k_b: k_b.unwrap(), k_c: k_c.unwrap() }))) + Ok((input, Ok(Command::FanCurve { k_a: k_a.unwrap() as f32, k_b: k_b.unwrap() as f32, k_c: k_c.unwrap() as f32 }))) } else { Err(nom::Err::Incomplete(Needed::Size(3))) } @@ -601,6 +602,7 @@ fn command(input: &[u8]) -> IResult<&[u8], Result> { value(Ok(Command::Dfu), tag("dfu")), fan, fan_curve, + value(Ok(Command::ShowHWRev), tag("hwrev")), ))(input) } @@ -856,4 +858,10 @@ mod test { let command = Command::parse(b"fcurve default"); assert_eq!(command, Ok(Command::FanCurveDefaults)); } + + #[test] + fn parse_hwrev() { + let command = Command::parse(b"hwrev"); + assert_eq!(command, Ok(Command::ShowHWRev)); + } } diff --git a/src/fan_ctrl.rs b/src/fan_ctrl.rs index 9b3fda9..9480493 100644 --- a/src/fan_ctrl.rs +++ b/src/fan_ctrl.rs @@ -6,65 +6,62 @@ use stm32f4xx_hal::{ }; use crate::{ - pins::HWRevPins, - channels::JsonBuffer, + hw_rev::HWRev, + command_handler::JsonBuffer, }; pub type FanPin = PwmChannels; // as stated in the schematics -const MAX_TEC_I: f64 = 3.0; +const MAX_TEC_I: f32 = 3.0; -const MAX_USER_FAN_PWM: f64 = 100.0; -const MIN_USER_FAN_PWM: f64 = 1.0; -const MAX_FAN_PWM: f64 = 1.0; +const MAX_USER_FAN_PWM: f32 = 100.0; +const MIN_USER_FAN_PWM: f32 = 1.0; +const MAX_FAN_PWM: f32 = 1.0; // below this value motor's autostart feature may fail -const MIN_FAN_PWM: f64 = 0.04; +const MIN_FAN_PWM: f32 = 0.04; -const DEFAULT_K_A: f64 = 1.0; -const DEFAULT_K_B: f64 = 0.0; -const DEFAULT_K_C: f64 = 0.0; - - -#[derive(Serialize, Copy, Clone)] -pub struct HWRev { - pub major: u8, - pub minor: u8, -} +const DEFAULT_K_A: f32 = 1.0; +const DEFAULT_K_B: f32 = 0.0; +const DEFAULT_K_C: f32 = 0.0; pub struct FanCtrl { fan: FanPin, fan_auto: bool, available: bool, - k_a: f64, - k_b: f64, - k_c: f64, - abs_max_tec_i: f64, + default_auto: bool, + pwm_enabled: bool, + k_a: f32, + k_b: f32, + k_c: f32, + abs_max_tec_i: f32, } impl FanCtrl { - pub fn new(mut fan: FanPin, hwrev: &HWRev) -> Self { + pub fn new(fan: FanPin, hwrev: HWRev) -> Self { let available = hwrev.fan_available(); + let default_auto = hwrev.fan_default_auto(); - if available { - fan.set_duty(0); - fan.enable(); - } - - FanCtrl { + let mut fan_ctrl = FanCtrl { fan, available, // do not enable auto mode by default, - // but allow to turn it on on customer's own risk - fan_auto: hwrev.fan_auto_mode_available(), + // but allow to turn it on on user's own risk + default_auto, + fan_auto: default_auto, + pwm_enabled: false, k_a: DEFAULT_K_A, k_b: DEFAULT_K_B, k_c: DEFAULT_K_C, - abs_max_tec_i: 0f64, + abs_max_tec_i: 0f32, + }; + if fan_ctrl.fan_auto { + fan_ctrl.enable_pwm(); } + fan_ctrl } - pub fn cycle(&mut self, abs_max_tec_i: f64) { + pub fn cycle(&mut self, abs_max_tec_i: f32) { self.abs_max_tec_i = abs_max_tec_i; self.adjust_speed(); } @@ -99,7 +96,7 @@ impl FanCtrl { self.fan_auto = fan_auto; } - pub fn set_curve(&mut self, k_a: f64, k_b: f64, k_c: f64) { + pub fn set_curve(&mut self, k_a: f32, k_b: f32, k_c: f32) { self.k_a = k_a; self.k_b = k_b; self.k_c = k_c; @@ -109,55 +106,47 @@ impl FanCtrl { self.set_curve(DEFAULT_K_A, DEFAULT_K_B, DEFAULT_K_C); } - pub fn set_pwm(&mut self, fan_pwm: u32) -> f64 { + pub fn set_pwm(&mut self, fan_pwm: u32) -> f32 { + if !self.pwm_enabled { + self.enable_pwm() + } let fan_pwm = fan_pwm.min(MAX_USER_FAN_PWM as u32).max(MIN_USER_FAN_PWM as u32); - let duty = Self::scale_number(fan_pwm as f64, MIN_FAN_PWM, MAX_FAN_PWM, MIN_USER_FAN_PWM, MAX_USER_FAN_PWM); + let duty = Self::scale_number(fan_pwm as f32, MIN_FAN_PWM, MAX_FAN_PWM, MIN_USER_FAN_PWM, MAX_USER_FAN_PWM); let max = self.fan.get_max_duty(); - let value = ((duty * (max as f64)) as u16).min(max); + let value = ((duty * (max as f32)) as u16).min(max); self.fan.set_duty(value); - value as f64 / (max as f64) + value as f32 / (max as f32) } - fn scale_number(unscaled: f64, to_min: f64, to_max: f64, from_min: f64, from_max: f64) -> f64 { + pub fn is_default_auto(&self) -> bool { + self.default_auto + } + + fn scale_number(unscaled: f32, to_min: f32, to_max: f32, from_min: f32, from_max: f32) -> f32 { (to_max - to_min) * (unscaled - from_min) / (from_max - from_min) + to_min } fn get_pwm(&self) -> u32 { let duty = self.fan.get_duty(); let max = self.fan.get_max_duty(); - Self::scale_number(duty as f64 / (max as f64), MIN_USER_FAN_PWM, MAX_USER_FAN_PWM, MIN_FAN_PWM, MAX_FAN_PWM).round() as u32 + Self::scale_number(duty as f32 / (max as f32), MIN_USER_FAN_PWM, MAX_USER_FAN_PWM, MIN_FAN_PWM, MAX_FAN_PWM).round() as u32 } -} -impl HWRev { - pub fn detect_hw_rev(hwrev_pins: &HWRevPins) -> Self { - let (h0, h1, h2, h3) = (hwrev_pins.hwrev0.is_high(), hwrev_pins.hwrev1.is_high(), - hwrev_pins.hwrev2.is_high(), hwrev_pins.hwrev3.is_high()); - match (h0, h1, h2, h3) { - (true, true, true, false) => HWRev { major: 1, minor: 0 }, - (true, false, false, false) => HWRev { major: 2, minor: 0 }, - (false, true, false, false) => HWRev { major: 2, minor: 2 }, - (_, _, _, _) => HWRev { major: 0, minor: 0 } + fn enable_pwm(&mut self) { + if self.available { + self.fan.set_duty(0); + self.fan.enable(); + self.pwm_enabled = true; } } - - pub fn fan_available(&self) -> bool { - self.major == 2 && self.minor == 2 - } - - pub fn fan_auto_mode_available(&self) -> bool { - // see https://github.com/sinara-hw/Thermostat/issues/115 and - // https://git.m-labs.hk/M-Labs/thermostat/issues/69#issuecomment-6464 for explanation - self.fan_available() && self.minor != 2 - } } #[derive(Serialize)] pub struct FanSummary { fan_pwm: u32, - abs_max_tec_i: f64, + abs_max_tec_i: f32, auto_mode: bool, - k_a: f64, - k_b: f64, - k_c: f64, + k_a: f32, + k_b: f32, + k_c: f32, } diff --git a/src/hw_rev.rs b/src/hw_rev.rs new file mode 100644 index 0000000..413dc72 --- /dev/null +++ b/src/hw_rev.rs @@ -0,0 +1,41 @@ +use serde::Serialize; + +use crate::{ + pins::HWRevPins, + command_handler::JsonBuffer +}; + +#[derive(Serialize, Copy, Clone)] +pub struct HWRev { + pub major: u8, + pub minor: u8, +} + + +impl HWRev { + pub fn detect_hw_rev(hwrev_pins: &HWRevPins) -> Self { + let (h0, h1, h2, h3) = (hwrev_pins.hwrev0.is_high(), hwrev_pins.hwrev1.is_high(), + hwrev_pins.hwrev2.is_high(), hwrev_pins.hwrev3.is_high()); + match (h0, h1, h2, h3) { + (true, true, true, false) => HWRev { major: 1, minor: 0 }, + (true, false, false, false) => HWRev { major: 2, minor: 0 }, + (false, true, false, false) => HWRev { major: 2, minor: 2 }, + (_, _, _, _) => HWRev { major: 0, minor: 0 } + } + } + + pub fn fan_available(&self) -> bool { + self.major == 2 && self.minor == 2 + } + + pub fn fan_default_auto(&self) -> bool { + // see https://github.com/sinara-hw/Thermostat/issues/115 and + // https://git.m-labs.hk/M-Labs/thermostat/issues/69#issuecomment-6464 for explanation + self.fan_available() && self.minor != 2 + } + + pub fn summary(&self) -> Result { + serde_json_core::to_vec(&self) + + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index ba2d21b..4fa16d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,8 @@ mod command_handler; use command_handler::Handler; mod fan_ctrl; use fan_ctrl::FanCtrl; +mod hw_rev; +use hw_rev::HWRev; const HSE: MegaHertz = MegaHertz(8); #[cfg(not(feature = "semihosting"))] @@ -138,6 +140,8 @@ fn main() -> ! { let mut store = flash_store::store(dp.FLASH); + let hwrev = HWRev::detect_hw_rev(&pins.hwrev); + let mut channels = Channels::new(pins); for c in 0..CHANNELS { match store.read_value::(CHANNEL_CONFIG_KEY[c]) { @@ -150,7 +154,7 @@ fn main() -> ! { } } - let mut fan_ctrl = FanCtrl::new(fan, &channels.hwrev); + let mut fan_ctrl = FanCtrl::new(fan, hwrev); // default net config: let mut ipv4_config = Ipv4Config { @@ -185,7 +189,7 @@ fn main() -> ! { server.for_each(|_, session| session.set_report_pending(channel.into())); } - fan_ctrl.cycle(channels.current_abs_max_tec_i()); + fan_ctrl.cycle(channels.current_abs_max_tec_i() as f32); let instant = Instant::from_millis(i64::from(timer::now())); cortex_m::interrupt::free(net::clear_pending); @@ -210,7 +214,7 @@ fn main() -> ! { // Do nothing and feed more data to the line reader in the next loop cycle. Ok(SessionInput::Nothing) => {} Ok(SessionInput::Command(command)) => { - match Handler::handle_command(command, &mut socket, &mut channels, session, &mut leds, &mut store, &mut ipv4_config, &mut fan_ctrl) { + match Handler::handle_command(command, &mut socket, &mut channels, session, &mut leds, &mut store, &mut ipv4_config, &mut fan_ctrl, hwrev) { Ok(Handler::NewIPV4(ip)) => new_ipv4_config = Some(ip), Ok(Handler::Handled) => {}, Ok(Handler::CloseSocket) => socket.close(), diff --git a/src/pins.rs b/src/pins.rs index 64b3821..be62540 100644 --- a/src/pins.rs +++ b/src/pins.rs @@ -227,7 +227,8 @@ impl Pins { }; // According to `SUNON DC Brushless Fan & Blower(255-E)` catalogue p.36-37 - // Model name: MF35101V1-1000U-G99 + // model MF35101V1-1000U-G99 doesn't have a PWM wire, so it is advised to have + // higher frequency to have less audible noise. let fan = Timer::new(tim8, &clocks).pwm(gpioc.pc9.into_alternate(), 25u32.khz()); (pins, leds, eeprom, eth_pins, usb, fan)