diff --git a/README.md b/README.md index e66c5b5..5275de7 100644 --- a/README.md +++ b/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 ` | 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) | +| 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 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 ` - 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`. diff --git a/src/command_handler.rs b/src/command_handler.rs index edf05fd..6049b99 100644 --- a/src/command_handler.rs +++ b/src/command_handler.rs @@ -342,7 +342,7 @@ impl Handler { Ok(Handler::Reset) } - fn fan (socket: &mut TcpSocket, fan_pwm: Option, fan_ctrl: &mut FanCtrl) -> Result { + fn fan(socket: &mut TcpSocket, fan_pwm: Option, fan_ctrl: &mut FanCtrl) -> Result { 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 { - 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 { + 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 { + 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 { + 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 { 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), } } diff --git a/src/command_parser.rs b/src/command_parser.rs index 497b741..66c2777 100644 --- a/src/command_parser.rs +++ b/src/command_parser.rs @@ -182,7 +182,7 @@ pub enum Command { Fan { fan_pwm: Option }, - FanCoeff { + FanCurve { k_a: f64, k_b: f64, k_c: f64, @@ -546,9 +546,9 @@ fn fan(input: &[u8]) -> IResult<&[u8], Result> { Ok((input, result)) } -fn fan_coeff(input: &[u8]) -> IResult<&[u8], Result> { +fn fan_curve(input: &[u8]) -> IResult<&[u8], Result> { 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> { 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> { 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) } diff --git a/src/fan_ctrl.rs b/src/fan_ctrl.rs index 3c6d9cc..4999ff9 100644 --- a/src/fan_ctrl.rs +++ b/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; 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 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, 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(), } } -} \ No newline at end of file +} + +#[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); + } + } +} diff --git a/src/main.rs b/src/main.rs index dd1263e..ef629ef 100644 --- a/src/main.rs +++ b/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::(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);