Resolve "Design new GUI"
This commit is contained in:
parent
891e3d03cd
commit
3668d9b80d
@ -8,15 +8,15 @@ version = "0.1.0"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
embedded-hal = "0.2.6"
|
embedded-hal = "0.2.6"
|
||||||
nb = "0.1.2"
|
nb = "0.1.2"
|
||||||
cortex-m = "0.6.0"
|
cortex-m = "0.7.4"
|
||||||
cortex-m-rt = "0.6.10"
|
cortex-m-rt = "0.7.1"
|
||||||
cortex-m-semihosting = "0.3.3"
|
cortex-m-semihosting = "0.3.7"
|
||||||
panic-halt = "0.2.0"
|
panic-halt = "0.2.0"
|
||||||
hd44780-driver = "0.3.0"
|
hd44780-driver = "0.3.0"
|
||||||
libm = "0.2.1"
|
libm = "0.2.1"
|
||||||
|
|
||||||
[dependencies.stm32f1xx-hal]
|
[dependencies.stm32f1xx-hal]
|
||||||
version = "0.7.0"
|
version = "0.8.0"
|
||||||
features = ["stm32f103", "rt", "medium"]
|
features = ["stm32f103", "rt", "medium"]
|
||||||
|
|
||||||
# this lets you use `cargo fix`!
|
# this lets you use `cargo fix`!
|
||||||
|
|||||||
BIN
docs/Graphs.ods
BIN
docs/Graphs.ods
Binary file not shown.
15
docs/encoder_accel.py
Normal file
15
docs/encoder_accel.py
Normal 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()
|
||||||
|
|
||||||
290
src/config.rs
290
src/config.rs
@ -1,23 +1,40 @@
|
|||||||
|
use core::fmt;
|
||||||
|
|
||||||
|
use crate::lcd_gui::LCDScreen;
|
||||||
|
|
||||||
|
//---Config struct----------------------------------------------------------------------------------
|
||||||
pub enum ConfigError {
|
pub enum ConfigError {
|
||||||
LoadError,
|
LoadError,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
pub enum FanOutput {
|
pub enum FanOutput {
|
||||||
P1,
|
P1,
|
||||||
P2,
|
P2,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum FanState {
|
#[derive(Copy, Clone)]
|
||||||
ON,
|
pub struct CalData {
|
||||||
OFF,
|
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 struct SystemConfig {
|
||||||
pub max_temp_diff: f32,
|
pub max_temp_diff: f32,
|
||||||
pub min_temp_diff: f32,
|
pub min_temp_diff: f32,
|
||||||
pub ext_offset: f32,
|
pub ext_offset: CalData,
|
||||||
pub p1_offset: f32,
|
pub p1_offset: CalData,
|
||||||
pub p2_offset: f32,
|
pub p2_offset: CalData,
|
||||||
pub fan_output: FanOutput,
|
pub fan_output: FanOutput,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,18 +44,273 @@ impl SystemConfig {
|
|||||||
SystemConfig {
|
SystemConfig {
|
||||||
max_temp_diff: 8.0,
|
max_temp_diff: 8.0,
|
||||||
min_temp_diff: 4.0,
|
min_temp_diff: 4.0,
|
||||||
ext_offset: 0.0,
|
ext_offset: CalData::new(),
|
||||||
p1_offset: 0.0,
|
p1_offset: CalData::new(),
|
||||||
p2_offset: 0.0,
|
p2_offset: CalData::new(),
|
||||||
fan_output: FanOutput::P1,
|
fan_output: FanOutput::P1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn load() -> Result<ConfigError, SystemConfig> {
|
pub fn load() -> Result<ConfigError, SystemConfig> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn store() -> SystemConfig {
|
pub fn store() -> SystemConfig {
|
||||||
unimplemented!();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
205
src/lcd_gui.rs
205
src/lcd_gui.rs
@ -17,8 +17,8 @@ use hd44780_driver::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::SystemConfig,
|
|
||||||
state::SystemState,
|
state::SystemState,
|
||||||
|
config::{Menu, SystemConfig, FanOutput},
|
||||||
};
|
};
|
||||||
|
|
||||||
//---Temp Enum--------------------------------------------------------------------------------------
|
//---Temp Enum--------------------------------------------------------------------------------------
|
||||||
@ -33,16 +33,53 @@ pub enum Temp {
|
|||||||
impl fmt::Display for Temp {
|
impl fmt::Display for Temp {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
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"),
|
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----------------------------------------------------------------------------------
|
//---LCDGui Struct----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub enum GUIState {
|
||||||
|
Off,
|
||||||
|
Idle,
|
||||||
|
Menu,
|
||||||
|
}
|
||||||
|
|
||||||
/// Manages the lcd screen and inputs (encoder + button) and display relevant information. Can also
|
/// 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
|
/// 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>
|
pub struct LCDGui<L, Q, B>
|
||||||
where
|
where
|
||||||
L: LCDScreen,
|
L: LCDScreen,
|
||||||
@ -53,9 +90,13 @@ where
|
|||||||
qei: Q,
|
qei: Q,
|
||||||
button: &'static AtomicBool,
|
button: &'static AtomicBool,
|
||||||
backlight: Option<B>,
|
backlight: Option<B>,
|
||||||
count: u16,
|
count: i32,
|
||||||
|
state: GUIState,
|
||||||
|
menu: Menu,
|
||||||
ext_deg: Temp,
|
ext_deg: Temp,
|
||||||
p1_deg: Temp,
|
p1_deg: Temp,
|
||||||
|
p2_deg: Temp,
|
||||||
|
should_refresh: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<L, Q, B> LCDGui<L, Q, B>
|
impl<L, Q, B> LCDGui<L, Q, B>
|
||||||
@ -65,19 +106,26 @@ where
|
|||||||
B: OutputPin,
|
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> {
|
-> LCDGui<L, Q, B> {
|
||||||
|
|
||||||
use hd44780_driver::{Cursor, CursorBlink, Display};
|
use hd44780_driver::{Cursor, CursorBlink, Display};
|
||||||
|
|
||||||
let count = qei.count();
|
let count = qei.count() as i32;
|
||||||
let mut gui = LCDGui {
|
let mut gui = LCDGui {
|
||||||
lcd,
|
lcd,
|
||||||
qei,
|
qei,
|
||||||
button,
|
button,
|
||||||
backlight,
|
backlight,
|
||||||
count,
|
count,
|
||||||
|
state: GUIState::Idle,
|
||||||
|
menu: Menu::new(config),
|
||||||
ext_deg: Temp::Invalid,
|
ext_deg: Temp::Invalid,
|
||||||
p1_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(); };
|
if let Some(bl) = &mut gui.backlight { let _ = bl.set_high(); };
|
||||||
@ -87,52 +135,135 @@ where
|
|||||||
gui.lcd.set_display_mode(
|
gui.lcd.set_display_mode(
|
||||||
DisplayMode {
|
DisplayMode {
|
||||||
display: Display::On,
|
display: Display::On,
|
||||||
cursor_visibility: Cursor::Visible,
|
cursor_visibility: Cursor::Invisible,
|
||||||
cursor_blink: CursorBlink::On,
|
cursor_blink: CursorBlink::Off,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
let _ = gui.lcd.write_str("Hello world!");
|
|
||||||
|
|
||||||
gui
|
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() {
|
let mut input_update = false;
|
||||||
Some(deg) => Temp::Valid(deg),
|
|
||||||
None => Temp::Invalid,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.p1_deg = match state.p1_temp() {
|
// manage button press
|
||||||
Some(deg) => Temp::Valid(deg),
|
|
||||||
None => Temp::Invalid,
|
|
||||||
};
|
|
||||||
|
|
||||||
//TODO deduplicate button detection
|
|
||||||
if self.button.swap(false, Ordering::AcqRel) {
|
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() {
|
// display relevant screen
|
||||||
self.count = self.qei.count();
|
match self.state {
|
||||||
self.lcd.set_cursor_pos(0x40);
|
GUIState::Off => {},
|
||||||
self.lcd.write_str(" ").unwrap();
|
GUIState::Idle => {
|
||||||
self.lcd.set_cursor_pos(0x40);
|
if self.should_refresh {
|
||||||
write!(self.lcd, "{}", self.qei.count()/2).unwrap();
|
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);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
|
input_update
|
||||||
}
|
}
|
||||||
|
|
||||||
self.lcd.set_cursor_pos(0);
|
/// Update the temperatures to display on the idle screen. Should be called whenever the
|
||||||
self.lcd.write_str(" ").unwrap();
|
/// temperatures are modified.
|
||||||
self.lcd.set_cursor_pos(0);
|
pub fn update_temps<O: OutputPin>(&mut self, state: &SystemState<O>) {
|
||||||
let _ = write!(self.lcd, "{}\u{00df}C", self.ext_deg);
|
|
||||||
|
|
||||||
self.lcd.set_cursor_pos(0x40);
|
self.ext_deg = state.ext_temp().into();
|
||||||
self.lcd.write_str(" ").unwrap();
|
self.p1_deg = state.p1_temp().into();
|
||||||
self.lcd.set_cursor_pos(0x40);
|
self.p2_deg = state.p2_temp().into();
|
||||||
let _ = write!(self.lcd, "{}\u{00df}C", self.p1_deg);
|
|
||||||
|
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 {
|
pub trait LCDScreen: core::fmt::Write {
|
||||||
|
|
||||||
fn reset(&mut self);
|
fn reset(&mut self);
|
||||||
|
|||||||
92
src/main.rs
92
src/main.rs
@ -12,7 +12,6 @@ use cortex_m::interrupt::Mutex;
|
|||||||
use cortex_m_rt::entry;
|
use cortex_m_rt::entry;
|
||||||
|
|
||||||
use embedded_hal::digital::{
|
use embedded_hal::digital::{
|
||||||
v2::OutputPin,
|
|
||||||
v1_compat::OldOutputPin,
|
v1_compat::OldOutputPin,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -24,7 +23,7 @@ use stm32f1xx_hal::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
timer::{Timer, CountDownTimer, Event},
|
timer::{Timer, CountDownTimer, Event},
|
||||||
qei::{QeiOptions, SlaveMode},
|
qei::{QeiOptions, SlaveMode},
|
||||||
rtc::Rtc,
|
rtc::{Rtc, RtcClkLsi},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod lcd_gui;
|
mod lcd_gui;
|
||||||
@ -45,9 +44,10 @@ use state::SystemState;
|
|||||||
/* system config */
|
/* system config */
|
||||||
|
|
||||||
const GUI_TICK_SEC: f32 = 0.2;
|
const GUI_TICK_SEC: f32 = 0.2;
|
||||||
|
const AWAKE_TIMEOUT_SEC: f32 = 20.0;
|
||||||
const HEARTBEAT_SEC: f32 = 1.0;
|
const HEARTBEAT_SEC: f32 = 1.0;
|
||||||
|
|
||||||
const TEMPS_TICK_SEC: u32 = 2;
|
const TEMPS_TICK_SEC: u32 = 30;
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
/* interrupt variables */
|
/* interrupt variables */
|
||||||
@ -64,10 +64,17 @@ static BUTTON_PIN: Mutex<RefCell<Option<gpiob::PB8<Input<PullUp>>>>>
|
|||||||
static BUTTON_FLAG: AtomicBool = AtomicBool::new(false);
|
static BUTTON_FLAG: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
// temps interrupt
|
// 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 G_EXTI: Mutex<RefCell<Option<EXTI>>> = Mutex::new(RefCell::new(None));
|
||||||
static TEMP_FLAG: AtomicBool = AtomicBool::new(false);
|
static TEMP_FLAG: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/* power management */
|
||||||
|
enum PowerState {
|
||||||
|
Awake,
|
||||||
|
Sleeping,
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
/* interrupt service routines */
|
/* interrupt service routines */
|
||||||
#[interrupt]
|
#[interrupt]
|
||||||
@ -126,16 +133,16 @@ fn main() -> ! {
|
|||||||
let mut dp = pac::Peripherals::take().unwrap();
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
|
|
||||||
// clocks config
|
// clocks config
|
||||||
let mut rcc = dp.RCC.constrain();
|
let rcc = dp.RCC.constrain();
|
||||||
let mut flash = dp.FLASH.constrain();
|
let mut flash = dp.FLASH.constrain();
|
||||||
let clocks = rcc.cfgr
|
let clocks = rcc.cfgr
|
||||||
.use_hse(8.mhz())
|
.use_hse(8.mhz())
|
||||||
.freeze(&mut flash.acr);
|
.freeze(&mut flash.acr);
|
||||||
|
|
||||||
// GPIOs
|
// GPIOs
|
||||||
let mut gpioa = dp.GPIOA.split(&mut rcc.apb2);
|
let mut gpioa = dp.GPIOA.split();
|
||||||
let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
|
let mut gpiob = dp.GPIOB.split();
|
||||||
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
|
let mut gpioc = dp.GPIOC.split();
|
||||||
|
|
||||||
// setup LED
|
// setup LED
|
||||||
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
|
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
|
// 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());
|
.start_count_down(((1.0/GUI_TICK_SEC) as u32).hz());
|
||||||
timer.listen(Event::Update);
|
timer.listen(Event::Update);
|
||||||
|
|
||||||
@ -159,8 +166,8 @@ fn main() -> ! {
|
|||||||
dp.EXTI.imr.write(|w| w.mr17().set_bit());
|
dp.EXTI.imr.write(|w| w.mr17().set_bit());
|
||||||
|
|
||||||
// setup RTC
|
// setup RTC
|
||||||
let mut backup_domain = rcc.bkp.constrain(dp.BKP, &mut rcc.apb1, &mut dp.PWR);
|
let mut backup_domain = rcc.bkp.constrain(dp.BKP, &mut dp.PWR);
|
||||||
let mut rtc = Rtc::rtc(dp.RTC, &mut backup_domain);
|
let mut rtc = Rtc::<RtcClkLsi>::rtc(dp.RTC, &mut backup_domain);
|
||||||
rtc.set_alarm(rtc.current_time() + TEMPS_TICK_SEC);
|
rtc.set_alarm(rtc.current_time() + TEMPS_TICK_SEC);
|
||||||
rtc.listen_alarm();
|
rtc.listen_alarm();
|
||||||
|
|
||||||
@ -168,7 +175,11 @@ fn main() -> ! {
|
|||||||
RTC.borrow(cs).borrow_mut().replace(rtc)
|
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
|
// Setup display
|
||||||
let mut gui = {
|
let mut gui = {
|
||||||
@ -193,7 +204,7 @@ fn main() -> ! {
|
|||||||
|
|
||||||
// Force write mode
|
// Force write mode
|
||||||
let mut rw_pin = gpioa.pa9.into_push_pull_output(&mut gpioa.crh);
|
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(
|
let lcd = HD44780::new_8bit(
|
||||||
OldOutputPin::new(rs_pin),
|
OldOutputPin::new(rs_pin),
|
||||||
@ -212,7 +223,7 @@ fn main() -> ! {
|
|||||||
// setup button (interrupt)
|
// setup button (interrupt)
|
||||||
let mut but_pin = gpiob.pb8.into_pull_up_input(&mut gpiob.crh);
|
let mut but_pin = gpiob.pb8.into_pull_up_input(&mut gpiob.crh);
|
||||||
but_pin.make_interrupt_source(&mut afio);
|
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);
|
but_pin.enable_interrupt(&dp.EXTI);
|
||||||
|
|
||||||
cortex_m::interrupt::free(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
@ -223,14 +234,14 @@ fn main() -> ! {
|
|||||||
let backlight_pin = gpiob.pb9.into_push_pull_output(&mut gpiob.crh);
|
let backlight_pin = gpiob.pb9.into_push_pull_output(&mut gpiob.crh);
|
||||||
|
|
||||||
// setup encoder
|
// 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,
|
.qei((gpiob.pb6, gpiob.pb7), &mut afio.mapr,
|
||||||
QeiOptions {
|
QeiOptions {
|
||||||
slave_mode:SlaveMode::EncoderMode2,
|
slave_mode:SlaveMode::EncoderMode1,
|
||||||
auto_reload_value: 200
|
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;
|
let exti = dp.EXTI;
|
||||||
@ -254,42 +265,65 @@ fn main() -> ! {
|
|||||||
// configure stop mode (not working yet, probably due to fake chip)
|
// 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.pdds().stop_mode());
|
||||||
//dp.PWR.cr.write(|w| w.lpds().set_bit());
|
//dp.PWR.cr.write(|w| w.lpds().set_bit());
|
||||||
cp.SCB.set_sleepdeep();
|
|
||||||
|
|
||||||
// setup adc
|
// 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);
|
adc.set_sample_time(SampleTime::T_71);
|
||||||
let adc = RefCell::new(adc);
|
let adc = RefCell::new(adc);
|
||||||
let ext1 = gpioa.pa4.into_analog(&mut gpioa.crl);
|
let ext1 = gpioa.pa4.into_analog(&mut gpioa.crl);
|
||||||
let ext2 = gpioa.pa5.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 p2_1 = gpioa.pa2.into_analog(&mut gpioa.crl);
|
||||||
let p1_2 = gpioa.pa3.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 ext_probe = TemperatureProbe::new(&adc, ext1, ext2).unwrap();
|
||||||
let mut p1_probe = TemperatureProbe::new(&adc, p1_1, p1_2).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 mut power_state = PowerState::Awake;
|
||||||
let config = SystemConfig::new();
|
let mut timeout = 0;
|
||||||
let mut state = SystemState::new(config, gpiob.pb12.into_push_pull_output(&mut gpiob.crh));
|
|
||||||
|
|
||||||
/* run */
|
/* run */
|
||||||
let _ = TEMP_FLAG.swap(true, Ordering::Release);
|
let _ = TEMP_FLAG.swap(true, Ordering::Release);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
||||||
//compute temps
|
|
||||||
if TEMP_FLAG.swap(false, Ordering::AcqRel) {
|
if TEMP_FLAG.swap(false, Ordering::AcqRel) {
|
||||||
|
|
||||||
|
// compute temps
|
||||||
let ext_temp = ext_probe.read().ok();
|
let ext_temp = ext_probe.read().ok();
|
||||||
let p1_temp = p1_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);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
match gui.update(&mut state) {
|
||||||
|
true => timeout = 0,
|
||||||
|
false => timeout += 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.update(&mut state);
|
// 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)
|
// put device in sleep mode until next interrupt (button or timer)
|
||||||
cortex_m::asm::wfi();
|
cortex_m::asm::wfi();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
31
src/state.rs
31
src/state.rs
@ -7,8 +7,8 @@ use crate::config::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub enum FanState {
|
pub enum FanState {
|
||||||
ON,
|
On,
|
||||||
OFF,
|
Off,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Fan<O: OutputPin> {
|
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...
|
// erroring here means the pin is wrongly configured, there is nothing else to do...
|
||||||
pin.set_low().map_err(|_| "fan pin configuration").unwrap();
|
pin.set_low().map_err(|_| "fan pin configuration").unwrap();
|
||||||
Fan {
|
Fan {
|
||||||
state: FanState::OFF,
|
state: FanState::Off,
|
||||||
pin,
|
pin,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on(&mut self) {
|
pub fn on(&mut self) {
|
||||||
self.pin.set_high().map_err(|_| "fan pin configuration").unwrap();
|
self.pin.set_high().map_err(|_| "fan pin configuration").unwrap();
|
||||||
self.state = FanState::ON;
|
self.state = FanState::On;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn off(&mut self) {
|
pub fn off(&mut self) {
|
||||||
self.pin.set_low().map_err(|_| "fan pin configuration").unwrap();
|
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 }
|
pub fn state(&self) -> &FanState { &self.state }
|
||||||
@ -70,9 +70,9 @@ impl<O: OutputPin> SystemState<O> {
|
|||||||
pub fn update_config(&mut self, config: SystemConfig) {
|
pub fn update_config(&mut self, config: SystemConfig) {
|
||||||
|
|
||||||
// remove offsets
|
// remove offsets
|
||||||
let ext_temp = self.ext_temp.map(|temp| temp - self.config.ext_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);
|
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);
|
let p2_temp = self.p2_temp.map(|temp| temp - self.config.p2_offset.offset);
|
||||||
|
|
||||||
// replace config
|
// replace config
|
||||||
self.config = 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>) {
|
pub fn update(&mut self, ext_temp: Option<f32>, p1_temp: Option<f32>, p2_temp: Option<f32>) {
|
||||||
|
|
||||||
// apply offsets
|
// apply offsets
|
||||||
self.ext_temp = ext_temp.map(|temp| temp + self.config.ext_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);
|
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);
|
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
|
// select right probe and check if data is available
|
||||||
let p = match match self.config.fan_output {
|
let p = match match self.config.fan_output {
|
||||||
@ -112,12 +117,12 @@ impl<O: OutputPin> SystemState<O> {
|
|||||||
|
|
||||||
// compute fan state
|
// compute fan state
|
||||||
match self.fan.state() {
|
match self.fan.state() {
|
||||||
FanState::ON => {
|
FanState::On => {
|
||||||
if (p - ext) < self.config.min_temp_diff {
|
if (p - ext) < self.config.min_temp_diff {
|
||||||
self.fan.off();
|
self.fan.off();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FanState::OFF => {
|
FanState::Off => {
|
||||||
if (p - ext) > self.config.max_temp_diff {
|
if (p - ext) > self.config.max_temp_diff {
|
||||||
self.fan.on();
|
self.fan.on();
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/utils.rs
19
src/utils.rs
@ -1,6 +1,7 @@
|
|||||||
use core::{
|
use core::{
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
ops::Range,
|
||||||
};
|
};
|
||||||
|
|
||||||
use embedded_hal::adc::*;
|
use embedded_hal::adc::*;
|
||||||
@ -9,16 +10,21 @@ use libm::*;
|
|||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
/* 2nd order linearisation factor for the temperature probe */
|
/* 2nd order linearisation factor for the temperature probe */
|
||||||
const A: f32 = -0.00058;
|
const A: f32 = -0.000574;
|
||||||
const B: f32 = 0.0677;
|
const B: f32 = 0.0676;
|
||||||
const C: f32 = -0.07;
|
const C: f32 = -0.07;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
/* valid temperature range */
|
||||||
|
pub const TEMP_RANGE: Range<f32> = -10.0..45.0;
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
/* error management */
|
/* error management */
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ProbeError {
|
pub enum ProbeError {
|
||||||
ReadError,
|
ReadError,
|
||||||
Initializing,
|
Initializing,
|
||||||
|
OutOfRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@ -76,9 +82,9 @@ where
|
|||||||
// compute first temp approximation to speed up stabilization
|
// compute first temp approximation to speed up stabilization
|
||||||
let mut temp = 0.0;
|
let mut temp = 0.0;
|
||||||
for _ in 0..10 {
|
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 {
|
Ok(TemperatureProbe {
|
||||||
adc,
|
adc,
|
||||||
@ -97,7 +103,10 @@ where
|
|||||||
match self.stabilized {
|
match self.stabilized {
|
||||||
true => {
|
true => {
|
||||||
self.filtered_temp += (temp - self.filtered_temp)/FILTER_TIME_CONSTANT;
|
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
|
false => { // filter is not yet stabilized
|
||||||
let old_temp = self.filtered_temp;
|
let old_temp = self.filtered_temp;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user