Support fan PWM settings #73
12
README.md
12
README.md
|
@ -125,7 +125,7 @@ formatted as line-delimited JSON.
|
|||
| `dfu` | Reset device and enters USB device firmware update (DFU) mode |
|
||||
| `ipv4 <X.X.X.X/L> [Y.Y.Y.Y]` | Configure IPv4 address, netmask length, and optional default gateway |
|
||||
| `fan` | Show current fan settings and sensors' measurements |
|
||||
| `fan <value>` | Set fan power with values from 0 to 100, where 0 is auto mode (TODO) |
|
||||
| `fan <value>` | Set fan power with values from 0 to 100, where 0 is auto mode |
|
||||
|
||||
|
||||
## USB
|
||||
|
@ -271,3 +271,13 @@ with the following keys.
|
|||
## PID Tuning
|
||||
|
||||
The thermostat implements a PID control loop for each of the TEC channels, more details on setting up the PID control loop can be found [here](./doc/PID%20tuning.md).
|
||||
|
||||
## Fan control
|
||||
|
||||
Fan control is available for the thermostat revisions with integrated fan system. For this purpose two commands are available:
|
||||
1. `fan` - show fan stats: `fan_pwm`, `tacho`, `abs_max_tec_i`, `auto_mode`. Please note that `tacho` shows *approximate* value, which
|
||||
sb10q marked this conversation as resolved
|
||||
linearly correlates with the actual fan speed.
|
||||
2. `fan <value>` - set the fan power with the value from `0` to `100`. Since there is no hardware way to disable the fan,
|
||||
`0` value is used for enabling automatic fan control mode, which correlates with the square of the TEC's current.
|
||||
Values from `1` to `100` are used for setting the power from minimum to maximum respectively.
|
||||
Please note that power doesn't correlate with the actual speed linearly.
|
||||
|
|
|
@ -26,6 +26,9 @@ pub const CHANNELS: usize = 2;
|
|||
pub const R_SENSE: f64 = 0.05;
|
||||
// DAC chip outputs 0-5v, which is then passed through a resistor dividor to provide 0-3v range
|
||||
const DAC_OUT_V_MAX: f64 = 3.0;
|
||||
const MAX_TEC_I: f64 = 3.0; // as stated in the schemes
|
||||
const MAX_FAN_PWM: f64 = 100.0;
|
||||
const MIN_FAN_PWM: f64 = 1.0;
|
||||
|
||||
#[derive(Serialize, Copy, Clone)]
|
||||
pub struct HWRev {
|
||||
|
@ -41,7 +44,8 @@ pub struct Channels {
|
|||
/// stm32f4 integrated adc
|
||||
pins_adc: pins::PinsAdc,
|
||||
pub pwm: pins::PwmPins,
|
||||
pub hw_rev: HWRev
|
||||
hw_rev: HWRev,
|
||||
fan_auto: bool
|
||||
}
|
||||
|
||||
impl Channels {
|
||||
|
@ -63,7 +67,8 @@ impl Channels {
|
|||
let channel1 = Channel::new(pins.channel1, adc_calibration1);
|
||||
let pins_adc = pins.pins_adc;
|
||||
let pwm = pins.pwm;
|
||||
let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm, hw_rev: Self::detect_hw_rev(&pins.hwrev) };
|
||||
let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm,
|
||||
hw_rev: Self::detect_hw_rev(&pins.hwrev), fan_auto: true };
|
||||
for channel in 0..CHANNELS {
|
||||
channels.channel_state(channel).vref = channels.read_vref(channel);
|
||||
channels.calibrate_dac_value(channel);
|
||||
|
@ -407,7 +412,7 @@ impl Channels {
|
|||
(self.read_tec_u_meas(channel) - ElectricPotential::new::<volt>(1.5)) * 4.0
|
||||
}
|
||||
|
||||
pub fn set_pwm(&mut self, channel: usize, pin: PwmPin, duty: f64) -> f64 {
|
||||
fn set_pwm(&mut self, channel: usize, pin: PwmPin, duty: f64) -> f64 {
|
||||
fn set<P: hal::PwmPin<Duty=u16>>(pin: &mut P, duty: f64) -> f64 {
|
||||
let max = pin.get_max_duty();
|
||||
let value = ((duty * (max as f64)) as u16).min(max);
|
||||
|
@ -458,12 +463,16 @@ impl Channels {
|
|||
}
|
||||
|
||||
pub fn set_fan_pwm(&mut self, fan_pwm: u32) -> f64 {
|
||||
let duty = fan_pwm as f64 / 100.0;
|
||||
let duty = fan_pwm as f64 / MAX_FAN_PWM;
|
||||
self.set_pwm(0, PwmPin::Fan, duty)
|
||||
}
|
||||
|
||||
pub fn get_fan_pwm(&mut self) -> u32 {
|
||||
(self.get_pwm(0, PwmPin::Fan) * 100.0) as u32
|
||||
(self.get_pwm(0, PwmPin::Fan) * MAX_FAN_PWM) as u32
|
||||
}
|
||||
|
||||
pub fn set_fan_auto_mode(&mut self, fan_auto: bool) {
|
||||
self.fan_auto = fan_auto;
|
||||
}
|
||||
|
||||
fn report(&mut self, channel: usize) -> Report {
|
||||
|
@ -557,12 +566,19 @@ impl Channels {
|
|||
serde_json_core::to_vec(&summaries)
|
||||
}
|
||||
|
||||
fn current_abs_max_tec_i(&mut self) -> f64 {
|
||||
max_by(self.get_tec_i(0).abs().get::<ampere>(),
|
||||
self.get_tec_i(1).abs().get::<ampere>(),
|
||||
|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal))
|
||||
}
|
||||
|
||||
pub fn fan_summary(&mut self, tacho: Option<u32>) -> Result<JsonBuffer, serde_json_core::ser::Error> {
|
||||
if self.fan_available() {
|
||||
let summary = FanSummary {
|
||||
fan_pwm: self.get_fan_pwm(),
|
||||
tacho: tacho.unwrap_or(u32::MAX),
|
||||
abs_max_tec_i: max_by(self.get_tec_i(0).abs().value, self.get_tec_i(1).abs().value, |a, b| a.partial_cmp(b).unwrap())
|
||||
abs_max_tec_i: self.current_abs_max_tec_i(),
|
||||
auto_mode: self.fan_auto
|
||||
};
|
||||
serde_json_core::to_vec(&summary)
|
||||
} else {
|
||||
|
@ -570,6 +586,16 @@ impl Channels {
|
|||
serde_json_core::to_vec(&summary)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fan_ctrl(&mut self) {
|
||||
if self.fan_auto && self.fan_available() {
|
||||
let scaled_current = self.current_abs_max_tec_i() / MAX_TEC_I;
|
||||
let pwm = max_by(scaled_current * scaled_current * MAX_FAN_PWM,
|
||||
MIN_FAN_PWM,
|
||||
|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)) as u32;
|
||||
self.set_fan_pwm(pwm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type JsonBuffer = Vec<u8, U1024>;
|
||||
|
@ -650,4 +676,5 @@ pub struct FanSummary {
|
|||
fan_pwm: u32,
|
||||
tacho: u32,
|
||||
abs_max_tec_i: f64,
|
||||
auto_mode: bool,
|
||||
}
|
||||
|
|
|
@ -347,13 +347,14 @@ impl Handler {
|
|||
fn fan (socket: &mut TcpSocket, channels: &mut Channels, fan_pwm: Option<u32>, tacho_value: Option<u32>) -> Result<Handler, Error> {
|
||||
match fan_pwm {
|
||||
Some(val) => {
|
||||
channels.set_fan_auto_mode(val == 0);
|
||||
channels.set_fan_pwm(val);
|
||||
Ok(Handler::Handled)
|
||||
},
|
||||
None => {
|
||||
match channels.fan_summary(tacho_value) {
|
||||
Ok(buf) => {
|
||||
send_line(socket, &buf);
|
||||
return Ok(Handler::Handled);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("unable to serialize fan summary: {:?}", e);
|
||||
|
@ -361,10 +362,11 @@ impl Handler {
|
|||
return Err(Error::ReportError);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
send_line(socket, b"{}");
|
||||
Ok(Handler::Handled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_command (command: Command, socket: &mut TcpSocket, channels: &mut Channels, session: &Session, leds: &mut Leds, store: &mut FlashStore, ipv4_config: &mut Ipv4Config, tacho_value: Option<u32>) -> Result<Self, Error> {
|
||||
match command {
|
||||
|
|
|
@ -303,8 +303,7 @@ fn pwm_setup(input: &[u8]) -> IResult<&[u8], Result<(PwmPin, f64), Error>> {
|
|||
)
|
||||
),
|
||||
result_with_pin(PwmPin::MaxV)
|
||||
),
|
||||
)
|
||||
))
|
||||
)(input)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,14 +19,14 @@ use stm32f4xx_hal::{
|
|||
stm32::{CorePeripherals, Peripherals, SCB},
|
||||
time::{U32Ext, MegaHertz},
|
||||
watchdog::IndependentWatchdog,
|
||||
gpio::{Edge, ExtiPin},
|
||||
syscfg::SysCfgExt
|
||||
};
|
||||
use smoltcp::{
|
||||
time::Instant,
|
||||
socket::TcpSocket,
|
||||
wire::EthernetAddress,
|
||||
};
|
||||
use stm32f4xx_hal::gpio::{Edge, ExtiPin};
|
||||
use stm32f4xx_hal::syscfg::SysCfgExt;
|
||||
|
||||
mod init_log;
|
||||
use init_log::init_log;
|
||||
|
@ -220,6 +220,8 @@ fn main() -> ! {
|
|||
}
|
||||
}
|
||||
|
||||
channels.fan_ctrl();
|
||||
|
||||
cortex_m::interrupt::free(net::clear_pending);
|
||||
server.poll(instant)
|
||||
.unwrap_or_else(|e| {
|
||||
|
|
Loading…
Reference in New Issue
Why is it so approximate?
Mostly because I cannot guarantee it is precise and would not like to see how someone would tie something-very-important to its value.