Resolve "Implement temperature computations"

This commit is contained in:
Steins7 2021-12-20 12:42:00 +00:00
parent 82a4b95480
commit c24133cbab
7 changed files with 415 additions and 38 deletions

View File

@ -13,6 +13,7 @@ cortex-m-rt = "0.6.10"
cortex-m-semihosting = "0.3.3"
panic-halt = "0.2.0"
hd44780-driver = "0.3.0"
libm = "0.2.1"
[dependencies.stm32f1xx-hal]
version = "0.7.0"

Binary file not shown.

44
src/config.rs Normal file
View File

@ -0,0 +1,44 @@
pub enum ConfigError {
LoadError,
}
pub enum FanOutput {
P1,
P2,
}
pub enum FanState {
ON,
OFF,
}
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 fan_output: FanOutput,
}
impl SystemConfig {
pub fn new() -> SystemConfig {
SystemConfig {
max_temp_diff: 8.0,
min_temp_diff: 4.0,
ext_offset: 0.0,
p1_offset: 0.0,
p2_offset: 0.0,
fan_output: FanOutput::P1,
}
}
pub fn load() -> Result<ConfigError, SystemConfig> {
unimplemented!();
}
pub fn store() -> SystemConfig {
unimplemented!();
}
}

View File

@ -1,4 +1,7 @@
use core::{sync::atomic::{AtomicBool, Ordering}};
use core::{
sync::atomic::{AtomicBool, Ordering},
fmt,
};
use embedded_hal::{
//digital::v1_compat::OldOutputPin,
@ -13,6 +16,33 @@ use hd44780_driver::{
bus::DataBus,
};
use crate::{
config::SystemConfig,
state::SystemState,
};
//---Temp Enum--------------------------------------------------------------------------------------
/// A simple enum to handle displaying temps on the GUI. Temperatures can be valid or not. An
/// invalid temperature is displayed as "inval°C" whereas a valid temperature is displayed as
/// XX.XX°C
pub enum Temp {
Valid (f32),
Invalid,
}
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::Invalid => formatter.write_str("inval"),
}
}
}
//---LCDGui Struct----------------------------------------------------------------------------------
/// 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)
pub struct LCDGui<L, Q, B>
where
L: LCDScreen,
@ -24,6 +54,8 @@ where
button: &'static AtomicBool,
backlight: Option<B>,
count: u16,
ext_deg: Temp,
p1_deg: Temp,
}
impl<L, Q, B> LCDGui<L, Q, B>
@ -38,7 +70,15 @@ where
use hd44780_driver::{Cursor, CursorBlink, Display};
let count = qei.count();
let mut gui = LCDGui {lcd, qei, button, backlight, count};
let mut gui = LCDGui {
lcd,
qei,
button,
backlight,
count,
ext_deg: Temp::Invalid,
p1_deg: Temp::Invalid,
};
if let Some(bl) = &mut gui.backlight { let _ = bl.set_high(); };
@ -56,8 +96,18 @@ where
gui
}
pub fn run(&mut self) -> ! {
loop {
pub fn update<O: OutputPin>(&mut self, state: &mut SystemState<O>) {
self.ext_deg = match state.ext_temp() {
Some(deg) => Temp::Valid(deg),
None => Temp::Invalid,
};
self.p1_deg = match state.p1_temp() {
Some(deg) => Temp::Valid(deg),
None => Temp::Invalid,
};
//TODO deduplicate button detection
if self.button.swap(false, Ordering::AcqRel) {
self.lcd.write_str("paf").unwrap();
@ -71,9 +121,15 @@ where
write!(self.lcd, "{}", self.qei.count()/2).unwrap();
}
// put device in sleep mode until next interrupt (button or timer)
cortex_m::asm::wfi();
}
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);
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);
}
}

View File

