Support fan PWM settings #73
|
@ -26,9 +26,10 @@ const MAX_TEC_I: f64 = 3.0;
|
||||||
const MAX_FAN_PWM: f64 = 100.0;
|
const MAX_FAN_PWM: f64 = 100.0;
|
||||||
|
|||||||
const MIN_FAN_PWM: f64 = 1.0;
|
const MIN_FAN_PWM: f64 = 1.0;
|
||||||
const TACHO_MEASURE_MS: i64 = 2500;
|
const TACHO_MEASURE_MS: i64 = 2500;
|
||||||
|
const TACHO_LOW_THRESHOLD: u32 = 100;
|
||||||
const DEFAULT_K_A: f64 = 1.0;
|
const DEFAULT_K_A: f64 = 1.0;
|
||||||
const DEFAULT_K_B: f64 = 0.0;
|
const DEFAULT_K_B: f64 = 0.0;
|
||||||
const DEFAULT_K_C: f64 = 0.0;
|
const DEFAULT_K_C: f64 = 0.04;
|
||||||
sb10q
commented
Where are those default K_A/B/C from? Certainly not from the schematics so the comment is misleading. Where are those default K_A/B/C from? Certainly not from the schematics so the comment is misleading.
Have you tested those defaults and how?
esavkin
commented
Well, they are subject of change, but they come from the power formula P=I^2 * R Well, they are subject of change, but they come from the power formula P=I^2 * R
|
|||||||
|
|
||||||
#[derive(Serialize, Copy, Clone)]
|
#[derive(Serialize, Copy, Clone)]
|
||||||
pub struct HWRev {
|
pub struct HWRev {
|
||||||
|
@ -36,11 +37,20 @@ pub struct HWRev {
|
||||||
pub minor: u8,
|
pub minor: u8,
|
||||||
}
|
}
|
||||||
sb10q
commented
at the user's own risk at the user's own risk
|
|||||||
|
|
||||||
|
#[derive(Serialize, Clone, Copy, PartialEq)]
|
||||||
|
pub enum FanStatus {
|
||||||
|
OK,
|
||||||
|
NotAvailable,
|
||||||
|
Stalled,
|
||||||
|
LowSignal,
|
||||||
|
}
|
||||||
|
|
||||||
struct TachoCtrl {
|
struct TachoCtrl {
|
||||||
tacho: TachoPin,
|
tacho: TachoPin,
|
||||||
tacho_cnt: u32,
|
tacho_cnt: u32,
|
||||||
sb10q
commented
Did you double-check that this results in no PWM pulses being emitted at all, even short ones? Did you double-check that this results in no PWM pulses being emitted at all, even short ones?
esavkin
commented
Fixed this and checked that there is no pulses by default. On oscilloscope there is pulse on Fixed this and checked that there is no pulses by default. On oscilloscope there is pulse on `fan 100` command, but no such pulse on `reset` command
|
|||||||
tacho_value: Option<u32>,
|
tacho_value: Option<u32>,
|
||||||
prev_epoch: i64,
|
prev_epoch: i64,
|
||||||
|
past_record: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FanCtrl<'a> {
|
pub struct FanCtrl<'a> {
|
||||||
|
@ -52,6 +62,7 @@ pub struct FanCtrl<'a> {
|
||||||
k_b: f64,
|
k_b: f64,
|
||||||
k_c: f64,
|
k_c: f64,
|
||||||
channels: &'a mut Channels,
|
channels: &'a mut Channels,
|
||||||
|
last_status: FanStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FanCtrl<'a> {
|
impl<'a> FanCtrl<'a> {
|
||||||
|
@ -74,14 +85,22 @@ impl<'a> FanCtrl<'a> {
|
||||||
k_b: DEFAULT_K_B,
|
k_b: DEFAULT_K_B,
|
||||||
k_c: DEFAULT_K_C,
|
k_c: DEFAULT_K_C,
|
||||||
sb10q
commented
this doesn't need to be this doesn't need to be ``pub``
|
|||||||
channels,
|
channels,
|
||||||
|
last_status: FanStatus::OK,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cycle(&mut self) {
|
pub fn cycle(&mut self) -> Result<(), FanStatus>{
|
||||||
if self.available {
|
if self.available {
|
||||||
self.tacho.cycle();
|
self.tacho.cycle();
|
||||||
}
|
}
|
||||||
self.adjust_speed();
|
self.adjust_speed();
|
||||||
|
let diagnose = self.diagnose();
|
||||||
|
if diagnose != self.last_status {
|
||||||
|
self.last_status = diagnose;
|
||||||
sb10q
commented
Not sure if Not sure if ``[inline]`` here and in several other functions is relevant.
esavkin
commented
These functions are short, used just in a very limited number of places. They all look to be a good candidate to be inlined These functions are short, used just in a very limited number of places. They all look to be a good candidate to be inlined
sb10q
commented
Probably the compiler can figure this out already, and it doesn't seem to be performance-critical code so it doesn't matter if it doesn't. Probably the compiler can figure this out already, and it doesn't seem to be performance-critical code so it doesn't matter if it doesn't.
|
|||||||
|
Err(diagnose)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn summary(&mut self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
|
pub fn summary(&mut self) -> Result<JsonBuffer, serde_json_core::ser::Error> {
|
||||||
|
@ -91,6 +110,7 @@ impl<'a> FanCtrl<'a> {
|
||||||
tacho: self.tacho.get(),
|
tacho: self.tacho.get(),
|
||||||
abs_max_tec_i: self.channels.current_abs_max_tec_i(),
|
abs_max_tec_i: self.channels.current_abs_max_tec_i(),
|
||||||
auto_mode: self.fan_auto,
|
auto_mode: self.fan_auto,
|
||||||
|
status: self.diagnose(),
|
||||||
k_a: self.k_a,
|
k_a: self.k_a,
|
||||||
k_b: self.k_b,
|
k_b: self.k_b,
|
||||||
k_c: self.k_c,
|
k_c: self.k_c,
|
||||||
|
@ -139,6 +159,13 @@ impl<'a> FanCtrl<'a> {
|
||||||
value as f64 / (max as f64)
|
value as f64 / (max as f64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn diagnose(&mut self) -> FanStatus {
|
||||||
|
if !self.available {
|
||||||
|
return FanStatus::NotAvailable;
|
||||||
sb10q
commented
Doesn't seem relevant Doesn't seem relevant
|
|||||||
|
}
|
||||||
|
self.tacho.diagnose()
|
||||||
|
}
|
||||||
|
|
||||||
fn get_pwm(&self) -> u32 {
|
fn get_pwm(&self) -> u32 {
|
||||||
let duty = self.fan.get_duty();
|
let duty = self.fan.get_duty();
|
||||||
let max = self.fan.get_max_duty();
|
let max = self.fan.get_max_duty();
|
||||||
|
@ -147,16 +174,17 @@ impl<'a> FanCtrl<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TachoCtrl {
|
impl TachoCtrl {
|
||||||
pub fn new(tacho: TachoPin) -> Self {
|
fn new(tacho: TachoPin) -> Self {
|
||||||
TachoCtrl {
|
TachoCtrl {
|
||||||
tacho,
|
tacho,
|
||||||
tacho_cnt: 0,
|
tacho_cnt: 0,
|
||||||
tacho_value: None,
|
tacho_value: None,
|
||||||
prev_epoch: 0,
|
prev_epoch: 0,
|
||||||
|
past_record: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self, exti: &mut EXTI, syscfg: &mut SysCfg) {
|
fn init(&mut self, exti: &mut EXTI, syscfg: &mut SysCfg) {
|
||||||
// These lines do not cause NVIC to run the ISR,
|
// These lines do not cause NVIC to run the ISR,
|
||||||
// since the interrupt should be unmasked in the cortex_m::peripheral::NVIC.
|
// since the interrupt should be unmasked in the cortex_m::peripheral::NVIC.
|
||||||
sb10q
commented
"since the interrupt is still masked in cortex_m::peripheral::NVIC" "since the interrupt is still masked in cortex_m::peripheral::NVIC"
|
|||||||
// Also using interrupt-related workaround is the best
|
// Also using interrupt-related workaround is the best
|
||||||
|
@ -169,7 +197,17 @@ impl TachoCtrl {
|
||||||
self.tacho.enable_interrupt(exti);
|
self.tacho.enable_interrupt(exti);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cycle(&mut self) {
|
#[inline]
|
||||||
|
fn add_record(&mut self, value: u32) {
|
||||||
|
self.past_record = self.past_record << 2;
|
||||||
esavkin marked this conversation as resolved
Outdated
sb10q
commented
Just use Just use ``[bool; 32]`` or ``Vec<bool>``
sb10q
commented
Or some custom enum instead of bool. Or some custom enum instead of bool.
|
|||||||
|
if value >= TACHO_LOW_THRESHOLD {
|
||||||
sb10q
commented
That's naive. The expected range for the tacho value obviously depends on the fan PWM setting. That's naive. The expected range for the tacho value obviously depends on the fan PWM setting.
|
|||||||
|
self.past_record += 0b11;
|
||||||
esavkin marked this conversation as resolved
Outdated
sb10q
commented
|= |=
|
|||||||
|
} else if value > 0 && self.tacho_cnt < TACHO_LOW_THRESHOLD {
|
||||||
|
self.past_record += 0b10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cycle(&mut self) {
|
||||||
let tacho_input = self.tacho.check_interrupt();
|
let tacho_input = self.tacho.check_interrupt();
|
||||||
if tacho_input {
|
if tacho_input {
|
||||||
self.tacho.clear_interrupt_pending_bit();
|
self.tacho.clear_interrupt_pending_bit();
|
||||||
|
@ -179,14 +217,25 @@ impl TachoCtrl {
|
||||||
let instant = Instant::from_millis(i64::from(timer::now()));
|
let instant = Instant::from_millis(i64::from(timer::now()));
|
||||||
if instant.millis - self.prev_epoch >= TACHO_MEASURE_MS {
|
if instant.millis - self.prev_epoch >= TACHO_MEASURE_MS {
|
||||||
self.tacho_value = Some(self.tacho_cnt);
|
self.tacho_value = Some(self.tacho_cnt);
|
||||||
|
self.add_record(self.tacho_cnt);
|
||||||
self.tacho_cnt = 0;
|
self.tacho_cnt = 0;
|
||||||
self.prev_epoch = instant.millis;
|
self.prev_epoch = instant.millis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self) -> u32 {
|
fn get(&self) -> u32 {
|
||||||
self.tacho_value.unwrap_or(u32::MAX)
|
self.tacho_value.unwrap_or(u32::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn diagnose(&mut self) -> FanStatus {
|
||||||
|
if self.past_record & 0b11 == 0b11 {
|
||||||
|
FanStatus::OK
|
||||||
|
} else if self.past_record & 0xAAAAAAAAAAAAAAAA > 0 {
|
||||||
esavkin marked this conversation as resolved
Outdated
sb10q
commented
??????? ???????
|
|||||||
|
FanStatus::LowSignal
|
||||||
|
} else {
|
||||||
|
FanStatus::Stalled
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HWRev {
|
impl HWRev {
|
||||||
|
@ -212,7 +261,19 @@ pub struct FanSummary {
|
||||||
tacho: u32,
|
tacho: u32,
|
||||||
abs_max_tec_i: f64,
|
abs_max_tec_i: f64,
|
||||||
auto_mode: bool,
|
auto_mode: bool,
|
||||||
|
status: FanStatus,
|
||||||
k_a: f64,
|
k_a: f64,
|
||||||
k_b: f64,
|
k_b: f64,
|
||||||
k_c: f64,
|
k_c: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FanStatus {
|
||||||
|
pub fn fmt_u8(&self) -> &'static [u8] {
|
||||||
|
match *self {
|
||||||
|
FanStatus::OK => "Fan is OK".as_bytes(),
|
||||||
|
FanStatus::NotAvailable => "Fan is not available".as_bytes(),
|
||||||
|
FanStatus::Stalled => "Fan is stalled".as_bytes(),
|
||||||
|
FanStatus::LowSignal => "Fan is low signal".as_bytes(),
|
||||||
esavkin marked this conversation as resolved
Outdated
sb10q
commented
What does "fan is low signal" mean? What does "fan is low signal" mean?
esavkin
commented
That there are some signals being registered, but in general it so low so that it can be zero on some small timeline. It tipically happens on very low fan speed (from 1 to 3 included), and that behavior is shown on oscilloscope. That there are some signals being registered, but in general it so low so that it can be zero on some small timeline. It tipically happens on very low fan speed (from 1 to 3 included), and that behavior is shown on oscilloscope.
sb10q
commented
Maybe just put a minimum fan speed to remove this edge case. I doubt running the fan at a couple rotations per second (which is what I guess it spins at when this happens) has much effect on cooling anyway. Maybe just put a minimum fan speed to remove this edge case. I doubt running the fan at a couple rotations per second (which is what I guess it spins at when this happens) has much effect on cooling anyway.
sb10q
commented
And "low signal" is very confusing and not the proper term to use, it sounds like you are receiving some kind of analog transmission being drowned in noise. And "low signal" is very confusing and not the proper term to use, it sounds like you are receiving some kind of analog transmission being drowned in noise.
Moot but you should have said something about the tacho frequency being low.
|
|||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/main.rs
|
@ -187,10 +187,10 @@ fn main() -> ! {
|
||||||
if let Some(channel) = updated_channel {
|
if let Some(channel) = updated_channel {
|
||||||
server.for_each(|_, session| session.set_report_pending(channel.into()));
|
server.for_each(|_, session| session.set_report_pending(channel.into()));
|
||||||
}
|
}
|
||||||
fan_ctrl.cycle();
|
|
||||||
|
let fan_status = fan_ctrl.cycle();
|
||||||
|
|
||||||
let instant = Instant::from_millis(i64::from(timer::now()));
|
let instant = Instant::from_millis(i64::from(timer::now()));
|
||||||
|
|
||||||
cortex_m::interrupt::free(net::clear_pending);
|
cortex_m::interrupt::free(net::clear_pending);
|
||||||
server.poll(instant)
|
server.poll(instant)
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
|
@ -241,6 +241,12 @@ fn main() -> ! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
match fan_status {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(status) => {
|
||||||
|
send_line(&mut socket, status.fmt_u8());
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
Those naive values will need proper testing and determination at some point.