forked from M-Labs/thermostat
Fan status via quadratic regression
* statuses: detect slow fan via quadratic regression and halts (hard stops) with constant threshold * remove unsafe channels' copy and make FanCtrl own the channels * scale fan values so that they would fit 0.05-1.0 PWM at all times * rename fan-restore to fcurve-restore * style, names and docs adjustments Signed-off-by: Egor Savkin <es@m-labs.hk>
This commit is contained in:
parent
630635486e
commit
21fc244eac
70
README.md
70
README.md
@ -94,40 +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 <off/on>` | Set report mode |
|
||||
| `pwm` | Show current PWM settings |
|
||||
| `pwm <0/1> max_i_pos <amp>` | Set maximum positive output current |
|
||||
| `pwm <0/1> max_i_neg <amp>` | Set maximum negative output current |
|
||||
| `pwm <0/1> max_v <volt>` | Set maximum output voltage |
|
||||
| `pwm <0/1> i_set <amp>` | Disengage PID, set fixed output current |
|
||||
| `pwm <0/1> pid` | Let output current to be controlled by the PID |
|
||||
| `center <0/1> <volt>` | 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 <deg_celsius>` | Set the PID controller target temperature |
|
||||
| `pid <0/1> kp <value>` | Set proportional gain |
|
||||
| `pid <0/1> ki <value>` | Set integral gain |
|
||||
| `pid <0/1> kd <value>` | Set differential gain |
|
||||
| `pid <0/1> output_min <amp>` | Set mininum output |
|
||||
| `pid <0/1> output_max <amp>` | Set maximum output |
|
||||
| `s-h` | Show Steinhart-Hart equation parameters |
|
||||
| `s-h <0/1> <t0/b/r0> <value>` | Set Steinhart-Hart parameter for a channel |
|
||||
| `postfilter` | Show postfilter settings |
|
||||
| `postfilter <0/1> off` | Disable postfilter |
|
||||
| `postfilter <0/1> rate <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 <X.X.X.X/L> [Y.Y.Y.Y]` | Configure IPv4 address, netmask length, and optional default gateway |
|
||||
| `fan` | Show current fan settings and sensors' measurements |
|
||||
| `fan <value>` | Set fan power with values from 0 to 100, where 0 is auto mode |
|
||||
| `fcurve <a> <b> <c>` | Set fan controller coefficients (see *Fan control* section) |
|
||||
| `fan-restore` | Set fan controller coefficients to defaults (see *Fan control* section) |
|
||||
| Syntax | Function |
|
||||
|----------------------------------|-------------------------------------------------------------------------------|
|
||||
| `report` | Show current input |
|
||||
| `report mode` | Show current report mode |
|
||||
| `report mode <off/on>` | Set report mode |
|
||||
| `pwm` | Show current PWM settings |
|
||||
| `pwm <0/1> max_i_pos <amp>` | Set maximum positive output current |
|
||||
| `pwm <0/1> max_i_neg <amp>` | Set maximum negative output current |
|
||||
| `pwm <0/1> max_v <volt>` | Set maximum output voltage |
|
||||
| `pwm <0/1> i_set <amp>` | Disengage PID, set fixed output current |
|
||||
| `pwm <0/1> pid` | Let output current to be controlled by the PID |
|
||||
| `center <0/1> <volt>` | 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 <deg_celsius>` | Set the PID controller target temperature |
|
||||
| `pid <0/1> kp <value>` | Set proportional gain |
|
||||
| `pid <0/1> ki <value>` | Set integral gain |
|
||||
| `pid <0/1> kd <value>` | Set differential gain |
|
||||
| `pid <0/1> output_min <amp>` | Set mininum output |
|
||||
| `pid <0/1> output_max <amp>` | Set maximum output |
|
||||
| `s-h` | Show Steinhart-Hart equation parameters |
|
||||
| `s-h <0/1> <t0/b/r0> <value>` | Set Steinhart-Hart parameter for a channel |
|
||||
| `postfilter` | Show postfilter settings |
|
||||
| `postfilter <0/1> off` | Disable postfilter |
|
||||
| `postfilter <0/1> rate <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 <X.X.X.X/L> [Y.Y.Y.Y]` | Configure IPv4 address, netmask length, and optional default gateway |
|
||||
| `fan` | Show current fan settings and sensors' measurements |
|
||||
| `fan <value>` | Set fan power with values from 0 to 100, where 0 is auto mode |
|
||||
| `fcurve <a> <b> <c>` | Set fan controller curve coefficients (see *Fan control* section) |
|
||||
| `fcurve-restore` | Set fan controller curve coefficients to defaults (see *Fan control* section) |
|
||||
|
||||
|
||||
## USB
|
||||
@ -286,4 +286,4 @@ Please note that power doesn't correlate with the actual speed linearly.
|
||||
3. `fcurve <a> <b> <c>` - 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.04`.
|
||||
4. `fcurve-restore` - restore fan settings to defaults: `auto = true, a = 1.0, b = 0.0, c = 0.00`.
|
||||
|
@ -342,7 +342,7 @@ impl Handler {
|
||||
Ok(Handler::Reset)
|
||||
}
|
||||
|
||||
fn fan (socket: &mut TcpSocket, fan_pwm: Option<u32>, fan_ctrl: &mut FanCtrl) -> Result<Handler, Error> {
|
||||
fn fan(socket: &mut TcpSocket, fan_pwm: Option<u32>, fan_ctrl: &mut FanCtrl) -> Result<Handler, Error> {
|
||||
match fan_pwm {
|
||||
Some(val) => {
|
||||
fan_ctrl.set_auto_mode(val == 0);
|
||||
@ -366,43 +366,43 @@ impl Handler {
|
||||
Ok(Handler::Handled)
|
||||
}
|
||||
|
||||
fn fan_coeff (socket: &mut TcpSocket, fan_ctrl: &mut FanCtrl, k_a: f64, k_b: f64, k_c: f64) -> Result<Handler, Error> {
|
||||
fan_ctrl.set_coefficients(k_a, k_b, k_c);
|
||||
fn fan_curve(socket: &mut TcpSocket, fan_ctrl: &mut FanCtrl, k_a: f64, k_b: f64, k_c: f64) -> Result<Handler, Error> {
|
||||
fan_ctrl.set_curve(k_a, k_b, k_c);
|
||||
send_line(socket, b"{}");
|
||||
Ok(Handler::Handled)
|
||||
}
|
||||
|
||||
fn fan_defaults (socket: &mut TcpSocket, fan_ctrl: &mut FanCtrl) -> Result<Handler, Error> {
|
||||
fn fan_defaults(socket: &mut TcpSocket, fan_ctrl: &mut FanCtrl) -> Result<Handler, Error> {
|
||||
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<Self, Error> {
|
||||
pub fn handle_command(command: Command, socket: &mut TcpSocket, session: &Session, leds: &mut Leds, store: &mut FlashStore, ipv4_config: &mut Ipv4Config, fan_ctrl: &mut FanCtrl) -> Result<Self, Error> {
|
||||
match command {
|
||||
Command::Quit => Ok(Handler::CloseSocket),
|
||||
Command::Reporting(_reporting) => Handler::reporting(socket),
|
||||
Command::Show(ShowCommand::Reporting) => Handler::show_report_mode(socket, session),
|
||||
Command::Show(ShowCommand::Input) => Handler::show_report(socket, channels),
|
||||
Command::Show(ShowCommand::Pid) => Handler::show_pid(socket, channels),
|
||||
Command::Show(ShowCommand::Pwm) => Handler::show_pwm(socket, channels),
|
||||
Command::Show(ShowCommand::SteinhartHart) => Handler::show_steinhart_hart(socket, channels),
|
||||
Command::Show(ShowCommand::PostFilter) => Handler::show_post_filter(socket, channels),
|
||||
Command::Show(ShowCommand::Input) => Handler::show_report(socket, &mut fan_ctrl.channels),
|
||||
Command::Show(ShowCommand::Pid) => Handler::show_pid(socket, &mut fan_ctrl.channels),
|
||||
Command::Show(ShowCommand::Pwm) => Handler::show_pwm(socket, &mut fan_ctrl.channels),
|
||||
Command::Show(ShowCommand::SteinhartHart) => Handler::show_steinhart_hart(socket, &mut fan_ctrl.channels),
|
||||
Command::Show(ShowCommand::PostFilter) => Handler::show_post_filter(socket, &mut fan_ctrl.channels),
|
||||
Command::Show(ShowCommand::Ipv4) => Handler::show_ipv4(socket, ipv4_config),
|
||||
Command::PwmPid { channel } => Handler::engage_pid(socket, channels, leds, channel),
|
||||
Command::Pwm { channel, pin, value } => Handler::set_pwm(socket, channels, leds, 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),
|
||||
Command::SteinhartHart { channel, parameter, value } => Handler::set_steinhart_hart(socket, channels, channel, parameter, value),
|
||||
Command::PostFilter { channel, rate: None } => Handler::reset_post_filter(socket, channels, channel),
|
||||
Command::PostFilter { channel, rate: Some(rate) } => Handler::set_post_filter(socket, channels, channel, rate),
|
||||
Command::Load { channel } => Handler::load_channel(socket, channels, store, channel),
|
||||
Command::Save { channel } => Handler::save_channel(socket, channels, channel, store),
|
||||
Command::PwmPid { channel } => Handler::engage_pid(socket, &mut fan_ctrl.channels, leds, channel),
|
||||
Command::Pwm { channel, pin, value } => Handler::set_pwm(socket, &mut fan_ctrl.channels, leds, channel, pin, value),
|
||||
Command::CenterPoint { channel, center } => Handler::set_center_point(socket, &mut fan_ctrl.channels, channel, center),
|
||||
Command::Pid { channel, parameter, value } => Handler::set_pid(socket, &mut fan_ctrl.channels, channel, parameter, value),
|
||||
Command::SteinhartHart { channel, parameter, value } => Handler::set_steinhart_hart(socket, &mut fan_ctrl.channels, channel, parameter, value),
|
||||
Command::PostFilter { channel, rate: None } => Handler::reset_post_filter(socket, &mut fan_ctrl.channels, channel),
|
||||
Command::PostFilter { channel, rate: Some(rate) } => Handler::set_post_filter(socket, &mut fan_ctrl.channels, channel, rate),
|
||||
Command::Load { channel } => Handler::load_channel(socket, &mut fan_ctrl.channels, store, channel),
|
||||
Command::Save { channel } => Handler::save_channel(socket, &mut fan_ctrl.channels, channel, store),
|
||||
Command::Ipv4(config) => Handler::set_ipv4(socket, store, config),
|
||||
Command::Reset => Handler::reset(channels),
|
||||
Command::Dfu => Handler::dfu(channels),
|
||||
Command::Reset => Handler::reset(&mut fan_ctrl.channels),
|
||||
Command::Dfu => Handler::dfu(&mut fan_ctrl.channels),
|
||||
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::FanCurve { k_a, k_b, k_c } => Handler::fan_curve(socket, fan_ctrl, k_a, k_b, k_c),
|
||||
Command::FanDefaults => Handler::fan_defaults(socket, fan_ctrl),
|
||||
}
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ pub enum Command {
|
||||
Fan {
|
||||
fan_pwm: Option<u32>
|
||||
},
|
||||
FanCoeff {
|
||||
FanCurve {
|
||||
k_a: f64,
|
||||
k_b: f64,
|
||||
k_c: f64,
|
||||
@ -546,9 +546,9 @@ fn fan(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
Ok((input, result))
|
||||
}
|
||||
|
||||
fn fan_coeff(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
fn fan_curve(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
let (input, _) = tag("fcurve")(input)?;
|
||||
let (input, coeffs) = alt((
|
||||
let (input, curve) = alt((
|
||||
|input| {
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, k_a) = float(input)?;
|
||||
@ -566,8 +566,8 @@ fn fan_coeff(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
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 }),
|
||||
let result = match curve {
|
||||
Some(curve) => Ok(Command::FanCurve { k_a: curve.0, k_b: curve.1, k_c: curve.2 }),
|
||||
None => Err(Error::ParseFloat)
|
||||
};
|
||||
Ok((input, result))
|
||||
@ -586,9 +586,9 @@ fn command(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
steinhart_hart,
|
||||
postfilter,
|
||||
value(Ok(Command::Dfu), tag("dfu")),
|
||||
value(Ok(Command::FanDefaults), tag("fan-restore")),
|
||||
value(Ok(Command::FanDefaults), tag("fcurve-restore")),
|
||||
fan,
|
||||
fan_coeff,
|
||||
fan_curve,
|
||||
))(input)
|
||||
}
|
||||
|
||||
|
149
src/fan_ctrl.rs
149
src/fan_ctrl.rs
@ -1,4 +1,3 @@
|
||||
use core::{cmp::max_by};
|
||||
use serde::Serialize;
|
||||
use stm32f4xx_hal::{
|
||||
pwm::{self, PwmChannels},
|
||||
@ -21,15 +20,33 @@ use crate::{
|
||||
pub type FanPin = PwmChannels<TIM8, pwm::C4>;
|
||||
pub type TachoPin = PC8<Input<Floating>>;
|
||||
|
||||
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 MAX_TEC_I: f64 = 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;
|
||||
// below this value, motor pulse signal is too weak to be registered by tachometer
|
||||
const MIN_FAN_PWM: f64 = 0.05;
|
||||
|
||||
const TACHO_MEASURE_MS: i64 = 2500;
|
||||
const TACHO_LOW_THRESHOLD: u32 = 100;
|
||||
// by default up to 2 cycles are skipped on changes in PWM output,
|
||||
// and the halt threshold will help detect the failure during these skipped cycles
|
||||
const TACHO_HALT_THRESHOLD: u32 = 250;
|
||||
const TACHO_SKIP_CYCLES: u8 = 2;
|
||||
|
||||
const DEFAULT_K_A: f64 = 1.0;
|
||||
const DEFAULT_K_B: f64 = 0.0;
|
||||
const DEFAULT_K_C: f64 = 0.04;
|
||||
const DEFAULT_K_C: f64 = 0.0;
|
||||
|
||||
// This regression is from 6% to 25% lower than values registered in the experiments.
|
||||
// Actual values would be better estimated by logarithmic regression, but that would require more
|
||||
// runtime computation, and wouldn't give significant correlation difference
|
||||
// (0.996 for log and 0.966 for quadratic regression).
|
||||
const TACHO_REGRESSION_A: f64 = -0.04135128436;
|
||||
const TACHO_REGRESSION_B: f64 = 6.23015531;
|
||||
const TACHO_REGRESSION_C: f64 = 403.6833577;
|
||||
|
||||
|
||||
#[derive(Serialize, Copy, Clone)]
|
||||
pub struct HWRev {
|
||||
@ -41,8 +58,8 @@ pub struct HWRev {
|
||||
pub enum FanStatus {
|
||||
OK,
|
||||
NotAvailable,
|
||||
Stalled,
|
||||
LowSignal,
|
||||
TooSlow,
|
||||
Halted
|
||||
}
|
||||
|
||||
struct TachoCtrl {
|
||||
@ -50,10 +67,9 @@ struct TachoCtrl {
|
||||
tacho_cnt: u32,
|
||||
tacho_value: Option<u32>,
|
||||
prev_epoch: i64,
|
||||
past_record: u64,
|
||||
}
|
||||
|
||||
pub struct FanCtrl<'a> {
|
||||
pub struct FanCtrl {
|
||||
fan: FanPin,
|
||||
tacho: TachoCtrl,
|
||||
fan_auto: bool,
|
||||
@ -61,12 +77,13 @@ pub struct FanCtrl<'a> {
|
||||
k_a: f64,
|
||||
k_b: f64,
|
||||
k_c: f64,
|
||||
channels: &'a mut Channels,
|
||||
last_status: FanStatus
|
||||
pub channels: Channels,
|
||||
last_status: FanStatus,
|
||||
skip_cycles: u8,
|
||||
}
|
||||
|
||||
impl<'a> FanCtrl<'a> {
|
||||
pub fn new(mut fan: FanPin, tacho: TachoPin, channels: &'a mut Channels, exti: &mut EXTI, syscfg: &mut SysCfg) -> Self {
|
||||
impl FanCtrl {
|
||||
pub fn new(mut fan: FanPin, tacho: TachoPin, channels: Channels, exti: &mut EXTI, syscfg: &mut SysCfg) -> Self {
|
||||
let available = channels.hwrev.fan_available();
|
||||
|
||||
let mut tacho_ctrl = TachoCtrl::new(tacho);
|
||||
@ -85,17 +102,20 @@ impl<'a> FanCtrl<'a> {
|
||||
k_b: DEFAULT_K_B,
|
||||
k_c: DEFAULT_K_C,
|
||||
channels,
|
||||
last_status: FanStatus::OK,
|
||||
last_status: if available { FanStatus::OK } else { FanStatus::NotAvailable },
|
||||
skip_cycles: 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cycle(&mut self) -> Result<(), FanStatus>{
|
||||
pub fn cycle(&mut self) -> Result<(), FanStatus> {
|
||||
if self.available {
|
||||
self.tacho.cycle();
|
||||
if self.tacho.cycle() {
|
||||
self.skip_cycles >>= 1;
|
||||
}
|
||||
}
|
||||
self.adjust_speed();
|
||||
let diagnose = self.diagnose();
|
||||
if diagnose != self.last_status {
|
||||
if (self.skip_cycles == 0 || diagnose == FanStatus::Halted) && diagnose != self.last_status {
|
||||
self.last_status = diagnose;
|
||||
Err(diagnose)
|
||||
} else {
|
||||
@ -126,9 +146,7 @@ impl<'a> FanCtrl<'a> {
|
||||
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;
|
||||
let pwm = (MAX_USER_FAN_PWM * (scaled_current * (scaled_current * self.k_a + self.k_b) + self.k_c)) as u32;
|
||||
self.set_pwm(pwm);
|
||||
}
|
||||
}
|
||||
@ -139,7 +157,7 @@ impl<'a> FanCtrl<'a> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_coefficients(&mut self, k_a: f64, k_b: f64, k_c: f64) {
|
||||
pub fn set_curve(&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;
|
||||
@ -148,28 +166,50 @@ impl<'a> FanCtrl<'a> {
|
||||
#[inline]
|
||||
pub fn restore_defaults(&mut self) {
|
||||
self.set_auto_mode(true);
|
||||
self.set_coefficients(DEFAULT_K_A, DEFAULT_K_B, DEFAULT_K_C);
|
||||
self.set_curve(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 fan_pwm = fan_pwm.min(MAX_USER_FAN_PWM as u32).max(MIN_USER_FAN_PWM as u32);
|
||||
self.skip_cycles = if (self.tacho.get() as f64) <= Self::threshold_for_pwm(fan_pwm as f64) {
|
||||
TACHO_SKIP_CYCLES
|
||||
} else { self.skip_cycles };
|
||||
let duty = Self::scale_number(fan_pwm as f64, 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);
|
||||
self.fan.set_duty(value);
|
||||
value as f64 / (max as f64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn threshold_for_pwm(fan_pwm: f64) -> f64 {
|
||||
(TACHO_REGRESSION_A * fan_pwm + TACHO_REGRESSION_B) * fan_pwm + TACHO_REGRESSION_C
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale_number(unscaled: f64, to_min: f64, to_max: f64, from_min: f64, from_max: f64) -> f64 {
|
||||
(to_max - to_min) * (unscaled - from_min) / (from_max - from_min) + to_min
|
||||
}
|
||||
|
||||
fn diagnose(&mut self) -> FanStatus {
|
||||
if !self.available {
|
||||
return FanStatus::NotAvailable;
|
||||
}
|
||||
self.tacho.diagnose()
|
||||
let threshold = Self::threshold_for_pwm(self.get_pwm() as f64) as u32;
|
||||
let tacho = self.tacho.get();
|
||||
if tacho >= threshold {
|
||||
FanStatus::OK
|
||||
} else if tacho >= TACHO_HALT_THRESHOLD {
|
||||
FanStatus::TooSlow
|
||||
} else {
|
||||
FanStatus::Halted
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
(Self::scale_number(duty as f64 / (max as f64), MIN_USER_FAN_PWM, MAX_USER_FAN_PWM, MIN_FAN_PWM, MAX_FAN_PWM) + 0.5) as u32
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,34 +220,24 @@ impl TachoCtrl {
|
||||
tacho_cnt: 0,
|
||||
tacho_value: None,
|
||||
prev_epoch: 0,
|
||||
past_record: 0,
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
// since the interrupt is masked 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.
|
||||
// since tying the IC's PC8 with the PWM's PC9 to the same TIM8 is not supported.
|
||||
// The possible solution would be to update the library to >=v0.14.*,
|
||||
// and use its Timer's counter functionality.
|
||||
self.tacho.make_interrupt_source(syscfg);
|
||||
self.tacho.trigger_on_edge(exti, Edge::Rising);
|
||||
self.tacho.enable_interrupt(exti);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_record(&mut self, value: u32) {
|
||||
self.past_record = self.past_record << 2;
|
||||
if value >= TACHO_LOW_THRESHOLD {
|
||||
self.past_record += 0b11;
|
||||
} else if value > 0 && self.tacho_cnt < TACHO_LOW_THRESHOLD {
|
||||
self.past_record += 0b10;
|
||||
}
|
||||
}
|
||||
|
||||
fn cycle(&mut self) {
|
||||
// returns whether the epoch elapsed
|
||||
fn cycle(&mut self) -> bool {
|
||||
let tacho_input = self.tacho.check_interrupt();
|
||||
if tacho_input {
|
||||
self.tacho.clear_interrupt_pending_bit();
|
||||
@ -217,25 +247,17 @@ impl TachoCtrl {
|
||||
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.add_record(self.tacho_cnt);
|
||||
self.tacho_cnt = 0;
|
||||
self.prev_epoch = instant.millis;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self) -> u32 {
|
||||
self.tacho_value.unwrap_or(u32::MAX)
|
||||
}
|
||||
|
||||
fn diagnose(&mut self) -> FanStatus {
|
||||
if self.past_record & 0b11 == 0b11 {
|
||||
FanStatus::OK
|
||||
} else if self.past_record & 0xAAAAAAAAAAAAAAAA > 0 {
|
||||
FanStatus::LowSignal
|
||||
} else {
|
||||
FanStatus::Stalled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HWRev {
|
||||
@ -272,8 +294,23 @@ impl FanStatus {
|
||||
match *self {
|
||||
FanStatus::OK => "Fan is OK".as_bytes(),
|
||||
FanStatus::NotAvailable => "Fan is not available".as_bytes(),
|
||||
FanStatus::Stalled => "Fan is stalled".as_bytes(),
|
||||
FanStatus::LowSignal => "Fan is low signal".as_bytes(),
|
||||
FanStatus::TooSlow => "Fan is too slow".as_bytes(),
|
||||
FanStatus::Halted => "Fan is halted".as_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_scaler() {
|
||||
for x in 1..100 {
|
||||
assert_eq!((FanCtrl::scale_number(
|
||||
FanCtrl::scale_number(x as f64, MIN_FAN_PWM, MAX_FAN_PWM, MIN_USER_FAN_PWM, MAX_USER_FAN_PWM),
|
||||
MIN_USER_FAN_PWM, MAX_USER_FAN_PWM, MIN_FAN_PWM, MAX_FAN_PWM) + 0.5) as i32,
|
||||
x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
16
src/main.rs
16
src/main.rs
@ -10,7 +10,6 @@ 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::{
|
||||
@ -140,20 +139,19 @@ fn main() -> ! {
|
||||
|
||||
let mut store = flash_store::store(dp.FLASH);
|
||||
|
||||
let mut channels = RefCell::new(Channels::new(pins));
|
||||
let mut channels = Channels::new(pins);
|
||||
for c in 0..CHANNELS {
|
||||
match store.read_value::<ChannelConfig>(CHANNEL_CONFIG_KEY[c]) {
|
||||
Ok(Some(config)) =>
|
||||
config.apply(channels.get_mut(), c),
|
||||
config.apply(&mut channels, c),
|
||||
Ok(None) =>
|
||||
error!("flash config not found for channel {}", c),
|
||||
Err(e) =>
|
||||
error!("unable to load config {} from flash: {:?}", c, e),
|
||||
}
|
||||
}
|
||||
// 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());
|
||||
|
||||
let mut fan_ctrl = FanCtrl::new(fan, tacho, channels, &mut dp.EXTI, &mut dp.SYSCFG.constrain());
|
||||
|
||||
// default net config:
|
||||
let mut ipv4_config = Ipv4Config {
|
||||
@ -183,7 +181,7 @@ fn main() -> ! {
|
||||
loop {
|
||||
let mut new_ipv4_config = None;
|
||||
let instant = Instant::from_millis(i64::from(timer::now()));
|
||||
let updated_channel = channels.get_mut().poll_adc(instant);
|
||||
let updated_channel = fan_ctrl.channels.poll_adc(instant);
|
||||
if let Some(channel) = updated_channel {
|
||||
server.for_each(|_, session| session.set_report_pending(channel.into()));
|
||||
}
|
||||
@ -213,7 +211,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, channels.get_mut(), session, &mut leds, &mut store, &mut ipv4_config, &mut fan_ctrl) {
|
||||
match Handler::handle_command(command, &mut socket, 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(),
|
||||
@ -230,7 +228,7 @@ fn main() -> ! {
|
||||
}
|
||||
} else if socket.can_send() {
|
||||
if let Some(channel) = session.is_report_pending() {
|
||||
match channels.get_mut().reports_json() {
|
||||
match fan_ctrl.channels.reports_json() {
|
||||
Ok(buf) => {
|
||||
send_line(&mut socket, &buf[..]);
|
||||
session.mark_report_sent(channel);
|
||||
|
Loading…
Reference in New Issue
Block a user