Resolve "Design new GUI"

This commit is contained in:
Steins7 2022-01-07 07:45:37 +00:00
parent 891e3d03cd
commit 3668d9b80d
8 changed files with 566 additions and 100 deletions

View File

@ -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`!

Binary file not shown.

15
docs/encoder_accel.py Normal file
View File

@ -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()

View File

@ -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<f32>,
}
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<ConfigError, SystemConfig> {
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<L: LCDScreen>(&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<B: core::fmt::Write>(&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);
}
}

View File

@ -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<Option<f32>> for Temp {
fn from(val: Option<f32>) -> Self {
match val {
Some(deg) => Temp::Valid(deg),
None => Temp::Invalid,
}
}
}
impl core::convert::From<f32> for Temp {
fn from(val: f32) -> Self {
Temp::Valid(val)
}
}
impl core::cmp::PartialEq<Temp> 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<L, Q, B>
where
L: LCDScreen,
@ -53,9 +90,13 @@ where
qei: Q,
button: &'static AtomicBool,
backlight: Option<B>,
count: u16,
count: i32,
state: GUIState,
menu: Menu,
ext_deg: Temp,
p1_deg: Temp,
p2_deg: Temp,
should_refresh: bool,
}
impl<L, Q, B> LCDGui<L, Q, B>
@ -65,19 +106,26 @@ where
B: OutputPin,
{
pub fn new(lcd: L, qei: Q, button: &'static AtomicBool, backlight: Option<B>)
/// 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<B>,
config: &SystemConfig)
-> LCDGui<L, Q, B> {
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<O: OutputPin>(&mut self, state: &mut SystemState<O>) {
/// 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<O: OutputPin>(&mut self, state: &mut SystemState<O>) -> 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<O: OutputPin>(&mut self, state: &SystemState<O>) {
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<L: LCDScreen>(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);

View File

@ -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<RefCell<Option<gpiob::PB8<Input<PullUp>>>>>
static BUTTON_FLAG: AtomicBool = AtomicBool::new(false);
// temps interrupt
static RTC: Mutex<RefCell<Option<Rtc>>> = Mutex::new(RefCell::new(None));
static RTC: Mutex<RefCell<Option<Rtc::<RtcClkLsi>>>> = Mutex::new(RefCell::new(None));
static G_EXTI: Mutex<RefCell<Option<EXTI>>> = 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::<RtcClkLsi>::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();
let mut p2_probe = TemperatureProbe::new(&adc, p2_1, p2_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 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();
}
}

View File

@ -7,8 +7,8 @@ use crate::config::{
};
pub enum FanState {
ON,
OFF,
On,
Off,
}
pub struct Fan<O: OutputPin> {
@ -23,19 +23,19 @@ impl<O: OutputPin> Fan<O> {
// 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<O: OutputPin> SystemState<O> {
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<O: OutputPin> SystemState<O> {
pub fn update(&mut self, ext_temp: Option<f32>, p1_temp: Option<f32>, p2_temp: Option<f32>) {
// 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<O: OutputPin> SystemState<O> {
// 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();
}

View File

@ -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<f32> = -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;