@ -1,10 +1,6 @@
#![no_std]
#![no_main]
mod lcd_gui;
use lcd_gui::LCDGui;
extern crate panic_halt;
use core::{
@ -22,6 +18,7 @@ use embedded_hal::digital::{
use stm32f1xx_hal::{
gpio::*,
adc::{Adc, SampleTime},
pac,
pac::{interrupt, Interrupt, EXTI},
prelude::*,
@ -30,13 +27,27 @@ use stm32f1xx_hal::{
rtc::Rtc,
};
mod lcd_gui;
use lcd_gui::LCDGui;
mod utils;
use utils::{
TemperatureProbe,
};
mod config;
use config::SystemConfig;
mod state;
use state::SystemState;
//--------------------------------------------------------------------------------------------------
/* system config */
const GUI_TICK_SEC: f32 = 0.2;
const HEARTBEAT_SEC: f32 = 1.0;
const TEMPS_TICK_SEC: u32 = 5;
const TEMPS_TICK_SEC: u32 = 2;
//--------------------------------------------------------------------------------------------------
/* interrupt variables */
@ -53,10 +64,9 @@ static BUTTON_PIN: Mutex<RefCell<Option<gpiob::PB8<Input<PullUp>>>>>
static BUTTON_FLAG: AtomicBool = AtomicBool::new(false);
// temps interrupt
static RED_LED: Mutex<RefCell<Option<gpiob::PB11<Output<PushPull>>>>>
= Mutex::new(RefCell::new(None));
static RTC: Mutex<RefCell<Option<Rtc>>> = Mutex::new(RefCell::new(None));
static G_EXTI: Mutex<RefCell<Option<EXTI>>> = Mutex::new(RefCell::new(None));
static TEMP_FLAG: AtomicBool = AtomicBool::new(false);
//--------------------------------------------------------------------------------------------------
/* interrupt service routines */
@ -101,9 +111,7 @@ fn RTCALARM() {
rtc.as_mut().unwrap().set_alarm(time + TEMPS_TICK_SEC); //also clears the flag
});
cortex_m::interrupt::free(|cs| {
let _ = RED_LED.borrow(cs).borrow_mut().as_mut().unwrap().toggle();
});
TEMP_FLAG.store(true, Ordering::Release);
}
//--------------------------------------------------------------------------------------------------
@ -146,14 +154,6 @@ fn main() -> ! {
TICK_TIMER.borrow(cs).borrow_mut().replace(timer)
});
// setup LED
let mut red_led = gpiob.pb11.into_push_pull_output(&mut gpiob.crh);
let _ = red_led.set_high();
cortex_m::interrupt::free(|cs| {
RED_LED.borrow(cs).borrow_mut().replace(red_led)
});
// set EXTI 17 (see note in 18.4.2, in short : needed for rtc ISR to trigger)
dp.EXTI.ftsr.write(|w| w.tr17().set_bit());
dp.EXTI.imr.write(|w| w.mr17().set_bit());
@ -256,7 +256,40 @@ fn main() -> ! {
//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);
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 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));
/* run */
gui.run();
let _ = TEMP_FLAG.swap(true, Ordering::Release);
loop {
//compute temps
if TEMP_FLAG.swap(false, Ordering::AcqRel) {
let ext_temp = ext_probe.read().ok();
let p1_temp = p1_probe.read().ok();
state.update(ext_temp, p1_temp, None);
}
gui.update(&mut state);
// put device in sleep mode until next interrupt (button or timer)
cortex_m::asm::wfi();
}
}

127
src/state.rs Normal file
View File

@ -0,0 +1,127 @@
use embedded_hal::digital::v2::OutputPin;
use crate::config::{
SystemConfig,
FanOutput,
};
pub enum FanState {
ON,
OFF,
}
pub struct Fan<O: OutputPin> {
state: FanState,
pin: O,
}
impl<O: OutputPin> Fan<O> {
pub fn new(mut pin: O) -> 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,
pin,
}
}
pub fn on(&mut self) {
self.pin.set_high().map_err(|_| "fan pin configuration").unwrap();
self.state = FanState::ON;
}
pub fn off(&mut self) {
self.pin.set_low().map_err(|_| "fan pin configuration").unwrap();
self.state = FanState::OFF;
}
pub fn state(&self) -> &FanState { &self.state }
}
pub struct SystemState<O: OutputPin> {
config: SystemConfig,
ext_temp: Option<f32>,
p1_temp: Option<f32>,
p2_temp: Option<f32>,
fan: Fan<O>,
}
impl<O: OutputPin> SystemState<O> {
pub fn new(config: SystemConfig, fan_control_pin: O) -> SystemState<O> {
SystemState {
config,
ext_temp: None,
p1_temp: None,
p2_temp: None,
fan: Fan::new(fan_control_pin),
}
}
pub fn config(&self) -> &SystemConfig { &self.config }
pub fn ext_temp(&self) -> Option<f32> { self.ext_temp }
pub fn p1_temp(&self) -> Option<f32> { self.p1_temp }
pub fn p2_temp(&self) -> Option<f32> { self.p2_temp }
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);
// replace config
self.config = config;
// reset fan state before recomputing fan state
self.fan.off();
// recompute offsets and fan state
self.update(ext_temp, p1_temp, p2_temp);
}
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);
// select right probe and check if data is available
let p = match match self.config.fan_output {
FanOutput::P1 => self.p1_temp,
FanOutput::P2 => self.p2_temp,
} {
Some(temp) => temp,
None => {
self.fan.off();
return;
},
};
let ext = match self.ext_temp {
Some(temp) => temp,
None => {
self.fan.off();
return;
},
};
// compute fan state
match self.fan.state() {
FanState::ON => {
if (p - ext) < self.config.min_temp_diff {
self.fan.off();
}
},
FanState::OFF => {
if (p - ext) > self.config.max_temp_diff {
self.fan.on();
}
},
}
}
}

