Compare commits

...

3 Commits

Author SHA1 Message Date
linuswck 6ee45b4814 thermostat: Limit Pid Temp SetPt to TempMon limits 2024-02-21 17:37:56 +08:00
linuswck a8787430b1 Add Temperature Monitor to Thermostat
- Issue an alarm when temperature goes out of user-defined operating range during Pid Controller startup
    or reading is outside of +-0.5 Degree from temperature set point after Pid Controller becomes stable
- If alarm is observed, power down laser and tec controller and disengage Pid Controller
- Add the corresponding cmd for configuring the temperature monitor
2024-02-21 17:32:11 +08:00
linuswck ed785b7c85 ld: correct timer and dac reset in pwr_up seq 2024-02-21 11:11:12 +08:00
6 changed files with 225 additions and 4 deletions

View File

@ -116,8 +116,8 @@ impl LdDrive{
} }
pub fn power_up(&mut self){ pub fn power_up(&mut self){
let _ = self.ctrl.set_i(ElectricCurrent::new::<milliampere>(0.0), Settings::LD_DRIVE_TRANSIMPEDANCE, Settings::DAC_OUT_V_MAX);
LdCurrentOutCtrlTimer::reset(); LdCurrentOutCtrlTimer::reset();
let _ = self.ctrl.set_i(ElectricCurrent::new::<milliampere>(0.0), Settings::LD_DRIVE_TRANSIMPEDANCE, Settings::DAC_OUT_V_MAX);
LdPwrExcProtector::pwr_on_and_arm_protection(); LdPwrExcProtector::pwr_on_and_arm_protection();
// Wait for LD Power Supply to start up before driving current to laser diode // Wait for LD Power Supply to start up before driving current to laser diode
sleep(30); sleep(30);

View File

@ -97,6 +97,12 @@ fn main() -> ! {
laser.poll_and_update_output_current(); laser.poll_and_update_output_current();
if thermostat.poll_adc_and_update_pid() { if thermostat.poll_adc_and_update_pid() {
if thermostat.get_temp_mon_status().over_temp_alarm {
laser.power_down();
thermostat.set_pid_engaged(false);
thermostat.power_down();
}
info!("curr_dac_vfb: {:?}", volt_fmt.with(thermostat.get_dac_vfb())); info!("curr_dac_vfb: {:?}", volt_fmt.with(thermostat.get_dac_vfb()));
info!("curr_vref: {:?}", volt_fmt.with(thermostat.get_vref())); info!("curr_vref: {:?}", volt_fmt.with(thermostat.get_vref()));
info!("curr_tec_i: {:?}", amp_fmt.with(thermostat.get_tec_i())); info!("curr_tec_i: {:?}", amp_fmt.with(thermostat.get_tec_i()));

View File

@ -71,6 +71,10 @@ enum ThermostatCmdEnum {
SetPidOutMin, SetPidOutMin,
SetPidOutMax, SetPidOutMax,
SetPidUpdateInterval, // Update Interval is set based on the sampling rate of ADC SetPidUpdateInterval, // Update Interval is set based on the sampling rate of ADC
// TempMon
SetTempMonUpperLimit,
SetTempMonLowerLimit,
ClearAlarm,
// Steinhart-Hart Equation // Steinhart-Hart Equation
SetShT0, SetShT0,
SetShR0, SetShR0,
@ -342,6 +346,29 @@ pub fn execute_cmd(buffer: &mut [u8], buffer_size: usize, mut laser: LdDrive, mu
Some(ThermostatCmdEnum::SetPidUpdateInterval) => { Some(ThermostatCmdEnum::SetPidUpdateInterval) => {
info!("Not supported Yet") info!("Not supported Yet")
} }
Some(ThermostatCmdEnum::SetTempMonUpperLimit) => {
match cmd.json.data_f64 {
Some(val) => {
tec.set_temp_mon_upper_limit(ThermodynamicTemperature::new::<degree_celsius>(val));
}
None => {
info!("Wrong Data type is received")
}
}
}
Some(ThermostatCmdEnum::SetTempMonLowerLimit) => {
match cmd.json.data_f64 {
Some(val) => {
tec.set_temp_mon_lower_limit(ThermodynamicTemperature::new::<degree_celsius>(val));
}
None => {
info!("Wrong Data type is received")
}
}
}
Some(ThermostatCmdEnum::ClearAlarm) => {
tec.clear_temp_mon_alarm();
}
Some(ThermostatCmdEnum::SetShT0) => { Some(ThermostatCmdEnum::SetShT0) => {
match cmd.json.data_f64 { match cmd.json.data_f64 {
Some(val) => { Some(val) => {

View File

@ -4,3 +4,4 @@ pub mod thermostat;
pub mod ad7172; pub mod ad7172;
pub mod steinhart_hart; pub mod steinhart_hart;
pub mod pid_state; pub mod pid_state;
pub mod temp_mon;

155
src/thermostat/temp_mon.rs Normal file
View File

@ -0,0 +1,155 @@
use serde::{Deserialize, Serialize};
use miniconf::Tree;
use uom::si::{
thermodynamic_temperature::degree_celsius,
f64::ThermodynamicTemperature
};
use num_traits::Float;
#[derive(PartialEq, Deserialize, Serialize, Copy, Clone, Default, Debug)]
pub enum TempStatusEnum {
#[default]
Off,
OverTemp,
Unstable,
Stable,
}
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
pub struct TempStatus {
pub status: TempStatusEnum,
pub over_temp_alarm: bool,
}
pub struct TempMonSettings {
pub upper_limit: ThermodynamicTemperature,
pub lower_limit: ThermodynamicTemperature,
}
pub struct TempMon {
pub upper_limit: ThermodynamicTemperature,
pub lower_limit: ThermodynamicTemperature,
pub set_point: ThermodynamicTemperature,
pub status: TempStatus,
state: State,
count: u32,
is_set_point_changed: bool,
}
impl Default for TempMon {
fn default() -> Self {
Self {
upper_limit: ThermodynamicTemperature::new::<degree_celsius>(45.0),
lower_limit: ThermodynamicTemperature::new::<degree_celsius>(0.0),
set_point: ThermodynamicTemperature::new::<degree_celsius>(0.0),
status: TempStatus {
status: TempStatusEnum::Off,
over_temp_alarm: false
},
state: State::default(),
count: 0,
// stable_temp_count: 0,
is_set_point_changed: false,
}
}
}
#[derive(Default, PartialEq, Debug)]
enum State {
#[default]
PidOff,
PidStartUp,
PidStable,
OverTempAlarm,
}
impl TempMon {
const OVER_TEMP_COUNT_LIMIT: u32 = 25;
const TEMP_STABLE_COUNT_LIMIT: u32 = 100;
pub fn set_upper_limit(&mut self, upper_limit: ThermodynamicTemperature) {
self.upper_limit = upper_limit;
}
pub fn set_lower_limit(&mut self, lower_limit: ThermodynamicTemperature) {
self.lower_limit = lower_limit;
}
pub fn set_setpoint(&mut self, set_point: ThermodynamicTemperature) {
if self.set_point != set_point {
self.is_set_point_changed = true;
self.count = 0;
}
self.set_point = set_point;
}
pub fn clear_alarm(&mut self) {
self.status.over_temp_alarm = false;
self.state = State::default();
}
pub fn update_status(&mut self, pid_engaged: bool, temp: ThermodynamicTemperature) {
match self.state {
State::PidOff => {
self.is_set_point_changed = false;
self.status.status = TempStatusEnum::Off;
self.count = 0;
// State Transition
if pid_engaged {
self.state = State::PidStartUp;
}
}
State::PidStartUp | State::PidStable => {
let is_over_temp: bool;
if self.state == State::PidStartUp {
is_over_temp = temp > self.upper_limit || temp < self.lower_limit;
} else {
is_over_temp = (temp.value - self.set_point.value).abs() > 0.5;
}
let is_within_spec: bool = (temp.value - self.set_point.value).abs() < 0.001;
if is_over_temp {
if self.count > TempMon::OVER_TEMP_COUNT_LIMIT {
self.status.status = TempStatusEnum::OverTemp;
} else {
self.count += 1;
}
} else if is_within_spec {
if self.count > TempMon::TEMP_STABLE_COUNT_LIMIT {
self.status.status = TempStatusEnum::Stable;
} else {
self.count += 1;
}
} else {
self.status.status = TempStatusEnum::Unstable;
self.count = 0;
}
// State Transition
if self.status.status == TempStatusEnum::OverTemp {
self.state = State::OverTempAlarm;
} else if self.is_set_point_changed {
self.is_set_point_changed = false;
self.state = State::PidStartUp;
} else if self.status.status == TempStatusEnum::Stable {
self.state = State::PidStable;
}
}
State::OverTempAlarm => {
self.is_set_point_changed = false;
self.status.over_temp_alarm = true;
}
}
}
pub fn get_status(&mut self) -> TempStatus {
self.status
}
pub fn get_settings(&mut self) -> TempMonSettings {
TempMonSettings {
upper_limit: self.upper_limit,
lower_limit: self.lower_limit,
}
}
}

View File

@ -6,6 +6,7 @@ use crate::thermostat::max1968::{MAX1968, AdcReadTarget, PwmPinsEnum};
use crate::thermostat::ad7172; use crate::thermostat::ad7172;
use crate::thermostat::pid_state::{PidState, PidSettings}; use crate::thermostat::pid_state::{PidState, PidSettings};
use crate::thermostat::steinhart_hart; use crate::thermostat::steinhart_hart;
use crate::thermostat::temp_mon::{TempMon, TempStatus, TempMonSettings};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use log::debug; use log::debug;
use uom::si::{ use uom::si::{
@ -103,6 +104,7 @@ pub struct Thermostat {
ad7172: ad7172::AdcPhy, ad7172: ad7172::AdcPhy,
pub tec_setting: TecSettings, pub tec_setting: TecSettings,
pid_ctrl_ch0: PidState, pid_ctrl_ch0: PidState,
temp_mon: TempMon,
} }
impl Thermostat{ impl Thermostat{
@ -112,6 +114,7 @@ impl Thermostat{
ad7172: ad7172, ad7172: ad7172,
tec_setting: TecSettings::default(), tec_setting: TecSettings::default(),
pid_ctrl_ch0: PidState::default(), pid_ctrl_ch0: PidState::default(),
temp_mon: TempMon::default(),
} }
} }
pub fn setup(&mut self){ pub fn setup(&mut self){
@ -148,7 +151,8 @@ impl Thermostat{
let state: &mut PidState = &mut self.pid_ctrl_ch0; let state: &mut PidState = &mut self.pid_ctrl_ch0;
state.update(data); state.update(data);
debug!("state.get_pid_engaged(): {:?}", state.get_pid_engaged()); debug!("state.get_pid_engaged(): {:?}", state.get_pid_engaged());
if state.get_pid_engaged() { let pid_engaged = state.get_pid_engaged();
if pid_engaged {
match state.update_pid() { match state.update_pid() {
Some(pid_output) => { Some(pid_output) => {
self.set_i(ElectricCurrent::new::<ampere>(pid_output)); self.set_i(ElectricCurrent::new::<ampere>(pid_output));
@ -157,12 +161,18 @@ impl Thermostat{
None => { } None => { }
} }
} }
debug!("Temperature: {:?} degree", self.get_temperature().get::<degree_celsius>()); let temp = self.get_temperature();
self.temp_mon.update_status(pid_engaged, temp);
debug!("Temperature: {:?} degree", temp.get::<degree_celsius>());
data_rdy = true; data_rdy = true;
}); });
data_rdy data_rdy
} }
pub fn get_temp_mon_status(&mut self) -> TempStatus {
self.temp_mon.get_status()
}
pub fn power_up(&mut self){ pub fn power_up(&mut self){
self.max1968.power_up(); self.max1968.power_up();
} }
@ -278,6 +288,7 @@ impl Thermostat{
StatusReport { StatusReport {
ts: sys_timer::now(), ts: sys_timer::now(),
pid_engaged: self.get_pid_engaged(), pid_engaged: self.get_pid_engaged(),
temp_mon_status: self.temp_mon.get_status(),
temperature: self.pid_ctrl_ch0.get_temperature(), temperature: self.pid_ctrl_ch0.get_temperature(),
i_set: self.tec_setting.i_set, i_set: self.tec_setting.i_set,
tec_i: self.get_tec_i(), tec_i: self.get_tec_i(),
@ -334,14 +345,35 @@ impl Thermostat{
} }
pub fn set_temperature_setpoint(&mut self, t: ThermodynamicTemperature) { pub fn set_temperature_setpoint(&mut self, t: ThermodynamicTemperature) {
let temp_mon_settings = self.temp_mon.get_settings();
let t = t.min(temp_mon_settings.upper_limit).max(temp_mon_settings.lower_limit);
self.pid_ctrl_ch0.set_pid_setpoint(t); self.pid_ctrl_ch0.set_pid_setpoint(t);
self.temp_mon.set_setpoint(t);
} }
pub fn set_temp_mon_upper_limit(&mut self, t: ThermodynamicTemperature) {
self.temp_mon.set_upper_limit(t);
}
pub fn set_temp_mon_lower_limit(&mut self, t: ThermodynamicTemperature) {
self.temp_mon.set_lower_limit(t);
}
pub fn clear_temp_mon_alarm(&mut self) {
self.temp_mon.clear_alarm();
}
pub fn get_temp_mon_settings(&mut self) -> TempMonSettings {
self.temp_mon.get_settings()
}
} }
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)] #[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
pub struct StatusReport { pub struct StatusReport {
ts: u32, ts: u32,
pid_engaged: bool, pid_engaged: bool,
temp_mon_status: TempStatus,
temperature: Option<ThermodynamicTemperature>, temperature: Option<ThermodynamicTemperature>,
i_set: ElectricCurrent, i_set: ElectricCurrent,
tec_i: ElectricCurrent, tec_i: ElectricCurrent,