diff --git a/Cargo.toml b/Cargo.toml index 14ffaf2..00450cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,15 +8,15 @@ version = "0.1.0" [dependencies] embedded-hal = "0.2.6" nb = "0.1.2" -cortex-m = "0.6.0" -cortex-m-rt = "0.6.10" -cortex-m-semihosting = "0.3.3" +cortex-m = "0.7.4" +cortex-m-rt = "0.7.1" +cortex-m-semihosting = "0.3.7" panic-halt = "0.2.0" hd44780-driver = "0.3.0" libm = "0.2.1" [dependencies.stm32f1xx-hal] -version = "0.7.0" +version = "0.8.0" features = ["stm32f103", "rt", "medium"] # this lets you use `cargo fix`! diff --git a/docs/Graphs.ods b/docs/Graphs.ods index f1eb706..a2f8294 100644 Binary files a/docs/Graphs.ods and b/docs/Graphs.ods differ diff --git a/docs/encoder_accel.py b/docs/encoder_accel.py new file mode 100644 index 0000000..180dc1d --- /dev/null +++ b/docs/encoder_accel.py @@ -0,0 +1,15 @@ +import matplotlib.pyplot as plt +import numpy as np +import seaborn as sns + +sns.set_theme(style="whitegrid") + +a = 0.85 +b = 0.2 + +x = np.arange(-5, 6) +y = np.copysign(a*abs(x) * 10**(b*abs(x)) * 0.1, x) + +sns.lineplot(x=x, y=y, palette="tab10", linewidth=2.5) +plt.show() + diff --git a/src/config.rs b/src/config.rs index a433624..36c9866 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,23 +1,40 @@ +use core::fmt; + +use crate::lcd_gui::LCDScreen; + +//---Config struct---------------------------------------------------------------------------------- pub enum ConfigError { LoadError, } +#[derive(Copy, Clone)] pub enum FanOutput { P1, P2, } -pub enum FanState { - ON, - OFF, +#[derive(Copy, Clone)] +pub struct CalData { + pub offset: f32, + pub raw_value: Option, } +impl CalData { + fn new() -> CalData { + CalData { + offset: 0.0, + raw_value: None, + } + } +} + +#[derive(Copy, Clone)] pub struct SystemConfig { pub max_temp_diff: f32, pub min_temp_diff: f32, - pub ext_offset: f32, - pub p1_offset: f32, - pub p2_offset: f32, + pub ext_offset: CalData, + pub p1_offset: CalData, + pub p2_offset: CalData, pub fan_output: FanOutput, } @@ -27,18 +44,273 @@ impl SystemConfig { SystemConfig { max_temp_diff: 8.0, min_temp_diff: 4.0, - ext_offset: 0.0, - p1_offset: 0.0, - p2_offset: 0.0, + ext_offset: CalData::new(), + p1_offset: CalData::new(), + p2_offset: CalData::new(), fan_output: FanOutput::P1, } } + #[allow(dead_code)] pub fn load() -> Result { unimplemented!(); } + #[allow(dead_code)] pub fn store() -> SystemConfig { unimplemented!(); } } + +//---Menu struct------------------------------------------------------------------------------------ +const MENU_ENTRY_NB: usize = 6; + +pub enum MenuState { + Scroll, + Entry, +} + +pub struct Menu { + entries: [Entry; MENU_ENTRY_NB], + state: MenuState, + should_refresh: bool, + entry_index: usize, +} + +impl Menu { + + pub fn new(config: &SystemConfig) -> Menu { + + Menu { + entries: [ + Entry::new("fan out", EntryValue::FanOut (FanOutput::P1), config), + Entry::new("max diff", EntryValue::MaxTempDiff (0.0), config), + Entry::new("min diff", EntryValue::MinTempDiff (0.0), config), + Entry::new("ext cal", EntryValue::ExtOffset (CalData::new()), config), + Entry::new("p1 cal", EntryValue::P1Offset (CalData::new()), config), + Entry::new("p2 cal", EntryValue::P2Offset (CalData::new()), config), + ], + state: MenuState::Scroll, + should_refresh: true, + entry_index: 0, + } + } + + pub fn button_update(&mut self, config: &mut SystemConfig) -> bool { + + self.should_refresh = true; + + // update state machine + match self.state { + MenuState::Scroll => { + if self.entry_index == 0 { return true } + // read desired value from config + self.state = MenuState::Entry; + self.entries[self.entry_index - 1].select(config); + }, + MenuState::Entry => { + // write modified value to config + self.state = MenuState::Scroll; + self.entries[self.entry_index - 1].deselect(config); + } + } + + false + } + + pub fn movement_update(&mut self, movement: i32) { + + if movement != 0 { + match self.state { + MenuState::Scroll => { + + // convert movement to entry index + let mut index = self.entry_index as i32 + movement; + if index > MENU_ENTRY_NB as i32 { + index = MENU_ENTRY_NB as i32; + } else if index < 0 { + index = 0; + } + self.entry_index = index as usize; + }, + MenuState::Entry => { + + // update entry + self.entries[self.entry_index - 1].update(-movement); + //note : "-" sign is just to revert encoder direction, feels better when using + //the interface + }, + } + + self.should_refresh = true; + } + } + + pub fn reset(&mut self) { + + match self.state { + MenuState::Entry => { + // if an entry is selected, disguard all modifications + self.entries[self.entry_index - 1].disgard(); + self.state = MenuState::Scroll; + }, + _ => (), + } + + self.entry_index = 0; + } + + pub fn display(&mut self, screen: &mut L) { + + if self.should_refresh { + + // first line + screen.clear(); + screen.set_cursor_pos(0); + let _ = write!(screen, "\u{007e}"); + if self.entry_index == 0 { + let _ = write!(screen, "retour"); + } else { + self.entries[self.entry_index - 1].display(screen); + } + + // second line (if any) + if self.entry_index < MENU_ENTRY_NB { + screen.set_cursor_pos(0x40); + let _ = write!(screen, " "); + self.entries[self.entry_index].display(screen); + } + + self.should_refresh = false; + } + } +} + +pub enum EntryValue { + MaxTempDiff (f32), + MinTempDiff (f32), + ExtOffset (CalData), + P1Offset (CalData), + P2Offset (CalData), + FanOut (FanOutput), +} + +impl EntryValue { + + fn modify(&mut self, n: i32) { + use libm::{powf, copysignf, fabsf}; + + use crate::utils::TEMP_RANGE; + + match self { + Self::MaxTempDiff (val) | Self::MinTempDiff (val) => { + *val += copysignf(fabsf(n as f32) * 0.85 * powf(10.0, 0.15 * fabsf(n as f32)) + * 0.1, n as f32); + // avoid display issues by limiting value range + if *val > 99.9 { *val = 99.9; } + else if *val < -99.9 { *val = -99.9; } + }, + Self::ExtOffset (data) | Self::P1Offset (data) | Self::P2Offset (data) => { + data.offset += copysignf(fabsf(n as f32) * 0.85 + * powf(10.0, 0.15 * fabsf(n as f32)) * 0.1, n as f32); + // avoid limiting issues like before, taking probe bounds into account + if data.offset + TEMP_RANGE.end > 99.9 { + data.offset = 99.9 - TEMP_RANGE.end; + } + else if data.offset + TEMP_RANGE.start < -99.9 { + data.offset = -99.9 - TEMP_RANGE.start; + } + }, + Self::FanOut (output) => { + if n%2 != 0 { + match output { + FanOutput::P1 => *output = FanOutput::P2, + FanOutput::P2 => *output = FanOutput::P1, + }}}} + } + + fn load(&mut self, config: &SystemConfig) { + match self { + Self::MaxTempDiff (val) => *val = config.max_temp_diff, + Self::MinTempDiff (val) => *val = config.min_temp_diff, + Self::ExtOffset (data) => *data = config.ext_offset, + Self::P1Offset (data) => *data = config.p1_offset, + Self::P2Offset (data) => *data = config.p2_offset, + Self::FanOut (val) => *val = config.fan_output, + }; + } + + fn store(&mut self, config: &mut SystemConfig) { + match self { + Self::MaxTempDiff (val) => config.max_temp_diff = *val, + Self::MinTempDiff (val) => config.min_temp_diff = *val, + Self::ExtOffset (data) => config.ext_offset = *data, + Self::P1Offset (data) => config.p1_offset = *data, + Self::P2Offset (data) => config.p2_offset = *data, + Self::FanOut (val) => config.fan_output = *val, + }; + } +} + +impl fmt::Display for EntryValue { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + use crate::lcd_gui::Temp; + + match self { + Self::MaxTempDiff (val) | Self::MinTempDiff (val) => + formatter.write_fmt(format_args!("{:.1}", val)), + Self::ExtOffset (data) | Self::P1Offset (data) | Self::P2Offset (data) => { + let temp: Temp = data.raw_value.map(|val| val + data.offset).into(); + formatter.write_fmt(format_args!("{}", temp)) + }, + Self::FanOut (output) => { + match output { + FanOutput::P1 => formatter.write_fmt(format_args!("1")), + FanOutput::P2 => formatter.write_fmt(format_args!("2")), + }}} + } +} + + +pub struct Entry { + text: &'static str, + value: EntryValue, + is_selected: bool, +} + +impl Entry { + + pub fn new(text: &'static str, mut value: EntryValue, config: &SystemConfig) -> Entry { + + value.load(config); + Entry { + text, + value, + is_selected: false, + } + } + + pub fn display(&self, buffer: &mut B) { + let _ = match self.is_selected { + true => write!(buffer, "{}:{}", self.text, self.value), + false => write!(buffer, "{} {}", self.text, self.value), + }; + } + + pub fn select(&mut self, config: &SystemConfig) { + self.is_selected = true; + self.value.load(config); + } + pub fn deselect(&mut self, config: &mut SystemConfig) { + self.is_selected = false; + self.value.store(config); + } + + pub fn disgard(&mut self) { + self.is_selected = false; + } + + pub fn update(&mut self, n: i32) { + self.value.modify(n); + } +} diff --git a/src/lcd_gui.rs b/src/lcd_gui.rs index 355def2..d0e7a52 100644 --- a/src/lcd_gui.rs +++ b/src/lcd_gui.rs @@ -17,8 +17,8 @@ use hd44780_driver::{ }; use crate::{ - config::SystemConfig, state::SystemState, + config::{Menu, SystemConfig, FanOutput}, }; //---Temp Enum-------------------------------------------------------------------------------------- @@ -33,16 +33,53 @@ pub enum Temp { impl fmt::Display for Temp { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { match self { - Temp::Valid (deg) => formatter.write_fmt(format_args!("{:.2}", deg)), + Temp::Valid (deg) => formatter.write_fmt(format_args!("{:.1}", deg)), Temp::Invalid => formatter.write_str("inval"), } } } +impl core::convert::From> for Temp { + fn from(val: Option) -> Self { + match val { + Some(deg) => Temp::Valid(deg), + None => Temp::Invalid, + } + } +} + +impl core::convert::From for Temp { + fn from(val: f32) -> Self { + Temp::Valid(val) + } +} + +impl core::cmp::PartialEq for Temp { + fn eq(&self, other: &Self) -> bool { + match self { + Self::Valid (val) => match other { + Self::Invalid => false, + Self::Valid (other_val) => val == other_val, + }, + Self::Invalid => match other { + Self::Invalid => true, + _ => false, + }, + } + } +} + //---LCDGui Struct---------------------------------------------------------------------------------- + +pub enum GUIState { + Off, + Idle, + Menu, +} + /// Manages the lcd screen and inputs (encoder + button) and display relevant information. Can also /// be used to configure the system. The update() function should be called frequently to refresh -/// the GUI (every 0.2s is enough) +/// the GUI (every 0.2s is enough). pub struct LCDGui where L: LCDScreen, @@ -53,9 +90,13 @@ where qei: Q, button: &'static AtomicBool, backlight: Option, - count: u16, + count: i32, + state: GUIState, + menu: Menu, ext_deg: Temp, p1_deg: Temp, + p2_deg: Temp, + should_refresh: bool, } impl LCDGui @@ -65,19 +106,26 @@ where B: OutputPin, { - pub fn new(lcd: L, qei: Q, button: &'static AtomicBool, backlight: Option) + /// Create and configure a new GUI. By default the screen will display the idle screen. + pub fn new(lcd: L, qei: Q, button: &'static AtomicBool, backlight: Option, + config: &SystemConfig) -> LCDGui { + use hd44780_driver::{Cursor, CursorBlink, Display}; - let count = qei.count(); + let count = qei.count() as i32; let mut gui = LCDGui { lcd, qei, button, backlight, count, + state: GUIState::Idle, + menu: Menu::new(config), ext_deg: Temp::Invalid, p1_deg: Temp::Invalid, + p2_deg: Temp::Invalid, + should_refresh: true, }; if let Some(bl) = &mut gui.backlight { let _ = bl.set_high(); }; @@ -87,52 +135,135 @@ where gui.lcd.set_display_mode( DisplayMode { display: Display::On, - cursor_visibility: Cursor::Visible, - cursor_blink: CursorBlink::On, - } - ); - let _ = gui.lcd.write_str("Hello world!"); + cursor_visibility: Cursor::Invisible, + cursor_blink: CursorBlink::Off, + }); gui } - pub fn update(&mut self, state: &mut SystemState) { + /// Perform the necessary operations after an action on the button or the encoder. This + /// function should be called frequently (every 0.2s is enough) to keep track of the state of + /// the encoder. For added reactivness, it can also be called on a button press. The function + /// can modifify the system's config through the system state and will take care of updating + /// the latter. Return true when something has been updated during the function call, usefull + /// for implementing powersaving features. + pub fn update(&mut self, state: &mut SystemState) -> bool { - self.ext_deg = match state.ext_temp() { - Some(deg) => Temp::Valid(deg), - None => Temp::Invalid, - }; + let mut input_update = false; - self.p1_deg = match state.p1_temp() { - Some(deg) => Temp::Valid(deg), - None => Temp::Invalid, - }; - - //TODO deduplicate button detection + // manage button press if self.button.swap(false, Ordering::AcqRel) { - self.lcd.write_str("paf").unwrap(); + input_update = true; + + // update state machine + match self.state { + GUIState::Off => { + self.state = GUIState::Idle; + self.lcd.clear(); + if let Some(bl) = &mut self.backlight { let _ = bl.set_high(); }; + self.should_refresh = true; + }, + GUIState::Idle => { + self.state = GUIState::Menu; + self.menu.display(&mut self.lcd); + }, + GUIState::Menu => { + + //TODO improve that + let mut config: SystemConfig = *state.config(); + if self.menu.button_update(&mut config) { + self.state = GUIState::Idle; + self.update_temps(state); + } else { + state.update_config(config); + self.menu.display(&mut self.lcd); + } + }}} + + // manage encoder movement + let count = self.qei.count() as i32 / 2; + let diff1 = count - self.count; + let diff2 = 511 - i32::abs(diff1); + let diff = if i32::abs(diff1) < diff2 { + diff1 + } else { + diff2 * -i32::signum(diff1) + }; + + if diff != 0 { + input_update = true; + self.count = count; } - if self.count != self.qei.count() { - self.count = self.qei.count(); - self.lcd.set_cursor_pos(0x40); - self.lcd.write_str(" ").unwrap(); - self.lcd.set_cursor_pos(0x40); - write!(self.lcd, "{}", self.qei.count()/2).unwrap(); - } + // display relevant screen + match self.state { + GUIState::Off => {}, + GUIState::Idle => { + if self.should_refresh { + display_idle_screen(&mut self.lcd, &self.ext_deg, &self.p1_deg, &self.p2_deg, + &state.config().fan_output); + self.should_refresh = false; + } + }, + GUIState::Menu => { + if diff != 0 { + self.menu.movement_update(diff); + self.menu.display(&mut self.lcd); + } + }} - self.lcd.set_cursor_pos(0); - self.lcd.write_str(" ").unwrap(); - self.lcd.set_cursor_pos(0); - let _ = write!(self.lcd, "{}\u{00df}C", self.ext_deg); + input_update + } - self.lcd.set_cursor_pos(0x40); - self.lcd.write_str(" ").unwrap(); - self.lcd.set_cursor_pos(0x40); - let _ = write!(self.lcd, "{}\u{00df}C", self.p1_deg); + /// Update the temperatures to display on the idle screen. Should be called whenever the + /// temperatures are modified. + pub fn update_temps(&mut self, state: &SystemState) { + + self.ext_deg = state.ext_temp().into(); + self.p1_deg = state.p1_temp().into(); + self.p2_deg = state.p2_temp().into(); + + self.should_refresh = true; + } + + pub fn sleep(&mut self) { + + self.state = GUIState::Off; + + // reset menu in case the user was in-menu + self.menu.reset(); + + // manage lcd + self.lcd.clear(); + //TODO set display off ? + if let Some(bl) = &mut self.backlight { let _ = bl.set_low(); }; } } +fn display_idle_screen(screen: &mut L, ext_deg: &Temp, p1_deg: &Temp, p2_deg: &Temp, + fan_output: &FanOutput) { + + // display temperatures + screen.clear(); + screen.set_cursor_pos(0); + let _ = write!(screen, "Ext:{}", ext_deg); + screen.set_cursor_pos(0x41); + let _ = write!(screen, "1:{}", p1_deg); + screen.set_cursor_pos(0x49); + let _ = write!(screen, "2:{}", p2_deg); + + // display probe selection + match fan_output { + FanOutput::P1 => screen.set_cursor_pos(0x40), + FanOutput::P2 => screen.set_cursor_pos(0x48), + } + let _ = write!(screen, "*"); +} + +//---LCDScreen trait-------------------------------------------------------------------------------- +/// A wrapper trait to complement the write trait by adding the extra functions provided by a lcd +/// screen, such as cursor position. pub trait LCDScreen: core::fmt::Write { fn reset(&mut self); diff --git a/src/main.rs b/src/main.rs index b788158..a1c8ad9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,6 @@ use cortex_m::interrupt::Mutex; use cortex_m_rt::entry; use embedded_hal::digital::{ - v2::OutputPin, v1_compat::OldOutputPin, }; @@ -24,7 +23,7 @@ use stm32f1xx_hal::{ prelude::*, timer::{Timer, CountDownTimer, Event}, qei::{QeiOptions, SlaveMode}, - rtc::Rtc, + rtc::{Rtc, RtcClkLsi}, }; mod lcd_gui; @@ -45,9 +44,10 @@ use state::SystemState; /* system config */ const GUI_TICK_SEC: f32 = 0.2; +const AWAKE_TIMEOUT_SEC: f32 = 20.0; const HEARTBEAT_SEC: f32 = 1.0; -const TEMPS_TICK_SEC: u32 = 2; +const TEMPS_TICK_SEC: u32 = 30; //-------------------------------------------------------------------------------------------------- /* interrupt variables */ @@ -64,10 +64,17 @@ static BUTTON_PIN: Mutex>>>> static BUTTON_FLAG: AtomicBool = AtomicBool::new(false); // temps interrupt -static RTC: Mutex>> = Mutex::new(RefCell::new(None)); +static RTC: Mutex>>> = Mutex::new(RefCell::new(None)); static G_EXTI: Mutex>> = Mutex::new(RefCell::new(None)); static TEMP_FLAG: AtomicBool = AtomicBool::new(false); +//-------------------------------------------------------------------------------------------------- +/* power management */ +enum PowerState { + Awake, + Sleeping, +} + //-------------------------------------------------------------------------------------------------- /* interrupt service routines */ #[interrupt] @@ -126,16 +133,16 @@ fn main() -> ! { let mut dp = pac::Peripherals::take().unwrap(); // clocks config - let mut rcc = dp.RCC.constrain(); + let rcc = dp.RCC.constrain(); let mut flash = dp.FLASH.constrain(); let clocks = rcc.cfgr .use_hse(8.mhz()) .freeze(&mut flash.acr); // GPIOs - let mut gpioa = dp.GPIOA.split(&mut rcc.apb2); - let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); - let mut gpioc = dp.GPIOC.split(&mut rcc.apb2); + let mut gpioa = dp.GPIOA.split(); + let mut gpiob = dp.GPIOB.split(); + let mut gpioc = dp.GPIOC.split(); // setup LED let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); @@ -146,7 +153,7 @@ fn main() -> ! { }); // Configure the timer 2 as tick timer - let mut timer = Timer::tim2(dp.TIM2, &clocks, &mut rcc.apb1) + let mut timer = Timer::tim2(dp.TIM2, &clocks) .start_count_down(((1.0/GUI_TICK_SEC) as u32).hz()); timer.listen(Event::Update); @@ -159,8 +166,8 @@ fn main() -> ! { dp.EXTI.imr.write(|w| w.mr17().set_bit()); // setup RTC - let mut backup_domain = rcc.bkp.constrain(dp.BKP, &mut rcc.apb1, &mut dp.PWR); - let mut rtc = Rtc::rtc(dp.RTC, &mut backup_domain); + let mut backup_domain = rcc.bkp.constrain(dp.BKP, &mut dp.PWR); + let mut rtc = Rtc::::rtc(dp.RTC, &mut backup_domain); rtc.set_alarm(rtc.current_time() + TEMPS_TICK_SEC); rtc.listen_alarm(); @@ -168,7 +175,11 @@ fn main() -> ! { RTC.borrow(cs).borrow_mut().replace(rtc) }); - let mut afio = dp.AFIO.constrain(&mut rcc.apb2); + let mut afio = dp.AFIO.constrain(); + + // initialize system state + let config = SystemConfig::new(); + let mut state = SystemState::new(config, gpiob.pb12.into_push_pull_output(&mut gpiob.crh)); // Setup display let mut gui = { @@ -193,7 +204,7 @@ fn main() -> ! { // Force write mode let mut rw_pin = gpioa.pa9.into_push_pull_output(&mut gpioa.crh); - rw_pin.set_low().unwrap(); + rw_pin.set_low(); let lcd = HD44780::new_8bit( OldOutputPin::new(rs_pin), @@ -212,7 +223,7 @@ fn main() -> ! { // setup button (interrupt) let mut but_pin = gpiob.pb8.into_pull_up_input(&mut gpiob.crh); but_pin.make_interrupt_source(&mut afio); - but_pin.trigger_on_edge(&dp.EXTI, Edge::RISING); + but_pin.trigger_on_edge(&dp.EXTI, Edge::Rising); but_pin.enable_interrupt(&dp.EXTI); cortex_m::interrupt::free(|cs| { @@ -223,14 +234,14 @@ fn main() -> ! { let backlight_pin = gpiob.pb9.into_push_pull_output(&mut gpiob.crh); // setup encoder - let enc = Timer::tim4(dp.TIM4, &clocks, &mut rcc.apb1) + let enc = Timer::tim4(dp.TIM4, &clocks) .qei((gpiob.pb6, gpiob.pb7), &mut afio.mapr, QeiOptions { - slave_mode:SlaveMode::EncoderMode2, - auto_reload_value: 200 + slave_mode:SlaveMode::EncoderMode1, + auto_reload_value: 1022, }); - LCDGui::new(lcd, enc, &BUTTON_FLAG, Some(backlight_pin)) + LCDGui::new(lcd, enc, &BUTTON_FLAG, Some(backlight_pin), state.config()) }; let exti = dp.EXTI; @@ -254,42 +265,65 @@ fn main() -> ! { // configure stop mode (not working yet, probably due to fake chip) //dp.PWR.cr.write(|w| w.pdds().stop_mode()); //dp.PWR.cr.write(|w| w.lpds().set_bit()); - cp.SCB.set_sleepdeep(); // setup adc - let mut adc = Adc::adc1(dp.ADC1, &mut rcc.apb2, clocks); + let mut adc = Adc::adc1(dp.ADC1, clocks); adc.set_sample_time(SampleTime::T_71); let adc = RefCell::new(adc); let ext1 = gpioa.pa4.into_analog(&mut gpioa.crl); let ext2 = gpioa.pa5.into_analog(&mut gpioa.crl); - let p1_1 = gpioa.pa2.into_analog(&mut gpioa.crl); - let p1_2 = gpioa.pa3.into_analog(&mut gpioa.crl); + let p2_1 = gpioa.pa2.into_analog(&mut gpioa.crl); + let p2_2 = gpioa.pa3.into_analog(&mut gpioa.crl); + let p1_1 = gpioa.pa0.into_analog(&mut gpioa.crl); + let p1_2 = gpioa.pa1.into_analog(&mut gpioa.crl); let mut ext_probe = TemperatureProbe::new(&adc, ext1, ext2).unwrap(); let mut p1_probe = TemperatureProbe::new(&adc, p1_1, p1_2).unwrap(); - - // initialize system state - let config = SystemConfig::new(); - let mut state = SystemState::new(config, gpiob.pb12.into_push_pull_output(&mut gpiob.crh)); + let mut p2_probe = TemperatureProbe::new(&adc, p2_1, p2_2).unwrap(); + let mut power_state = PowerState::Awake; + let mut timeout = 0; + /* run */ let _ = TEMP_FLAG.swap(true, Ordering::Release); loop { - //compute temps if TEMP_FLAG.swap(false, Ordering::AcqRel) { + + // compute temps let ext_temp = ext_probe.read().ok(); let p1_temp = p1_probe.read().ok(); + let p2_temp = p2_probe.read().ok(); - state.update(ext_temp, p1_temp, None); - } + state.update(ext_temp, p1_temp, p2_temp); + gui.update_temps(&mut state); - gui.update(&mut state); + } else { + + match gui.update(&mut state) { + true => timeout = 0, + false => timeout += 1, + } + + // manage power state + match power_state { + PowerState::Awake => { // normal gui update + if timeout as f32 * GUI_TICK_SEC >= AWAKE_TIMEOUT_SEC { + // go to sleep + power_state = PowerState::Sleeping; + gui.sleep(); + cp.SCB.set_sleepdeep(); + } + }, + PowerState::Sleeping => { // button was pressed during sleep + // wake up + power_state = PowerState::Awake; + cp.SCB.clear_sleepdeep(); + }}} // put device in sleep mode until next interrupt (button or timer) cortex_m::asm::wfi(); } } - diff --git a/src/state.rs b/src/state.rs index b892465..9186545 100644 --- a/src/state.rs +++ b/src/state.rs @@ -7,8 +7,8 @@ use crate::config::{ }; pub enum FanState { - ON, - OFF, + On, + Off, } pub struct Fan { @@ -23,19 +23,19 @@ impl Fan { // erroring here means the pin is wrongly configured, there is nothing else to do... pin.set_low().map_err(|_| "fan pin configuration").unwrap(); Fan { - state: FanState::OFF, + state: FanState::Off, pin, } } pub fn on(&mut self) { self.pin.set_high().map_err(|_| "fan pin configuration").unwrap(); - self.state = FanState::ON; + self.state = FanState::On; } pub fn off(&mut self) { self.pin.set_low().map_err(|_| "fan pin configuration").unwrap(); - self.state = FanState::OFF; + self.state = FanState::Off; } pub fn state(&self) -> &FanState { &self.state } @@ -70,9 +70,9 @@ impl SystemState { pub fn update_config(&mut self, config: SystemConfig) { // remove offsets - let ext_temp = self.ext_temp.map(|temp| temp - self.config.ext_offset); - let p1_temp = self.p1_temp.map(|temp| temp - self.config.p1_offset); - let p2_temp = self.p2_temp.map(|temp| temp - self.config.p2_offset); + let ext_temp = self.ext_temp.map(|temp| temp - self.config.ext_offset.offset); + let p1_temp = self.p1_temp.map(|temp| temp - self.config.p1_offset.offset); + let p2_temp = self.p2_temp.map(|temp| temp - self.config.p2_offset.offset); // replace config self.config = config; @@ -87,9 +87,14 @@ impl SystemState { pub fn update(&mut self, ext_temp: Option, p1_temp: Option, p2_temp: Option) { // apply offsets - self.ext_temp = ext_temp.map(|temp| temp + self.config.ext_offset); - self.p1_temp = p1_temp.map(|temp| temp + self.config.p1_offset); - self.p2_temp = p2_temp.map(|temp| temp + self.config.p2_offset); + self.ext_temp = ext_temp.map(|temp| temp + self.config.ext_offset.offset); + self.p1_temp = p1_temp.map(|temp| temp + self.config.p1_offset.offset); + self.p2_temp = p2_temp.map(|temp| temp + self.config.p2_offset.offset); + + // update cal values + self.config.ext_offset.raw_value = ext_temp; + self.config.p1_offset.raw_value = p1_temp; + self.config.p2_offset.raw_value = p2_temp; // select right probe and check if data is available let p = match match self.config.fan_output { @@ -112,12 +117,12 @@ impl SystemState { // compute fan state match self.fan.state() { - FanState::ON => { + FanState::On => { if (p - ext) < self.config.min_temp_diff { self.fan.off(); } }, - FanState::OFF => { + FanState::Off => { if (p - ext) > self.config.max_temp_diff { self.fan.on(); } diff --git a/src/utils.rs b/src/utils.rs index 1c396f5..0a8d5a3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,7 @@ use core::{ marker::PhantomData, cell::RefCell, + ops::Range, }; use embedded_hal::adc::*; @@ -9,16 +10,21 @@ use libm::*; //-------------------------------------------------------------------------------------------------- /* 2nd order linearisation factor for the temperature probe */ -const A: f32 = -0.00058; -const B: f32 = 0.0677; +const A: f32 = -0.000574; +const B: f32 = 0.0676; const C: f32 = -0.07; +//-------------------------------------------------------------------------------------------------- +/* valid temperature range */ +pub const TEMP_RANGE: Range = -10.0..45.0; + //-------------------------------------------------------------------------------------------------- /* error management */ #[derive(Debug)] pub enum ProbeError { ReadError, Initializing, + OutOfRange, } //-------------------------------------------------------------------------------------------------- @@ -76,9 +82,9 @@ where // compute first temp approximation to speed up stabilization let mut temp = 0.0; for _ in 0..10 { - temp += read_temp(&adc, &mut pos_pin, &mut neg_pin)?; + temp += read_temp(adc, &mut pos_pin, &mut neg_pin)?; } - temp = temp/10.0; + temp /= 10.0; Ok(TemperatureProbe { adc, @@ -97,7 +103,10 @@ where match self.stabilized { true => { self.filtered_temp += (temp - self.filtered_temp)/FILTER_TIME_CONSTANT; - Ok(self.filtered_temp) + match TEMP_RANGE.contains(&self.filtered_temp) { + true => Ok(self.filtered_temp), + false => Err(ProbeError::OutOfRange), + } }, false => { // filter is not yet stabilized let old_temp = self.filtered_temp;