116
src/utils.rs Normal file
View File

@ -0,0 +1,116 @@
use core::{
marker::PhantomData,
cell::RefCell,
};
use embedded_hal::adc::*;
use libm::*;
//--------------------------------------------------------------------------------------------------
/* 2nd order linearisation factor for the temperature probe */
const A: f32 = -0.00058;
const B: f32 = 0.0677;
const C: f32 = -0.07;
//--------------------------------------------------------------------------------------------------
/* error management */
#[derive(Debug)]
pub enum ProbeError {
ReadError,
Initializing,
}
//--------------------------------------------------------------------------------------------------
/* TemperatureProbe */
const FILTER_TIME_CONSTANT: f32 = 100.0;
pub struct TemperatureProbe<'a, ADC, O, PINP, PINN>
where
PINP: Channel<ADC>,
PINN: Channel<ADC>,
O: OneShot<ADC, u16, PINP> + OneShot<ADC, u16, PINN>,
{
adc: &'a RefCell<O>,
pos_pin: PINP,
neg_pin: PINN,
phantom: PhantomData<ADC>,
filtered_temp: f32,
stabilized: bool,
}
fn read_temp<'a, ADC, O, PINP, PINN>(adc: &'a RefCell<O>, pos: &mut PINP, neg: &mut PINN)
-> Result<f32, ProbeError>
where
PINP: Channel<ADC>,
PINN: Channel<ADC>,
O: OneShot<ADC, u16, PINP> + OneShot<ADC, u16, PINN>,
{
//first read is bugged, may be due to fake chip
let _ = adc.borrow_mut().read(pos);
let pos = adc.borrow_mut().read(pos)
.map_err(|_| ProbeError::ReadError)? as f32;
//first read is bugged, may be due to fake chip
let _ = adc.borrow_mut().read(neg);
let neg = adc.borrow_mut().read(neg)
.map_err(|_| ProbeError::ReadError)? as f32;
// compute back voltage, then reverse linearisation to compute temperature
let volt = (pos - neg) * 3.3 / 4095.0;
Ok((-B + sqrtf(B*B - 4.0*(C - volt)*A)) / (2.0*A))
}
impl<'a, ADC, O, PINP, PINN> TemperatureProbe<'a, ADC, O, PINP, PINN>
where
PINP: Channel<ADC>,
PINN: Channel<ADC>,
O: OneShot<ADC, u16, PINP> + OneShot<ADC, u16, PINN>,
{
pub fn new(adc: &'a RefCell<O>, pos: PINP, neg: PINN)
-> Result<TemperatureProbe<ADC, O, PINP, PINN>, ProbeError> {
let mut pos_pin = pos;
let mut neg_pin = neg;
// 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 = temp/10.0;
Ok(TemperatureProbe {
adc,
pos_pin,
neg_pin,
phantom: PhantomData,
filtered_temp: temp,
stabilized: false,
})
}
pub fn read(&mut self) -> Result<f32, ProbeError> {
let temp = read_temp(self.adc, &mut self.pos_pin, &mut self.neg_pin)?;
match self.stabilized {
true => {
self.filtered_temp += (temp - self.filtered_temp)/FILTER_TIME_CONSTANT;
Ok(self.filtered_temp)
},
false => { // filter is not yet stabilized
let old_temp = self.filtered_temp;
self.filtered_temp += (temp - self.filtered_temp)/FILTER_TIME_CONSTANT;
match fabsf(old_temp - self.filtered_temp) < 0.01 {
true => self.stabilized = true,
false => (),
}
Err(ProbeError::Initializing)
},
}
}
}