From 58650d37f13e676bd93b01b95ae32050a1a314c1 Mon Sep 17 00:00:00 2001 From: Egor Savkin Date: Thu, 22 Dec 2022 17:28:08 +0800 Subject: [PATCH] Refactor and coefficients implemented Move all the fan logic to the separate file. Add controls for controlling curve. Signed-off-by: Egor Savkin --- README.md | 72 +++++++------- src/channels.rs | 87 ++-------------- src/command_handler.rs | 32 ++++-- src/command_parser.rs | 49 ++++++--- src/fan_ctrl.rs | 218 +++++++++++++++++++++++++++++++++++++++++ src/main.rs | 59 +++-------- src/pins.rs | 33 +++---- 7 files changed, 351 insertions(+), 199 deletions(-) create mode 100644 src/fan_ctrl.rs diff --git a/README.md b/README.md index 6c9c81e..b7187ba 100644 --- a/README.md +++ b/README.md @@ -94,38 +94,40 @@ The scope of this setting is per TCP session. Send commands as simple text string terminated by `\n`. Responses are formatted as line-delimited JSON. -| 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> 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 0 to 100, where 0 is auto mode | +| 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> 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 0 to 100, where 0 is auto mode | +| `fcurve ` | Set fan controller coefficients (see *Fan control* section) | +| `fan-restore` | Set fan controller coefficients to defaults (see *Fan control* section) | ## USB @@ -274,10 +276,14 @@ The thermostat implements a PID control loop for each of the TEC channels, more ## Fan control -Fan control is available for the thermostat revisions with integrated fan system. For this purpose two commands are available: +Fan control is available for the thermostat revisions with integrated fan system. For this purpose four 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. +3. `fcurve ` - set coefficients of the controlling curve `a*x^2 + b*x + c`, where `x` is `abs_max_tec_i/MAX_TEC_I`, +i.e. receives values from 0 to 1 linearly tied to the maximum current. The controlling curve should produce values from 0 to 1, +as below and beyond values would be substituted by 0 and 1 respectively. +4. `fan-restore` - restore fan settings to defaults: `auto = true, a = 1.0, b = 0.0, c = 0.0`. diff --git a/src/channels.rs b/src/channels.rs index 40927be..c594c34 100644 --- a/src/channels.rs +++ b/src/channels.rs @@ -19,22 +19,13 @@ use crate::{ command_parser::{CenterPoint, PwmPin}, pins, steinhart_hart, + fan_ctrl::HWRev, }; -use crate::pins::HWRevPins; 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 { - pub major: u8, - pub minor: u8 -} // TODO: -pub pub struct Channels { @@ -44,8 +35,7 @@ pub struct Channels { /// stm32f4 integrated adc pins_adc: pins::PinsAdc, pub pwm: pins::PwmPins, - hw_rev: HWRev, - fan_auto: bool + pub hwrev: HWRev, } impl Channels { @@ -68,7 +58,7 @@ impl Channels { 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), fan_auto: true }; + hwrev: HWRev::detect_hw_rev(&pins.hwrev)}; for channel in 0..CHANNELS { channels.channel_state(channel).vref = channels.read_vref(channel); channels.calibrate_dac_value(channel); @@ -77,21 +67,6 @@ impl Channels { channels } - fn detect_hw_rev(hwrev_pins: &HWRevPins) -> HWRev { - 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.hw_rev.major == 2 && self.hw_rev.minor == 2 - } - pub fn channel_state>(&mut self, channel: I) -> &mut ChannelState { match channel.into() { 0 => &mut self.channel0.state, @@ -365,8 +340,6 @@ impl Channels { match (channel, pin) { (_, PwmPin::ISet) => panic!("i_set is no pwm pin"), - (_, PwmPin::Fan) => - get(&self.pwm.fan), (0, PwmPin::MaxIPos) => get(&self.pwm.max_i_pos0), (0, PwmPin::MaxINeg) => @@ -422,8 +395,6 @@ impl Channels { match (channel, pin) { (_, PwmPin::ISet) => panic!("i_set is no pwm pin"), - (_, PwmPin::Fan) => - set(&mut self.pwm.fan, duty), (0, PwmPin::MaxIPos) => set(&mut self.pwm.max_i_pos0, duty), (0, PwmPin::MaxINeg) => @@ -462,19 +433,6 @@ impl Channels { (duty * max, max) } - pub fn set_fan_pwm(&mut self, fan_pwm: u32) -> f64 { - 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) * 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 { let vref = self.channel_state(channel).vref; let i_set = self.get_i(channel); @@ -500,7 +458,7 @@ impl Channels { tec_i, tec_u_meas: self.get_tec_v(channel), pid_output, - hwrev: self.hw_rev + hwrev: self.hwrev } } @@ -566,39 +524,14 @@ impl Channels { serde_json_core::to_vec(&summaries) } - fn current_abs_max_tec_i(&mut self) -> f64 { + pub 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: self.current_abs_max_tec_i(), - auto_mode: self.fan_auto - }; - serde_json_core::to_vec(&summary) - } else { - let summary: Option<()> = None; - 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; +pub type JsonBuffer = Vec; #[derive(Serialize)] pub struct Report { @@ -670,11 +603,3 @@ pub struct SteinhartHartSummary { channel: usize, params: steinhart_hart::Parameters, } - -#[derive(Serialize)] -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 79a2b5a..edf05fd 100644 --- a/src/command_handler.rs +++ b/src/command_handler.rs @@ -22,7 +22,8 @@ use super::{ config::ChannelConfig, dfu, flash_store::FlashStore, - session::Session + session::Session, + FanCtrl, }; use uom::{ @@ -199,9 +200,6 @@ impl Handler { let current = ElectricCurrent::new::(value); channels.set_max_i_neg(channel, current); } - PwmPin::Fan => { - channels.set_fan_pwm(value as u32); - } } send_line(socket, b"{}"); Ok(Handler::Handled) @@ -344,14 +342,14 @@ impl Handler { Ok(Handler::Reset) } - fn fan (socket: &mut TcpSocket, channels: &mut Channels, fan_pwm: Option, tacho_value: Option) -> Result { + fn fan (socket: &mut TcpSocket, fan_pwm: Option, fan_ctrl: &mut FanCtrl) -> Result { match fan_pwm { Some(val) => { - channels.set_fan_auto_mode(val == 0); - channels.set_fan_pwm(val); + fan_ctrl.set_auto_mode(val == 0); + fan_ctrl.set_pwm(val); }, None => { - match channels.fan_summary(tacho_value) { + match fan_ctrl.summary() { Ok(buf) => { send_line(socket, &buf); return Ok(Handler::Handled); @@ -368,7 +366,19 @@ 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, tacho_value: Option) -> Result { + fn fan_coeff (socket: &mut TcpSocket, fan_ctrl: &mut FanCtrl, k_a: f64, k_b: f64, k_c: f64) -> Result { + fan_ctrl.set_coefficients(k_a, k_b, k_c); + send_line(socket, b"{}"); + Ok(Handler::Handled) + } + + fn fan_defaults (socket: &mut TcpSocket, fan_ctrl: &mut FanCtrl) -> Result { + fan_ctrl.restore_defaults(); + 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, fan_ctrl: &mut FanCtrl) -> Result { match command { Command::Quit => Ok(Handler::CloseSocket), Command::Reporting(_reporting) => Handler::reporting(socket), @@ -391,7 +401,9 @@ impl Handler { Command::Ipv4(config) => Handler::set_ipv4(socket, store, config), Command::Reset => Handler::reset(channels), Command::Dfu => Handler::dfu(channels), - Command::Fan {fan_pwm} => Handler::fan(socket, channels, fan_pwm, tacho_value) + Command::Fan {fan_pwm} => Handler::fan(socket, fan_pwm, fan_ctrl), + Command::FanCoeff { k_a, k_b, k_c } => Handler::fan_coeff(socket, fan_ctrl, k_a, k_b, k_c), + Command::FanDefaults => Handler::fan_defaults(socket, fan_ctrl), } } } \ No newline at end of file diff --git a/src/command_parser.rs b/src/command_parser.rs index b87b40a..df2372b 100644 --- a/src/command_parser.rs +++ b/src/command_parser.rs @@ -1,16 +1,7 @@ use core::fmt; use core::num::ParseIntError; use core::str::{from_utf8, Utf8Error}; -use nom::{ - IResult, - branch::alt, - bytes::complete::{is_a, tag, take_while1}, - character::{is_digit, complete::{char, one_of}}, - combinator::{complete, map, opt, value}, - sequence::preceded, - multi::{fold_many0, fold_many1}, - error::ErrorKind, -}; +use nom::{IResult, branch::alt, bytes::complete::{is_a, tag, take_while1}, character::{is_digit, complete::{char, one_of}}, combinator::{complete, map, opt, value}, sequence::preceded, multi::{fold_many0, fold_many1}, error::ErrorKind, Needed}; use num_traits::{Num, ParseFloatError}; use serde::{Serialize, Deserialize}; @@ -127,7 +118,6 @@ pub enum PwmPin { MaxIPos, MaxINeg, MaxV, - Fan } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -181,7 +171,13 @@ pub enum Command { Dfu, Fan { fan_pwm: Option - } + }, + FanCoeff { + k_a: f64, + k_b: f64, + k_c: f64, + }, + FanDefaults, } fn end(input: &[u8]) -> IResult<&[u8], ()> { @@ -540,6 +536,33 @@ fn fan(input: &[u8]) -> IResult<&[u8], Result> { Ok((input, result)) } +fn fan_coeff(input: &[u8]) -> IResult<&[u8], Result> { + let (input, _) = tag("fcurve")(input)?; + let (input, coeffs) = alt(( + |input| { + let (input, _) = whitespace(input)?; + let (input, k_a) = float(input)?; + let (input, _) = whitespace(input)?; + let (input, k_b) = float(input)?; + let (input, _) = whitespace(input)?; + 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, Some((k_a.unwrap(), k_b.unwrap(), k_c.unwrap())))) + } else { + Err(nom::Err::Incomplete(Needed::Size(3))) + } + }, + value(None, end) + ))(input)?; + + let result = match coeffs { + Some(coeffs) => Ok(Command::FanCoeff { k_a: coeffs.0, k_b: coeffs.1, k_c: coeffs.2 }), + None => Err(Error::ParseFloat) + }; + Ok((input, result)) +} + fn command(input: &[u8]) -> IResult<&[u8], Result> { alt((value(Ok(Command::Quit), tag("quit")), load, @@ -553,7 +576,9 @@ fn command(input: &[u8]) -> IResult<&[u8], Result> { steinhart_hart, postfilter, value(Ok(Command::Dfu), tag("dfu")), + value(Ok(Command::FanDefaults), tag("fan-restore")), fan, + fan_coeff, ))(input) } diff --git a/src/fan_ctrl.rs b/src/fan_ctrl.rs new file mode 100644 index 0000000..7f9a2f1 --- /dev/null +++ b/src/fan_ctrl.rs @@ -0,0 +1,218 @@ +use core::{cmp::max_by}; +use serde::Serialize; +use stm32f4xx_hal::{ + pwm::{self, PwmChannels}, + pac::TIM8, + gpio::{ + Floating, Input, ExtiPin, + gpioc::PC8, Edge, + }, + stm32::EXTI, + syscfg::{SysCfg}, +}; +use smoltcp::time::Instant; + +use crate::{ + pins::HWRevPins, + channels::{Channels, JsonBuffer}, + timer +}; + +pub type FanPin = PwmChannels; +pub type TachoPin = PC8>; + +const MAX_TEC_I: f64 = 3.0; +// as stated in the schematics +const MAX_FAN_PWM: f64 = 100.0; +const MIN_FAN_PWM: f64 = 1.0; +const TACHO_MEASURE_MS: i64 = 2500; +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, +} + +struct TachoCtrl { + tacho: TachoPin, + tacho_cnt: u32, + tacho_value: Option, + prev_epoch: i64, +} + +pub struct FanCtrl<'a> { + fan: FanPin, + tacho: TachoCtrl, + fan_auto: bool, + available: bool, + k_a: f64, + k_b: f64, + k_c: f64, + channels: &'a mut Channels, +} + +impl<'a> FanCtrl<'a> { + pub fn new(mut fan: FanPin, tacho: TachoPin, channels: &'a mut Channels, exti: &mut EXTI, syscfg: &mut SysCfg) -> Self { + let available = channels.hwrev.fan_available(); + + let mut tacho_ctrl = TachoCtrl::new(tacho); + if available { + fan.set_duty(0); + fan.enable(); + tacho_ctrl.init(exti, syscfg); + } + + FanCtrl { + fan, + tacho: tacho_ctrl, + available, + fan_auto: true, + k_a: DEFAULT_K_A, + k_b: DEFAULT_K_B, + k_c: DEFAULT_K_C, + channels, + } + } + + pub fn cycle(&mut self) { + if self.available { + self.tacho.cycle(); + } + self.adjust_speed(); + } + + pub fn summary(&mut self) -> Result { + if self.available { + let summary = FanSummary { + fan_pwm: self.get_pwm(), + tacho: self.tacho.get(), + abs_max_tec_i: self.channels.current_abs_max_tec_i(), + auto_mode: self.fan_auto, + k_a: self.k_a, + k_b: self.k_b, + k_c: self.k_c, + }; + serde_json_core::to_vec(&summary) + } else { + let summary: Option<()> = None; + serde_json_core::to_vec(&summary) + } + } + + pub fn adjust_speed(&mut self) { + if self.fan_auto && self.available { + let scaled_current = self.channels.current_abs_max_tec_i() / MAX_TEC_I; + // do not limit upper bound, as it will be limited in the set_pwm() + let pwm = max_by(MAX_FAN_PWM * (scaled_current * (scaled_current * self.k_a + self.k_b) + self.k_c), + MIN_FAN_PWM, + |a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)) as u32; + self.set_pwm(pwm); + } + } + + #[inline] + pub fn set_auto_mode(&mut self, fan_auto: bool) { + self.fan_auto = fan_auto; + } + + #[inline] + pub fn set_coefficients(&mut self, k_a: f64, k_b: f64, k_c: f64) { + self.k_a = k_a; + self.k_b = k_b; + self.k_c = k_c; + } + + #[inline] + pub fn restore_defaults(&mut self) { + self.set_auto_mode(true); + self.set_coefficients(DEFAULT_K_A, DEFAULT_K_B, DEFAULT_K_C); + } + + pub fn set_pwm(&mut self, fan_pwm: u32) -> f64 { + let duty = fan_pwm as f64 / MAX_FAN_PWM; + let max = self.fan.get_max_duty(); + let value = ((duty * (max as f64)) as u16).min(max); + self.fan.set_duty(value); + value as f64 / (max as f64) + } + + fn get_pwm(&self) -> u32 { + let duty = self.fan.get_duty(); + let max = self.fan.get_max_duty(); + ((duty as f64 / (max as f64)) * MAX_FAN_PWM) as u32 + } +} + +impl TachoCtrl { + pub fn new(tacho: TachoPin) -> Self { + TachoCtrl { + tacho, + tacho_cnt: 0, + tacho_value: None, + prev_epoch: 0, + } + } + + pub fn init(&mut self, exti: &mut EXTI, syscfg: &mut SysCfg) { + // These lines do not cause NVIC to run the ISR, + // since the interrupt should be unmasked in the cortex_m::peripheral::NVIC. + // Also using interrupt-related workaround is the best + // option for the current version of stm32f4xx-hal, + // since tying the IC's PC8 with the PWM's PC9 to the same TIM8 is not supported, + // and therefore would require even more weirder and unsafe hacks. + // Also such hacks wouldn't guarantee it to be more precise. + self.tacho.make_interrupt_source(syscfg); + self.tacho.trigger_on_edge(exti, Edge::Rising); + self.tacho.enable_interrupt(exti); + } + + pub fn cycle(&mut self) { + let tacho_input = self.tacho.check_interrupt(); + if tacho_input { + self.tacho.clear_interrupt_pending_bit(); + self.tacho_cnt += 1; + } + + let instant = Instant::from_millis(i64::from(timer::now())); + if instant.millis - self.prev_epoch >= TACHO_MEASURE_MS { + self.tacho_value = Some(self.tacho_cnt); + self.tacho_cnt = 0; + self.prev_epoch = instant.millis; + } + } + + pub fn get(&self) -> u32 { + self.tacho_value.unwrap_or(u32::MAX) + } +} + +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 + } +} + +#[derive(Serialize)] +pub struct FanSummary { + fan_pwm: u32, + tacho: u32, + abs_max_tec_i: f64, + auto_mode: bool, + k_a: f64, + k_b: f64, + k_c: f64, +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 7314c43..798c3b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ use panic_abort as _; use panic_semihosting as _; use log::{error, info, warn}; - +use core::cell::RefCell; use cortex_m::asm::wfi; use cortex_m_rt::entry; use stm32f4xx_hal::{ @@ -19,7 +19,6 @@ use stm32f4xx_hal::{ stm32::{CorePeripherals, Peripherals, SCB}, time::{U32Ext, MegaHertz}, watchdog::IndependentWatchdog, - gpio::{Edge, ExtiPin}, syscfg::SysCfgExt }; use smoltcp::{ @@ -56,6 +55,8 @@ mod flash_store; mod dfu; mod command_handler; use command_handler::Handler; +mod fan_ctrl; +use fan_ctrl::FanCtrl; const HSE: MegaHertz = MegaHertz(8); #[cfg(not(feature = "semihosting"))] @@ -120,7 +121,7 @@ fn main() -> ! { timer::setup(cp.SYST, clocks); - let (pins, mut leds, mut eeprom, eth_pins, usb, mut tacho) = Pins::setup( + let (pins, mut leds, mut eeprom, eth_pins, usb, fan, tacho) = Pins::setup( clocks, dp.TIM1, dp.TIM3, dp.TIM8, dp.GPIOA, dp.GPIOB, dp.GPIOC, dp.GPIOD, dp.GPIOE, dp.GPIOF, dp.GPIOG, dp.I2C1, @@ -137,22 +138,22 @@ fn main() -> ! { usb::State::setup(usb); - let mut store = flash_store::store(dp.FLASH); - + let mut channels = RefCell::new(Channels::new(pins)); - let mut channels = Channels::new(pins); + let mut store = flash_store::store(dp.FLASH); for c in 0..CHANNELS { match store.read_value::(CHANNEL_CONFIG_KEY[c]) { Ok(Some(config)) => - config.apply(&mut channels, c), + config.apply(channels.get_mut(), c), Ok(None) => error!("flash config not found for channel {}", c), Err(e) => error!("unable to load config {} from flash: {:?}", c, e), } } - - let fan_available = channels.fan_available(); + // considered safe since `channels` is being mutated in a single thread, + // while mutex would be excessive + let mut fan_ctrl = FanCtrl::new(fan, tacho, unsafe{ &mut *channels.as_ptr() }, &mut dp.EXTI, &mut dp.SYSCFG.constrain()); // default net config: let mut ipv4_config = Ipv4Config { @@ -174,54 +175,22 @@ fn main() -> ! { let hwaddr = EthernetAddress(eui48); info!("EEPROM MAC address: {}", hwaddr); - if fan_available { - // These lines do not cause NVIC to run the ISR, - // since the interrupt should be unmasked in the cortex_m::peripheral::NVIC. - // Also using interrupt-related workaround is the best - // option for the current version of stm32f4xx-hal, - // since tying the IC's PC8 with the PWM's PC9 to the same TIM8 is not supported, - // and therefore would require even more weirder and unsafe hacks. - // Also such hacks wouldn't guarantee it to be more precise. - tacho.make_interrupt_source(&mut dp.SYSCFG.constrain()); - tacho.trigger_on_edge(&mut dp.EXTI, Edge::Rising); - tacho.enable_interrupt(&mut dp.EXTI); - } - net::run(clocks, dp.ETHERNET_MAC, dp.ETHERNET_DMA, eth_pins, hwaddr, ipv4_config.clone(), |iface| { Server::::run(iface, |server| { leds.r1.off(); let mut should_reset = false; - let (mut tacho_cnt, mut tacho_value) = (0u32, None); - let mut prev_epoch: i64 = 0; - loop { let mut new_ipv4_config = None; let instant = Instant::from_millis(i64::from(timer::now())); - let updated_channel = channels.poll_adc(instant); + let updated_channel = channels.get_mut().poll_adc(instant); if let Some(channel) = updated_channel { server.for_each(|_, session| session.set_report_pending(channel.into())); } + fan_ctrl.cycle(); let instant = Instant::from_millis(i64::from(timer::now())); - if fan_available { - let tacho_input = tacho.check_interrupt(); - if tacho_input { - tacho.clear_interrupt_pending_bit(); - tacho_cnt += 1; - } - - let epoch = instant.secs(); - if epoch > prev_epoch { - tacho_value = Some(tacho_cnt); - tacho_cnt = 0; - prev_epoch = epoch; - } - } - - channels.fan_ctrl(); - cortex_m::interrupt::free(net::clear_pending); server.poll(instant) .unwrap_or_else(|e| { @@ -244,7 +213,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, tacho_value) { + match Handler::handle_command(command, &mut socket, channels.get_mut(), session, &mut leds, &mut store, &mut ipv4_config, &mut fan_ctrl) { Ok(Handler::NewIPV4(ip)) => new_ipv4_config = Some(ip), Ok(Handler::Handled) => {}, Ok(Handler::CloseSocket) => socket.close(), @@ -261,7 +230,7 @@ fn main() -> ! { } } else if socket.can_send() { if let Some(channel) = session.is_report_pending() { - match channels.reports_json() { + match channels.get_mut().reports_json() { Ok(buf) => { send_line(&mut socket, &buf[..]); session.mark_report_sent(channel); diff --git a/src/pins.rs b/src/pins.rs index dd565a1..1b53a51 100644 --- a/src/pins.rs +++ b/src/pins.rs @@ -26,15 +26,18 @@ use stm32f4xx_hal::{ TIM1, TIM3, TIM8 }, timer::Timer, - time::U32Ext, + time::{U32Ext, KiloHertz}, }; use eeprom24x::{self, Eeprom24x}; use stm32_eth::EthPins; use crate::{ channel::{Channel0, Channel1}, leds::Leds, + fan_ctrl::{TachoPin, FanPin} }; +const PWM_FREQ: KiloHertz = KiloHertz(20u32); + pub type Eeprom = Eeprom24x< I2c>, @@ -128,7 +131,7 @@ impl Pins { spi2: SPI2, spi4: SPI4, spi5: SPI5, adc1: ADC1, otg_fs_global: OTG_FS_GLOBAL, otg_fs_device: OTG_FS_DEVICE, otg_fs_pwrclk: OTG_FS_PWRCLK, - ) -> (Self, Leds, Eeprom, EthernetPins, USB, PC8>) { + ) -> (Self, Leds, Eeprom, EthernetPins, USB, FanPin, TachoPin) { let gpioa = gpioa.split(); let gpiob = gpiob.split(); let gpioc = gpioc.split(); @@ -143,10 +146,10 @@ impl Pins { let pins_adc = Adc::adc1(adc1, true, Default::default()); let pwm = PwmPins::setup( - clocks, tim1, tim3, tim8, + clocks, tim1, tim3, gpioc.pc6, gpioc.pc7, gpioe.pe9, gpioe.pe11, - gpioe.pe13, gpioe.pe14, gpioc.pc9 + gpioe.pe13, gpioe.pe14 ); let (dac0_spi, dac0_sync) = Self::setup_dac0( @@ -225,7 +228,9 @@ impl Pins { hclk: clocks.hclk(), }; - (pins, leds, eeprom, eth_pins, usb, gpioc.pc8) + let fan = Timer::new(tim8, &clocks).pwm(gpioc.pc9.into_alternate(), 20u32.khz()); + + (pins, leds, eeprom, eth_pins, usb, fan, gpioc.pc8) } /// Configure the GPIO pins for SPI operation, and initialize SPI @@ -293,24 +298,20 @@ pub struct PwmPins { pub max_i_pos1: PwmChannels, pub max_i_neg0: PwmChannels, pub max_i_neg1: PwmChannels, - pub fan: PwmChannels, } impl PwmPins { - fn setup( + fn setup( clocks: Clocks, tim1: TIM1, tim3: TIM3, - tim8: TIM8, max_v0: PC6, max_v1: PC7, max_i_pos0: PE9, max_i_pos1: PE11, max_i_neg0: PE13, max_i_neg1: PE14, - fan: PC9, ) -> PwmPins { - let freq = 20u32.khz(); fn init_pwm_pin>(pin: &mut P) { pin.set_duty(0); @@ -320,14 +321,11 @@ impl PwmPins { max_v0.into_alternate(), max_v1.into_alternate(), ); - //let (mut max_v0, mut max_v1) = pwm::tim3(tim3, channels, clocks, freq); - let (mut max_v0, mut max_v1) = Timer::new(tim3, &clocks).pwm(channels, freq); + //let (mut max_v0, mut max_v1) = pwm::tim3(tim3, channels, clocks, PWM_FREQ); + let (mut max_v0, mut max_v1) = Timer::new(tim3, &clocks).pwm(channels, PWM_FREQ); init_pwm_pin(&mut max_v0); init_pwm_pin(&mut max_v1); - let mut fan = Timer::new(tim8, &clocks).pwm(fan.into_alternate(), freq); - init_pwm_pin(&mut fan); - let channels = ( max_i_pos0.into_alternate(), max_i_pos1.into_alternate(), @@ -335,7 +333,7 @@ impl PwmPins { max_i_neg1.into_alternate(), ); let (mut max_i_pos0, mut max_i_pos1, mut max_i_neg0, mut max_i_neg1) = - Timer::new(tim1, &clocks).pwm(channels, freq); + Timer::new(tim1, &clocks).pwm(channels, PWM_FREQ); init_pwm_pin(&mut max_i_pos0); init_pwm_pin(&mut max_i_neg0); init_pwm_pin(&mut max_i_pos1); @@ -344,8 +342,7 @@ impl PwmPins { PwmPins { max_v0, max_v1, max_i_pos0, max_i_pos1, - max_i_neg0, max_i_neg1, - fan + max_i_neg0, max_i_neg1 } } }